diff --git a/packages/json-api/src/-private/cache.ts b/packages/json-api/src/-private/cache.ts index 44833e873cc..511aee27908 100644 --- a/packages/json-api/src/-private/cache.ts +++ b/packages/json-api/src/-private/cache.ts @@ -70,6 +70,7 @@ interface CachedResource { id: string | null; remoteAttrs: Record | null; localAttrs: Record | null; + defaultAttrs: Record | null; inflightAttrs: Record | null; changes: Record | null; errors: JsonApiError[] | null; @@ -90,6 +91,7 @@ function makeCache(): CachedResource { id: null, remoteAttrs: null, localAttrs: null, + defaultAttrs: null, inflightAttrs: null, changes: null, errors: null, @@ -1036,6 +1038,7 @@ export default class JSONAPICache implements Cache { // we report as `isEmpty` during teardown. cached.localAttrs = null; cached.remoteAttrs = null; + cached.defaultAttrs = null; cached.inflightAttrs = null; const relatedIdentifiers = _allRelatedIdentifiers(storeWrapper, identifier); @@ -1096,11 +1099,18 @@ export default class JSONAPICache implements Cache { return cached.inflightAttrs[attr]; } else if (cached.remoteAttrs && attr in cached.remoteAttrs) { return cached.remoteAttrs[attr]; + } else if (cached.defaultAttrs && attr in cached.defaultAttrs) { + return cached.defaultAttrs[attr]; } else { const attrSchema = this._capabilities.schema.fields(identifier).get(attr); upgradeCapabilities(this._capabilities); - return getDefaultValue(attrSchema, identifier, this._capabilities._store); + const defaultValue = getDefaultValue(attrSchema, identifier, this._capabilities._store); + if (typeof attrSchema?.options?.defaultValue === 'function') { + cached.defaultAttrs = cached.defaultAttrs || (Object.create(null) as Record); + cached.defaultAttrs[attr] = defaultValue; + } + return defaultValue; } } @@ -1133,6 +1143,10 @@ export default class JSONAPICache implements Cache { delete cached.changes![attr]; } + if (cached.defaultAttrs && attr in cached.defaultAttrs) { + delete cached.defaultAttrs[attr]; + } + this._capabilities.notifyChange(identifier, 'attributes', attr); } @@ -1195,6 +1209,7 @@ export default class JSONAPICache implements Cache { } cached.inflightAttrs = null; + cached.defaultAttrs = null; if (cached.errors) { cached.errors = null; @@ -1635,7 +1650,7 @@ function setupRelationships( } function patchLocalAttributes(cached: CachedResource): boolean { - const { localAttrs, remoteAttrs, inflightAttrs, changes } = cached; + const { localAttrs, remoteAttrs, inflightAttrs, defaultAttrs, changes } = cached; if (!localAttrs) { cached.changes = null; return false; @@ -1657,6 +1672,10 @@ function patchLocalAttributes(cached: CachedResource): boolean { delete localAttrs[attr]; delete changes![attr]; } + + if (defaultAttrs && attr in defaultAttrs) { + delete defaultAttrs[attr]; + } } return hasAppliedPatch; } diff --git a/tests/ember-data__json-api/tests/integration/cache/resource-data-documents-test.ts b/tests/ember-data__json-api/tests/integration/cache/resource-data-documents-test.ts index 24d16f2904d..610d5ae4e7a 100644 --- a/tests/ember-data__json-api/tests/integration/cache/resource-data-documents-test.ts +++ b/tests/ember-data__json-api/tests/integration/cache/resource-data-documents-test.ts @@ -491,4 +491,77 @@ module('Integration | @ember-data/json-api Cache.put()', f 'We can fetch more included data from the cache' ); }); + + test('generated default values are retained', function (assert) { + const store = new TestStore(); + let i = 0; + + store.registerSchema( + new TestSchema<'user'>({ + user: { + attributes: { + name: { + kind: 'attribute', + name: 'name', + type: null, + options: { + defaultValue: () => { + i++; + return `Name ${i}`; + }, + }, + }, + }, + relationships: {}, + }, + }) + ); + + store._run(() => { + store.cache.put({ + content: { + data: { + type: 'user', + id: '1', + attributes: {}, + }, + }, + }) as SingleResourceDataDocument; + }); + const identifier = store.identifierCache.getOrCreateRecordIdentifier({ type: 'user', id: '1' }); + + const name1 = store.cache.getAttr(identifier, 'name'); + assert.equal(name1, 'Name 1', 'The default value was generated'); + const name2 = store.cache.getAttr(identifier, 'name'); + assert.equal(name2, 'Name 1', 'The default value was cached'); + + store.cache.setAttr(identifier, 'name', 'Chris'); + const name3 = store.cache.getAttr(identifier, 'name'); + assert.equal(name3, 'Chris', 'The value was updated'); + + store.cache.setAttr(identifier, 'name', null); + const name4 = store.cache.getAttr(identifier, 'name'); + assert.equal(name4, null, 'Null was set and maintained'); + + store.cache.rollbackAttrs(identifier); + const name5 = store.cache.getAttr(identifier, 'name'); + assert.equal(name5, 'Name 2', 'The default value was regenerated'); + + store._run(() => { + store.cache.put({ + content: { + data: { + type: 'user', + id: '1', + attributes: { + name: 'Tomster', + }, + }, + }, + }) as SingleResourceDataDocument; + }); + + const name6 = store.cache.getAttr(identifier, 'name'); + assert.equal(name6, 'Tomster', 'The value was updated on put'); + }); });