From f76a0e05d7fcfa2dec26507d16cf4ca9bd3a6131 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Mon, 10 May 2021 22:52:14 -0700 Subject: [PATCH] feat: activate all feature flags related to custom model classes (#7510) * feat: activate all ffs related to CUSTOM_MODEL_CLASS * fix groupRecordsForFindMany * add key param to property/attributes/relationships buckets * fix unsubscribe * fix type signature * disable minification to see what's going on in prod * fix IE11 support * Revert "disable minification to see what's going on in prod" This reverts commit edb19ea162c2e1e62b161dc4ecc5875e28b88732. * add note to property --- .../canary-features/addon/default-features.ts | 10 +-- .../store/addon/-private/system/core-store.ts | 6 +- .../addon/-private/system/ds-model-store.ts | 8 +- .../addon/-private/system/fetch-manager.ts | 7 +- .../-private/system/model/internal-model.ts | 14 ++-- .../-private/system/model/notify-changes.ts | 74 ++++++++++++------- .../-private/system/model/shim-model-class.ts | 14 +++- .../system/record-notification-manager.ts | 33 ++++++--- 8 files changed, 113 insertions(+), 53 deletions(-) diff --git a/packages/canary-features/addon/default-features.ts b/packages/canary-features/addon/default-features.ts index 6fca8af5a3b..d395e9f3011 100644 --- a/packages/canary-features/addon/default-features.ts +++ b/packages/canary-features/addon/default-features.ts @@ -171,12 +171,12 @@ */ export default { SAMPLE_FEATURE_FLAG: null, - RECORD_DATA_ERRORS: null, - RECORD_DATA_STATE: null, + RECORD_DATA_ERRORS: true, + RECORD_DATA_STATE: true, IDENTIFIERS: true, - REQUEST_SERVICE: null, - CUSTOM_MODEL_CLASS: null, + REQUEST_SERVICE: true, + CUSTOM_MODEL_CLASS: true, FULL_LINKS_ON_RELATIONSHIPS: true, RECORD_ARRAY_MANAGER_IDENTIFIERS: true, - REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT: null, + REMOVE_RECORD_ARRAY_MANAGER_LEGACY_COMPAT: true, }; diff --git a/packages/store/addon/-private/system/core-store.ts b/packages/store/addon/-private/system/core-store.ts index b76a2e2d268..3a7b76184a4 100644 --- a/packages/store/addon/-private/system/core-store.ts +++ b/packages/store/addon/-private/system/core-store.ts @@ -2529,7 +2529,11 @@ abstract class CoreStore extends Service { } }); }, - ({ error, parsedErrors }) => { + (e) => { + if (typeof e === 'string') { + throw e; + } + const { error, parsedErrors } = e; this.recordWasInvalid(internalModel, parsedErrors, error); throw error; } diff --git a/packages/store/addon/-private/system/ds-model-store.ts b/packages/store/addon/-private/system/ds-model-store.ts index fa667630baf..9daad7f0a79 100644 --- a/packages/store/addon/-private/system/ds-model-store.ts +++ b/packages/store/addon/-private/system/ds-model-store.ts @@ -14,6 +14,8 @@ import { getShimClass } from './model/shim-model-class'; import normalizeModelName from './normalize-model-name'; import { DSModelSchemaDefinitionService, getModelFactory } from './schema-definition-service'; +type NotificationType = import('./record-notification-manager').NotificationType; + type RelationshipsSchema = import('../ts-interfaces/record-data-schemas').RelationshipsSchema; type SchemaDefinitionService = import('../ts-interfaces/schema-definition-service').SchemaDefinitionService; type RecordDataRecordWrapper = import('../ts-interfaces/record-data-record-wrapper').RecordDataRecordWrapper; @@ -51,7 +53,11 @@ class Store extends CoreStore { delete createOptions.container; let record = this._modelFactoryFor(modelName).create(createOptions); //todo optimize - notificationManager.subscribe(identifier, (identifier, value) => notifyChanges(identifier, value, record, this)); + notificationManager.subscribe( + identifier, + (identifier: StableRecordIdentifier, value: NotificationType, key?: string) => + notifyChanges(identifier, value, key, record, this) + ); return record; } diff --git a/packages/store/addon/-private/system/fetch-manager.ts b/packages/store/addon/-private/system/fetch-manager.ts index 3ed02c1ff75..2b34f575e88 100644 --- a/packages/store/addon/-private/system/fetch-manager.ts +++ b/packages/store/addon/-private/system/fetch-manager.ts @@ -485,7 +485,12 @@ export default class FetchManager { snapshots[i] = new Snapshot(options, identifiers[i], this._store); } - let groups: Snapshot[][] = adapter.groupRecordsForFindMany(this, snapshots); + let groups: Snapshot[][]; + if (adapter.groupRecordsForFindMany) { + groups = adapter.groupRecordsForFindMany(this, snapshots); + } else { + groups = [snapshots]; + } for (let i = 0, l = groups.length; i < l; i++) { this._processCoalescedGroup(seeking, groups[i], adapter, optionsMap, modelName); diff --git a/packages/store/addon/-private/system/model/internal-model.ts b/packages/store/addon/-private/system/model/internal-model.ts index fc32a8989af..17ac57dd98e 100644 --- a/packages/store/addon/-private/system/model/internal-model.ts +++ b/packages/store/addon/-private/system/model/internal-model.ts @@ -942,7 +942,7 @@ export default class InternalModel { manyArrayRecordAdded(key: string) { if (this.hasRecord) { if (CUSTOM_MODEL_CLASS) { - this.store._notificationManager.notify(this.identifier, 'relationships'); + this.store._notificationManager.notify(this.identifier, 'relationships', key); } else { this._record.notifyHasManyAdded(key); } @@ -952,7 +952,7 @@ export default class InternalModel { notifyHasManyChange(key: string) { if (this.hasRecord) { if (CUSTOM_MODEL_CLASS) { - this.store._notificationManager.notify(this.identifier, 'relationships'); + this.store._notificationManager.notify(this.identifier, 'relationships', key); } else { let manyArray = this._manyArrayCache[key] || this._retainedManyArrayCache[key]; if (manyArray) { @@ -970,7 +970,7 @@ export default class InternalModel { notifyBelongsToChange(key: string) { if (this.hasRecord) { if (CUSTOM_MODEL_CLASS) { - this.store._notificationManager.notify(this.identifier, 'relationships'); + this.store._notificationManager.notify(this.identifier, 'relationships', key); } else { this._record.notifyBelongsToChange(key, this._record); } @@ -991,10 +991,14 @@ export default class InternalModel { return didRemoveUnloadedModel; } - notifyPropertyChange(key) { + notifyPropertyChange(key: string) { if (this.hasRecord) { if (CUSTOM_MODEL_CLASS) { - this.store._notificationManager.notify(this.identifier, 'property'); + // TODO this should likely *mostly* be the `attributes` bucket + // but it seems for local mutations we rely on computed updating + // iteself when set. As we design our own thing we may need to change + // that. + this.store._notificationManager.notify(this.identifier, 'property', key); } else { this._record.notifyPropertyChange(key); } diff --git a/packages/store/addon/-private/system/model/notify-changes.ts b/packages/store/addon/-private/system/model/notify-changes.ts index 641aee36171..a11133b884b 100644 --- a/packages/store/addon/-private/system/model/notify-changes.ts +++ b/packages/store/addon/-private/system/model/notify-changes.ts @@ -1,40 +1,37 @@ import { cacheFor } from '@ember/object/internals'; +type CoreStore = import('../core-store').default; + +type NotificationType = import('../record-notification-manager').NotificationType; + type Store = import('../ds-model-store').default; -type DSModel = import('../../ts-interfaces/ds-model').DSModel; +type Model = InstanceType; type StableRecordIdentifier = import('../../ts-interfaces/identifier').StableRecordIdentifier; export default function notifyChanges( identifier: StableRecordIdentifier, - value: 'attributes' | 'relationships' | 'errors' | 'meta' | 'unload' | 'identity' | 'property' | 'state', - record: DSModel, + value: NotificationType, + key: string | undefined, + record: Model, store: Store ) { if (value === 'attributes') { - record.eachAttribute((key) => { - let currentValue = cacheFor(record, key); - let internalModel = store._internalModelForResource(identifier); - if (currentValue !== internalModel._recordData.getAttr(key)) { - record.notifyPropertyChange(key); - } - }); + if (key) { + notifyAttribute(store, identifier, key, record); + } else { + record.eachAttribute((key) => { + notifyAttribute(store, identifier, key, record); + }); + } } else if (value === 'relationships') { - record.eachRelationship((key, meta) => { - let internalModel = store._internalModelForResource(identifier); - if (meta.kind === 'belongsTo') { - record.notifyPropertyChange(key); - } else if (meta.kind === 'hasMany') { - let didRemoveUnloadedModel = false; - if (meta.options.async) { - record.notifyPropertyChange(key); - didRemoveUnloadedModel = internalModel.hasManyRemovalCheck(key); - } - let manyArray = internalModel._manyArrayCache[key] || internalModel._retainedManyArrayCache[key]; - if (manyArray && !didRemoveUnloadedModel) { - manyArray.retrieveLatest(); - } - } - }); + if (key) { + let meta = record.constructor.relationshipsByName.get(key); + notifyRelationship(store, identifier, key, record, meta); + } else { + record.eachRelationship((key, meta) => { + notifyRelationship(store, identifier, key, record, meta); + }); + } } else if (value === 'errors') { let internalModel = store._internalModelForResource(identifier); //TODO guard @@ -47,3 +44,28 @@ export default function notifyChanges( record.notifyPropertyChange('id'); } } + +function notifyRelationship(store: CoreStore, identifier: StableRecordIdentifier, key: string, record: Model, meta) { + let internalModel = store._internalModelForResource(identifier); + if (meta.kind === 'belongsTo') { + record.notifyPropertyChange(key); + } else if (meta.kind === 'hasMany') { + let didRemoveUnloadedModel = false; + if (meta.options.async) { + record.notifyPropertyChange(key); + didRemoveUnloadedModel = internalModel.hasManyRemovalCheck(key); + } + let manyArray = internalModel._manyArrayCache[key] || internalModel._retainedManyArrayCache[key]; + if (manyArray && !didRemoveUnloadedModel) { + manyArray.retrieveLatest(); + } + } +} + +function notifyAttribute(store: CoreStore, identifier: StableRecordIdentifier, key: string, record: Model) { + let currentValue = cacheFor(record, key); + let internalModel = store._internalModelForResource(identifier); + if (currentValue !== internalModel._recordData.getAttr(key)) { + record.notifyPropertyChange(key); + } +} diff --git a/packages/store/addon/-private/system/model/shim-model-class.ts b/packages/store/addon/-private/system/model/shim-model-class.ts index 919b16f198a..328e7bd6e70 100644 --- a/packages/store/addon/-private/system/model/shim-model-class.ts +++ b/packages/store/addon/-private/system/model/shim-model-class.ts @@ -23,6 +23,16 @@ export function getShimClass(store: CoreStore, modelName: string): ShimModelClas return shim; } +function mapFromHash(hash: Dict): Map { + let map = new Map(); + for (let i in hash) { + if (Object.prototype.hasOwnProperty.call(hash, i)) { + map.set(i, hash[i]); + } + } + return map; +} + // Mimics the static apis of DSModel export default class ShimModelClass implements ModelSchema { // TODO Maybe expose the class here? @@ -39,12 +49,12 @@ export default class ShimModelClass implements ModelSchema { get attributes(): Map { let attrs = this.__store._attributesDefinitionFor(this.modelName); - return new Map(Object.entries(attrs) as [string, AttributeSchema][]); + return mapFromHash(attrs); } get relationshipsByName(): Map { let relationships = this.__store._relationshipsDefinitionFor(this.modelName); - return new Map(Object.entries(relationships) as [string, RelationshipSchema][]); + return mapFromHash(relationships); } eachAttribute(callback: (this: T, key: string, attribute: AttributeSchema) => void, binding?: T) { diff --git a/packages/store/addon/-private/system/record-notification-manager.ts b/packages/store/addon/-private/system/record-notification-manager.ts index 675d6ccdc4e..d48944e4291 100644 --- a/packages/store/addon/-private/system/record-notification-manager.ts +++ b/packages/store/addon/-private/system/record-notification-manager.ts @@ -9,11 +9,20 @@ type UnsubscribeToken = Object; const Cache = new WeakMap(); const Tokens = new WeakMap(); -interface NotificationCallback { - ( - identifier: StableRecordIdentifier, - notificationType: 'attributes' | 'relationships' | 'identity' | 'errors' | 'meta' | 'unload' | 'property' | 'state' - ): void; +export type NotificationType = + | 'attributes' + | 'relationships' + | 'identity' + | 'errors' + | 'meta' + | 'unload' + | 'state' + | 'property'; // 'property' is an internal EmberData only transition period concept. + +export interface NotificationCallback { + (identifier: RecordIdentifier, notificationType: 'attributes' | 'relationships' | 'property', key?: string): void; + (identifier: RecordIdentifier, notificationType: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): void; + (identifier: StableRecordIdentifier, notificationType: NotificationType, key?: string): void; } export function unsubscribe(token: UnsubscribeToken) { @@ -21,6 +30,7 @@ export function unsubscribe(token: UnsubscribeToken) { if (!identifier) { throw new Error('Passed unknown unsubscribe token to unsubscribe'); } + Tokens.delete(token); Cache.delete(identifier); } /* @@ -32,21 +42,20 @@ export default class NotificationManager { subscribe(identifier: RecordIdentifier, callback: NotificationCallback): UnsubscribeToken { let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier); Cache.set(stableIdentifier, callback); - let unsubToken = new Object(); + let unsubToken = {}; Tokens.set(unsubToken, stableIdentifier); - return identifier; + return unsubToken; } - notify( - identifier: RecordIdentifier, - value: 'attributes' | 'relationships' | 'errors' | 'meta' | 'identity' | 'unload' | 'property' | 'state' - ): boolean { + notify(identifier: RecordIdentifier, value: 'attributes' | 'relationships' | 'property', key?: string): boolean; + notify(identifier: RecordIdentifier, value: 'errors' | 'meta' | 'identity' | 'unload' | 'state'): boolean; + notify(identifier: RecordIdentifier, value: NotificationType, key?: string): boolean { let stableIdentifier = identifierCacheFor(this.store).getOrCreateRecordIdentifier(identifier); let callback = Cache.get(stableIdentifier); if (!callback) { return false; } - callback(stableIdentifier, value); + callback(stableIdentifier, value, key); return true; } }