diff --git a/packages/json-api/src/-private/cache.ts b/packages/json-api/src/-private/cache.ts index f2c26340cba..646fbe8e9ac 100644 --- a/packages/json-api/src/-private/cache.ts +++ b/packages/json-api/src/-private/cache.ts @@ -1209,17 +1209,27 @@ export default class JSONAPICache implements Cache { if (existingAttr !== value) { cached.localAttrs = cached.localAttrs || (Object.create(null) as Record); - cached.localAttrs[basePath] = cached.localAttrs[basePath] || (Object.create(null) as ObjectValue); + cached.localAttrs[basePath] = cached.localAttrs[basePath] || structuredClone(existing); cached.changes = cached.changes || (Object.create(null) as Record); let currentLocal = cached.localAttrs[basePath] as ObjectValue; + let nextLink = 1; - for (let i = 1; i < path.length; i++) { - currentLocal[path[i]] = currentLocal[path[i]] || (Object.create(null) as ObjectValue); - currentLocal = currentLocal[path[i]] as ObjectValue; + while (nextLink < path.length - 1) { + currentLocal = currentLocal[path[nextLink++]] as ObjectValue; } - currentLocal = value as ObjectValue; + currentLocal[path[nextLink]] = value as ObjectValue; cached.changes[basePath] = [existing, cached.localAttrs[basePath] as ObjectValue]; + + // since we initiaize the value as basePath as a clone of the value at the remote basePath + // then in theory we can use JSON.stringify to compare the two values as key insertion order + // ought to be consistent. + // we try/catch this because users have a habit of doing "Bad Things"TM wherein the cache contains + // stateful values that are not JSON serializable correctly such as Dates. + // in the case that we error, we fallback to not removing the local value + // so that any changes we don't understand are preserved. Thse objects would then sometimes + // appear to be dirty unnecessarily, and for folks that open an issue we can guide them + // to make their cache data less stateful. } else if (cached.localAttrs) { delete cached.localAttrs[basePath]; delete cached.changes![basePath]; diff --git a/packages/schema-record/src/managed-array.ts b/packages/schema-record/src/managed-array.ts index 8719c20fd7a..232146e80f0 100644 --- a/packages/schema-record/src/managed-array.ts +++ b/packages/schema-record/src/managed-array.ts @@ -179,13 +179,15 @@ export class ManagedArray { ); Object.assign(record, val); return record; - } - if (field.type !== null) { - const transform = schema.transforms.get(field.type); - if (!transform) { - throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`); + } else { + if (field.type !== null) { + const transform = schema.transforms.get(field.type); + if (!transform) { + debugger; + throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`); + } + return transform.hydrate(val as Value, field.options ?? null, self.owner); } - return transform.hydrate(val as Value, field.options ?? null, self.owner); } return val; } @@ -245,13 +247,16 @@ export class ManagedArray { return true; } - const transform = schema.transforms.get(field.type); - if (!transform) { - throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`); + let rawValue = self[SOURCE] as ArrayValue; + if (!isSchemaArray) { + const transform = schema.transforms.get(field.type); + if (!transform) { + throw new Error(`No '${field.type}' transform defined for use by ${address.type}.${String(prop)}`); + } + rawValue = (self[SOURCE] as ArrayValue).map((item) => + transform.serialize(item, field.options ?? null, self.owner) + ); } - const rawValue = (self[SOURCE] as ArrayValue).map((item) => - transform.serialize(item, field.options ?? null, self.owner) - ); cache.setAttr(self.address, self.key, rawValue as Value); _SIGNAL.shouldReset = true; } diff --git a/packages/schema-record/src/record.ts b/packages/schema-record/src/record.ts index 82776eb5c5d..854bb54733f 100644 --- a/packages/schema-record/src/record.ts +++ b/packages/schema-record/src/record.ts @@ -486,6 +486,7 @@ export class SchemaRecord { } }, set(target: SchemaRecord, prop: string | number | symbol, value: unknown, receiver: typeof Proxy) { + debugger; if (!IS_EDITABLE) { throw new Error(`Cannot set ${String(prop)} on ${identifier.type} because the record is not editable`); }