@warp-drive-mirror/json-api 5.10.0-alpha.6 → 5.10.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2136,20 +2136,89 @@ function getDefaultValue(schema, identifier, store) {
2136
2136
  if (transform?.defaultValue) return transform.defaultValue(options || null, identifier);
2137
2137
  }
2138
2138
  }
2139
- function calculateChangedKeys(cached, updates, fields) {
2140
- const changedKeys = /* @__PURE__ */ new Set();
2141
- const keys = Object.keys(updates);
2142
- const length = keys.length;
2143
- const localAttrs = cached.localAttrs;
2144
- const original = Object.assign(Object.create(null), cached.remoteAttrs, cached.inflightAttrs);
2145
- for (let i = 0; i < length; i++) {
2139
+ /**
2140
+ * Narrow `subset` to the members that survived {@link patchLocalAttributes}' pruning. Only called
2141
+ * when that pruning actually removed something, so the allocation is skipped on the common path
2142
+ * where a resource has no local edits at all.
2143
+ */
2144
+ function retainSurviving(subset, survivors) {
2145
+ if (!subset?.size) return subset;
2146
+ const result = /* @__PURE__ */ new Set();
2147
+ for (const key of subset) if (survivors.has(key)) result.add(key);
2148
+ return result.size ? result : void 0;
2149
+ }
2150
+ /**
2151
+ * Which projection each changed key moved, so the caller can notify on the matching channel:
2152
+ * `localOnly` and `remoteOnly` on theirs, `both` unscoped. `remoteChanged` is the union
2153
+ * {@link patchLocalAttributes} reconciles against.
2154
+ *
2155
+ * `undefined` rather than empty Sets: this runs on every `upsert`, and a resource where nothing
2156
+ * moved should cost no allocation.
2157
+ */
2158
+ const NO_PROJECTION_CHANGES = Object.freeze({
2159
+ localOnly: void 0,
2160
+ remoteOnly: void 0,
2161
+ both: void 0,
2162
+ remoteChanged: void 0
2163
+ });
2164
+ /**
2165
+ * The keys a merge could move: everything the payload carries, plus anything being promoted that
2166
+ * the payload did not mention. `null` when there is nothing to examine.
2167
+ *
2168
+ * `upsert` promotes nothing, so it gets the `Object.keys` array back unchanged. Only a commit
2169
+ * carrying both a payload and promoted attrs pays for a copy, and that is once per save rather
2170
+ * than once per resource in a document.
2171
+ */
2172
+ function candidateKeys(updates, promoted) {
2173
+ const updateKeys = updates ? Object.keys(updates) : null;
2174
+ const promotedKeys = promoted ? Object.keys(promoted) : null;
2175
+ if (!promotedKeys?.length) return updateKeys?.length ? updateKeys : null;
2176
+ if (!updateKeys?.length) return promotedKeys;
2177
+ const keys = updateKeys.slice();
2178
+ for (let i = 0; i < promotedKeys.length; i++) if (!(promotedKeys[i] in updates)) keys.push(promotedKeys[i]);
2179
+ return keys;
2180
+ }
2181
+ /**
2182
+ * Partition the keys an incoming payload touches by which projection each one moves.
2183
+ *
2184
+ * Takes the values rather than the `CachedResource` so it cannot observe the merge it is
2185
+ * describing: `prevRemote` is the remote state as it stands *before* `promoted` and `updates` are
2186
+ * applied, and the two callers merge different things. Reading them off `cached` would make the
2187
+ * result depend on whether the caller had already mutated it.
2188
+ */
2189
+ function calculateChangedKeys(prevRemote, localAttrs, updates, promoted, fields) {
2190
+ const keys = candidateKeys(updates, promoted);
2191
+ if (keys === null) return NO_PROJECTION_CHANGES;
2192
+ let localOnly;
2193
+ let remoteOnly;
2194
+ let both;
2195
+ let remoteChanged;
2196
+ for (let i = 0; i < keys.length; i++) {
2146
2197
  const key = keys[i];
2147
- if (!fields.has(key)) continue;
2148
- const value = updates[key];
2149
- if (localAttrs && localAttrs[key] !== void 0) continue;
2150
- if (original[key] !== value) changedKeys.add(key);
2151
- }
2152
- return changedKeys;
2198
+ const field = fields.get(key);
2199
+ if (!field || isRelationship(field)) continue;
2200
+ const isPromoted = promoted !== null && key in promoted;
2201
+ const isUpdated = updates !== void 0 && key in updates;
2202
+ const wasRemote = prevRemote ? prevRemote[key] : void 0;
2203
+ const nowRemote = isUpdated ? updates[key] : isPromoted ? promoted[key] : wasRemote;
2204
+ const localEdit = localAttrs ? localAttrs[key] : void 0;
2205
+ const isEdited = localEdit !== void 0;
2206
+ const wasLocal = isEdited ? localEdit : isPromoted ? promoted[key] : wasRemote;
2207
+ const nowLocal = isEdited ? localEdit : nowRemote;
2208
+ const remoteMoved = wasRemote !== nowRemote;
2209
+ const localMoved = wasLocal !== nowLocal;
2210
+ if (remoteMoved) {
2211
+ (remoteChanged ??= /* @__PURE__ */ new Set()).add(key);
2212
+ if (localMoved) (both ??= /* @__PURE__ */ new Set()).add(key);
2213
+ else (remoteOnly ??= /* @__PURE__ */ new Set()).add(key);
2214
+ } else if (localMoved) (localOnly ??= /* @__PURE__ */ new Set()).add(key);
2215
+ }
2216
+ return remoteChanged || localOnly ? {
2217
+ localOnly,
2218
+ remoteOnly,
2219
+ both,
2220
+ remoteChanged
2221
+ } : NO_PROJECTION_CHANGES;
2153
2222
  }
2154
2223
  function cacheIsEmpty(cached) {
2155
2224
  return !cached || cached.remoteAttrs === null && cached.inflightAttrs === null && cached.localAttrs === null;
@@ -2323,7 +2392,7 @@ function cacheUpsert(cache, identifier, data, calculateChanges) {
2323
2392
  cache._capabilities.notifyChange(identifier, "state", null);
2324
2393
  }
2325
2394
  const fields = getCacheFields(cache, identifier);
2326
- if (calculateChanges && existed && data.attributes) changedKeys = calculateChangedKeys(cached, data.attributes, fields);
2395
+ if (calculateChanges && existed && data.attributes) changedKeys = calculateChangedKeys(cached.remoteAttrs, cached.localAttrs, data.attributes, null, fields).remoteChanged;
2327
2396
  cached.remoteAttrs = Object.assign(cached.remoteAttrs || Object.create(null), data.attributes);
2328
2397
  if (cached.localAttrs) {
2329
2398
  if (patchLocalAttributes(cached, changedKeys)) cache._capabilities.notifyChange(identifier, "state", null);
@@ -2503,15 +2572,21 @@ function didCommit(cache, committedIdentifier, data, op) {
2503
2572
  }
2504
2573
  newCanonicalAttributes = data.attributes;
2505
2574
  }
2506
- const changedKeys = newCanonicalAttributes && calculateChangedKeys(cached, newCanonicalAttributes, fields);
2575
+ const changes = calculateChangedKeys(cached.remoteAttrs, cached.localAttrs, newCanonicalAttributes, cached.inflightAttrs, fields);
2507
2576
  cached.remoteAttrs = Object.assign(cached.remoteAttrs || Object.create(null), cached.inflightAttrs, newCanonicalAttributes);
2508
2577
  cached.inflightAttrs = null;
2509
- patchLocalAttributes(cached, changedKeys);
2578
+ const pruned = patchLocalAttributes(cached, changes.remoteChanged);
2510
2579
  if (cached.errors) {
2511
2580
  cached.errors = null;
2512
2581
  cache._capabilities.notifyChange(identifier, "errors", null);
2513
2582
  }
2514
- if (changedKeys?.size) cache._capabilities.notifyChange(identifier, "attributes", changedKeys);
2583
+ const { remoteChanged, localOnly } = changes;
2584
+ const reprune = pruned && remoteChanged !== void 0;
2585
+ const both = reprune ? retainSurviving(changes.both, remoteChanged) : changes.both;
2586
+ const remoteOnly = reprune ? retainSurviving(changes.remoteOnly, remoteChanged) : changes.remoteOnly;
2587
+ if (both?.size) cache._capabilities.notifyChange(identifier, "attributes", both);
2588
+ if (remoteOnly?.size) cache._capabilities.notifyChange(identifier, "attributes", remoteOnly, "remote");
2589
+ if (localOnly?.size) cache._capabilities.notifyChange(identifier, "attributes", localOnly, "local");
2515
2590
  cache._capabilities.notifyChange(identifier, "state", null);
2516
2591
  }
2517
2592
  function willCommit(cache, identifier) {