@warp-drive/core 5.9.0-alpha.16 → 5.9.0-alpha.18

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.
Files changed (56) hide show
  1. package/declarations/graph/-private/-state.d.ts +1 -0
  2. package/declarations/reactive/-private/default-mode.d.ts +22 -9
  3. package/declarations/reactive/-private/kind/belongs-to-field.d.ts +3 -0
  4. package/declarations/types/runtime.d.ts +16 -0
  5. package/declarations/types/schema/fields.d.ts +40 -12
  6. package/dist/graph/-private.js +56 -37
  7. package/dist/{index-CFwnjldF.js → index-BoY6aNE5.js} +88 -9
  8. package/dist/index.js +1 -1
  9. package/dist/reactive.js +1 -1
  10. package/dist/store/-private.js +1 -1
  11. package/dist/types/-private.js +1 -1
  12. package/dist/types/runtime.js +20 -1
  13. package/dist/unpkg/dev/graph/-private.js +56 -37
  14. package/dist/unpkg/dev/{index-BZRBBa34.js → index-BZ6PKU-a.js} +88 -9
  15. package/dist/unpkg/dev/index.js +1 -1
  16. package/dist/unpkg/dev/reactive.js +1 -1
  17. package/dist/unpkg/dev/store/-private.js +2 -2
  18. package/dist/unpkg/dev/types/-private.js +1 -1
  19. package/dist/unpkg/dev/types/runtime.js +20 -1
  20. package/dist/unpkg/dev-deprecated/graph/-private.js +56 -37
  21. package/dist/unpkg/dev-deprecated/{index-Bu52tQ9I.js → index-CDjd631s.js} +88 -9
  22. package/dist/unpkg/dev-deprecated/index.js +1 -1
  23. package/dist/unpkg/dev-deprecated/reactive.js +1 -1
  24. package/dist/unpkg/dev-deprecated/store/-private.js +1 -1
  25. package/dist/unpkg/dev-deprecated/types/-private.js +1 -1
  26. package/dist/unpkg/dev-deprecated/types/runtime.js +20 -1
  27. package/dist/unpkg/prod/{-leaked-BHLmJyaw.js → -leaked-DgjQ5X55.js} +2 -2
  28. package/dist/unpkg/prod/configure.js +1 -1
  29. package/dist/unpkg/prod/graph/-private.js +23 -26
  30. package/dist/unpkg/prod/{schema-BdC3DirM.js → index-BMBm3kYx.js} +2256 -91
  31. package/dist/unpkg/prod/index.js +5 -483
  32. package/dist/unpkg/prod/{promise-cache-DIT8Ypjq.js → promise-cache-DUblkX-U.js} +1 -1
  33. package/dist/unpkg/prod/reactive.js +3 -29
  34. package/dist/unpkg/prod/{request-BrJSCG6r.js → request-eb7Zm-oS.js} +1 -1
  35. package/dist/unpkg/prod/request.js +2 -2
  36. package/dist/unpkg/prod/signals/-leaked.js +1 -1
  37. package/dist/unpkg/prod/store/-private.js +2 -3
  38. package/dist/unpkg/prod/types/-private.js +1 -1
  39. package/dist/unpkg/prod/types/runtime.js +20 -1
  40. package/dist/unpkg/prod-deprecated/{-leaked-D_5Yrs5c.js → -leaked-BNVttukY.js} +1 -1
  41. package/dist/unpkg/prod-deprecated/configure.js +1 -1
  42. package/dist/unpkg/prod-deprecated/graph/-private.js +23 -26
  43. package/dist/unpkg/prod-deprecated/{schema-h0nuUTCj.js → index-CZkDXZog.js} +890 -7
  44. package/dist/unpkg/prod-deprecated/index.js +5 -483
  45. package/dist/unpkg/prod-deprecated/{promise-cache-DIT8Ypjq.js → promise-cache-DUblkX-U.js} +1 -1
  46. package/dist/unpkg/prod-deprecated/reactive.js +2 -3
  47. package/dist/unpkg/prod-deprecated/{request-BrJSCG6r.js → request-eb7Zm-oS.js} +1 -1
  48. package/dist/unpkg/prod-deprecated/request.js +2 -2
  49. package/dist/unpkg/prod-deprecated/signals/-leaked.js +1 -1
  50. package/dist/unpkg/prod-deprecated/store/-private.js +1 -2
  51. package/dist/unpkg/prod-deprecated/types/-private.js +1 -1
  52. package/dist/unpkg/prod-deprecated/types/runtime.js +20 -1
  53. package/package.json +3 -3
  54. package/dist/unpkg/prod/handler-BJogFbfS.js +0 -1619
  55. package/dist/unpkg/prod-deprecated/handler-kGR8zguj.js +0 -334
  56. package/dist/unpkg/prod-deprecated/hooks-wJdveHPi.js +0 -26
