import EmberArray from '@ember/array'; import ArrayProxy from '@ember/array/proxy'; import EmberDataModel, { AsyncBelongsTo, AsyncHasMany } from '@ember-data/model'; import { BelongsTo, HasMany, ModelDefinition } from 'ember-cli-mirage/-types'; import { FilterKeysByType } from 'app/types/util'; /** * Converts the given Ember Data Model Registry into a Mirage Model Registry. * * @example * Given the following Registry: * ```ts * import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; * * export default class User extends Model { * @hasMany('post') * declare posts: AsyncHasMany<Post>; * } * * export default class User extends Model { * @belongsTo('user') * declare user: AsyncBelongsTo<User>; * } * * export default interface ModelRegistry { * user: User; * post: Post; * } * ``` * * We can generate the Mirage model Registry with: * ```ts * declare const model: ConvertModelRegistry<ModelRegistry>; * ``` * * The resulting Mirage model Registry will look like: * ``` * type Registry = { * user: ModelDefinition<{ posts: HasMany<'post'> }>; * post: ModelDefinition<{ posts: BelongsTo<'user'> }>; * } * ``` * * Note that only relationships are included on the Mirage model data type. * We will get types from `@attr`s from Factories in the Mirage Registry, * and getters should not be included in Mirage types bc they don't exist on the * backend. */ export type ConvertModelRegistry<EmberDataModelRegistry> = { [K in keyof EmberDataModelRegistry]: ModelDefinitionFor< EmberDataModelRegistry, K >; }; /** * Wraps the `ModelDefinitionData` for the given Ember Data Model Registry and * model key in Mirage's `ModelDefinition` type, which is necessary for the * Mirage Registry to recognize the data as a Mirage model. */ type ModelDefinitionFor< EmberDataModelRegistry, K extends keyof EmberDataModelRegistry > = ModelDefinition< ModelDefinitionData<EmberDataModelRegistry, K> & { id: string } >; /** * Converts an Ember Data model into Data for a Mirage model definition. * * @example * Given the following Ember Data model for a user: * ```ts * import Model, { attr, belongsTo, hasMany } from '@ember-data/model'; * * export default class User extends Model { * @belongsTo('organization') * declare organization: PromiseRecord<Organization>; * * @hasMany('user') * declare friends: PromiseManyArray<User>; * * @attr('string') * declare name: string; * * @attr('number') * age?: number; * * get description(): string { * return `User ${name} is ${age} years old.` * } * } * * export default interface ModelRegistry { * user: User; * } * ``` * * We can generate the Mirage model data for the User model with: * ```ts * declare const model: ModelDefinitionData<ModelRegistry, 'user'>; * ``` * * The resulting Mirage model data type will look like: * ``` * import { BelongsTo, HasMany } from 'miragejs/-types'; * * type UserMirageModel = { * organization: BelongsTo<'organization'>; * friends: HasMany<'friend'>; * } * ``` * * Note that the `@attr` properties and the `description` getter from the Ember * Data model are not included in the Mirage model data type. We will get types * from `@attr`s from Factories in the Mirage Registry, and getters should not * be included in Mirage types bc they don't exist on the backend. */ export type ModelDefinitionData< EmberDataModelRegistry, K extends keyof EmberDataModelRegistry > = EmberDataModelRegistry[K] extends EmberDataModel ? MapEmberDataRelationshipsToMirage< EmberDataModelRegistry, RelationshipsFor<EmberDataModelRegistry[K]> > : never; /** * Extract model attributes from the Ember Data model. */ type RelationshipsFor<M extends EmberDataModel> = Pick< M, FilterKeysByType< M, PromiseManyArray<EmberDataModel> | PromiseRecord<EmberDataModel> > >; /** * Maps Ember Data model types to Mirage model types. * Ember Data relationships will be converted to Mirage relationships. * All other attribute types will remain unchanged. */ type MapEmberDataRelationshipsToMirage< EmberDataModelRegistry, M extends Partial<EmberDataModel> > = { [K in keyof M]: MapEmberDataRelationshipToMirage< EmberDataModelRegistry, M[K] >; }; /** * Converts an Ember Data model attribute's type to a Mirage model type. * If T is a AsyncHasMany<M>, * then it will be converted to HasMany<'m'>. * If T is a AsyncBelongsTo<M>, * then it will be converted to BelongsTo<'m'>. * Otherwise, T will be never. */ type MapEmberDataRelationshipToMirage< EmberDataModelRegistry, T > = T extends AsyncHasMany<infer Model> ? HasMany<ModelName<EmberDataModelRegistry, Model>> : T extends AsyncBelongsTo<infer Model> ? BelongsTo<ModelName<EmberDataModelRegistry, Model>> : never; /** * Gets the ModelName from the Ember Data Model Registry for the given model. */ type ModelName<EmberDataModelRegistry, Model> = FilterKeysByType< EmberDataModelRegistry, Model > extends string ? FilterKeysByType<EmberDataModelRegistry, Model> : never;