Skip to content

Commit

Permalink
chore: extract model/json:api hooks installation into meta package
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Jul 14, 2023
1 parent 7243558 commit b6b64dd
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 222 deletions.
20 changes: 20 additions & 0 deletions ember-data-types/q/ds-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ import type EmberObject from '@ember/object';
import type { Errors } from '@ember-data/model/-private';
import type Store from '@ember-data/store';

import { Cache } from './cache';
import { StableRecordIdentifier } from './identifier';
import type { JsonApiError } from './record-data-json-api';
import type { AttributeSchema, RelationshipSchema, RelationshipsSchema } from './record-data-schemas';

export type ModelFactory = { class: DSModelSchema };
export type FactoryCache = Record<string, ModelFactory>;
// we put this on the store for interop because it's used by modelFor and
// instantiateRecord as well.
export type ModelStore = Store & { _modelFactoryCache: FactoryCache };

// Placeholder until model.js is typed
export interface DSModel extends EmberObject {
constructor: DSModelSchema;
Expand Down Expand Up @@ -37,11 +45,23 @@ export interface ModelSchema {
toString(): string;
}

export type DSModelCreateArgs = {
_createProps: Record<string, unknown>;
// TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for
_secretInit: {
identifier: StableRecordIdentifier;
cache: Cache;
store: Store;
cb: (record: DSModel, cache: Cache, identifier: StableRecordIdentifier, store: Store) => void;
};
};

// This is the static side of DSModel should become DSModel
// once we can type it.
export interface DSModelSchema extends ModelSchema {
isModel: true;
relationshipsObject: RelationshipsSchema;
extend(...mixins: unknown[]): DSModelSchema;
reopenClass(...mixins: unknown[]): void;
create(createArgs: DSModelCreateArgs): DSModel;
}
31 changes: 31 additions & 0 deletions packages/-ember-data/addon/-private/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,48 @@ import ArrayProxy from '@ember/array/proxy';
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
import ObjectProxy from '@ember/object/proxy';

import Cache from '@ember-data/json-api';
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
import { instantiateRecord, teardownRecord } from '@ember-data/model';
import { modelFor, ModelSchemaDefinitionService } from '@ember-data/model/-private';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import BaseStore, { CacheHandler } from '@ember-data/store';
import type ShimModelClass from '@ember-data/store/-private/legacy-model-support/shim-model-class';
import type { CacheStoreWrapper } from '@ember-data/types/q/cache-store-wrapper';
import type { DSModel, DSModelSchema, ModelFactory, ModelStore } from '@ember-data/types/q/ds-model';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
import type { RecordInstance } from '@ember-data/types/q/record-instance';

export class Store extends BaseStore {
declare _modelFactoryCache: Record<string, ModelFactory>;
constructor(args: Record<string, unknown>) {
super(args);
this.requestManager = new RequestManager();
this.requestManager.use([LegacyNetworkHandler, Fetch]);
this.requestManager.useCache(CacheHandler);
this._modelFactoryCache = Object.create(null) as Record<string, ModelFactory>;
this.registerSchema(new ModelSchemaDefinitionService(this));
}

createCache(storeWrapper: CacheStoreWrapper) {
return new Cache(storeWrapper);
}

instantiateRecord(
this: ModelStore,
identifier: StableRecordIdentifier,
createRecordArgs: Record<string, unknown>
): DSModel {
return instantiateRecord.call(this, identifier, createRecordArgs);
}

teardownRecord(record: RecordInstance): void {
teardownRecord.call(this, record as DSModel);
}

modelFor(type: string): ShimModelClass | DSModelSchema {
return modelFor.call(this, type) || super.modelFor(type);
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/model/src/-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export { default as ManyArray } from './-private/many-array';
export { default as PromiseBelongsTo } from './-private/promise-belongs-to';
export { default as PromiseManyArray } from './-private/promise-many-array';
export { default as _modelForMixin } from './-private/model-for-mixin';
export { instantiateRecord, teardownRecord, modelFor } from './-private/hooks';
export { ModelSchemaDefinitionService } from './-private/schema-definition-service';

// // Used by tests
export { LEGACY_SUPPORT } from './-private/model';
72 changes: 72 additions & 0 deletions packages/model/src/-private/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { getOwner, setOwner } from '@ember/application';
import { assert } from '@ember/debug';

import { setCacheFor, setRecordIdentifier, type Store, StoreMap } from '@ember-data/store/-private';
import type { Cache } from '@ember-data/types/cache/cache';
import type { DSModel, DSModelSchema, ModelStore } from '@ember-data/types/q/ds-model';
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';

import { getModelFactory } from './schema-definition-service';
import { normalizeModelName } from './util';

export function instantiateRecord(
this: ModelStore,
identifier: StableRecordIdentifier,
createRecordArgs: { [key: string]: unknown }
): DSModel {
const type = identifier.type;

const cache = this.cache;
// TODO deprecate allowing unknown args setting
const createOptions = {
_createProps: createRecordArgs,
// TODO @deprecate consider deprecating accessing record properties during init which the below is necessary for
_secretInit: {
identifier,
cache,
store: this,
cb: secretInit,
},
};

// ensure that `getOwner(this)` works inside a model instance
setOwner(createOptions, getOwner(this)!);
const factory = getModelFactory(this, this._modelFactoryCache, type);

assert(`No model was found for '${type}'`, factory);
return factory.class.create(createOptions);
}

export function teardownRecord(record: DSModel): void {
assert(
`expected to receive an instance of DSModel. If using a custom model make sure you implement teardownRecord`,
'destroy' in record
);
record.destroy();
}

export function modelFor(this: ModelStore, modelName: string): DSModelSchema | void {
assert(`You need to pass a model name to the store's modelFor method`, modelName);
assert(
`Please pass a proper model name to the store's modelFor method`,
typeof modelName === 'string' && modelName.length
);
const type = normalizeModelName(modelName);
const maybeFactory = getModelFactory(this, this._modelFactoryCache, type);
const klass = maybeFactory && maybeFactory.class ? maybeFactory.class : null;

const ignoreType = !klass || !klass.isModel || this._forceShim;
if (!ignoreType) {
return klass;
}
assert(
`No model was found for '${type}' and no schema handles the type`,
this.getSchemaDefinitionService().doesTypeExist(type)
);
}

function secretInit(record: DSModel, cache: Cache, identifier: StableRecordIdentifier, store: Store): void {
setRecordIdentifier(record, identifier);
StoreMap.set(record, store);
setCacheFor(record, cache);
}
94 changes: 94 additions & 0 deletions packages/model/src/-private/schema-definition-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { getOwner } from '@ember/application';

import type { DSModelSchema, FactoryCache, ModelFactory, ModelStore } from '@ember-data/types/q/ds-model';
import type { RecordIdentifier } from '@ember-data/types/q/identifier';
import type { AttributesSchema, RelationshipsSchema } from '@ember-data/types/q/record-data-schemas';

import _modelForMixin from './model-for-mixin';
import { normalizeModelName } from './util';

export class ModelSchemaDefinitionService {
declare store: ModelStore;
declare _relationshipsDefCache: Record<string, RelationshipsSchema>;
declare _attributesDefCache: Record<string, AttributesSchema>;

constructor(store: ModelStore) {
this.store = store;
this._relationshipsDefCache = Object.create(null) as Record<string, RelationshipsSchema>;
this._attributesDefCache = Object.create(null) as Record<string, AttributesSchema>;
}

// Following the existing RD implementation
attributesDefinitionFor(identifier: RecordIdentifier | { type: string }): AttributesSchema {
const { type } = identifier;
let attributes: AttributesSchema;

attributes = this._attributesDefCache[type];

if (attributes === undefined) {
let modelClass = this.store.modelFor(type);
let attributeMap = modelClass.attributes;

attributes = Object.create(null) as AttributesSchema;
attributeMap.forEach((meta, name) => (attributes[name] = meta));
this._attributesDefCache[type] = attributes;
}

return attributes;
}

// Following the existing RD implementation
relationshipsDefinitionFor(identifier: RecordIdentifier | { type: string }): RelationshipsSchema {
const { type } = identifier;
let relationships: RelationshipsSchema;

relationships = this._relationshipsDefCache[type];

if (relationships === undefined) {
let modelClass = this.store.modelFor(type) as DSModelSchema;
relationships = modelClass.relationshipsObject || null;
this._relationshipsDefCache[type] = relationships;
}

return relationships;
}

doesTypeExist(modelName: string): boolean {
const type = normalizeModelName(modelName);
const factory = getModelFactory(this.store, this.store._modelFactoryCache, type);

return factory !== null;
}
}

export function getModelFactory(store: ModelStore, cache: FactoryCache, type: string): ModelFactory | null {
let factory: ModelFactory | undefined = cache[type];

if (!factory) {
const owner = getOwner(store)!;
factory = owner.factoryFor(`model:${type}`) as ModelFactory | undefined;

if (!factory) {
//Support looking up mixins as base types for polymorphic relationships
factory = _modelForMixin(store, type);
}

if (!factory) {
// we don't cache misses in case someone wants to register a missing model
return null;
}

let klass = factory.class;

if (klass.isModel) {
let hasOwnModelNameSet = klass.modelName && Object.prototype.hasOwnProperty.call(klass, 'modelName');
if (!hasOwnModelNameSet) {
Object.defineProperty(klass, 'modelName', { value: type });
}
}

cache[type] = factory;
}

return factory;
}
6 changes: 6 additions & 0 deletions packages/model/src/-private/util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { dasherize } from '@ember/string';

export type DecoratorPropertyDescriptor = (PropertyDescriptor & { initializer?: any }) | undefined;

export function isElementDescriptor(args: any[]): args is [object, string, DecoratorPropertyDescriptor] {
Expand All @@ -23,3 +25,7 @@ export function isElementDescriptor(args: any[]): args is [object, string, Decor
export function computedMacroWithOptionalParams(fn) {
return (...maybeDesc: any[]) => (isElementDescriptor(maybeDesc) ? fn()(...maybeDesc) : fn(...maybeDesc));
}

export function normalizeModelName(modelName: string): string {
return dasherize(modelName);
}
2 changes: 1 addition & 1 deletion packages/model/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@
@module @ember-data/model
@main @ember-data/model
*/
export { Model as default, attr, belongsTo, hasMany } from './-private';
export { Model as default, attr, belongsTo, hasMany, instantiateRecord, teardownRecord } from './-private';
4 changes: 0 additions & 4 deletions packages/store/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,13 @@
],
"peerDependencies": {
"@ember-data/graph": "workspace:5.3.0-alpha.4",
"@ember-data/json-api": "workspace:5.3.0-alpha.4",
"@ember-data/legacy-compat": "workspace:5.3.0-alpha.4",
"@ember-data/model": "workspace:5.3.0-alpha.4",
"@ember-data/tracking": "workspace:5.3.0-alpha.4",
"@ember/string": "^3.1.1",
"@glimmer/tracking": "^1.1.2"
},
"peerDependenciesMeta": {
"@ember-data/json-api": {
"optional": true
},
"@ember-data/graph": {
"optional": true
},
Expand Down
4 changes: 4 additions & 0 deletions packages/store/src/-private/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ export { default as RecordArrayManager, fastPush } from './managers/record-array
// leaked for private use / test use, should investigate removing
export { _clearCaches } from './caches/instance-cache';
export { default as peekCache, removeRecordDataFor } from './caches/cache-utils';

// @ember-data/model needs these temporarily
export { setRecordIdentifier, StoreMap } from './caches/instance-cache';
export { setCacheFor } from './caches/cache-utils';
Loading

0 comments on commit b6b64dd

Please sign in to comment.