Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Last active September 13, 2024 00:06
Show Gist options
  • Save WebReflection/a0d8383841c1dba4696fae63de44ceb4 to your computer and use it in GitHub Desktop.
Save WebReflection/a0d8383841c1dba4696fae63de44ceb4 to your computer and use it in GitHub Desktop.
Bun sqlite `.as(Class)` alternative

This is a simple demo of how latest Bun could've implemented .as(Class) instead of using the quite jurassic Object.create which is slow, bloated due descriptors logic, if used at all, and incapable to play nicely with features introduced after Object.create was even specified.

// what `as(Class)` should return
const { assign } = Object;
const asClass = new WeakMap;
const setAsClass = Class => {
  class As extends Class {
    constructor(fields) {
      assign(super(), fields);
    }
  }
  asClass.set(Class, As);
  return As;
};
const as = Class => asClass.get(Class) || setAsClass(Class);

// a db mock for this demo
const db = {
  query(_) {
    return {
      as(Class) {
        const As = as(Class);
        return {
          all() {
            return [
              new As({ name: 'Alice' }),
              new As({ name: 'Bob' }),
              new As({ name: 'Chuck' }),
            ];
          }
        };
      }
    };
  }
};

// a class for this demo
class User {
  name = '';
  #age = 0;
  birthday() {
    this.#age++;
  }
  toString() {
    return `${this.name} is ${this.#age}`;
  }
}

// just asserting expectations
console.assert(new User().name === '');

// an actual result
const results = db.query('SELECT name FROM users').as(User);
for (const user of results.all()) {
  console.assert(user instanceof User);
  console.assert(user.name !== '');
  if (Math.random() < .7) user.birthday();
  console.log(`${user}`);
}

The output would be likely similar to this one with no assertions failed whatsoever:

Alice is 1
Bob is 0
Chuck is 1

The only contract around this ORM like logic is that classes should have no constructor or one that has defaults or regular setup without implying result fields are already known AOT.

Right now instead, we have a mechanism that really doesn't play well at all with modern classes abilities 😥

@WebReflection
Copy link
Author

WebReflection commented Jun 19, 2024

If worth exploring, the as could take an optional second argument that indicates the init method to use if / when desired ...

// amend #1
const setAsClass = (Class, init = '') => {
  class As extends Class {
    constructor(fields) {
      assign(super(), fields);
      if (init) this[init]();
    }
  }
  asClass.set(Class, As);
  return As;
};
const as = (Class, ...rest) => asClass.get(Class) || setAsClass(Class, ...rest);

// amend #2
class User {
  name = '';
  #age = 0;
  birthday() {
    this.#age++;
  }
  init() {
    // do stuff here when the instance is ready
  }
  toString() {
    return `${this.name} is ${this.#age}`;
  }
}

// amend #3
db.query('SELECT name FROM users').as(User, 'init');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment