From c3f30684ac447afd1ce318fbb21a56e448d0acc0 Mon Sep 17 00:00:00 2001 From: Chris Thoburn Date: Thu, 4 Aug 2022 23:42:11 -0700 Subject: [PATCH] the shape of things --- .eslintrc.js | 2 - ember-data-types/q/ds-model.ts | 2 - packages/-ember-data/addon/-private/index.ts | 2 - packages/-ember-data/addon/index.js | 2 - .../node-tests/fixtures/expected.js | 3 - .../-private/legacy-relationships-support.ts | 10 +- packages/model/addon/-private/model.js | 20 +- .../addon/-private/promise-many-array.ts | 4 +- packages/model/addon/-private/record-state.ts | 6 +- .../addon/current-deprecations.ts | 1 - .../private-build-infra/addon/deprecations.ts | 1 - .../record-data/addon/-private/coerce-id.ts | 4 +- .../addon/-private/graph/-utils.ts | 3 +- .../record-data/addon/-private/graph/index.ts | 6 +- .../record-data/addon/-private/record-data.ts | 3 +- .../-private/relationships/state/has-many.ts | 12 +- packages/store/addon/-debug/index.js | 2 +- .../addon/-private/caches/identifier-cache.ts | 4 +- .../addon/-private/caches/instance-cache.ts | 334 +++++++++++------- packages/store/addon/-private/index.ts | 5 +- .../legacy-model-support/internal-model.ts | 80 ----- .../-private/managers/record-array-manager.ts | 14 - .../managers/record-data-store-wrapper.ts | 7 +- .../store/addon/-private/network/snapshot.ts | 13 +- .../store/addon/-private/store-service.ts | 58 +-- tsconfig.json | 2 - 26 files changed, 264 insertions(+), 336 deletions(-) delete mode 100644 packages/store/addon/-private/legacy-model-support/internal-model.ts diff --git a/.eslintrc.js b/.eslintrc.js index 82b1c025c49..c14145eee93 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -195,7 +195,6 @@ module.exports = { 'ember-data-types/q/ember-data-json-api.ts', 'ember-data-types/q/ds-model.ts', 'packages/store/addon/-private/managers/record-data-store-wrapper.ts', - 'packages/store/addon/-private/caches/internal-model-factory.ts', 'packages/store/addon/-private/network/snapshot.ts', 'packages/store/addon/-private/network/snapshot-record-array.ts', 'packages/store/addon/-private/legacy-model-support/schema-definition-service.ts', @@ -205,7 +204,6 @@ module.exports = { 'packages/store/addon/-private/caches/record-data-for.ts', 'packages/store/addon/-private/utils/normalize-model-name.ts', 'packages/store/addon/-private/legacy-model-support/shim-model-class.ts', - 'packages/store/addon/-private/legacy-model-support/internal-model.ts', 'packages/store/addon/-private/network/fetch-manager.ts', 'packages/store/addon/-private/store-service.ts', 'packages/store/addon/-private/utils/coerce-id.ts', diff --git a/ember-data-types/q/ds-model.ts b/ember-data-types/q/ds-model.ts index 27eeec4d295..711e7fdcb49 100644 --- a/ember-data-types/q/ds-model.ts +++ b/ember-data-types/q/ds-model.ts @@ -2,7 +2,6 @@ import type EmberObject from '@ember/object'; import type { Errors } from '@ember-data/model/-private'; import type Store from '@ember-data/store'; -import type InternalModel from '@ember-data/store/-private/legacy-model-support/internal-model'; import type { JsonApiValidationError } from './record-data-json-api'; import type { AttributeSchema, RelationshipSchema, RelationshipsSchema } from './record-data-schemas'; @@ -12,7 +11,6 @@ export interface DSModel extends EmberObject { constructor: DSModelSchema; store: Store; errors: Errors; - _internalModel: InternalModel; toString(): string; save(): Promise; eachRelationship(callback: (this: T, key: string, meta: RelationshipSchema) => void, binding?: T): void; diff --git a/packages/-ember-data/addon/-private/index.ts b/packages/-ember-data/addon/-private/index.ts index 0c32ba10e4c..f36e0474224 100644 --- a/packages/-ember-data/addon/-private/index.ts +++ b/packages/-ember-data/addon/-private/index.ts @@ -4,11 +4,9 @@ export { default as DS } from './core'; export { Errors } from '@ember-data/model/-private'; export { Snapshot } from '@ember-data/store/-private'; -// `ember-data-model-fragments` relies on `InternalModel` // `ember-data-model-fragments' and `ember-data-change-tracker` rely on `normalizeModelName` export { AdapterPopulatedRecordArray, - InternalModel, PromiseArray, PromiseObject, RecordArray, diff --git a/packages/-ember-data/addon/index.js b/packages/-ember-data/addon/index.js index e8172e88f58..b8085ebe3d1 100644 --- a/packages/-ember-data/addon/index.js +++ b/packages/-ember-data/addon/index.js @@ -30,7 +30,6 @@ import { AdapterPopulatedRecordArray, DS, Errors, - InternalModel, ManyArray, PromiseArray, PromiseManyArray, @@ -52,7 +51,6 @@ DS.Model = Model; DS.attr = attr; DS.Errors = Errors; -DS.InternalModel = InternalModel; DS.Snapshot = Snapshot; DS.Adapter = Adapter; diff --git a/packages/-ember-data/node-tests/fixtures/expected.js b/packages/-ember-data/node-tests/fixtures/expected.js index cdd46eaad7d..0ea39b24a2b 100644 --- a/packages/-ember-data/node-tests/fixtures/expected.js +++ b/packages/-ember-data/node-tests/fixtures/expected.js @@ -73,8 +73,6 @@ module.exports = { '(private) @ember-data/store IdentifierCache#_getRecordIdentifier', '(private) @ember-data/store IdentifierCache#_mergeRecordIdentifiers', '(private) @ember-data/store IdentifierCache#peekRecordIdentifier', - '(private) @ember-data/store InternalModelFactory#lookup', - '(private) @ember-data/store InternalModelFactory#peek', '(private) @ember-data/store ManyArray#isPolymorphic', '(private) @ember-data/store ManyArray#relationship', '(private) @ember-data/store RecordArray#_createSnapshot', @@ -89,7 +87,6 @@ module.exports = { '(private) @ember-data/store Store#_push', '(private) @ember-data/store Store#find', '(private) @ember-data/store Store#init', - '(private) @ember-data/store Store#recordWasInvalid', '(public) @ember-data/adapter Adapter#coalesceFindRequests', '(public) @ember-data/adapter Adapter#createRecord', '(public) @ember-data/adapter Adapter#deleteRecord', diff --git a/packages/model/addon/-private/legacy-relationships-support.ts b/packages/model/addon/-private/legacy-relationships-support.ts index 03109c90790..2a44477df31 100644 --- a/packages/model/addon/-private/legacy-relationships-support.ts +++ b/packages/model/addon/-private/legacy-relationships-support.ts @@ -13,7 +13,6 @@ import type { import type { UpgradedMeta } from '@ember-data/record-data/-private/graph/-edge-definition'; import type { RelationshipState } from '@ember-data/record-data/-private/graph/-state'; import type Store from '@ember-data/store'; -import type { InternalModel } from '@ember-data/store/-private'; import { recordDataFor, recordIdentifierFor, storeFor } from '@ember-data/store/-private'; import type { IdentifierCache } from '@ember-data/store/-private/caches/identifier-cache'; import type { DSModel } from '@ember-data/types/q/ds-model'; @@ -141,7 +140,8 @@ export class LegacySupport { `You looked up the '${key}' relationship on a '${identifier.type}' with id ${ identifier.id || 'null' } but some of the associated records were not loaded. Either make sure they are all loaded together with the parent record, or specify that the relationship is async (\`belongsTo({ async: true })\`)`, - toReturn === null || !store._instanceCache.peek({ identifier: relatedIdentifier, bucket: 'recordData' })?.isEmpty?.() + toReturn === null || + !store._instanceCache.peek({ identifier: relatedIdentifier, bucket: 'recordData' })?.isEmpty?.() ); return toReturn; } @@ -705,11 +705,7 @@ function areAllInverseRecordsLoaded(store: Store, resource: JsonApiRelationship) } } -function isEmpty( - store: Store, - cache: IdentifierCache, - resource: ResourceIdentifierObject -): boolean { +function isEmpty(store: Store, cache: IdentifierCache, resource: ResourceIdentifierObject): boolean { const identifier = cache.getOrCreateRecordIdentifier(resource); const recordData = store._instanceCache.peek({ identifier, bucket: 'recordData' }); return !recordData || !!recordData.isEmpty?.(); diff --git a/packages/model/addon/-private/model.js b/packages/model/addon/-private/model.js index 9f02fc2eade..25f2cf7ed31 100644 --- a/packages/model/addon/-private/model.js +++ b/packages/model/addon/-private/model.js @@ -22,8 +22,7 @@ import { DEPRECATE_SAVE_PROMISE_ACCESS, } from '@ember-data/private-build-infra/deprecations'; import { recordIdentifierFor, storeFor } from '@ember-data/store'; -import { coerceId, deprecatedPromiseObject, InternalModel, WeakCache } from '@ember-data/store/-private'; -import { recordDataFor } from '@ember-data/store/-private'; +import { coerceId, deprecatedPromiseObject, recordDataFor, WeakCache } from '@ember-data/store/-private'; import Errors from './errors'; import { LegacySupport } from './legacy-relationships-support'; @@ -138,8 +137,6 @@ class Model extends EmberObject { this.setProperties(createProps); - // TODO pass something in such that we don't need internalModel - // to get this info let store = storeFor(this); let notifications = store._notificationManager; let identity = recordIdentifierFor(this); @@ -458,8 +455,12 @@ class Model extends EmberObject { // this guard exists, because some dev-only deprecation code // (addListener via validatePropertyInjections) invokes toString before the // object is real. - if (DEBUG && !this._internalModel) { - return void 0; + if (DEBUG) { + try { + return recordIdentifierFor(this).id; + } catch { + return void 0; + } } return recordIdentifierFor(this).id; } @@ -467,7 +468,10 @@ class Model extends EmberObject { const normalizedId = coerceId(id); const identifier = recordIdentifierFor(this); let didChange = normalizedId !== identifier.id; - assert(`Cannot set ${identifier.type} record's id to ${id}, because id is already ${identifier.id}`, !didChange || identifier.id === null); + assert( + `Cannot set ${identifier.type} record's id to ${id}, because id is already ${identifier.id}`, + !didChange || identifier.id === null + ); if (normalizedId !== null && didChange) { this.store._instanceCache.setRecordId(identifier.type, normalizedId, identifier.lid); @@ -815,7 +819,7 @@ class Model extends EmberObject { rollbackAttributes() { const { currentState } = this; recordDataFor(this).rollbackAttributes(); - record.errors.clear(); + this.errors.clear(); currentState.cleanErrorRequests(); } diff --git a/packages/model/addon/-private/promise-many-array.ts b/packages/model/addon/-private/promise-many-array.ts index da91a3739b0..6f8336055d6 100644 --- a/packages/model/addon/-private/promise-many-array.ts +++ b/packages/model/addon/-private/promise-many-array.ts @@ -9,7 +9,7 @@ import { resolve } from 'rsvp'; import type { ManyArray } from 'ember-data/-private'; -import type { InternalModel } from '@ember-data/store/-private'; +import { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; export interface HasManyProxyCreateArgs { @@ -42,7 +42,7 @@ export interface HasManyProxyCreateArgs { @class PromiseManyArray @public */ -export default interface PromiseManyArray extends Omit, 'destroy'> {} +export default interface PromiseManyArray extends Omit, 'destroy'> {} export default class PromiseManyArray { declare promise: Promise | null; declare isDestroyed: boolean; diff --git a/packages/model/addon/-private/record-state.ts b/packages/model/addon/-private/record-state.ts index 832908b774e..1a76f324f5c 100644 --- a/packages/model/addon/-private/record-state.ts +++ b/packages/model/addon/-private/record-state.ts @@ -104,8 +104,9 @@ export function tagged(_target, key, desc) { } /** -Historically InternalModel managed a state machine -the currentState for which was reflected onto Model. +Historically EmberData managed a state machine +for each record, the currentState for which +was reflected onto Model. This implements the flags and stateName for backwards compat with the state tree that used to be possible (listed below). @@ -304,7 +305,6 @@ export default class RecordState { return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0; } - // TODO @runspired handle "unloadRecord" see note in InternalModel @tagged get isLoaded() { if (this.isNew) { diff --git a/packages/private-build-infra/addon/current-deprecations.ts b/packages/private-build-infra/addon/current-deprecations.ts index f8b014e3a38..64b70f3a597 100644 --- a/packages/private-build-infra/addon/current-deprecations.ts +++ b/packages/private-build-infra/addon/current-deprecations.ts @@ -44,7 +44,6 @@ export default { DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS: '4.5', DEPRECATE_STORE_FIND: '4.5', DEPRECATE_HAS_RECORD: '4.5', - DEPRECATE_RECORD_WAS_INVALID: '4.5', DEPRECATE_STRING_ARG_SCHEMAS: '4.5', DEPRECATE_JSON_API_FALLBACK: '4.5', DEPRECATE_MODEL_REOPEN: '4.8', diff --git a/packages/private-build-infra/addon/deprecations.ts b/packages/private-build-infra/addon/deprecations.ts index 2a21099af5c..f0dd718a135 100644 --- a/packages/private-build-infra/addon/deprecations.ts +++ b/packages/private-build-infra/addon/deprecations.ts @@ -13,7 +13,6 @@ export const DEPRECATE_RSVP_PROMISE = deprecationState('DEPRECATE_RSVP_PROMISE') export const DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS = deprecationState('DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS'); export const DEPRECATE_STORE_FIND = deprecationState('DEPRECATE_STORE_FIND'); export const DEPRECATE_HAS_RECORD = deprecationState('DEPRECATE_HAS_RECORD'); -export const DEPRECATE_RECORD_WAS_INVALID = deprecationState('DEPRECATE_RECORD_WAS_INVALID'); export const DEPRECATE_STRING_ARG_SCHEMAS = deprecationState('DEPRECATE_STRING_ARG_SCHEMAS'); export const DEPRECATE_JSON_API_FALLBACK = deprecationState('DEPRECATE_JSON_API_FALLBACK'); export const DEPRECATE_MODEL_REOPEN = deprecationState('DEPRECATE_MODEL_REOPEN'); diff --git a/packages/record-data/addon/-private/coerce-id.ts b/packages/record-data/addon/-private/coerce-id.ts index e9f341d774a..405d647bd64 100644 --- a/packages/record-data/addon/-private/coerce-id.ts +++ b/packages/record-data/addon/-private/coerce-id.ts @@ -8,7 +8,7 @@ import { DEBUG } from '@glimmer/env'; // corresponding record, we will not know if it is a string or a number. type Coercable = string | number | boolean | null | undefined | symbol; -function coerceId(id: Coercable): string | null { +export function coerceId(id: Coercable): string | null { if (id === null || id === undefined || id === '') { return null; } @@ -35,5 +35,3 @@ export function ensureStringId(id: Coercable): string { return normalized!; } - -export default coerceId; diff --git a/packages/record-data/addon/-private/graph/-utils.ts b/packages/record-data/addon/-private/graph/-utils.ts index 713b7ed913a..3033bf15b19 100644 --- a/packages/record-data/addon/-private/graph/-utils.ts +++ b/packages/record-data/addon/-private/graph/-utils.ts @@ -1,11 +1,12 @@ import { assert, inspect, warn } from '@ember/debug'; -import { coerceId, recordDataFor as peekRecordData } from '@ember-data/store/-private'; +import { recordDataFor as peekRecordData } from '@ember-data/store/-private'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; import type { RelationshipRecordData } from '@ember-data/types/q/relationship-record-data'; import type { Dict } from '@ember-data/types/q/utils'; +import { coerceId } from '../coerce-id'; import type BelongsToRelationship from '../relationships/state/belongs-to'; import type ManyRelationship from '../relationships/state/has-many'; import type ImplicitRelationship from '../relationships/state/implicit'; diff --git a/packages/record-data/addon/-private/graph/index.ts b/packages/record-data/addon/-private/graph/index.ts index 7ce5ba4fcca..cc0f6146303 100644 --- a/packages/record-data/addon/-private/graph/index.ts +++ b/packages/record-data/addon/-private/graph/index.ts @@ -59,7 +59,7 @@ export function graphFor(store: RecordDataStoreWrapper | Store): Graph { * Graph acts as the cache for relationship data. It allows for * us to ask about and update relationships for a given Identifier * without requiring other objects for that Identifier to be - * instantiated (such as `InternalModel`, `RecordData` or a `Record`) + * instantiated (such as `RecordData` or a `Record`) * * This also allows for us to make more substantive changes to relationships * with increasingly minor alterations to other portions of the internals @@ -398,9 +398,9 @@ function destroyRelationship(rel) { rel.clear(); // necessary to clear relationships in the ui from dematerialized records - // hasMany is managed by InternalModel which calls `retreiveLatest` after + // hasMany is managed by Model which calls `retreiveLatest` after // dematerializing the recordData instance. - // but sync belongsTo require this since they don't have a proxy to update. + // but sync belongsTo requires this since they don't have a proxy to update. // so we have to notify so it will "update" to null. // we should discuss whether we still care about this, probably fine to just // leave the ui relationship populated since the record is destroyed and diff --git a/packages/record-data/addon/-private/record-data.ts b/packages/record-data/addon/-private/record-data.ts index 6a06c3c48eb..393c3470863 100644 --- a/packages/record-data/addon/-private/record-data.ts +++ b/packages/record-data/addon/-private/record-data.ts @@ -16,7 +16,6 @@ import type { RelationshipRecordData, } from '@ember-data/types/q/relationship-record-data'; -import coerceId from './coerce-id'; import { isImplicit } from './graph/-utils'; import { graphFor } from './graph/index'; import type BelongsToRelationship from './relationships/state/belongs-to'; @@ -689,7 +688,7 @@ export default class RecordDataDefault implements RelationshipRecordData { } /* - Ember Data has 3 buckets for storing the value of an attribute on an internalModel. + Ember Data has 3 buckets for storing the value of an attribute. `_data` holds all of the attributes that have been acknowledged by a backend via the adapter. When rollbackAttributes is called on a model all diff --git a/packages/record-data/addon/-private/relationships/state/has-many.ts b/packages/record-data/addon/-private/relationships/state/has-many.ts index fabbed87401..449636eeda5 100755 --- a/packages/record-data/addon/-private/relationships/state/has-many.ts +++ b/packages/record-data/addon/-private/relationships/state/has-many.ts @@ -96,20 +96,20 @@ export default class ManyRelationship { let seen = Object.create(null); for (let i = 0; i < this.currentState.length; i++) { - const inverseInternalModel = this.currentState[i]; - const id = inverseInternalModel.lid; + const inverseIdentifier = this.currentState[i]; + const id = inverseIdentifier.lid; if (!seen[id]) { seen[id] = true; - callback(inverseInternalModel); + callback(inverseIdentifier); } } for (let i = 0; i < this.canonicalState.length; i++) { - const inverseInternalModel = this.canonicalState[i]; - const id = inverseInternalModel.lid; + const inverseIdentifier = this.canonicalState[i]; + const id = inverseIdentifier.lid; if (!seen[id]) { seen[id] = true; - callback(inverseInternalModel); + callback(inverseIdentifier); } } } diff --git a/packages/store/addon/-debug/index.js b/packages/store/addon/-debug/index.js index 8bc49a30cd4..c0e32c40e09 100644 --- a/packages/store/addon/-debug/index.js +++ b/packages/store/addon/-debug/index.js @@ -9,7 +9,7 @@ import { DEBUG } from '@glimmer/env'; relationship (specified via `relationshipMeta`) of the `record`. This utility should only be used internally, as both record parameters must - be an InternalModel and the `relationshipMeta` needs to be the meta + be stable record identifiers and the `relationshipMeta` needs to be the meta information about the relationship, retrieved via `record.relationshipFor(key)`. */ diff --git a/packages/store/addon/-private/caches/identifier-cache.ts b/packages/store/addon/-private/caches/identifier-cache.ts index ffdcefe6ba8..5d2598b6a0a 100644 --- a/packages/store/addon/-private/caches/identifier-cache.ts +++ b/packages/store/addon/-private/caches/identifier-cache.ts @@ -141,8 +141,8 @@ export class IdentifierCache { /** * Internal hook to allow management of merge conflicts with identifiers. * - * we allow late binding of this private internal merge so that `internalModelFactory` - * can insert itself here to handle elimination of duplicates + * we allow late binding of this private internal merge so that + * the cache can insert itself here to handle elimination of duplicates * * @method __configureMerge * @private diff --git a/packages/store/addon/-private/caches/instance-cache.ts b/packages/store/addon/-private/caches/instance-cache.ts index e6ea37cbf28..85472688276 100644 --- a/packages/store/addon/-private/caches/instance-cache.ts +++ b/packages/store/addon/-private/caches/instance-cache.ts @@ -3,17 +3,23 @@ import { DEBUG } from '@glimmer/env'; import { resolve } from 'rsvp'; -import type { ExistingResourceObject, NewResourceIdentifierObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api'; +import type { + ExistingResourceObject, + NewResourceIdentifierObject, + ResourceIdentifierObject, +} from '@ember-data/types/q/ember-data-json-api'; import type { RecordIdentifier, StableExistingRecordIdentifier, StableRecordIdentifier, } from '@ember-data/types/q/identifier'; import type { RecordData } from '@ember-data/types/q/record-data'; +import { JsonApiRelationship, JsonApiResource } from '@ember-data/types/q/record-data-json-api'; +import { RelationshipSchema } from '@ember-data/types/q/record-data-schemas'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { FindOptions } from '@ember-data/types/q/store'; +import { Dict } from '@ember-data/types/q/utils'; -import InternalModel from '../legacy-model-support/internal-model'; import RecordReference from '../legacy-model-support/record-reference'; import RecordDataStoreWrapper from '../managers/record-data-store-wrapper'; import Snapshot from '../network/snapshot'; @@ -24,11 +30,60 @@ import coerceId, { ensureStringId } from '../utils/coerce-id'; import constructResource from '../utils/construct-resource'; import normalizeModelName from '../utils/normalize-model-name'; import WeakCache from '../utils/weak-cache'; -import { internalModelFactoryFor, recordIdentifierFor, setRecordIdentifier } from './internal-model-factory'; import recordDataFor, { setRecordDataFor } from './record-data-for'; -import { JsonApiResource } from '@ember-data/types/q/record-data-json-api'; -import { Dict } from '@ember-data/types/q/utils'; -import { isStableIdentifier } from './identifier-cache'; + +/** + @module @ember-data/store +*/ + +const RecordCache = new WeakCache(DEBUG ? 'identifier' : ''); +if (DEBUG) { + RecordCache._expectMsg = (key: RecordInstance | RecordData) => + `${String(key)} is not a record instantiated by @ember-data/store`; +} + +export function peekRecordIdentifier(record: RecordInstance | RecordData): StableRecordIdentifier | undefined { + return RecordCache.get(record); +} + +/** + Retrieves the unique referentially-stable [RecordIdentifier](/ember-data/release/classes/StableRecordIdentifier) + assigned to the given record instance. + ```js + import { recordIdentifierFor } from "@ember-data/store"; + // ... gain access to a record, for instance with peekRecord or findRecord + const record = store.peekRecord("user", "1"); + // get the identifier for the record (see docs for StableRecordIdentifier) + const identifier = recordIdentifierFor(record); + // access the identifier's properties. + const { id, type, lid } = identifier; + ``` + @method recordIdentifierFor + @public + @static + @for @ember-data/store + @param {Object} record a record instance previously obstained from the store. + @returns {StableRecordIdentifier} + */ +export function recordIdentifierFor(record: RecordInstance | RecordData): StableRecordIdentifier { + return RecordCache.getWithError(record); +} + +export function setRecordIdentifier(record: RecordInstance | RecordData, identifier: StableRecordIdentifier): void { + if (DEBUG && RecordCache.has(record) && RecordCache.get(record) !== identifier) { + throw new Error(`${String(record)} was already assigned an identifier`); + } + + /* + It would be nice to do a reverse check here that an identifier has not + previously been assigned a record; however, unload + rematerialization + prevents us from having a great way of doing so when CustomRecordClasses + don't necessarily give us access to a `isDestroyed` for dematerialized + instance. + */ + + RecordCache.set(record, identifier); +} const RECORD_REFERENCES = new WeakCache(DEBUG ? 'reference' : ''); export const StoreMap = new WeakCache(DEBUG ? 'store' : ''); @@ -48,13 +103,11 @@ type Caches = { recordData: WeakMap; }; - export class InstanceCache { declare store: Store; declare _storeWrapper: RecordDataStoreWrapper; declare peekList: Dict>; - #instances: Caches = { record: new WeakMap(), recordData: new WeakMap(), @@ -65,7 +118,7 @@ export class InstanceCache { if (!recordData) { return false; } - const isNew = recordData.isNew?.() || false; + const isNew = recordData.isNew?.() || false; const isEmpty = recordData.isEmpty?.() || false; // if we are new we must consider ourselves loaded @@ -79,12 +132,12 @@ export class InstanceCache { // we should consider allowing for something to be loaded that is simply "not empty". // which is how RecordState currently handles this case; however, RecordState is buggy // in that it does not account for unloading. - return !isEmpty + return !isEmpty; } constructor(store: Store) { this.store = store; - this.peekList = Object.create(null); + this.peekList = Object.create(null) as Dict>; this._storeWrapper = new RecordDataStoreWrapper(this.store); this.__recordDataFor = this.__recordDataFor.bind(this); @@ -93,72 +146,84 @@ export class InstanceCache { return new RecordReference(this.store, identifier); }; - store.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => { - let intendedIdentifier = identifier; - if (identifier.id !== matchedIdentifier.id) { - intendedIdentifier = 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier; - } else if (identifier.type !== matchedIdentifier.type) { - intendedIdentifier = - 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier; - } - let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier; - - // check for duplicate entities - let imHasRecord = this.#instances.record.has(intendedIdentifier); - let otherHasRecord = this.#instances.record.has(altIdentifier); - let imRecordData = this.#instances.recordData.get(intendedIdentifier) || null; - let otherRecordData = this.#instances.record.get(altIdentifier) || null; - - // we cannot merge entities when both have records - // (this may not be strictly true, we could probably swap the recordData the record points at) - if (imHasRecord && otherHasRecord) { - // TODO we probably don't need to throw these errors anymore - // once InternalModel is fully removed, as we can just "swap" - // what data source the abandoned record points at so long as - // it itself is not retained by the store in any way. - if ('id' in resourceData) { - throw new Error( - `Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to '${resourceData.id}', because that id is already in use by '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'` + store.identifierCache.__configureMerge( + (identifier: StableRecordIdentifier, matchedIdentifier: StableRecordIdentifier, resourceData) => { + let intendedIdentifier = identifier; + if (identifier.id !== matchedIdentifier.id) { + intendedIdentifier = + 'id' in resourceData && identifier.id === resourceData.id ? identifier : matchedIdentifier; + } else if (identifier.type !== matchedIdentifier.type) { + intendedIdentifier = + 'type' in resourceData && identifier.type === resourceData.type ? identifier : matchedIdentifier; + } + let altIdentifier = identifier === intendedIdentifier ? matchedIdentifier : identifier; + + // check for duplicate entities + let imHasRecord = this.#instances.record.has(intendedIdentifier); + let otherHasRecord = this.#instances.record.has(altIdentifier); + let imRecordData = this.#instances.recordData.get(intendedIdentifier) || null; + let otherRecordData = this.#instances.recordData.get(altIdentifier) || null; + + // we cannot merge entities when both have records + // (this may not be strictly true, we could probably swap the recordData the record points at) + if (imHasRecord && otherHasRecord) { + // TODO we probably don't need to throw these errors anymore + // we can probably just "swap" what data source the abandoned + // record points at so long as + // it itself is not retained by the store in any way. + if ('id' in resourceData) { + throw new Error( + `Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${ + identifier.lid + })' to '${String(resourceData.id)}', because that id is already in use by '${ + matchedIdentifier.type + }:${String(matchedIdentifier.id)} (${matchedIdentifier.lid})'` + ); + } + // TODO @runspired determine when this is even possible + assert( + `Failed to update the RecordIdentifier '${identifier.type}:${String(identifier.id)} (${ + identifier.lid + })' to merge with the detected duplicate identifier '${matchedIdentifier.type}:${String( + matchedIdentifier.id + )} (${String(matchedIdentifier.lid)})'` ); } - // TODO @runspired determine when this is even possible - assert( - `Failed to update the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to merge with the detected duplicate identifier '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'` - ); - } - - // remove "other" from cache - if (otherHasRecord) { - cache.delete(altIdentifier); - this.peekList[altIdentifier.type]?.delete(altIdentifier); - } - if (imRecordData === null && otherRecordData === null) { - // nothing more to do - return intendedIdentifier; + // remove "other" from cache + if (otherHasRecord) { + // TODO probably need to release other things + this.peekList[altIdentifier.type]?.delete(altIdentifier); + } - // only the other has a RecordData - // OR only the other has a Record - } else if ((imRecordData === null && otherRecordData !== null) || (imRecordData && !imHasRecord && otherRecordData && otherHasRecord)) { - if (imRecordData) { - // TODO check if we are retained in any async relationships - cache.delete(intendedIdentifier); - this.peekList[intendedIdentifier.type]?.delete(intendedIdentifier); - // im.destroy(); + if (imRecordData === null && otherRecordData === null) { + // nothing more to do + return intendedIdentifier; + + // only the other has a RecordData + // OR only the other has a Record + } else if ( + (imRecordData === null && otherRecordData !== null) || + (imRecordData && !imHasRecord && otherRecordData && otherHasRecord) + ) { + if (imRecordData) { + // TODO check if we are retained in any async relationships + // TODO probably need to release other things + this.peekList[intendedIdentifier.type]?.delete(intendedIdentifier); + // im.destroy(); + } + imRecordData = otherRecordData!; + // TODO do we need to notify the id change? + // TODO swap recordIdentifierFor result? + this.peekList[intendedIdentifier.type] = this.peekList[intendedIdentifier.type] || new Set(); + this.peekList[intendedIdentifier.type]!.add(intendedIdentifier); + + // just use im + } else { + // otherIm.destroy(); } - im = otherIm!; - // TODO do we need to notify the id change? - im.identifier = intendedIdentifier; - cache.set(intendedIdentifier, im); - this.peekList[intendedIdentifier.type] = this.peekList[intendedIdentifier.type] || new Set(); - this.peekList[intendedIdentifier.type]!.add(intendedIdentifier); - - // just use im - } else { - // otherIm.destroy(); - } - /* + /* TODO @runspired consider adding this to make polymorphism even nicer if (HAS_RECORD_DATA_PACKAGE) { if (identifier.type !== matchedIdentifier.type) { @@ -168,8 +233,9 @@ export class InstanceCache { } */ - return intendedIdentifier; - }); + return intendedIdentifier; + } + ); } peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'record' }): RecordInstance | undefined; peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'recordData' }): RecordData | undefined; @@ -194,7 +260,6 @@ export class InstanceCache { this.#instances[bucket].set(identifier, value); } - getRecord(identifier: StableRecordIdentifier, properties?: CreateRecordProperties): RecordInstance { let record = this.peek({ identifier, bucket: 'record' }); @@ -239,15 +304,13 @@ export class InstanceCache { identifier: StableRecordIdentifier, options: FindOptions = {} ): Promise { - const internalModel = this.getInternalModel(identifier); - // pre-loading will change the isEmpty value - // TODO stpre this state somewhere other than InternalModel - const { isEmpty, isLoading } = internalModel; + const isEmpty = _isEmpty(this, identifier); + const isLoading = _isLoading(this, identifier); if (options.preload) { this.store._backburner.join(() => { - preloadData(this.store, identifier, options.preload); + preloadData(this.store, identifier, options.preload!); }); } @@ -347,7 +410,6 @@ export class InstanceCache { if (!recordData) { recordData = this._createRecordData(identifier); this.#instances.recordData.set(identifier, recordData); - this.getInternalModel(identifier).hasRecordData = true; this.peekList[identifier.type] = this.peekList[identifier.type] || new Set(); this.peekList[identifier.type]!.add(identifier); } @@ -355,11 +417,6 @@ export class InstanceCache { return recordData; } - // TODO move InternalModel cache into InstanceCache - getInternalModel(identifier: StableRecordIdentifier) { - return this._internalModelForResource(identifier); - } - createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot { return new Snapshot(options, identifier, this.store); } @@ -412,7 +469,7 @@ export class InstanceCache { // ID absolutely can't be different than oldID if oldID is not null // TODO this assertion and restriction may not strictly be needed in the identifiers world assert( - `Cannot update the id for '${modelName}:${lid}' from '${oldId}' to '${id}'.`, + `Cannot update the id for '${modelName}:${lid}' from '${String(oldId)}' to '${id}'.`, !(oldId !== null && id !== oldId) ); @@ -430,7 +487,7 @@ export class InstanceCache { assert( `'${modelName}' was saved to the server, but the response returned the new id '${id}', which has already been used with another record.'`, - existingIdentifier === identifier + existingIdentifier === identifier ); if (identifier.id === null) { @@ -455,31 +512,24 @@ export class InstanceCache { // TODO this should determine identifier via the cache before making assumptions const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid)); - const maybeIdentifier = this.store.identifierCache.peekRecordIdentifier(resource); - - let internalModel = internalModelFactoryFor(this.store).lookup(resource, data); + // we probably should ony peek here + let identifier = this.store.identifierCache.peekRecordIdentifier(resource); + let isUpdate = false; // store.push will be from empty // findRecord will be from root.loading // this cannot be loading state if we do not already have an identifier // all else will be updates - const isLoading = internalModel.isLoading || (maybeIdentifier && !this.recordIsLoaded(maybeIdentifier)); - const isUpdate = internalModel.isEmpty === false && !isLoading; - - // exclude store.push (root.empty) case - let identifier = internalModel.identifier; - if (isUpdate || isLoading) { - let updatedIdentifier = this.store.identifierCache.updateRecordIdentifier(identifier, data); - - if (updatedIdentifier !== identifier) { - // we encountered a merge of identifiers in which - // two identifiers (and likely two internalModels) - // existed for the same resource. Now that we have - // determined the correct identifier to use, make sure - // that we also use the correct internalModel. - identifier = updatedIdentifier; - internalModel = internalModelFactoryFor(this.store).lookup(identifier); + if (identifier) { + const isLoading = _isLoading(this, identifier) || !this.recordIsLoaded(identifier); + isUpdate = !_isEmpty(this, identifier) && !isLoading; + + // exclude store.push (root.empty) case + if (isUpdate || isLoading) { + identifier = this.store.identifierCache.updateRecordIdentifier(identifier, data); } + } else { + identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource); } const recordData = this.getRecordData(identifier); @@ -516,10 +566,9 @@ export class InstanceCache { return req.type === 'mutation'; }) ) { - assert('You can only unload a record which is not inFlight. `' + identifier + '`'); + assert(`You can only unload a record which is not inFlight. '${String(identifier)}'`); } } - const internalModel = this.getInternalModel(identifier); // this has to occur before the internal model is removed // for legacy compat. @@ -551,11 +600,7 @@ export function isHiddenFromRecordArrays(cache: InstanceCache, identifier: Stabl return true; } - // if isLoading return false - // if isDematerializing, destroyed, or has scheduled destroy return true - // TODO eliminate this internalModel need - const internalModel = cache.getInternalModel(identifier); - if (!internalModel.isEmpty || internalModel.isLoading) { + if (!_isEmpty(cache, identifier) || _isLoading(cache, identifier)) { return false; } if (recordData.isDeletionCommitted?.() || (recordData.isNew?.() && recordData.isDeleted?.())) { @@ -623,7 +668,7 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is return !!record.then; } - /* +/* When a find request is triggered on the store, the user can optionally pass in attributes and relationships to be preloaded. These are meant to behave as if they came back from the server, except the user obtained them out of band and is informing @@ -634,18 +679,24 @@ function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is Preloaded data can be attributes and relationships passed in either as IDs or as actual models. */ -function preloadData(store: Store, identifier: StableRecordIdentifier, preload) { +type PreloadRelationshipValue = RecordInstance | string; +function preloadData(store: Store, identifier: StableRecordIdentifier, preload: Dict) { let jsonPayload: JsonApiResource = {}; //TODO(Igor) consider the polymorphic case - const modelClass = store.modelFor(identifier.type); + const schemas = store.getSchemaDefinitionService(); + const relationships = schemas.relationshipsDefinitionFor(identifier); Object.keys(preload).forEach((key) => { let preloadValue = preload[key]; - let relationshipMeta = modelClass.metaForProperty(key); - if (relationshipMeta.isRelationship) { + + let relationshipMeta = relationships[key]; + if (relationshipMeta) { if (!jsonPayload.relationships) { jsonPayload.relationships = {}; } - jsonPayload.relationships[key] = preloadRelationship(modelClass, key, preloadValue); + jsonPayload.relationships[key] = preloadRelationship( + relationshipMeta, + preloadValue as PreloadRelationshipValue | null | Array + ); } else { if (!jsonPayload.attributes) { jsonPayload.attributes = {}; @@ -656,25 +707,26 @@ function preloadData(store: Store, identifier: StableRecordIdentifier, preload) store._instanceCache.getRecordData(identifier).pushData(jsonPayload); } +function preloadRelationship( + schema: RelationshipSchema, + preloadValue: PreloadRelationshipValue | null | Array +): JsonApiRelationship { + const relatedType = schema.type; -function preloadRelationship(schema, key: string, preloadValue) { - const relationshipMeta = schema.metaForProperty(key); - const relatedType = relationshipMeta.type; - let data; - if (relationshipMeta.kind === 'hasMany') { + if (schema.kind === 'hasMany') { assert('You need to pass in an array to set a hasMany property on a record', Array.isArray(preloadValue)); - data = preloadValue.map((value) => _convertPreloadRelationshipToJSON(value, relatedType)); - } else { - data = _convertPreloadRelationshipToJSON(preloadValue, relatedType); + return { data: preloadValue.map((value) => _convertPreloadRelationshipToJSON(value, relatedType)) }; } - return { data }; + + assert('You should not pass in an array to set a belongsTo property on a record', !Array.isArray(preloadValue)); + return { data: preloadValue ? _convertPreloadRelationshipToJSON(preloadValue, relatedType) : null }; } /* findRecord('user', '1', { preload: { friends: ['1'] }}); findRecord('user', '1', { preload: { friends: [record] }}); */ -function _convertPreloadRelationshipToJSON(value, type: string) { +function _convertPreloadRelationshipToJSON(value: RecordInstance | string, type: string): ResourceIdentifierObject { if (typeof value === 'string' || typeof value === 'number') { return { type, id: value }; } @@ -682,3 +734,27 @@ function _convertPreloadRelationshipToJSON(value, type: string) { // and allow identifiers to be used return recordIdentifierFor(value); } + +function _isEmpty(cache: InstanceCache, identifier: StableRecordIdentifier): boolean { + const recordData = cache.peek({ identifier: identifier, bucket: 'recordData' }); + if (!recordData) { + return true; + } + const isNew = recordData.isNew?.() || false; + const isDeleted = recordData.isDeleted?.() || false; + const isEmpty = recordData.isEmpty?.() || false; + + return (!isNew || isDeleted) && isEmpty; +} + +function _isLoading(cache: InstanceCache, identifier: StableRecordIdentifier): boolean { + const req = cache.store.getRequestStateService(); + // const fulfilled = req.getLastRequestForRecord(identifier); + const isLoaded = cache.recordIsLoaded(identifier); + + return ( + !isLoaded && + // fulfilled === null && + req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query') + ); +} diff --git a/packages/store/addon/-private/index.ts b/packages/store/addon/-private/index.ts index da37ffbd472..096ec00dca1 100644 --- a/packages/store/addon/-private/index.ts +++ b/packages/store/addon/-private/index.ts @@ -10,7 +10,7 @@ import _normalize from './utils/normalize-model-name'; export { default as Store, storeFor } from './store-service'; -export { recordIdentifierFor } from './caches/internal-model-factory'; +export { recordIdentifierFor } from './caches/instance-cache'; export { default as Snapshot } from './network/snapshot'; export { @@ -39,9 +39,6 @@ export function normalizeModelName(modelName: string) { export { default as coerceId } from './utils/coerce-id'; -// `ember-data-model-fragments` relies on `InternalModel` -export { default as InternalModel } from './legacy-model-support/internal-model'; - export { PromiseArray, PromiseObject, deprecatedPromiseObject } from './proxies/promise-proxies'; export { default as RecordArray } from './record-arrays/record-array'; diff --git a/packages/store/addon/-private/legacy-model-support/internal-model.ts b/packages/store/addon/-private/legacy-model-support/internal-model.ts deleted file mode 100644 index e305b8ad9e3..00000000000 --- a/packages/store/addon/-private/legacy-model-support/internal-model.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; -import type { RecordData } from '@ember-data/types/q/record-data'; - -import type Store from '../store-service'; - -export default class InternalModel { - declare hasRecordData: boolean; - declare _isDestroyed: boolean; - declare isDestroying: boolean; - declare _isUpdatingId: boolean; - - // Not typed yet - declare store: Store; - declare identifier: StableRecordIdentifier; - declare hasRecord: boolean; - - constructor(store: Store, identifier: StableRecordIdentifier) { - this.store = store; - this.identifier = identifier; - this._isUpdatingId = false; - - this.hasRecordData = false; - - this._isDestroyed = false; - } - - // STATE we end up needing for various reasons - get _recordData(): RecordData { - return this.store._instanceCache.getRecordData(this.identifier); - } - - isDeleted(): boolean { - if (this._recordData.isDeleted) { - return this._recordData.isDeleted(); - } else { - return false; - } - } - - isNew(): boolean { - if (this.hasRecordData && this._recordData.isNew) { - return this._recordData.isNew(); - } else { - return false; - } - } - - get isEmpty(): boolean { - if (!this.hasRecordData) { - return true; - } - const recordData = this._recordData; - const isNew = recordData.isNew?.() || false; - const isDeleted = recordData.isDeleted?.() || false; - const isEmpty = recordData.isEmpty?.() || false; - - return (!isNew || isDeleted) && isEmpty; - } - - get isLoading() { - const req = this.store.getRequestStateService(); - const { identifier } = this; - // const fulfilled = req.getLastRequestForRecord(identifier); - const isLoaded = this.store._instanceCache.recordIsLoaded(identifier); - - return ( - !isLoaded && - // fulfilled === null && - req.getPendingRequestsForRecord(identifier).some((req) => req.type === 'query') - ); - } - - get isDestroyed(): boolean { - return this._isDestroyed; - } - - toString() { - return `<${this.identifier.type}:${this.identifier.id}>`; - } -} diff --git a/packages/store/addon/-private/managers/record-array-manager.ts b/packages/store/addon/-private/managers/record-array-manager.ts index 60ed00f313b..e3970cc2dd2 100644 --- a/packages/store/addon/-private/managers/record-array-manager.ts +++ b/packages/store/addon/-private/managers/record-array-manager.ts @@ -26,17 +26,6 @@ export function recordArraysForIdentifier(identifier: StableRecordIdentifier): S const pendingForIdentifier: Set = new Set([]); -function getIdentifier(identifier: StableRecordIdentifier): StableRecordIdentifier { - // during dematerialization we will get an identifier that - // has already been removed from the identifiers cache - // so it will not behave as if stable. This is a bug we should fix. - // if (!isStableIdentifier(identifierOrInternalModel)) { - // console.log({ unstable: i }); - // } - - return identifier; -} - /** @class RecordArrayManager @internal @@ -325,7 +314,6 @@ class RecordArrayManager { _associateWithRecordArray(identifiers: StableRecordIdentifier[], array: RecordArray): void { for (let i = 0, l = identifiers.length; i < l; i++) { let identifier = identifiers[i]; - identifier = getIdentifier(identifier); let recordArrays = this.getRecordArraysForIdentifier(identifier); recordArrays.add(array); } @@ -340,7 +328,6 @@ class RecordArrayManager { return; } let modelName = identifier.type; - identifier = getIdentifier(identifier); if (pendingForIdentifier.has(identifier)) { return; @@ -421,7 +408,6 @@ function removeFromAdapterPopulatedRecordArrays(store: Store, identifiers: Stabl } function removeFromAll(store: Store, identifier: StableRecordIdentifier): void { - identifier = getIdentifier(identifier); const recordArrays = recordArraysForIdentifier(identifier); recordArrays.forEach(function (recordArray) { diff --git a/packages/store/addon/-private/managers/record-data-store-wrapper.ts b/packages/store/addon/-private/managers/record-data-store-wrapper.ts index cbf1621be91..6b36a793f66 100644 --- a/packages/store/addon/-private/managers/record-data-store-wrapper.ts +++ b/packages/store/addon/-private/managers/record-data-store-wrapper.ts @@ -186,9 +186,10 @@ export default class RecordDataStoreWrapper implements StoreWrapper { // enforcing someone to use the record-data and identifier-cache APIs to // create a new identifier and then call clientDidCreate on the RecordData // instead. - const identifier = !id && !lid ? - this.identifierCache.createIdentifierForNewRecord({ type: type }) : - this.identifierCache.getOrCreateRecordIdentifier(constructResource(type, id, lid)); + const identifier = + !id && !lid + ? this.identifierCache.createIdentifierForNewRecord({ type: type }) + : this.identifierCache.getOrCreateRecordIdentifier(constructResource(type, id, lid)); return this._store._instanceCache.getRecordData(identifier); } diff --git a/packages/store/addon/-private/network/snapshot.ts b/packages/store/addon/-private/network/snapshot.ts index ee7a1595bbb..4f9133f2b44 100644 --- a/packages/store/addon/-private/network/snapshot.ts +++ b/packages/store/addon/-private/network/snapshot.ts @@ -9,11 +9,7 @@ import { HAS_RECORD_DATA_PACKAGE } from '@ember-data/private-build-infra'; import { DEPRECATE_SNAPSHOT_MODEL_CLASS_ACCESS } from '@ember-data/private-build-infra/deprecations'; import type BelongsToRelationship from '@ember-data/record-data/addon/-private/relationships/state/belongs-to'; import type ManyRelationship from '@ember-data/record-data/addon/-private/relationships/state/has-many'; -import type { DSModel, DSModelSchema, ModelSchema } from '@ember-data/types/q/ds-model'; -import type { - ExistingResourceIdentifierObject, - NewResourceIdentifierObject, -} from '@ember-data/types/q/ember-data-json-api'; +import type { DSModelSchema, ModelSchema } from '@ember-data/types/q/ds-model'; import type { StableRecordIdentifier } from '@ember-data/types/q/identifier'; import type { OptionsHash } from '@ember-data/types/q/minimum-serializer-interface'; import type { ChangedAttributesHash } from '@ember-data/types/q/record-data'; @@ -22,7 +18,6 @@ import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { FindOptions } from '@ember-data/types/q/store'; import type { Dict } from '@ember-data/types/q/utils'; -import type InternalModel from '../legacy-model-support/internal-model'; import type Store from '../store-service'; type RecordId = string | null; @@ -158,7 +153,7 @@ export default class Snapshot implements Snapshot { let attributes = (this.__attributes = Object.create(null)); let attrs = Object.keys(this._store.getSchemaDefinitionService().attributesDefinitionFor(this.identifier)); let recordData = this._store._instanceCache.getRecordData(this.identifier); - const modelClass = this._store.modelFor(this.identifier.type); + const modelClass = this._store.modelFor(this.identifier.type); const isDSModel = schemaIsDSModel(modelClass); attrs.forEach((keyName) => { if (isDSModel) { @@ -319,7 +314,7 @@ export default class Snapshot implements Snapshot { // TODO @runspired it seems this code branch would not work with CUSTOM_MODEL_CLASSes // this check is not a regression in behavior because relationships don't currently - // function without access to intimate API contracts between RecordData and InternalModel. + // function without access to intimate API contracts between RecordData and Model. // This is a requirement we should fix as soon as the relationship layer does not require // this intimate API usage. if (!HAS_RECORD_DATA_PACKAGE) { @@ -422,7 +417,7 @@ export default class Snapshot implements Snapshot { // TODO @runspired it seems this code branch would not work with CUSTOM_MODEL_CLASSes // this check is not a regression in behavior because relationships don't currently - // function without access to intimate API contracts between RecordData and InternalModel. + // function without access to intimate API contracts between RecordData and Model. // This is a requirement we should fix as soon as the relationship layer does not require // this intimate API usage. if (!HAS_RECORD_DATA_PACKAGE) { diff --git a/packages/store/addon/-private/store-service.ts b/packages/store/addon/-private/store-service.ts index c593626f339..db507bea78b 100644 --- a/packages/store/addon/-private/store-service.ts +++ b/packages/store/addon/-private/store-service.ts @@ -17,7 +17,6 @@ import { HAS_MODEL_PACKAGE, HAS_RECORD_DATA_PACKAGE } from '@ember-data/private- import { DEPRECATE_HAS_RECORD, DEPRECATE_JSON_API_FALLBACK, - DEPRECATE_RECORD_WAS_INVALID, DEPRECATE_STORE_FIND, } from '@ember-data/private-build-infra/deprecations'; import type { RecordData as RecordDataClass } from '@ember-data/record-data/-private'; @@ -33,6 +32,7 @@ import type { StableExistingRecordIdentifier, StableRecordIdentifier } from '@em import type { MinimumAdapterInterface } from '@ember-data/types/q/minimum-adapter-interface'; import type { MinimumSerializerInterface } from '@ember-data/types/q/minimum-serializer-interface'; import type { RecordData } from '@ember-data/types/q/record-data'; +import { JsonApiValidationError } from '@ember-data/types/q/record-data-json-api'; import type { RecordDataRecordWrapper } from '@ember-data/types/q/record-data-record-wrapper'; import type { RecordInstance } from '@ember-data/types/q/record-instance'; import type { SchemaDefinitionService } from '@ember-data/types/q/schema-definition-service'; @@ -41,12 +41,15 @@ import type { Dict } from '@ember-data/types/q/utils'; import edBackburner from './backburner'; import { IdentifierCache } from './caches/identifier-cache'; -import { InstanceCache, recordDataIsFullyDeleted, storeFor, StoreMap } from './caches/instance-cache'; import { + InstanceCache, peekRecordIdentifier, + recordDataIsFullyDeleted, recordIdentifierFor, setRecordIdentifier, -} from './caches/internal-model-factory'; + storeFor, + StoreMap, +} from './caches/instance-cache'; import { setRecordDataFor } from './caches/record-data-for'; import RecordReference from './legacy-model-support/record-reference'; import { DSModelSchemaDefinitionService, getModelFactory } from './legacy-model-support/schema-definition-service'; @@ -65,8 +68,6 @@ import coerceId, { ensureStringId } from './utils/coerce-id'; import constructResource from './utils/construct-resource'; import normalizeModelName from './utils/normalize-model-name'; import promiseRecord from './utils/promise-record'; -import { JsonApiValidationError } from '@ember-data/types/q/record-data-json-api'; -import InternalModel from './legacy-model-support/internal-model'; export { storeFor }; @@ -1770,42 +1771,6 @@ class Store extends Service { } } - /** - This method is called once the promise returned by an - adapter's `createRecord`, `updateRecord` or `deleteRecord` - is rejected with a `InvalidError`. - - @method recordWasInvalid - @private - @deprecated - @param {InternalModel} internalModel - @param {Object} errors - */ - recordWasInvalid(internalModel: InternalModel, parsedErrors, error) { - if (DEPRECATE_RECORD_WAS_INVALID) { - deprecate( - `The private API recordWasInvalid will be removed in an upcoming release. Use record.errors add/remove instead if the intent was to move the record into an invalid state manually.`, - false, - { - id: 'ember-data:deprecate-record-was-invalid', - for: 'ember-data', - until: '5.0', - since: { enabled: '4.5', available: '4.5' }, - } - ); - if (DEBUG) { - assertDestroyingStore(this, 'recordWasInvalid'); - } - error = error || new Error(`unknown invalid error`); - error = typeof error === 'string' ? new Error(error) : error; - error._parsedErrors = parsedErrors; - // TODO figure out how to handle ember-model-validator given internalModel wouldn't be available - // anymore. - adapterDidInvalidate(this, internalModel.identifier, error); - } - assert(`store.recordWasInvalid has been removed`); - } - /** Push some data for a given type into the store. @@ -1985,7 +1950,7 @@ class Store extends Service { @method _push @private @param {Object} jsonApiDoc - @return {InternalModel|Array} pushed InternalModel(s) + @return {StableRecordIdentifier|Array} identifiers for the primary records that had data loaded */ _push(jsonApiDoc): StableExistingRecordIdentifier | StableExistingRecordIdentifier[] | null { if (DEBUG) { @@ -2134,7 +2099,8 @@ class Store extends Service { } // TODO we used to check if the record was destroyed here // Casting can be removed once REQUEST_SERVICE ff is turned on - // because a `Record` is provided there will always be a matching internalModel + // because a `Record` is provided there will always be a matching + // RecordData assert( `Cannot initiate a save request for an unloaded record: ${identifier}`, @@ -2544,7 +2510,11 @@ type SerializerWithParseErrors = MinimumSerializerInterface & { extractErrors?(store: Store, modelClass: ShimModelClass, error: AdapterErrors, recordId: string | null): any; }; -function adapterDidInvalidate(store: Store, identifier: StableRecordIdentifier, error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string }) { +function adapterDidInvalidate( + store: Store, + identifier: StableRecordIdentifier, + error: Error & { errors?: JsonApiValidationError[]; isAdapterError?: true; code?: string } +) { if (error && error.isAdapterError === true && error.code === 'InvalidError') { let serializer = store.serializerFor(identifier.type) as SerializerWithParseErrors; diff --git a/tsconfig.json b/tsconfig.json index 10951be73af..87414519978 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -43,7 +43,6 @@ "ember-data-types/q/ember-data-json-api.ts", "ember-data-types/q/ds-model.ts", "packages/store/addon/-private/managers/record-data-store-wrapper.ts", - "packages/store/addon/-private/caches/internal-model-factory.ts", "packages/store/addon/-private/network/snapshot.ts", "packages/store/addon/-private/network/snapshot-record-array.ts", "packages/store/addon/-private/legacy-model-support/schema-definition-service.ts", @@ -52,7 +51,6 @@ "packages/store/addon/-private/caches/record-data-for.ts", "packages/store/addon/-private/utils/normalize-model-name.ts", "packages/store/addon/-private/legacy-model-support/shim-model-class.ts", - "packages/store/addon/-private/legacy-model-support/internal-model.ts", "packages/store/addon/-private/network/fetch-manager.ts", "packages/store/addon/-private/store-service.ts", "packages/store/addon/-private/utils/coerce-id.ts",