Skip to content
Legacy

Legacy guide

This guide covers a legacy setup. New code should define its data with ResourceSchemas instead of Models.

Typing Models & Transforms ​

Type ​

Example: add the Type brand to the user model.

ts
import Model, { attr } from '@warp-drive/legacy/model';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @attr declare name: string;

  [Type] = 'user' as const;
}

The benefit of the above is that the value of Type is readable at runtime and thus easy to debug. However, you can also choose to do this via types only:

ts
import Model, { attr } from '@warp-drive/legacy/model';
import type { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @attr declare name: string;

  declare [Type]: 'user';
}

WarpDrive will never access Type as an actual value, these brands are purely for type inference.

Transforms ​

Transforms with a Type brand will have their type and options validated. Once we move to stage-3 decorators, the signature of the field would also be validated against the transform.

Example: Typing a Transform

ts
import type { Type } from '@warp-drive/core/types/symbols';

export default class BigIntTransform {
  deserialize(serialized: string): BigInt | null {
    return !serialized || serialized === '' ? null : BigInt(serialized + 'n');
  }
  serialize(deserialized: BigInt | null): string | null {
    return !deserialized ? null : String(deserialized);
  }

  declare [Type]: 'big-int';

  static create() {
    return new this();
  }
}

Example: Using Transforms

ts
import Model, { attr } from '@warp-drive/legacy/model';
import type { StringTransform } from '@warp-drive/legacy/serializer/transform';
import type { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @attr<StringTransform>('string') declare name: string;

  declare [Type]: 'user';
}

Sync BelongsTo ​

belongsTo relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { belongsTo } from '@warp-drive/legacy/model';
import type Address from './address';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @belongsTo<Address>('address', { async: false, inverse: null })
  declare address: Address;

  [Type] = 'user' as const;
}

Async BelongsTo ​

belongsTo relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { belongsTo, AsyncBelongsTo } from '@warp-drive/legacy/model';
import type Address from './address';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @belongsTo<Address>('address', { async: true, inverse: null })
  declare address: AsyncBelongsTo<Address>;

  [Type] = 'user' as const;
}

Sync HasMany (data only) ​

If you don't need access to meta or links on relationships, you can type the relationship as just an array.

hasMany relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { hasMany } from '@warp-drive/legacy/model';
import type Post from './post';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @hasMany<Post>('post', { async: false, inverse: 'author' })
  declare posts: Post[];

  [Type] = 'user' as const;
}

hasMany relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { hasMany, HasMany } from '@warp-drive/legacy/model';
import type Post from './post';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @hasMany<Post>('post', { async: false, inverse: 'author' })
  declare posts: HasMany<Post>;

  [Type] = 'user' as const;
}

Async HasMany (restricted) ​

If you don't need access to meta, links or template iterations on relationships, you can type the relationship as just a promise resolving to an array. Only use this if the value will always be awaited before iteration.

hasMany relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { hasMany, AsyncHasMany } from '@warp-drive/legacy/model';
import type Post from './post';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @hasMany<Post>('post', { async: true, inverse: 'author' })
  declare posts: Promise<Post[]>;

  [Type] = 'user' as const;
}

Promise<HasMany<Post>> also works.

hasMany relationships will have their resource type and options config validated against the passed in type.

Once we move to stage-3 decorators, explicitly setting the generic would not be required as it could be inferred from the field's type.

ts
import Model, { hasMany, AsyncHasMany } from '@warp-drive/legacy/model';
import type Post from './post';
import { Type } from '@warp-drive/core/types/symbols';

export default class User extends Model {
  @hasMany<Post>('post', { async: true, inverse: 'author' })
  declare posts: AsyncHasMany<Post>;

  [Type] = 'user' as const;
}

Released under the MIT License.