@@ -55,9 +55,14 @@ function assertValidRelationshipPayload(graph, op) {
55
55
  kind
56
56
  } = definition;
57
57
  if (payload.links) {
58
- console.warn(`You pushed a record of type '${type}' with a relationship '${field}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, isAsync || payload.data === null || !!payload.data || state.hasReceivedData, {
59
- id: 'ds.store.push-link-for-sync-relationship'
60
- });
58
+ // once JSON_API_CACHE_VALIDATION_ERRORS is active, this warning is instead
59
+ // the responsibility of the new document validator (see validateResourceRelationships
60
+ // in warp-drive-packages/json-api/src/-private/validator/1.1/7.2_resource-objects.ts)
61
+ {
62
+ console.warn(`You pushed a record of type '${type}' with a relationship '${field}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, isAsync || payload.data === null || !!payload.data || state.hasReceivedData, {
63
+ id: 'ds.store.push-link-for-sync-relationship'
64
+ });
65
+ }
61
66
  } else if (payload.data) {
62
67
  if (kind === 'belongsTo') {
63
68
  (test => {
@@ -1355,8 +1360,10 @@ function replaceRelatedRecord(graph, op, isRemote = false) {
1355
1360
  if (op.value === existingState) {
1356
1361
  // if we were empty before but now know we are empty this needs to be true
1357
1362
  state.hasReceivedData = true;
1363
+
1358
1364
  // if this is a remote update we still sync
1359
1365
  if (isRemote) {
1366
+ state.hasReceivedRemoteData = true;
1360
1367
  const {
1361
1368
  localState
1362
1369
  } = relationship;
@@ -1379,6 +1386,15 @@ function replaceRelatedRecord(graph, op, isRemote = false) {
1379
1386
  // update value to the new value
1380
1387
  relationship[prop] = op.value;
1381
1388
  state.hasReceivedData = true;
1389
+ /**
1390
+ * Local mutations never give us additional information about
1391
+ * whether the relationship has received *remote* data. This is
1392
+ * what `getRemoteRelationship` relies on to avoid reflecting
1393
+ * local-only edits.
1394
+ */
1395
+ if (isRemote) {
1396
+ state.hasReceivedRemoteData = true;
1397
+ }
1382
1398
  state.isEmpty = op.value === null;
1383
1399
  state.isStale = false;
1384
1400
  state.hasFailedLoadAttempt = false;
@@ -1585,12 +1601,12 @@ function diffCollection(finalState, relationship, onAdd, onDel) {
1585
1601
  return _compare(priorLocalState, finalState, finalSet, remoteState, remoteMembers, onAdd, onDel, relationship.definition.resetOnRemoteUpdate);
1586
1602
  }
1587
1603
  function computeLocalState(storage) {
1588
- if (!storage.isDirty) {
1589
- (test => {
1590
- if (!test) {
1591
- throw new Error(`Expected localState to be present`);
1592
- }
1593
- })(Array.isArray(storage.localState));
1604
+ // When the edge is clean and we already have a materialized localState we can
1605
+ // return it directly. A clean edge can still have a `null` localState though:
1606
+ // dematerializing an inverse member (e.g. when a route reloads its model)
1607
+ // nulls out localState without dirtying the edge. In that case we fall through
1608
+ // and recompute from remoteState rather than asserting.
1609
+ if (!storage.isDirty && storage.localState) {
1594
1610
  return storage.localState;
1595
1611
  }
1596
1612
  const state = storage.remoteState.slice();
@@ -1890,8 +1906,30 @@ function rollbackRelationship(graph, key, field, relationship) {
1890
1906
  }
1891
1907
  }
1892
1908
  function createState() {
1909
+ {
1910
+ {
1911
+ if (getGlobalConfig().WarpDrive.debug.LOG_GRAPH || globalThis.getWarpDriveRuntimeConfig().debug.LOG_GRAPH) {
1912
+ let hasReceivedData = false;
1913
+ return {
1914
+ get hasReceivedData() {
1915
+ return hasReceivedData;
1916
+ },
1917
+ set hasReceivedData(value) {
1918
+ hasReceivedData = value;
1919
+ },
1920
+ hasReceivedRemoteData: false,
1921
+ isEmpty: true,
1922
+ isStale: false,
1923
+ hasFailedLoadAttempt: false,
1924
+ shouldForceReload: false,
1925
+ hasDematerializedInverse: false
1926
+ };
1927
+ }
1928
+ }
1929
+ }
1893
1930
  return {
1894
1931
  hasReceivedData: false,
1932
+ hasReceivedRemoteData: false,
1895
1933
  isEmpty: true,
1896
1934
  isStale: false,
1897
1935
  hasFailedLoadAttempt: false,
@@ -1996,13 +2034,10 @@ function legacyGetResourceRelationshipData(source, getRemoteState) {
1996
2034
  source.accessed = true;
1997
2035
  let data;
1998
2036
  const payload = {};
1999
- if (getRemoteState && source.remoteState) {
2000
- data = source.remoteState;
2001
- } else if (!getRemoteState && source.localState) {
2002
- data = source.localState;
2003
- }
2004
- if ((getRemoteState && source.remoteState === null || source.localState === null) && source.state.hasReceivedData) {
2005
- data = null;
2037
+ if (getRemoteState && (source.remoteState || source.state.hasReceivedRemoteData)) {
2038
+ data = source.remoteState ?? null;
2039
+ } else if (!getRemoteState && (source.localState || source.state.hasReceivedData)) {
2040
+ data = source.localState ?? null;
2006
2041
  }
2007
2042
  if (source.links) {
2008
2043
  payload.links = source.links;
@@ -2216,7 +2251,6 @@ function updateRelationshipOperation(graph, op) {
2216
2251
  })(isHasMany(relationship) || isBelongsTo(relationship));
2217
2252
  const {
2218
2253
  definition,
2219
- state,
2220
2254
  identifier
2221
2255
  } = relationship;
2222
2256
  const {
@@ -2256,23 +2290,6 @@ function updateRelationshipOperation(graph, op) {
2256
2290
  value: payload.data ? graph.store.cacheKeyManager.upgradeIdentifier(payload.data) : null
2257
2291
  }, true);
2258
2292
  }
2259
- } else if (definition.isAsync === false && !state.hasReceivedData) {
2260
- hasRelationshipDataProperty = true;
2261
- if (isCollection) {
2262
- graph.update({
2263
- op: 'replaceRelatedRecords',
2264
- record: identifier,
2265
- field: op.field,
2266
- value: []
2267
- }, true);
2268
- } else {
2269
- graph.update({
2270
- op: 'replaceRelatedRecord',
2271
- record: identifier,
2272
- field: op.field,
2273
- value: null
2274
- }, true);
2275
- }
2276
2293
  }
2277
2294
  if (payload.links) {
2278
2295
  const originalLinks = relationship.links;
@@ -2282,9 +2299,10 @@ function updateRelationshipOperation(graph, op) {
2282
2299
  const currentLink = originalLinks && originalLinks.related ? _normalizeLink(originalLinks.related) : null;
2283
2300
  const currentLinkHref = currentLink ? currentLink.href : null;
2284
2301
  if (relatedLink && relatedLink.href && relatedLink.href !== currentLinkHref) {
2285
- console.warn(`You pushed a record of type '${identifier.type}' with a relationship '${definition.key}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, definition.isAsync || state.hasReceivedData, {
2286
- id: 'ds.store.push-link-for-sync-relationship'
2287
- });
2302
+ // Note: the equivalent warning for this case is issued eagerly (and with a more
2303
+ // complete condition, since it also accounts for `payload.data` being present) by
2304
+ // `assertValidRelationshipPayload` in `-utils.ts`, which always runs immediately
2305
+ // before this operation in DEBUG builds. Warning here too would double-warn.
2288
2306
  (test => {
2289
2307
  if (!test) {
2290
2308
  throw new Error(`You have pushed a record of type '${identifier.type}' with '${definition.key}' as a link, but the value of that link is not a string.`);
@@ -2315,6 +2333,7 @@ function updateRelationshipOperation(graph, op) {
2315
2333
  // we don't need to notify here as the update op we pushed in above will notify once
2316
2334
  // membership is in the correct state.
2317
2335
  relationship.state.hasReceivedData = true;
2336
+ relationship.state.hasReceivedRemoteData = true;
2318
2337
  relationship.state.isStale = false;
2319
2338
  relationship.state.hasDematerializedInverse = false;
2320
2339
  relationship.state.isEmpty = relationshipIsEmpty;
@@ -2,13 +2,13 @@ import { D as Destroy, C as Context, S as SOURCE, a as Checkout, b as Commit } f
2
2
  import { d as defineGate, w as withSignalStore, n as notifyInternalSignal, p as peekInternalSignal, a as willSyncFlushWatchers, g as getOrCreateInternalSignal, c as consumeInternalSignal, b as createInternalSignal, A as ARRAY_SIGNAL, S as Signals, e as createSignalDescriptor, f as defineSignal, h as entangleSignal, i as entangleInitiallyStaleSignal, O as OBJECT_SIGNAL, j as createInternalMemo, o as waitFor } from "./-leaked-BohmerbH.js";
3
3
  import { EnableHydration, SkipCache, STRUCTURED } from './types/request.js';
4
4
  import { isResourceSchema } from './types/schema/fields.js';
5
+ import { setLogging, getRuntimeConfig, setIsMaybeMirage } from './types/runtime.js';
5
6
  import { a as cloneResponseProperties, I as IS_CACHE_HANDLER, b as assertValidRequest, e as executeNextHandler, d as getRequestResult, u as upgradePromise, s as setPromiseResult, f as clearRequestResult } from "./future-DUuPqyLj.js";
6
7
  import { getOrSetGlobal, setTransient, peekTransient, peekUniversalTransient, setUniversalTransient } from './types/-private.js';
7
8
  import { DefaultCachePolicy } from './store.js';
8
9
  import { withBrand } from './request.js';
9
10
  import { CACHE_OWNER, DEBUG_STALE_CACHE_OWNER, DEBUG_KEY_TYPE, DEBUG_CLIENT_ORIGINATED } from './types/identifier.js';
10
11
  import { g as getGlobalConfig } from "./runtime-DGG4CvlW.js";
11
- import { setLogging, getRuntimeConfig } from './types/runtime.js';
12
12
  import { RecordStore, Type } from './types/symbols.js';
13
13
  function urlFromLink(link) {
14
14
  if (typeof link === 'string') return link;
@@ -6660,11 +6660,32 @@ function getBelongsToField(context) {
6660
6660
  }
6661
6661
  function setBelongsToField(context) {
6662
6662
  const {
6663
+ field,
6664
+ resourceKey,
6663
6665
  store
6664
6666
  } = context;
6665
6667
  const {
6666
- schema
6668
+ schema,
6669
+ cache
6667
6670
  } = store;
6671
+
6672
+ /**
6673
+ * We currently only allow editing sync belongsTo relationships
6674
+ * in PolarisMode
6675
+ */
6676
+ const isValidPolarisConfig = field.options.linksMode && !field.options.async;
6677
+ if (!context.legacy && isValidPolarisConfig) {
6678
+ const name = context.path.at(-1);
6679
+ cache.mutate({
6680
+ op: 'replaceRelatedRecord',
6681
+ record: resourceKey,
6682
+ field: name,
6683
+ value: getResourceKey(context.value)
6684
+ },
6685
+ // @ts-expect-error
6686
+ true);
6687
+ return true;
6688
+ }
6668
6689
  (test => {
6669
6690
  if (!test) {
6670
6691
  throw new Error(`Can only mutate belongsTo fields when the resource is in legacy mode`);
@@ -6673,6 +6694,12 @@ function setBelongsToField(context) {
6673
6694
  schema._kind('@legacy', 'belongsTo').set(store, context.record, context.resourceKey, context.field, context.value);
6674
6695
  return true;
6675
6696
  }
6697
+ function getResourceKey(record) {
6698
+ if (!record) {
6699
+ return null;
6700
+ }
6701
+ return recordIdentifierFor(record);
6702
+ }
6676
6703
  function getCollectionField(context) {
6677
6704
  entangleSignal(context.signals, context.record, context.path.at(-1), null);
6678
6705
  (test => {
@@ -7405,6 +7432,17 @@ function setSchemaObjectField(context) {
7405
7432
  // }
7406
7433
  return true;
7407
7434
  }
7435
+
7436
+ /**
7437
+ * A ReactiveResource context object passed to
7438
+ * the get/set functions of a field kind implementation.
7439
+ *
7440
+ * This provides all the necessary information for
7441
+ * the field kind to perform its operations.
7442
+ *
7443
+ * @internal
7444
+ */
7445
+
7408
7446
  const DefaultMode = {
7409
7447
  '@hash': {
7410
7448
  get: getHashField,
@@ -8198,6 +8236,14 @@ const TypeField = {
8198
8236
  key: 'type'
8199
8237
  }
8200
8238
  };
8239
+ const IdentifierField = {
8240
+ type: '@identity',
8241
+ name: '$key',
8242
+ kind: 'derived',
8243
+ options: {
8244
+ key: '^'
8245
+ }
8246
+ };
8201
8247
  const DefaultIdentityField = {
8202
8248
  name: 'id',
8203
8249
  kind: '@id'
@@ -8386,6 +8432,7 @@ function withDefaults(schema) {
8386
8432
  // we add TypeField to the beginning so that it will
8387
8433
  // appear right next to the identity field
8388
8434
  schema.fields.unshift(TypeField);
8435
+ schema.fields.unshift(IdentifierField);
8389
8436
  schema.fields.push(ConstructorField);
8390
8437
  return schema;
8391
8438
  }
@@ -8875,9 +8922,33 @@ function cloneResponse(response, overrides) {
8875
8922
  const props = cloneResponseProperties(response);
8876
8923
  return new Response(response.body, Object.assign(props, overrides));
8877
8924
  }
8925
+ function withDateHeader(response) {
8926
+ const headers = new Headers(response.headers);
8927
+ headers.set('date', new Date().toUTCString());
8928
+ return cloneResponse(response, {
8929
+ headers
8930
+ });
8931
+ }
8932
+
8933
+ // Detects Mirage (or another Pretender-based fetch mock), whose `Response`
8934
+ // may (unlike a spec-compliant `fetch()`, whose Response headers are always
8935
+ // immutable) allow header mutation and may not support streaming reads.
8936
+ //
8937
+ // This is inherently a heuristic - many legitimate tools (APM/monitoring
8938
+ // agents, browser extensions, polyfills) also patch `window.fetch`, and can
8939
+ // produce false positives. `setWarpDriveIsMaybeMirage(value)` lets an app
8940
+ // declare the answer explicitly (`true` or `false`), overriding the
8941
+ // heuristic in either direction; leave it unset to keep the default
8942
+ // detection. See https://github.com/warp-drive-data/warp-drive/issues/10535
8878
8943
  let IS_MAYBE_MIRAGE = () => false;
8879
8944
  {
8880
- IS_MAYBE_MIRAGE = () => Boolean(typeof window !== 'undefined' && (window.server?.pretender || window.fetch.toString().replace(/\s+/g, '') !== 'function fetch() { [native code] }'.replace(/\s+/g, '')));
8945
+ IS_MAYBE_MIRAGE = () => {
8946
+ const override = getRuntimeConfig().mirage;
8947
+ if (override !== undefined) {
8948
+ return override;
8949
+ }
8950
+ return Boolean(typeof window !== 'undefined' && (window.server?.pretender || window.fetch.toString().replace(/\s+/g, '') !== 'function fetch() { [native code] }'.replace(/\s+/g, '')));
8951
+ };
8881
8952
  }
8882
8953
  const MUTATION_OPS = new Set(['updateRecord', 'createRecord', 'deleteRecord']);
8883
8954
  const ERROR_STATUS_CODE_FOR = new Map([[400, 'Bad Request'], [401, 'Unauthorized'], [402, 'Payment Required'], [403, 'Forbidden'], [404, 'Not Found'], [405, 'Method Not Allowed'], [406, 'Not Acceptable'], [407, 'Proxy Authentication Required'], [408, 'Request Timeout'], [409, 'Conflict'], [410, 'Gone'], [411, 'Length Required'], [412, 'Precondition Failed'], [413, 'Payload Too Large'], [414, 'URI Too Long'], [415, 'Unsupported Media Type'], [416, 'Range Not Satisfiable'], [417, 'Expectation Failed'], [419, 'Page Expired'], [420, 'Enhance Your Calm'], [421, 'Misdirected Request'], [422, 'Unprocessable Entity'], [423, 'Locked'], [424, 'Failed Dependency'], [425, 'Too Early'], [426, 'Upgrade Required'], [428, 'Precondition Required'], [429, 'Too Many Requests'], [430, 'Request Header Fields Too Large'], [431, 'Request Header Fields Too Large'], [450, 'Blocked By Windows Parental Controls'], [451, 'Unavailable For Legal Reasons'], [500, 'Internal Server Error'], [501, 'Not Implemented'], [502, 'Bad Gateway'], [503, 'Service Unavailable'], [504, 'Gateway Timeout'], [505, 'HTTP Version Not Supported'], [506, 'Variant Also Negotiates'], [507, 'Insufficient Storage'], [508, 'Loop Detected'], [509, 'Bandwidth Limit Exceeded'], [510, 'Not Extended'], [511, 'Network Authentication Required']]);
@@ -8929,13 +9000,15 @@ const Fetch = {
8929
9000
  const isMutationOp = Boolean(op && MUTATION_OPS.has(op));
8930
9001
  if (!isError && !isMutationOp && response.status !== 204 && !response.headers.has('date')) {
8931
9002
  if (IS_MAYBE_MIRAGE()) {
8932
- response.headers.set('date', new Date().toUTCString());
9003
+ try {
9004
+ response.headers.set('date', new Date().toUTCString());
9005
+ } catch {
9006
+ // IS_MAYBE_MIRAGE() was a false positive, or the mock's headers
9007
+ // are (correctly) immutable - fall back to cloning.
9008
+ response = withDateHeader(response);
9009
+ }
8933
9010
  } else {
8934
- const headers = new Headers(response.headers);
8935
- headers.set('date', new Date().toUTCString());
8936
- response = cloneResponse(response, {
8937
- headers
8938
- });
9011
+ response = withDateHeader(response);
8939
9012
  }
8940
9013
  }
8941
9014
  context.setResponse(response);
@@ -9330,6 +9403,12 @@ class RequestManager {
9330
9403
  globalThis.getWarpDriveRuntimeConfig = getRuntimeConfig;
9331
9404
  }
9332
9405
 
9406
+ // Mirage (or another Pretender-based fetch mock) may be used outside of a
9407
+ // `TESTING` build (e.g. `ember serve` in dev mode), so this override is
9408
+ // exposed unconditionally rather than gated to the `TESTING` env.
9409
+ // @ts-expect-error adding to globalThis
9410
+ globalThis.setWarpDriveIsMaybeMirage = setIsMaybeMirage;
9411
+
9333
9412
  /**
9334
9413
  * Options for setting up a Store instance with `useRecommendedStore`.
9335
9414
  */
@@ -1,4 +1,4 @@
1
- export { C as CacheHandler, F as Fetch, y as RequestManager, S as Store, r as cacheKeyFor, r as recordIdentifierFor, D as setIdentifierForgetMethod, A as setIdentifierGenerationMethod, E as setIdentifierResetMethod, B as setIdentifierUpdateMethod, G as setKeyInfoForResource, s as storeFor, z as useRecommendedStore } from "./index-BZRBBa34.js";
1
+ export { C as CacheHandler, F as Fetch, y as RequestManager, S as Store, r as cacheKeyFor, r as recordIdentifierFor, D as setIdentifierForgetMethod, A as setIdentifierGenerationMethod, E as setIdentifierResetMethod, B as setIdentifierUpdateMethod, G as setKeyInfoForResource, s as storeFor, z as useRecommendedStore } from "./index-BZ6PKU-a.js";
2
2
  import "./-private-sql1_mdx.js";
3
3
  import "./-leaked-BohmerbH.js";
4
4
  import './store.js';
@@ -1,3 +1,3 @@
1
- export { q as SchemaService, o as checkout, x as commit, u as fromIdentity, p as instantiateRecord, v as registerDerivations, t as teardownRecord, w as withDefaults } from "./index-BZRBBa34.js";
1
+ export { q as SchemaService, o as checkout, x as commit, u as fromIdentity, p as instantiateRecord, v as registerDerivations, t as teardownRecord, w as withDefaults } from "./index-BZ6PKU-a.js";
2
2
  export { a as Checkout } from "./-private-sql1_mdx.js";
3
3
  export { k as createRequestSubscription, m as getPromiseState, l as getRequestState } from "./-leaked-BohmerbH.js";
@@ -1,6 +1,6 @@
1
1
  import '../types/request.js';
2
- import { i as isResourceKey, c as coerceId } from "../index-BZRBBa34.js";
3
- export { C as CacheHandler, R as RecordArrayManager, S as Store, j as StoreMap, _ as _clearCaches, n as _deprecatingNormalize, g as assertPrivateCapabilities, b as assertPrivateStore, k as createLegacyManyArray, e as ensureStringId, f as fastPush, d as isPrivateStore, a as isRequestKey, l as log, m as logGroup, r as recordIdentifierFor, h as setRecordIdentifier, s as storeFor } from "../index-BZRBBa34.js";
2
+ import { i as isResourceKey, c as coerceId } from "../index-BZ6PKU-a.js";
3
+ export { C as CacheHandler, R as RecordArrayManager, S as Store, j as StoreMap, _ as _clearCaches, n as _deprecatingNormalize, g as assertPrivateCapabilities, b as assertPrivateStore, k as createLegacyManyArray, e as ensureStringId, f as fastPush, d as isPrivateStore, a as isRequestKey, l as log, m as logGroup, r as recordIdentifierFor, h as setRecordIdentifier, s as storeFor } from "../index-BZ6PKU-a.js";
4
4
  function isNonEmptyString(str) {
5
5
  return Boolean(str && typeof str === 'string');
6
6
  }
@@ -1,5 +1,5 @@
1
1
  const name = "@warp-drive/core";
2
- const version = "5.9.0-alpha.16";
2
+ const version = "5.9.0-alpha.18";
3
3
 
4
4
  // in testing mode, we utilize globals to ensure only one copy exists of
5
5
  // these maps, due to bugs in ember-auto-import
@@ -31,4 +31,23 @@ function setLogging(config) {
31
31
  Object.assign(RuntimeConfig.debug, config);
32
32
  storage?.setItem('WarpDriveRuntimeConfig', JSON.stringify(RuntimeConfig));
33
33
  }
34
- export { getRuntimeConfig, setLogging };
34
+
35
+ /**
36
+ * Explicitly declares whether requests may be served by Mirage (or another
37
+ * Pretender-based fetch mock) instead of a native `fetch` implementation.
38
+ *
39
+ * The `Fetch` request handler otherwise infers this via a heuristic (the
40
+ * presence of `window.server.pretender`, or `window.fetch` appearing to be
41
+ * patched), which can be wrong in either direction: some Mirage setups don't
42
+ * expose `window.server`, while some unrelated tools (APM agents, browser
43
+ * extensions) also patch `fetch`. Call this to override that heuristic:
44
+ * `true` to force Mirage-compatible behavior on, `false` to force it off.
45
+ *
46
+ * globalThis.setWarpDriveIsMaybeMirage(true);
47
+ *
48
+ */
49
+ function setIsMaybeMirage(value) {
50
+ RuntimeConfig.mirage = value;
51
+ storage?.setItem('WarpDriveRuntimeConfig', JSON.stringify(RuntimeConfig));
52
+ }
53
+ export { getRuntimeConfig, setIsMaybeMirage, setLogging };
@@ -59,9 +59,14 @@ function assertValidRelationshipPayload(graph, op) {
59
59
  kind
60
60
  } = definition;
61
61
  if (payload.links) {
62
- console.warn(`You pushed a record of type '${type}' with a relationship '${field}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, isAsync || payload.data === null || !!payload.data || state.hasReceivedData, {
63
- id: 'ds.store.push-link-for-sync-relationship'
64
- });
62
+ // once JSON_API_CACHE_VALIDATION_ERRORS is active, this warning is instead
63
+ // the responsibility of the new document validator (see validateResourceRelationships
64
+ // in warp-drive-packages/json-api/src/-private/validator/1.1/7.2_resource-objects.ts)
65
+ {
66
+ console.warn(`You pushed a record of type '${type}' with a relationship '${field}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, isAsync || payload.data === null || !!payload.data || state.hasReceivedData, {
67
+ id: 'ds.store.push-link-for-sync-relationship'
68
+ });
69
+ }
65
70
  } else if (payload.data) {
66
71
  if (kind === 'belongsTo') {
67
72
  (test => {
@@ -1409,8 +1414,10 @@ function replaceRelatedRecord(graph, op, isRemote = false) {
1409
1414
  if (op.value === existingState) {
1410
1415
  // if we were empty before but now know we are empty this needs to be true
1411
1416
  state.hasReceivedData = true;
1417
+
1412
1418
  // if this is a remote update we still sync
1413
1419
  if (isRemote) {
1420
+ state.hasReceivedRemoteData = true;
1414
1421
  const {
1415
1422
  localState
1416
1423
  } = relationship;
@@ -1441,6 +1448,15 @@ function replaceRelatedRecord(graph, op, isRemote = false) {
1441
1448
  // update value to the new value
1442
1449
  relationship[prop] = op.value;
1443
1450
  state.hasReceivedData = true;
1451
+ /**
1452
+ * Local mutations never give us additional information about
1453
+ * whether the relationship has received *remote* data. This is
1454
+ * what `getRemoteRelationship` relies on to avoid reflecting
1455
+ * local-only edits.
1456
+ */
1457
+ if (isRemote) {
1458
+ state.hasReceivedRemoteData = true;
1459
+ }
1444
1460
  state.isEmpty = op.value === null;
1445
1461
  state.isStale = false;
1446
1462
  state.hasFailedLoadAttempt = false;
@@ -1780,12 +1796,12 @@ function diffCollection(finalState, relationship, onAdd, onDel) {
1780
1796
  return _compare(priorLocalState, finalState, finalSet, remoteState, remoteMembers, onAdd, onDel, relationship.definition.resetOnRemoteUpdate);
1781
1797
  }
1782
1798
  function computeLocalState(storage) {
1783
- if (!storage.isDirty) {
1784
- (test => {
1785
- if (!test) {
1786
- throw new Error(`Expected localState to be present`);
1787
- }
1788
- })(Array.isArray(storage.localState));
1799
+ // When the edge is clean and we already have a materialized localState we can
1800
+ // return it directly. A clean edge can still have a `null` localState though:
1801
+ // dematerializing an inverse member (e.g. when a route reloads its model)
1802
+ // nulls out localState without dirtying the edge. In that case we fall through
1803
+ // and recompute from remoteState rather than asserting.
1804
+ if (!storage.isDirty && storage.localState) {
1789
1805
  return storage.localState;
1790
1806
  }
1791
1807
  const state = storage.remoteState.slice();
@@ -2085,8 +2101,30 @@ function rollbackRelationship(graph, key, field, relationship) {
2085
2101
  }
2086
2102
  }
2087
2103
  function createState() {
2104
+ {
2105
+ {
2106
+ if (getGlobalConfig().WarpDrive.debug.LOG_GRAPH || globalThis.getWarpDriveRuntimeConfig().debug.LOG_GRAPH) {
2107
+ let hasReceivedData = false;
2108
+ return {
2109
+ get hasReceivedData() {
2110
+ return hasReceivedData;
2111
+ },
2112
+ set hasReceivedData(value) {
2113
+ hasReceivedData = value;
2114
+ },
2115
+ hasReceivedRemoteData: false,
2116
+ isEmpty: true,
2117
+ isStale: false,
2118
+ hasFailedLoadAttempt: false,
2119
+ shouldForceReload: false,
2120
+ hasDematerializedInverse: false
2121
+ };
2122
+ }
2123
+ }
2124
+ }
2088
2125
  return {
2089
2126
  hasReceivedData: false,
2127
+ hasReceivedRemoteData: false,
2090
2128
  isEmpty: true,
2091
2129
  isStale: false,
2092
2130
  hasFailedLoadAttempt: false,
@@ -2191,13 +2229,10 @@ function legacyGetResourceRelationshipData(source, getRemoteState) {
2191
2229
  source.accessed = true;
2192
2230
  let data;
2193
2231
  const payload = {};
2194
- if (getRemoteState && source.remoteState) {
2195
- data = source.remoteState;
2196
- } else if (!getRemoteState && source.localState) {
2197
- data = source.localState;
2198
- }
2199
- if ((getRemoteState && source.remoteState === null || source.localState === null) && source.state.hasReceivedData) {
2200
- data = null;
2232
+ if (getRemoteState && (source.remoteState || source.state.hasReceivedRemoteData)) {
2233
+ data = source.remoteState ?? null;
2234
+ } else if (!getRemoteState && (source.localState || source.state.hasReceivedData)) {
2235
+ data = source.localState ?? null;
2201
2236
  }
2202
2237
  if (source.links) {
2203
2238
  payload.links = source.links;
@@ -2411,7 +2446,6 @@ function updateRelationshipOperation(graph, op) {
2411
2446
  })(isHasMany(relationship) || isBelongsTo(relationship));
2412
2447
  const {
2413
2448
  definition,
2414
- state,
2415
2449
  identifier
2416
2450
  } = relationship;
2417
2451
  const {
@@ -2451,23 +2485,6 @@ function updateRelationshipOperation(graph, op) {
2451
2485
  value: payload.data ? graph.store.cacheKeyManager.upgradeIdentifier(payload.data) : null
2452
2486
  }, true);
2453
2487
  }
2454
- } else if (definition.isAsync === false && !state.hasReceivedData) {
2455
- hasRelationshipDataProperty = true;
2456
- if (isCollection) {
2457
- graph.update({
2458
- op: 'replaceRelatedRecords',
2459
- record: identifier,
2460
- field: op.field,
2461
- value: []
2462
- }, true);
2463
- } else {
2464
- graph.update({
2465
- op: 'replaceRelatedRecord',
2466
- record: identifier,
2467
- field: op.field,
2468
- value: null
2469
- }, true);
2470
- }
2471
2488
  }
2472
2489
  if (payload.links) {
2473
2490
  const originalLinks = relationship.links;
@@ -2477,9 +2494,10 @@ function updateRelationshipOperation(graph, op) {
2477
2494
  const currentLink = originalLinks && originalLinks.related ? _normalizeLink(originalLinks.related) : null;
2478
2495
  const currentLinkHref = currentLink ? currentLink.href : null;
2479
2496
  if (relatedLink && relatedLink.href && relatedLink.href !== currentLinkHref) {
2480
- console.warn(`You pushed a record of type '${identifier.type}' with a relationship '${definition.key}' configured as 'async: false'. You've included a link but no primary data, this may be an error in your payload. WarpDrive will treat this relationship as known-to-be-empty.`, definition.isAsync || state.hasReceivedData, {
2481
- id: 'ds.store.push-link-for-sync-relationship'
2482
- });
2497
+ // Note: the equivalent warning for this case is issued eagerly (and with a more
2498
+ // complete condition, since it also accounts for `payload.data` being present) by
2499
+ // `assertValidRelationshipPayload` in `-utils.ts`, which always runs immediately
2500
+ // before this operation in DEBUG builds. Warning here too would double-warn.
2483
2501
  (test => {
2484
2502
  if (!test) {
2485
2503
  throw new Error(`You have pushed a record of type '${identifier.type}' with '${definition.key}' as a link, but the value of that link is not a string.`);
@@ -2510,6 +2528,7 @@ function updateRelationshipOperation(graph, op) {
2510
2528
  // we don't need to notify here as the update op we pushed in above will notify once
2511
2529
  // membership is in the correct state.
2512
2530
  relationship.state.hasReceivedData = true;
2531
+ relationship.state.hasReceivedRemoteData = true;
2513
2532
  relationship.state.isStale = false;
2514
2533
  relationship.state.hasDematerializedInverse = false;
2515
2534
  relationship.state.isEmpty = relationshipIsEmpty;