@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
@@ -1,11 +1,13 @@
1
- import { d as defineGate, a as withSignalStore, n as notifyInternalSignal, p as peekInternalSignal, 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 } from "./-leaked-BHLmJyaw.js";
2
- import { EnableHydration, STRUCTURED } from './types/request.js';
3
1
  import { D as Destroy, C as Context, S as SOURCE, a as Checkout, b as Commit } from "./-private-sql1_mdx.js";
4
- import { isResourceSchema } from './types/schema/fields.js';
5
- import { setTransient, peekTransient, getOrSetGlobal } from './types/-private.js';
6
- import './types/runtime.js';
7
- import { w as withBrand } from "./request-BrJSCG6r.js";
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 } from "./-leaked-DgjQ5X55.js";
3
+ import { setTransient, peekTransient, getOrSetGlobal, peekUniversalTransient, setUniversalTransient } from './types/-private.js';
4
+ import { EnableHydration, SkipCache, STRUCTURED } from './types/request.js';
5
+ import { setIsMaybeMirage } from './types/runtime.js';
6
+ import { w as withBrand, c as cloneResponseProperties, I as IS_CACHE_HANDLER, e as executeNextHandler, u as upgradePromise } from "./request-eb7Zm-oS.js";
7
+ import { b as getRequestResult, a as setPromiseResult, c as clearRequestResult } from "./promise-cache-DUblkX-U.js";
8
+ import { DefaultCachePolicy } from './store.js';
8
9
  import { CACHE_OWNER } from './types/identifier.js';
10
+ import { isResourceSchema } from './types/schema/fields.js';
9
11
  import { RecordStore, Type } from './types/symbols.js';
10
12
  function urlFromLink(link) {
11
13
  if (typeof link === 'string') return link;
@@ -1211,6 +1213,212 @@ function unloadRecord(instances, identifier) {
1211
1213
  }
1212
1214
  instances.store._requestCache._clearEntries(identifier);
1213
1215
  }
1216
+ function isCacheOperationValue(value) {
1217
+ return value === 'added' || value === 'state' || value === 'updated' || value === 'removed' || value === 'invalidated';
1218
+ }
1219
+ function _unsubscribe(token, cache) {
1220
+ const cacheKey = token.for;
1221
+ if (cacheKey) {
1222
+ const callbacks = cache.get(cacheKey);
1223
+ if (!callbacks) {
1224
+ return;
1225
+ }
1226
+ const index = callbacks.indexOf(token);
1227
+ if (index === -1) {
1228
+ return;
1229
+ }
1230
+ callbacks.splice(index, 1);
1231
+ }
1232
+ }
1233
+
1234
+ /**
1235
+ * The NotificationManager provides the ability to subscribe to
1236
+ * changes to Cache state.
1237
+ *
1238
+ * This Feature is what allows WarpDrive to create subscriptions that
1239
+ * work with any framework or change-notification system.
1240
+ *
1241
+ * @hideconstructor
1242
+ * @public
1243
+ */
1244
+ class NotificationManager {
1245
+ /** @internal */
1246
+
1247
+ /** @internal */
1248
+
1249
+ /** @internal */
1250
+
1251
+ /** @internal */
1252
+
1253
+ /** @internal */
1254
+
1255
+ /** @internal */
1256
+
1257
+ constructor(store) {
1258
+ this.store = store;
1259
+ this.isDestroyed = false;
1260
+ this._buffered = new Map();
1261
+ this._hasFlush = false;
1262
+ this._cache = new Map();
1263
+ }
1264
+
1265
+ /**
1266
+ * Subscribe to changes for a given ResourceKey, RequestKey, or addition/removal of any resource
1267
+ * or document.
1268
+ *
1269
+ * ```ts
1270
+ * export type CacheOperation = 'added' | 'removed' | 'updated' | 'state';
1271
+ *
1272
+ * export interface NotificationCallback {
1273
+ * (cacheKey: ResourceKey, notificationType: 'attributes' | 'relationships', key?: string): void;
1274
+ * (cacheKey: ResourceKey, notificationType: 'errors' | 'meta' | 'identity' | 'state'): void;
1275
+ * (cacheKey: ResourceKey, notificationType: NotificationType, key?: string): void;
1276
+ * }
1277
+ * export interface ResourceOperationCallback {
1278
+ * // resource updates
1279
+ * (cacheKey: ResourceKey, notificationType: CacheOperation): void;
1280
+ * }
1281
+ * export interface DocumentOperationCallback {
1282
+ * // document updates
1283
+ * (cacheKey: RequestKey, notificationType: CacheOperation): void;
1284
+ * }
1285
+ * ```
1286
+ *
1287
+ * @public
1288
+ * @return an opaque token to be used with unsubscribe
1289
+ */
1290
+
1291
+ subscribe(cacheKey, callback) {
1292
+ let callbacks = this._cache.get(cacheKey);
1293
+ // we use the callback as the cancellation token
1294
+ //@ts-expect-error
1295
+ callback.for = cacheKey;
1296
+ if (!callbacks) {
1297
+ callbacks = [];
1298
+ this._cache.set(cacheKey, callbacks);
1299
+ }
1300
+ callbacks.push(callback);
1301
+ return callback;
1302
+ }
1303
+
1304
+ /**
1305
+ * remove a previous subscription
1306
+ *
1307
+ * @public
1308
+ */
1309
+ unsubscribe(token) {
1310
+ if (!this.isDestroyed) {
1311
+ _unsubscribe(token, this._cache);
1312
+ }
1313
+ }
1314
+
1315
+ /**
1316
+ * Custom Caches and Application Code should not call this method directly.
1317
+ *
1318
+ * @private
1319
+ */
1320
+
1321
+ notify(cacheKey, value, key) {
1322
+ if (this.isDestroyed) {
1323
+ return false;
1324
+ }
1325
+ if (!isResourceKey(cacheKey) && !isRequestKey(cacheKey)) {
1326
+ return false;
1327
+ }
1328
+ const _hasSubscribers = hasSubscribers(this._cache, cacheKey, value);
1329
+ if (_hasSubscribers) {
1330
+ let buffer = this._buffered.get(cacheKey);
1331
+ if (!buffer) {
1332
+ buffer = [];
1333
+ this._buffered.set(cacheKey, buffer);
1334
+ }
1335
+ buffer.push([value, key || null]);
1336
+ if (!this._scheduleNotify()) ;
1337
+ }
1338
+ return _hasSubscribers;
1339
+ }
1340
+
1341
+ /** @internal */
1342
+ _onNextFlush(cb) {
1343
+ this._onFlushCB = cb;
1344
+ }
1345
+
1346
+ /** @internal */
1347
+ _scheduleNotify() {
1348
+ const asyncFlush = this.store._enableAsyncFlush;
1349
+ if (this._hasFlush) {
1350
+ if (asyncFlush !== false && !willSyncFlushWatchers()) {
1351
+ return false;
1352
+ }
1353
+ }
1354
+ if (asyncFlush && !willSyncFlushWatchers()) {
1355
+ this._hasFlush = true;
1356
+ return false;
1357
+ }
1358
+ this._flush();
1359
+ return true;
1360
+ }
1361
+
1362
+ /** @internal */
1363
+ _flush() {
1364
+ const buffered = this._buffered;
1365
+ if (buffered.size) {
1366
+ this._buffered = new Map();
1367
+ for (const [cacheKey, states] of buffered) {
1368
+ for (let i = 0; i < states.length; i++) {
1369
+ // @ts-expect-error
1370
+ _flushNotification(this._cache, cacheKey, states[i][0], states[i][1]);
1371
+ }
1372
+ }
1373
+ }
1374
+ this._hasFlush = false;
1375
+ this._onFlushCB?.();
1376
+ this._onFlushCB = undefined;
1377
+ }
1378
+
1379
+ /** @internal */
1380
+ destroy() {
1381
+ this.isDestroyed = true;
1382
+ this._cache.clear();
1383
+ }
1384
+ }
1385
+
1386
+ /**
1387
+ * This type exists for internal use only for
1388
+ * where intimate contracts still exist either for
1389
+ * the Test Suite or for Legacy code.
1390
+ *
1391
+ * @private
1392
+ */
1393
+
1394
+ function _flushNotification(cache, cacheKey, value, key) {
1395
+ // TODO for documents this will need to switch based on Identifier kind
1396
+ if (isCacheOperationValue(value)) {
1397
+ const callbackMap = cache.get(isRequestKey(cacheKey) ? 'document' : 'resource');
1398
+ if (callbackMap) {
1399
+ callbackMap.forEach(cb => {
1400
+ cb(cacheKey, value);
1401
+ });
1402
+ }
1403
+ }
1404
+ const callbacks = cache.get(cacheKey);
1405
+ if (!callbacks || !callbacks.length) {
1406
+ return false;
1407
+ }
1408
+ callbacks.forEach(cb => {
1409
+ // @ts-expect-error overload doesn't narrow within body
1410
+ cb(cacheKey, value, key);
1411
+ });
1412
+ return true;
1413
+ }
1414
+ function hasSubscribers(cache, cacheKey, value) {
1415
+ const hasSubscriber = Boolean(cache.get(cacheKey)?.length);
1416
+ if (hasSubscriber || !isCacheOperationValue(value)) {
1417
+ return hasSubscriber;
1418
+ }
1419
+ const callbackMap = cache.get(isRequestKey(cacheKey) ? 'document' : 'resource');
1420
+ return Boolean(callbackMap?.length);
1421
+ }
1214
1422
  function update() {
1215
1423
  if (this.isUpdating) {
1216
1424
  return this._updatingPromise;
@@ -1578,7 +1786,7 @@ const ArrayHandler = {
1578
1786
  return false;
1579
1787
  }
1580
1788
  const original = target[index];
1581
- const newIdentifier = extractIdentifierFromRecord$1(value);
1789
+ const newIdentifier = extractIdentifierFromRecord$2(value);
1582
1790
  // We generate "transactions" whenever a setter method on the array
1583
1791
  // is called and might bulk update multiple array cells. Fundamentally,
1584
1792
  // all array operations decompose into individual cell replacements.
@@ -1649,7 +1857,7 @@ function createReactiveResourceArray(options) {
1649
1857
  // Ensure instanceof works correctly
1650
1858
  // Object.setPrototypeOf(IdentifierArray.prototype, Array.prototype);
1651
1859
 
1652
- function extractIdentifierFromRecord$1(record) {
1860
+ function extractIdentifierFromRecord$2(record) {
1653
1861
  if (!record) {
1654
1862
  return null;
1655
1863
  }
@@ -2447,93 +2655,1169 @@ function isLegacyLiveArray(array) {
2447
2655
  const context = array[Context];
2448
2656
  return context.features !== null && context.features.DEPRECATED_CLASS_NAME === 'LiveArray';
2449
2657
  }
2658
+ const Touching = getOrSetGlobal('Touching', Symbol('touching'));
2659
+ const RequestPromise = getOrSetGlobal('RequestPromise', Symbol('promise'));
2660
+ const EMPTY_ARR = [];
2661
+ function hasRecordIdentifier(op) {
2662
+ return 'recordIdentifier' in op;
2663
+ }
2450
2664
 
2451
2665
  /**
2452
- A `RecordReference` is a low-level API that allows users and
2453
- addon authors to perform meta-operations on a record.
2454
-
2455
- @hideconstructor
2456
- @public
2457
- */
2458
- class RecordReference {
2666
+ * The RequestStateService is used to track the state of requests
2667
+ * for fetching or updating known resource identifies that are inflight.
2668
+ *
2669
+ * @hideconstructor
2670
+ * @public
2671
+ */
2672
+ class RequestStateService {
2459
2673
  /** @internal */
2460
-
2461
- // unsubscribe token given to us by the notification manager
2674
+ _pending = new Map();
2462
2675
  /** @internal */
2463
- ___token;
2676
+ _done = new Map();
2464
2677
  /** @internal */
2465
- ___identifier;
2678
+ _subscriptions = new Map();
2679
+ /** @internal */
2680
+ _toFlush = [];
2681
+ /** @internal */
2682
+ _store;
2683
+ constructor(store) {
2684
+ this._store = store;
2685
+ }
2686
+ /** @internal */
2687
+ _clearEntries(identifier) {
2688
+ this._done.delete(identifier);
2689
+ }
2690
+ /** @internal */
2691
+ _enqueue(promise, queryRequest) {
2692
+ const query = queryRequest.data[0];
2693
+ if (hasRecordIdentifier(query)) {
2694
+ const identifier = query.recordIdentifier;
2695
+ const type = query.op === 'saveRecord' ? 'mutation' : 'query';
2696
+ if (!this._pending.has(identifier)) {
2697
+ this._pending.set(identifier, []);
2698
+ }
2699
+ const request = {
2700
+ state: 'pending',
2701
+ request: queryRequest,
2702
+ type
2703
+ };
2704
+ request[Touching] = [query.recordIdentifier];
2705
+ request[RequestPromise] = promise;
2706
+ this._pending.get(identifier).push(request);
2707
+ this._triggerSubscriptions(request);
2708
+ return promise.then(result => {
2709
+ this._dequeue(identifier, request);
2710
+ const finalizedRequest = {
2711
+ state: 'fulfilled',
2712
+ request: queryRequest,
2713
+ type,
2714
+ response: {
2715
+ data: result
2716
+ }
2717
+ };
2718
+ finalizedRequest[Touching] = request[Touching];
2719
+ this._addDone(finalizedRequest);
2720
+ this._triggerSubscriptions(finalizedRequest);
2721
+ return result;
2722
+ }, error => {
2723
+ this._dequeue(identifier, request);
2724
+ const finalizedRequest = {
2725
+ state: 'rejected',
2726
+ request: queryRequest,
2727
+ type,
2728
+ response: {
2729
+ data: error
2730
+ }
2731
+ };
2732
+ finalizedRequest[Touching] = request[Touching];
2733
+ this._addDone(finalizedRequest);
2734
+ this._triggerSubscriptions(finalizedRequest);
2735
+ throw error;
2736
+ });
2737
+ }
2738
+ }
2739
+
2740
+ /** @internal */
2741
+ _triggerSubscriptions(req) {
2742
+ if (req.state === 'pending') {
2743
+ this._flushRequest(req);
2744
+ return;
2745
+ }
2746
+ this._toFlush.push(req);
2747
+ if (this._toFlush.length === 1) {
2748
+ this._store.notifications._onNextFlush(() => {
2749
+ this._flush();
2750
+ });
2751
+ }
2752
+ }
2753
+
2466
2754
  /** @internal */
2755
+ _flush() {
2756
+ this._toFlush.forEach(req => {
2757
+ this._flushRequest(req);
2758
+ });
2759
+ this._toFlush = [];
2760
+ }
2467
2761
 
2468
- constructor(store, identifier) {
2469
- this.store = store;
2470
- this.___identifier = identifier;
2471
- this.___token = store.notifications.subscribe(identifier, (_, bucket, notifiedKey) => {
2472
- if (bucket === 'identity' || bucket === 'attributes' && notifiedKey === 'id') {
2473
- this._ref++;
2762
+ /** @internal */
2763
+ _flushRequest(req) {
2764
+ req[Touching].forEach(identifier => {
2765
+ const subscriptions = this._subscriptions.get(identifier);
2766
+ if (subscriptions) {
2767
+ subscriptions.forEach(callback => callback(req));
2474
2768
  }
2475
2769
  });
2476
2770
  }
2477
2771
 
2478
2772
  /** @internal */
2479
- destroy() {
2480
- this.store.notifications.unsubscribe(this.___token);
2773
+ _dequeue(identifier, request) {
2774
+ const pending = this._pending.get(identifier);
2775
+ this._pending.set(identifier, pending.filter(req => req !== request));
2481
2776
  }
2482
- get type() {
2483
- return this.identifier().type;
2777
+
2778
+ /** @internal */
2779
+ _addDone(request) {
2780
+ request[Touching].forEach(identifier => {
2781
+ // TODO add support for multiple
2782
+ const requestDataOp = request.request.data[0].op;
2783
+ let requests = this._done.get(identifier);
2784
+ if (requests) {
2785
+ requests = requests.filter(req => {
2786
+ // TODO add support for multiple
2787
+ let data;
2788
+ if (Array.isArray(req.request.data)) {
2789
+ data = req.request.data[0];
2790
+ } else {
2791
+ data = req.request.data;
2792
+ }
2793
+ return data.op !== requestDataOp;
2794
+ });
2795
+ }
2796
+ requests = requests || [];
2797
+ requests.push(request);
2798
+ this._done.set(identifier, requests);
2799
+ });
2484
2800
  }
2485
2801
 
2486
2802
  /**
2487
- The `id` of the record that this reference refers to.
2488
- Together, the `type` and `id` properties form a composite key for
2489
- the identity map.
2490
- Example
2491
- ```javascript
2492
- let userRef = store.getReference('user', 1);
2493
- userRef.id(); // '1'
2494
- ```
2495
- @public
2496
- @return The id of the record.
2497
- */
2498
- id() {
2499
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
2500
- this._ref; // consume the tracked prop
2501
- return this.___identifier.id;
2803
+ * Subscribe to requests for a given resource identity.
2804
+ *
2805
+ * The callback will receive the current state of the request.
2806
+ *
2807
+ * ```ts
2808
+ * interface RequestState {
2809
+ * state: 'pending' | 'fulfilled' | 'rejected';
2810
+ * type: 'query' | 'mutation';
2811
+ * request: Request;
2812
+ * response?: { data: unknown };
2813
+ * }
2814
+ * ```
2815
+ *
2816
+ * Note: It should be considered dangerous to use this API for more than simple
2817
+ * state derivation or debugging. The `request` and `response` properties are poorly
2818
+ * spec'd and may change unexpectedly when shifting what Handlers are in use or how
2819
+ * requests are issued from the Store.
2820
+ *
2821
+ * We expect to revisit this API in the near future as we continue to refine the
2822
+ * RequestManager ergonomics, as a simpler but more powerful direct integration
2823
+ * with the RequestManager for these purposes is likely to be a better long-term
2824
+ * design.
2825
+ *
2826
+ * @public
2827
+ * @param {ResourceKey} identifier
2828
+ * @param {(state: RequestCacheRequestState) => void} callback
2829
+ */
2830
+ subscribeForRecord(identifier, callback) {
2831
+ let subscriptions = this._subscriptions.get(identifier);
2832
+ if (!subscriptions) {
2833
+ subscriptions = [];
2834
+ this._subscriptions.set(identifier, subscriptions);
2835
+ }
2836
+ subscriptions.push(callback);
2502
2837
  }
2503
2838
 
2504
2839
  /**
2505
- The `identifier` of the record that this reference refers to.
2506
- Together, the `type` and `id` properties form a composite key for
2507
- the identity map.
2508
- Example
2509
- ```javascript
2510
- let userRef = store.getReference('user', 1);
2511
- userRef.identifier(); // '1'
2512
- ```
2513
- @public
2514
- @return The identifier of the record.
2515
- */
2516
- identifier() {
2517
- return this.___identifier;
2840
+ * Retrieve all active requests for a given resource identity.
2841
+ *
2842
+ * @public
2843
+ * @param {ResourceKey} identifier
2844
+ * @return {RequestCacheRequestState[]} an array of request states for any pending requests for the given identifier
2845
+ */
2846
+ getPendingRequestsForRecord(identifier) {
2847
+ return this._pending.get(identifier) || EMPTY_ARR;
2518
2848
  }
2519
2849
 
2520
2850
  /**
2521
- How the reference will be looked up when it is loaded. Currently
2522
- this always returns `identity` to signify that a record will be
2523
- loaded by its `type` and `id`.
2524
- Example
2525
- ```javascript
2526
- const userRef = store.getReference('user', 1);
2527
- userRef.remoteType(); // 'identity'
2528
- ```
2529
- @public
2530
- */
2531
- remoteType() {
2532
- return 'identity';
2851
+ * Retrieve the last completed request for a given resource identity.
2852
+ *
2853
+ * @public
2854
+ * @param {ResourceKey} identifier
2855
+ * @return {RequestCacheRequestState | null} the state of the most recent request for the given identifier
2856
+ */
2857
+ getLastRequestForRecord(identifier) {
2858
+ const requests = this._done.get(identifier);
2859
+ if (requests) {
2860
+ return requests[requests.length - 1];
2861
+ }
2862
+ return null;
2533
2863
  }
2864
+ }
2865
+
2866
+ /**
2867
+ * This type exists for internal use only for
2868
+ * where intimate contracts still exist either for
2869
+ * the Test Suite or for Legacy code.
2870
+ *
2871
+ * @private
2872
+ */
2873
+
2874
+ // this import location is deprecated but breaks in 4.8 and older
2875
+
2876
+ // `AwaitedKeys` is needed here to resolve any promise types like `PromiseBelongsTo`.
2877
+
2878
+ /**
2879
+ * Currently only records that extend object can be created via
2880
+ * store.createRecord. This is a limitation of the current API,
2881
+ * but can be worked around by creating a new identifier, running
2882
+ * the cache.clientDidCreate method, and then peeking the record
2883
+ * for the identifier.
2884
+ *
2885
+ * To assign primary key to a record during creation, only `id` will
2886
+ * work correctly for `store.createRecord`, other primary key may be
2887
+ * handled by updating the record after creation or using the flow
2888
+ * described above.
2889
+ *
2890
+ * TODO: These are limitations we want to (and can) address. If you
2891
+ * have need of lifting these limitations, please open an issue.
2892
+ *
2893
+ */
2894
+
2895
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2896
+
2897
+ // eslint-disable-next-line @typescript-eslint/no-extraneous-class
2898
+ const EmptyClass = class {
2899
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
2900
+ constructor(args) {}
2901
+ };
2902
+ const _BaseClass = EmptyClass;
2903
+ const BaseClass = _BaseClass.default ? _BaseClass.default : _BaseClass;
2904
+ /**
2905
+ * ```ts
2906
+ * import { Store } from '@warp-drive/core';
2907
+ * ```
2908
+ *
2909
+ * The `Store` is the central piece of the ***Warp*Drive** experience. It connects
2910
+ * requests for data with schemas, caching and reactivity.
2911
+ *
2912
+ * While it's easy to use ***just*** ***Warp*Drive**'s request management, most projects will find they
2913
+ * require far more than basic fetch management. For this reason it's often best to start with a `Store`
2914
+ * even when you aren't sure yet.
2915
+ *
2916
+ * Most projects will only have a single `Store`, though using multiple distinct stores
2917
+ * is possible.
2918
+ *
2919
+ * @public
2920
+ * @hideconstructor
2921
+ */
2922
+ class Store extends BaseClass {
2923
+ /** @internal */
2534
2924
 
2535
2925
  /**
2536
- This API allows you to provide a reference with new data. The
2926
+ * Provides access to the {@link NotificationManager} associated
2927
+ * with this Store instance.
2928
+ *
2929
+ * The NotificationManager can be used to subscribe to
2930
+ * changes to the cache.
2931
+ *
2932
+ * @public
2933
+ */
2934
+
2935
+ /**
2936
+ * Provides access to the SchemaService instance
2937
+ * for this Store instance.
2938
+ *
2939
+ * The SchemaService can be used to query for
2940
+ * information about the schema of a resource.
2941
+ *
2942
+ * @public
2943
+ */
2944
+ get schema() {
2945
+ if (!this._schema) {
2946
+ this._schema = this.createSchemaService();
2947
+ }
2948
+ return this._schema;
2949
+ }
2950
+ /** @internal */
2951
+
2952
+ /**
2953
+ * Provides access to the CacheKeyManager
2954
+ * for this store.
2955
+ *
2956
+ * The CacheKeyManager can be used to generate or
2957
+ * retrieve a stable unique CacheKey for any resource
2958
+ * or request.
2959
+ *
2960
+ * @public
2961
+ */
2962
+
2963
+ /**
2964
+ * Provides access to the {@link RequestManager} instance associated
2965
+ * with this Store instance.
2966
+ *
2967
+ * See also:
2968
+ * - {@link Fetch}
2969
+ * - {@link CacheHandlerInterface | CacheHandler (Interface)}
2970
+ * - {@link CacheHandler | CacheHandler (Class)}
2971
+ *
2972
+ * ```ts
2973
+ * import { CacheHandler, Fetch, RequestManager, Store } from '@warp-drive/core';
2974
+ *
2975
+ * class AppStore extends Store {
2976
+ * requestManager = new RequestManager()
2977
+ * .use([Fetch])
2978
+ * .useCache(CacheHandler);
2979
+ * }
2980
+ * ```
2981
+ *
2982
+ * @public
2983
+ */
2984
+
2985
+ /**
2986
+ * A Property which an App may set to provide a CachePolicy
2987
+ * to control when a cached request becomes stale.
2988
+ *
2989
+ * Note, when defined, these methods will only be invoked if a
2990
+ * cache key exists for the request, either because the request
2991
+ * contains `cacheOptions.key` or because the {@link CacheKeyManager}
2992
+ * was able to generate a key for the request using the configured
2993
+ * {@link setIdentifierGenerationMethod | generation method}.
2994
+ *
2995
+ * `isSoftExpired` will only be invoked if `isHardExpired` returns `false`.
2996
+ *
2997
+ * ```ts
2998
+ * store.lifetimes = {
2999
+ * // make the request and ignore the current cache state
3000
+ * isHardExpired(key: RequestKey): boolean {
3001
+ * return false;
3002
+ * }
3003
+ *
3004
+ * // make the request in the background if true, return cache state
3005
+ * isSoftExpired(key: RequestKey): boolean {
3006
+ * return false;
3007
+ * }
3008
+ * }
3009
+ * ```
3010
+ *
3011
+ * @public
3012
+ */
3013
+
3014
+ // Private
3015
+ /** @internal */
3016
+
3017
+ /** @internal */
3018
+
3019
+ /** @internal */
3020
+
3021
+ /** @internal */
3022
+
3023
+ /** @internal */
3024
+
3025
+ /**
3026
+ * Async flush buffers notifications until flushed
3027
+ * by finalization of a future configured by store.request
3028
+ *
3029
+ * This is useful for ensuring that notifications are delivered
3030
+ * prior to the promise resolving but without risk of promise
3031
+ * interleaving.
3032
+ *
3033
+ * @internal
3034
+ */
3035
+
3036
+ /**
3037
+ * Available in DEBUG Only
3038
+ * @internal
3039
+ */
3040
+
3041
+ /** @internal */
3042
+
3043
+ /** @internal */
3044
+
3045
+ /** @private */
3046
+ get isDestroying() {
3047
+ return this._isDestroying;
3048
+ }
3049
+ /** @internal */
3050
+ set isDestroying(value) {
3051
+ this._isDestroying = value;
3052
+ }
3053
+ /** @private */
3054
+ get isDestroyed() {
3055
+ return this._isDestroyed;
3056
+ }
3057
+ /** @internal */
3058
+ set isDestroyed(value) {
3059
+ this._isDestroyed = value;
3060
+ }
3061
+
3062
+ /** @deprecated use {@link Store.cacheKeyManager} */
3063
+ get identifierCache() {
3064
+ return this.cacheKeyManager;
3065
+ }
3066
+
3067
+ /**
3068
+ @private
3069
+ */
3070
+ constructor(createArgs) {
3071
+ super(createArgs);
3072
+ Object.assign(this, createArgs);
3073
+ this.cacheKeyManager = new CacheKeyManager();
3074
+ this.notifications = new NotificationManager(this);
3075
+
3076
+ // private but maybe useful to be here, somewhat intimate
3077
+ this.recordArrayManager = new RecordArrayManager({
3078
+ store: this
3079
+ });
3080
+
3081
+ // private
3082
+ this._requestCache = new RequestStateService(this);
3083
+ this._instanceCache = new InstanceCache(this);
3084
+ this.isDestroying = false;
3085
+ this.isDestroyed = false;
3086
+ }
3087
+
3088
+ /** @internal */
3089
+ _run(cb) {
3090
+ const _cbs = this._cbs = {};
3091
+ {
3092
+ cb();
3093
+ if (_cbs.coalesce) {
3094
+ _cbs.coalesce();
3095
+ }
3096
+ if (_cbs.sync) {
3097
+ _cbs.sync();
3098
+ }
3099
+ if (_cbs.notify) {
3100
+ _cbs.notify();
3101
+ }
3102
+ this._cbs = null;
3103
+ }
3104
+ }
3105
+
3106
+ /**
3107
+ * Executes the callback, ensurng that any work that calls
3108
+ * store._schedule is executed after in the right order.
3109
+ *
3110
+ * When queues already exist, scheduled callbacks will
3111
+ * join the existing queue.
3112
+ *
3113
+ * @internal
3114
+ */
3115
+ _join(cb) {
3116
+ if (this._cbs) {
3117
+ cb();
3118
+ } else {
3119
+ this._run(cb);
3120
+ }
3121
+ }
3122
+
3123
+ /** @internal */
3124
+ _schedule(name, cb) {
3125
+ this._cbs[name] = cb;
3126
+ }
3127
+
3128
+ /**
3129
+ * Retrieve the RequestStateService instance
3130
+ * associated with this Store.
3131
+ *
3132
+ * This can be used to query the status of requests
3133
+ * that have been initiated for a given identifier.
3134
+ *
3135
+ * @return {RequestStateService}
3136
+ * @public
3137
+ */
3138
+ getRequestStateService() {
3139
+ return this._requestCache;
3140
+ }
3141
+
3142
+ /** @internal */
3143
+ _getAllPending() {}
3144
+
3145
+ /**
3146
+ * ::: tip 💡 For a more complete overview see the [Request Guide](/guides/2-requests/1-overview)
3147
+ * :::
3148
+ *
3149
+ * Issue a request via the configured {@link RequestManager},
3150
+ * inserting the response into the {@link Store.cache | cache} and handing
3151
+ * back a {@link Future} which resolves to a {@link ReactiveDocument | ReactiveDocument}
3152
+ *
3153
+ * #### Request Cache Keys
3154
+ *
3155
+ * Only {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/GET | GET} requests with a url or requests with an explicit
3156
+ * {@link CacheOptions.key | cache key} will have the request result
3157
+ * and document cached.
3158
+ *
3159
+ * The cache key used is {@link RequestInfo.cacheOptions.key | RequestInfo.cacheOptions.key}
3160
+ * if present, falling back to {@link RequestInfo.url}.
3161
+ *
3162
+ * Params are not serialized as part of the cache-key, so
3163
+ * either ensure they are already in the url or utilize
3164
+ * `requestConfig.cacheOptions.key`. For queries issued
3165
+ * via the {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST | POST} method `requestConfig.cacheOptions.key`
3166
+ * MUST be supplied for the document to be cached.
3167
+ *
3168
+ * #### Requesting Without a Cache Key
3169
+ *
3170
+ * Resource data within the request is always updated in the cache,
3171
+ * regardless of whether a cache key is present for the request.
3172
+ *
3173
+ * #### Fulfilling From Cache
3174
+ *
3175
+ * When a cache-key is determined, the request may fulfill
3176
+ * from cache provided the cache is not stale.
3177
+ *
3178
+ * Cache staleness is determined by the configured {@link CachePolicy}
3179
+ * with priority given to the {@link CacheOptions.reload} and
3180
+ * {@link CacheOptions.backgroundReload} on the request if present.
3181
+ *
3182
+ * If the cache data has soft expired or the request asks for a background
3183
+ * reload, the request will fulfill from cache if possible and
3184
+ * make a non-blocking request in the background to update the cache.
3185
+ *
3186
+ * If the cache data has hard expired or the request asks for a reload,
3187
+ * the request will not fulfill from cache and will make a blocking
3188
+ * request to update the cache.
3189
+ *
3190
+ * #### The Response
3191
+ *
3192
+ * The primary difference between {@link RequestManager.request} and `store.request`
3193
+ * is that `store.request` will convert the response into a {@link ReactiveDocument}
3194
+ * containing {@link Store.instantiateRecord | ReactiveResources}.
3195
+ *
3196
+ * @public
3197
+ */
3198
+ request(requestConfig) {
3199
+ // we lazily set the cache handler when we issue the first request
3200
+ // because constructor doesn't allow for this to run after
3201
+ // the user has had the chance to set the prop.
3202
+ const opts = {
3203
+ store: this,
3204
+ [EnableHydration]: requestConfig[EnableHydration] ?? true
3205
+ };
3206
+ if (requestConfig.records) {
3207
+ const cacheKeyManager = this.cacheKeyManager;
3208
+ opts.records = requestConfig.records.map(r => cacheKeyManager.getOrCreateRecordIdentifier(r));
3209
+ }
3210
+ const request = Object.assign({}, requestConfig, opts);
3211
+ const future = this.requestManager.request(request);
3212
+ future.onFinalize(() => {
3213
+ // skip flush for legacy belongsTo
3214
+ if (requestConfig.op === 'findBelongsTo' && !requestConfig.url) {
3215
+ return;
3216
+ }
3217
+ this.notifications._flush();
3218
+ });
3219
+ return future;
3220
+ }
3221
+
3222
+ /**
3223
+ Creates a new record in the current store.
3224
+ > [!CAUTION]
3225
+ > This should not be used to mock records or to create
3226
+ > a record representing data that could be fetched from
3227
+ > the API.
3228
+ The properties passed to this method are set on
3229
+ the newly created record.
3230
+ For instance: to create a new `post`:
3231
+ ```js
3232
+ store.createRecord('post', {
3233
+ title: 'WarpDrive is Stellar!'
3234
+ });
3235
+ ```
3236
+ Relationships can be set during create. For instance,
3237
+ to create a new `post` that has an existing user as
3238
+ it's author:
3239
+ ```js
3240
+ const user = store.peekRecord('user', '1');
3241
+ store.createRecord('post', {
3242
+ title: 'WarpDrive is Stellar!',
3243
+ user: user
3244
+ });
3245
+ ```
3246
+ ### lid handling
3247
+ All new records are assigned an `lid` that can be used to handle
3248
+ transactional saves of multiple records, or to link the data to
3249
+ other data in scenarios involving eventual-consistency or remote
3250
+ syncing.
3251
+ ```ts
3252
+ const post = store.createRecord('post', {
3253
+ title: 'WarpDrive is Stellar!'
3254
+ });
3255
+ const { lid } = recordIdentifierFor(post);
3256
+ ```
3257
+ The `lid` defaults to a uuidv4 string.
3258
+ In order to support receiving knowledge about unpersisted creates
3259
+ from other sources (say a different tab in the same web-browser),
3260
+ createRecord allows for the `lid` to be provided as part of an
3261
+ optional third argument. **If this lid already exists in the store
3262
+ an error will be thrown.**
3263
+ ```ts
3264
+ const post = store.createRecord(
3265
+ 'post',
3266
+ { title: 'WarpDrive is Stellar!' },
3267
+ { lid: '4d47bb88-931f-496e-986d-c4888cef7373' }
3268
+ );
3269
+ ```
3270
+ @public
3271
+ @param type the name of the resource
3272
+ @param inputProperties a hash of properties to set on the
3273
+ newly created record.
3274
+ @return a record in the "isNew" state
3275
+ */
3276
+
3277
+ createRecord(type, inputProperties, context) {
3278
+ // This is wrapped in a `run.join` so that in test environments users do not need to manually wrap
3279
+ // calls to `createRecord`. The run loop usage here is because we batch the joining and updating
3280
+ // of record-arrays via ember's run loop, not our own.
3281
+ //
3282
+ // to remove this, we would need to move to a new `async` API.
3283
+ let record;
3284
+ this._join(() => {
3285
+ const normalizedModelName = normalizeModelName(type);
3286
+ const properties = {
3287
+ ...inputProperties
3288
+ };
3289
+
3290
+ // If the passed properties do not include a primary key,
3291
+ // give the adapter an opportunity to generate one. Typically,
3292
+ // client-side ID generators will use something like uuid.js
3293
+ // to avoid conflicts.
3294
+ let id = null;
3295
+ if (properties.id === null || properties.id === undefined) {
3296
+ const adapter = this.adapterFor?.(normalizedModelName, true);
3297
+ if (adapter && adapter.generateIdForRecord) {
3298
+ id = properties.id = coerceId(adapter.generateIdForRecord(this, normalizedModelName, properties));
3299
+ } else {
3300
+ id = properties.id = null;
3301
+ }
3302
+ } else {
3303
+ id = properties.id = coerceId(properties.id);
3304
+ }
3305
+ const resource = {
3306
+ type: normalizedModelName,
3307
+ id
3308
+ };
3309
+ if (resource.id) {
3310
+ this.cacheKeyManager.peekResourceKey(resource);
3311
+ }
3312
+ if (context?.lid) {
3313
+ this.cacheKeyManager.peekResourceKey({
3314
+ lid: context?.lid
3315
+ });
3316
+ resource.lid = context.lid;
3317
+ }
3318
+ const identifier = this.cacheKeyManager.createIdentifierForNewRecord(resource);
3319
+ const cache = this.cache;
3320
+ const createOptions = normalizeProperties(this, identifier, properties);
3321
+ const resultProps = cache.clientDidCreate(identifier, createOptions);
3322
+ record = getNewRecord(this._instanceCache, identifier, resultProps);
3323
+ });
3324
+ return record;
3325
+ }
3326
+
3327
+ /**
3328
+ For symmetry, a record can be deleted via the store.
3329
+ Example
3330
+ ```javascript
3331
+ let post = store.createRecord('post', {
3332
+ title: 'Ember is awesome!'
3333
+ });
3334
+ store.deleteRecord(post);
3335
+ ```
3336
+ @public
3337
+ @param {unknown} record
3338
+ */
3339
+ deleteRecord(record) {
3340
+ const identifier = peekResourceKey(record);
3341
+ const cache = this.cache;
3342
+ this._join(() => {
3343
+ cache.setIsDeleted(identifier, true);
3344
+ if (cache.isNew(identifier)) {
3345
+ this._instanceCache.unloadRecord(identifier);
3346
+ }
3347
+ });
3348
+ }
3349
+
3350
+ /**
3351
+ For symmetry, a record can be unloaded via the store.
3352
+ This will cause the record to be destroyed and freed up for garbage collection.
3353
+ Example
3354
+ ```javascript
3355
+ const { content: { data: post } } = await store.request(findRecord({ type: 'post', id: '1' }));
3356
+ store.unloadRecord(post);
3357
+ ```
3358
+ @public
3359
+ @param {Model} record
3360
+ */
3361
+ unloadRecord(record) {
3362
+ const identifier = peekResourceKey(record);
3363
+ if (identifier) {
3364
+ this._instanceCache.unloadRecord(identifier);
3365
+ }
3366
+ }
3367
+
3368
+ /**
3369
+ Get a record by a given type and ID without triggering a fetch.
3370
+ This method will synchronously return the record if it is available in the store,
3371
+ otherwise it will return `null`. A record is available if it has been fetched earlier, or
3372
+ pushed manually into the store.
3373
+ **Example 1**
3374
+ ```ts
3375
+ const post = store.peekRecord('post', '1');
3376
+ post.id; // '1'
3377
+ ```
3378
+ `peekRecord` can be called with a single identifier argument instead of the combination
3379
+ of `type` (modelName) and `id` as separate arguments. You may recognize this combo as
3380
+ the typical pairing from [JSON:API](https://jsonapi.org/format/#document-resource-object-identification)
3381
+ **Example 2**
3382
+ ```ts
3383
+ const post = store.peekRecord({ type: 'post', id: '1' });
3384
+ post.id; // '1'
3385
+ ```
3386
+ If you have previously received an lid from an Identifier for this record, you can lookup the record again using
3387
+ just the lid.
3388
+ **Example 3**
3389
+ ```js
3390
+ let post = store.peekRecord({ lid });
3391
+ post.id; // '1'
3392
+ ```
3393
+ @since 1.13.0
3394
+ @public
3395
+ @param type - either a string representing the modelName or a ResourceIdentifier object containing both the type (a string) and the id (a string) for the record or an lid (a string) of an existing record
3396
+ @param id - optional only if the first param is a ResourceIdentifier, else the string id of the record to be retrieved.
3397
+ */
3398
+
3399
+ peekRecord(identifier, id) {
3400
+ if (arguments.length === 1 && isMaybeIdentifier(identifier)) {
3401
+ const stableIdentifier = this.cacheKeyManager.peekResourceKey(identifier);
3402
+ const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
3403
+ // TODO come up with a better mechanism for determining if we have data and could peek.
3404
+ // this is basically an "are we not empty" query.
3405
+ return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
3406
+ }
3407
+ const type = normalizeModelName(identifier);
3408
+ const normalizedId = ensureStringId(id);
3409
+ const resource = {
3410
+ type,
3411
+ id: normalizedId
3412
+ };
3413
+ const stableIdentifier = this.cacheKeyManager.peekResourceKey(resource);
3414
+ const isLoaded = stableIdentifier && this._instanceCache.recordIsLoaded(stableIdentifier);
3415
+ return isLoaded ? this._instanceCache.getRecord(stableIdentifier) : null;
3416
+ }
3417
+
3418
+ /**
3419
+ This method returns the {@link LegacyLiveArray} that contains all of the
3420
+ known records for a given type in the store. Each ResourceType has only
3421
+ one LiveArray instance, so multiple calls to `peekAll` with the same type
3422
+ will always return the same instance.
3423
+ Note that because it's a LiveArray, the result will contain any
3424
+ locally created records of the type, however, it will not make a
3425
+ request to the backend to retrieve additional records.
3426
+ Example
3427
+ ```ts
3428
+ const allPosts = store.peekAll('post');
3429
+ ```
3430
+ @since 1.13.0
3431
+ @public
3432
+ @param type the name of the resource
3433
+ */
3434
+
3435
+ peekAll(type) {
3436
+ return this.recordArrayManager.liveArrayFor(normalizeModelName(type));
3437
+ }
3438
+
3439
+ /**
3440
+ This method unloads all records in the store.
3441
+ It schedules unloading to happen during the next run loop.
3442
+ Optionally you can pass a type which unload all records for a given type.
3443
+ ```javascript
3444
+ store.unloadAll();
3445
+ store.unloadAll('post');
3446
+ ```
3447
+ @param {String} type the name of the resource
3448
+ @public
3449
+ */
3450
+
3451
+ unloadAll(type) {
3452
+ this._join(() => {
3453
+ this._enableAsyncFlush = true;
3454
+ if (type === undefined) {
3455
+ this.recordArrayManager.pause();
3456
+ // destroy the graph before unloadAll
3457
+ // since then we avoid churning relationships
3458
+ // during unload
3459
+ this._graph?.identifiers.clear();
3460
+ this.recordArrayManager.clear();
3461
+ this._instanceCache.clear();
3462
+ } else {
3463
+ this._instanceCache.clear(normalizeModelName(type));
3464
+ }
3465
+ this._enableAsyncFlush = null;
3466
+ this.notifications._flush();
3467
+ if (type === undefined) {
3468
+ this.recordArrayManager.resume();
3469
+ }
3470
+ });
3471
+ }
3472
+
3473
+ /**
3474
+ Push some data for a given type into the store.
3475
+ This method expects normalized [JSON API](http://jsonapi.org/) document. This means you have to follow [JSON API specification](http://jsonapi.org/format/) with few minor adjustments:
3476
+ - record's `type` should always be in singular, dasherized form
3477
+ - members (properties) should be camelCased
3478
+ [Your primary data should be wrapped inside `data` property](http://jsonapi.org/format/#document-top-level):
3479
+ ```js
3480
+ store.push({
3481
+ data: {
3482
+ // primary data for single record of type `Person`
3483
+ id: '1',
3484
+ type: 'person',
3485
+ attributes: {
3486
+ firstName: 'Daniel',
3487
+ lastName: 'Kmak'
3488
+ }
3489
+ }
3490
+ });
3491
+ ```
3492
+ [Demo.](http://ember-twiddle.com/fb99f18cd3b4d3e2a4c7)
3493
+ `data` property can also hold an array (of records):
3494
+ ```js
3495
+ store.push({
3496
+ data: [
3497
+ // an array of records
3498
+ {
3499
+ id: '1',
3500
+ type: 'person',
3501
+ attributes: {
3502
+ firstName: 'Daniel',
3503
+ lastName: 'Kmak'
3504
+ }
3505
+ },
3506
+ {
3507
+ id: '2',
3508
+ type: 'person',
3509
+ attributes: {
3510
+ firstName: 'Tom',
3511
+ lastName: 'Dale'
3512
+ }
3513
+ }
3514
+ ]
3515
+ });
3516
+ ```
3517
+ [Demo.](http://ember-twiddle.com/69cdbeaa3702159dc355)
3518
+ There are some typical properties for `JSONAPI` payload:
3519
+ * `id` - mandatory, unique record's key
3520
+ * `type` - mandatory string which matches `model`'s dasherized name in singular form
3521
+ * `attributes` - object which holds data for record attributes - `attr`'s declared in model
3522
+ * `relationships` - object which must contain any of the following properties under each relationships' respective key (example path is `relationships.achievements.data`):
3523
+ - [`links`](http://jsonapi.org/format/#document-links)
3524
+ - [`data`](http://jsonapi.org/format/#document-resource-object-linkage) - place for primary data
3525
+ - [`meta`](http://jsonapi.org/format/#document-meta) - object which contains meta-information about relationship
3526
+ For this model:
3527
+ ```js [app/models/person.js]
3528
+ import Model, { attr, hasMany } from '@warp-drive/legacy/model';
3529
+ export default class PersonRoute extends Route {
3530
+ @attr('string') firstName;
3531
+ @attr('string') lastName;
3532
+ @hasMany('person') children;
3533
+ }
3534
+ ```
3535
+ To represent the children as IDs:
3536
+ ```js
3537
+ {
3538
+ data: {
3539
+ id: '1',
3540
+ type: 'person',
3541
+ attributes: {
3542
+ firstName: 'Tom',
3543
+ lastName: 'Dale'
3544
+ },
3545
+ relationships: {
3546
+ children: {
3547
+ data: [
3548
+ {
3549
+ id: '2',
3550
+ type: 'person'
3551
+ },
3552
+ {
3553
+ id: '3',
3554
+ type: 'person'
3555
+ },
3556
+ {
3557
+ id: '4',
3558
+ type: 'person'
3559
+ }
3560
+ ]
3561
+ }
3562
+ }
3563
+ }
3564
+ }
3565
+ ```
3566
+ [Demo.](http://ember-twiddle.com/343e1735e034091f5bde)
3567
+ To represent the children relationship as a URL:
3568
+ ```js
3569
+ {
3570
+ data: {
3571
+ id: '1',
3572
+ type: 'person',
3573
+ attributes: {
3574
+ firstName: 'Tom',
3575
+ lastName: 'Dale'
3576
+ },
3577
+ relationships: {
3578
+ children: {
3579
+ links: {
3580
+ related: '/people/1/children'
3581
+ }
3582
+ }
3583
+ }
3584
+ }
3585
+ }
3586
+ ```
3587
+ If you're streaming data, or implementing response handling, make sure
3588
+ that you have converted the incoming data into this form.
3589
+ This method can be used both to push in brand new
3590
+ records, as well as to update existing records.
3591
+ See also {@link Cache.patch}
3592
+ @public
3593
+ @param data
3594
+ @return the primary record(s) that created or updated.
3595
+ */
3596
+
3597
+ push(data) {
3598
+ const pushed = this._push(data, false);
3599
+ if (Array.isArray(pushed)) {
3600
+ return pushed.map(identifier => this._instanceCache.getRecord(identifier));
3601
+ }
3602
+ if (pushed === null) {
3603
+ return null;
3604
+ }
3605
+ return this._instanceCache.getRecord(pushed);
3606
+ }
3607
+
3608
+ /**
3609
+ Push some data in the form of a json-api document into the store,
3610
+ without creating materialized records.
3611
+ @private
3612
+ @return identifiers for the primary records that had data loaded
3613
+ */
3614
+ _push(jsonApiDoc, asyncFlush) {
3615
+ if (asyncFlush) {
3616
+ this._enableAsyncFlush = true;
3617
+ }
3618
+ let ret;
3619
+ this._join(() => {
3620
+ ret = this.cache.put({
3621
+ content: jsonApiDoc
3622
+ });
3623
+ });
3624
+ this._enableAsyncFlush = null;
3625
+ return 'data' in ret ? ret.data : null;
3626
+ }
3627
+
3628
+ /**
3629
+ * Returns the cache instance associated to this Store, instantiates the Cache
3630
+ * if necessary via `Store.createCache`
3631
+ *
3632
+ * @public
3633
+ */
3634
+ get cache() {
3635
+ let {
3636
+ cache
3637
+ } = this._instanceCache;
3638
+ if (!cache) {
3639
+ cache = this._instanceCache.cache = this.createCache(this._instanceCache._storeWrapper);
3640
+ }
3641
+ return cache;
3642
+ }
3643
+
3644
+ /** @private */
3645
+ destroy() {
3646
+ if (this.isDestroyed) {
3647
+ // @ember/test-helpers will call destroy multiple times
3648
+ return;
3649
+ }
3650
+ this.isDestroying = true;
3651
+ this._graph?.destroy();
3652
+ this._graph = undefined;
3653
+ this.notifications.destroy();
3654
+ this.recordArrayManager.destroy();
3655
+ this.cacheKeyManager.destroy();
3656
+ this._instanceCache.clear();
3657
+ this.isDestroyed = true;
3658
+ }
3659
+
3660
+ /**
3661
+ * This method
3662
+ *
3663
+ * @private
3664
+ */
3665
+ static create(args) {
3666
+ return new this(args);
3667
+ }
3668
+ }
3669
+
3670
+ /**
3671
+ * This type exists for internal use only for
3672
+ * where intimate contracts still exist either for
3673
+ * the Test Suite or for Legacy code.
3674
+ *
3675
+ * @private
3676
+ */
3677
+
3678
+ /**
3679
+ * Upgrade the type for Store to PrivateStore, which will also
3680
+ * upgrade any associated types to their private equivalents.
3681
+ *
3682
+ * @private
3683
+ */
3684
+ function assertPrivateStore(store) {}
3685
+ /**
3686
+ * Upgrade the type for Store to PrivateStore, which will also
3687
+ * upgrade any associated types to their private equivalents.
3688
+ *
3689
+ * @private
3690
+ */
3691
+ function isPrivateStore(store) {
3692
+ return store;
3693
+ }
3694
+ function isMaybeIdentifier(maybeIdentifier) {
3695
+ return Boolean(maybeIdentifier !== null && typeof maybeIdentifier === 'object' && ('id' in maybeIdentifier && 'type' in maybeIdentifier && maybeIdentifier.id && maybeIdentifier.type || maybeIdentifier.lid));
3696
+ }
3697
+ function normalizeProperties(store, identifier, properties) {
3698
+ // assert here
3699
+ if (properties !== undefined) {
3700
+ const {
3701
+ type
3702
+ } = identifier;
3703
+
3704
+ // convert relationship Records to RecordDatas before passing to RecordData
3705
+ const defs = store.schema.fields({
3706
+ type
3707
+ });
3708
+ if (defs.size) {
3709
+ const keys = Object.keys(properties);
3710
+ for (let i = 0; i < keys.length; i++) {
3711
+ const prop = keys[i];
3712
+ const field = defs.get(prop);
3713
+ if (!field) continue;
3714
+ if (field.kind === 'hasMany') {
3715
+ properties[prop] = extractIdentifiersFromRecords$1(properties[prop]);
3716
+ } else if (field.kind === 'belongsTo') {
3717
+ properties[prop] = extractIdentifierFromRecord$1(properties[prop]);
3718
+ }
3719
+ }
3720
+ }
3721
+ }
3722
+ return properties;
3723
+ }
3724
+ function extractIdentifiersFromRecords$1(records) {
3725
+ return records.map(record => extractIdentifierFromRecord$1(record));
3726
+ }
3727
+ function extractIdentifierFromRecord$1(recordOrPromiseRecord) {
3728
+ if (!recordOrPromiseRecord) {
3729
+ return null;
3730
+ }
3731
+ const extract = recordIdentifierFor;
3732
+ return extract(recordOrPromiseRecord);
3733
+ }
3734
+
3735
+ /**
3736
+ A `RecordReference` is a low-level API that allows users and
3737
+ addon authors to perform meta-operations on a record.
3738
+
3739
+ @hideconstructor
3740
+ @public
3741
+ */
3742
+ class RecordReference {
3743
+ /** @internal */
3744
+
3745
+ // unsubscribe token given to us by the notification manager
3746
+ /** @internal */
3747
+ ___token;
3748
+ /** @internal */
3749
+ ___identifier;
3750
+ /** @internal */
3751
+
3752
+ constructor(store, identifier) {
3753
+ this.store = store;
3754
+ this.___identifier = identifier;
3755
+ this.___token = store.notifications.subscribe(identifier, (_, bucket, notifiedKey) => {
3756
+ if (bucket === 'identity' || bucket === 'attributes' && notifiedKey === 'id') {
3757
+ this._ref++;
3758
+ }
3759
+ });
3760
+ }
3761
+
3762
+ /** @internal */
3763
+ destroy() {
3764
+ this.store.notifications.unsubscribe(this.___token);
3765
+ }
3766
+ get type() {
3767
+ return this.identifier().type;
3768
+ }
3769
+
3770
+ /**
3771
+ The `id` of the record that this reference refers to.
3772
+ Together, the `type` and `id` properties form a composite key for
3773
+ the identity map.
3774
+ Example
3775
+ ```javascript
3776
+ let userRef = store.getReference('user', 1);
3777
+ userRef.id(); // '1'
3778
+ ```
3779
+ @public
3780
+ @return The id of the record.
3781
+ */
3782
+ id() {
3783
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
3784
+ this._ref; // consume the tracked prop
3785
+ return this.___identifier.id;
3786
+ }
3787
+
3788
+ /**
3789
+ The `identifier` of the record that this reference refers to.
3790
+ Together, the `type` and `id` properties form a composite key for
3791
+ the identity map.
3792
+ Example
3793
+ ```javascript
3794
+ let userRef = store.getReference('user', 1);
3795
+ userRef.identifier(); // '1'
3796
+ ```
3797
+ @public
3798
+ @return The identifier of the record.
3799
+ */
3800
+ identifier() {
3801
+ return this.___identifier;
3802
+ }
3803
+
3804
+ /**
3805
+ How the reference will be looked up when it is loaded. Currently
3806
+ this always returns `identity` to signify that a record will be
3807
+ loaded by its `type` and `id`.
3808
+ Example
3809
+ ```javascript
3810
+ const userRef = store.getReference('user', 1);
3811
+ userRef.remoteType(); // 'identity'
3812
+ ```
3813
+ @public
3814
+ */
3815
+ remoteType() {
3816
+ return 'identity';
3817
+ }
3818
+
3819
+ /**
3820
+ This API allows you to provide a reference with new data. The
2537
3821
  simplest usage of this API is similar to `store.push`: you provide a
2538
3822
  normalized hash of data and the object represented by the reference
2539
3823
  will update.
@@ -2605,29 +3889,360 @@ class RecordReference {
2605
3889
  return this.store.findRecord(this.type, id);
2606
3890
  }
2607
3891
  }
2608
-
2609
- /**
2610
- Reloads the record if it is already loaded. If the record is not
2611
- loaded it will load the record via `store.findRecord`
2612
- Example
2613
- ```javascript
2614
- let userRef = store.getReference('user', 1);
2615
- // or trigger a reload
2616
- userRef.reload().then(...)
2617
- ```
2618
- @public
2619
- @return the record for this RecordReference
2620
- */
2621
- reload() {
2622
- const id = this.id();
2623
- if (id !== null) {
2624
- return this.store.findRecord(this.type, id, {
2625
- reload: true
3892
+
3893
+ /**
3894
+ Reloads the record if it is already loaded. If the record is not
3895
+ loaded it will load the record via `store.findRecord`
3896
+ Example
3897
+ ```javascript
3898
+ let userRef = store.getReference('user', 1);
3899
+ // or trigger a reload
3900
+ userRef.reload().then(...)
3901
+ ```
3902
+ @public
3903
+ @return the record for this RecordReference
3904
+ */
3905
+ reload() {
3906
+ const id = this.id();
3907
+ if (id !== null) {
3908
+ return this.store.findRecord(this.type, id, {
3909
+ reload: true
3910
+ });
3911
+ }
3912
+ }
3913
+ }
3914
+ defineSignal(RecordReference.prototype, '_ref');
3915
+ const MUTATION_OPS$1 = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
3916
+ function calcShouldFetch(store, request, hasCachedValue, identifier) {
3917
+ const {
3918
+ cacheOptions
3919
+ } = request;
3920
+ return request.op && MUTATION_OPS$1.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
3921
+ }
3922
+ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
3923
+ const {
3924
+ cacheOptions
3925
+ } = request;
3926
+ return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
3927
+ }
3928
+ function isMutation(request) {
3929
+ return Boolean(request.op && MUTATION_OPS$1.has(request.op));
3930
+ }
3931
+ function isCacheAffecting(document) {
3932
+ if (!isMutation(document.request)) {
3933
+ return true;
3934
+ }
3935
+ // a mutation combined with a 204 has no cache impact when no known records were involved
3936
+ // a createRecord with a 201 with an empty response and no known records should similarly
3937
+ // have no cache impact
3938
+
3939
+ if (document.request.op === 'createRecord' && document.response?.status === 201) {
3940
+ return document.content ? Object.keys(document.content).length > 0 : false;
3941
+ }
3942
+ return document.response?.status !== 204;
3943
+ }
3944
+ function isAggregateError(error) {
3945
+ return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
3946
+ }
3947
+ // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
3948
+ function cloneError(error) {
3949
+ const isAggregate = isAggregateError(error);
3950
+ const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
3951
+ cloned.stack = error.stack;
3952
+ cloned.error = error.error;
3953
+
3954
+ // copy over enumerable properties
3955
+ Object.assign(cloned, error);
3956
+ return cloned;
3957
+ }
3958
+ function getPriority(identifier, deduped, priority) {
3959
+ if (identifier) {
3960
+ const existing = deduped.get(identifier);
3961
+ if (existing) {
3962
+ return existing.priority;
3963
+ }
3964
+ }
3965
+ return priority;
3966
+ }
3967
+
3968
+ /**
3969
+ * A CacheHandler that adds support for using an WarpDrive Cache with a RequestManager.
3970
+ *
3971
+ * This handler will only run when a request has supplied a `store` instance. Requests
3972
+ * issued by the store via `store.request()` will automatically have the `store` instance
3973
+ * attached to the request.
3974
+ *
3975
+ * ```ts
3976
+ * requestManager.request({
3977
+ * store: store,
3978
+ * url: '/api/posts',
3979
+ * method: 'GET'
3980
+ * });
3981
+ * ```
3982
+ *
3983
+ * When this handler elects to handle a request, it will return the raw `StructuredDocument`
3984
+ * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
3985
+ * return a `Document` instance that will automatically update the UI when the cache is updated
3986
+ * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
3987
+ *
3988
+ * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
3989
+ * means that if desired you can issue requests that utilize the cache without needing to also
3990
+ * utilize Record instances if desired.
3991
+ *
3992
+ * Said differently, you could elect to issue all requests via a RequestManager, without ever using
3993
+ * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
3994
+ * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
3995
+ * approach of WarpDrive allows for this flexibility.
3996
+ *
3997
+ * ```ts
3998
+ * import { EnableHydration } from '@warp-drive/core/types/request';
3999
+ *
4000
+ * requestManager.request({
4001
+ * store: store,
4002
+ * url: '/api/posts',
4003
+ * method: 'GET',
4004
+ * [EnableHydration]: true
4005
+ * });
4006
+ *
4007
+ */
4008
+ const CacheHandler = {
4009
+ request(context, next) {
4010
+ // if we have no cache or no cache-key skip cache handling
4011
+ if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
4012
+ return next(context.request);
4013
+ }
4014
+ const {
4015
+ store
4016
+ } = context.request;
4017
+ const identifier = store.cacheKeyManager.getOrCreateDocumentIdentifier(context.request);
4018
+ if (identifier) {
4019
+ context.setIdentifier(identifier);
4020
+ }
4021
+
4022
+ // used to dedupe existing requests that match
4023
+ const DEDUPE = store.requestManager._deduped;
4024
+ const activeRequest = identifier && DEDUPE.get(identifier);
4025
+ const peeked = identifier ? store.cache.peekRequest(identifier) : null;
4026
+
4027
+ // determine if we should skip cache
4028
+ if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
4029
+ if (activeRequest) {
4030
+ activeRequest.priority = {
4031
+ blocking: true
4032
+ };
4033
+ return activeRequest.promise;
4034
+ }
4035
+ let promise = fetchContentAndHydrate(next, context, identifier, {
4036
+ blocking: true
4037
+ });
4038
+ if (identifier) {
4039
+ promise = promise.finally(() => {
4040
+ DEDUPE.delete(identifier);
4041
+ store.notifications.notify(identifier, 'state', null);
4042
+ });
4043
+ DEDUPE.set(identifier, {
4044
+ priority: {
4045
+ blocking: true
4046
+ },
4047
+ promise
4048
+ });
4049
+ queueMicrotask(() => {
4050
+ store.notifications.notify(identifier, 'state', null);
4051
+ });
4052
+ }
4053
+ store.requestManager._pending.set(context.id, promise);
4054
+ return promise;
4055
+ }
4056
+
4057
+ // if we have not skipped cache, determine if we should update behind the scenes
4058
+ if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
4059
+ let promise = activeRequest?.promise || fetchContentAndHydrate(next, context, identifier, {
4060
+ blocking: false
4061
+ });
4062
+ if (identifier && !activeRequest) {
4063
+ promise = promise.finally(() => {
4064
+ DEDUPE.delete(identifier);
4065
+ store.notifications.notify(identifier, 'state', null);
4066
+ });
4067
+ DEDUPE.set(identifier, {
4068
+ priority: {
4069
+ blocking: false
4070
+ },
4071
+ promise
4072
+ });
4073
+ queueMicrotask(() => {
4074
+ store.notifications.notify(identifier, 'state', null);
4075
+ });
4076
+ }
4077
+ store.requestManager._pending.set(context.id, promise);
4078
+ }
4079
+ const shouldHydrate = context.request[EnableHydration] || false;
4080
+ context.setResponse(peeked.response);
4081
+ if ('error' in peeked) {
4082
+ const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
4083
+ shouldHydrate,
4084
+ identifier
4085
+ }, peeked.content) : peeked.content;
4086
+ const newError = cloneError(peeked);
4087
+ newError.content = content;
4088
+ throw newError;
4089
+ }
4090
+ const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
4091
+ shouldHydrate,
4092
+ identifier
4093
+ }, peeked.content) : peeked.content;
4094
+ return result;
4095
+ }
4096
+ };
4097
+ function maybeUpdateUiObjects(store, request, options, document) {
4098
+ const {
4099
+ identifier
4100
+ } = options;
4101
+ if (!document || !options.shouldHydrate) {
4102
+ return document ?? null;
4103
+ }
4104
+ if (identifier) {
4105
+ return store._instanceCache.getDocument(identifier);
4106
+ }
4107
+
4108
+ // if we don't have an identifier, we give the document
4109
+ // its own local cache
4110
+ return createReactiveDocument(store, null, {
4111
+ request,
4112
+ document
4113
+ });
4114
+ }
4115
+ function updateCacheForSuccess(store, request, options, document) {
4116
+ let response = null;
4117
+ if (isMutation(request)) {
4118
+ if (Array.isArray(request.records)) {
4119
+ response = store.cache.didCommit(request.records, document);
4120
+ } else if (request.data?.record) {
4121
+ // legacy fallback, the data option should no longer be used for this
4122
+ response = store.cache.didCommit(request.data.record, document);
4123
+
4124
+ // a mutation combined with a 204 has no cache impact when no known records were involved
4125
+ // a createRecord with a 201 with an empty response and no known records should similarly
4126
+ // have no cache impact
4127
+ } else if (isCacheAffecting(document)) {
4128
+ response = store.cache.put(document);
4129
+ }
4130
+ } else {
4131
+ response = store.cache.put(document);
4132
+ }
4133
+ return maybeUpdateUiObjects(store, request, options, response);
4134
+ }
4135
+ function handleFetchSuccess(store, context, options, document) {
4136
+ const {
4137
+ request
4138
+ } = context;
4139
+ store.requestManager._pending.delete(context.id);
4140
+ store._enableAsyncFlush = true;
4141
+ let response;
4142
+ store._join(() => {
4143
+ response = updateCacheForSuccess(store, request, options, document);
4144
+ });
4145
+ store._enableAsyncFlush = null;
4146
+ if (store.lifetimes?.didRequest) {
4147
+ store.lifetimes.didRequest(context.request, document.response, options.identifier, store);
4148
+ }
4149
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
4150
+ if (finalPriority.blocking) {
4151
+ return response;
4152
+ } else {
4153
+ store.notifications._flush();
4154
+ }
4155
+ }
4156
+ function updateCacheForError(store, context, options, error) {
4157
+ let response;
4158
+ if (isMutation(context.request)) {
4159
+ // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
4160
+ // currently we let the response remain undefiend.
4161
+ const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
4162
+ if (Array.isArray(context.request.records)) {
4163
+ store.cache.commitWasRejected(context.request.records, errors);
4164
+ } else if (context.request.data?.record) {
4165
+ // legacy fallback, the data option should no longer be used for this
4166
+ store.cache.commitWasRejected(context.request.data.record, errors);
4167
+ } else {
4168
+ store.cache.put(error);
4169
+ }
4170
+ } else {
4171
+ response = store.cache.put(error);
4172
+ return maybeUpdateUiObjects(store, context.request, options, response);
4173
+ }
4174
+ }
4175
+ function handleFetchError(store, context, options, error) {
4176
+ store.requestManager._pending.delete(context.id);
4177
+ if (context.request.signal?.aborted) {
4178
+ throw error;
4179
+ }
4180
+ store._enableAsyncFlush = true;
4181
+ let response;
4182
+ store._join(() => {
4183
+ response = updateCacheForError(store, context, options, error);
4184
+ });
4185
+ store._enableAsyncFlush = null;
4186
+ if (options.identifier && store.lifetimes?.didRequest) {
4187
+ store.lifetimes.didRequest(context.request, error.response, options.identifier, store);
4188
+ }
4189
+ if (isMutation(context.request)) {
4190
+ throw error;
4191
+ }
4192
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
4193
+ if (finalPriority.blocking) {
4194
+ const newError = cloneError(error);
4195
+ newError.content = response;
4196
+ throw newError;
4197
+ } else {
4198
+ store.notifications._flush();
4199
+ }
4200
+ }
4201
+ function fetchContentAndHydrate(next, context, identifier, priority) {
4202
+ const {
4203
+ store
4204
+ } = context.request;
4205
+ const shouldHydrate = context.request[EnableHydration] || false;
4206
+ const options = {
4207
+ shouldHydrate,
4208
+ identifier,
4209
+ priority
4210
+ };
4211
+ let isMut = false;
4212
+ if (isMutation(context.request)) {
4213
+ isMut = true;
4214
+ if (Array.isArray(context.request.records)) {
4215
+ context.request.records.forEach(record => {
4216
+ store.cache.willCommit(record, context);
2626
4217
  });
4218
+ } else if (context.request.data?.record) {
4219
+ // legacy fallback, the data option should no longer be used for this
4220
+ store.cache.willCommit(context.request.data.record, context);
2627
4221
  }
2628
4222
  }
4223
+ if (store.lifetimes?.willRequest) {
4224
+ store.lifetimes.willRequest(context.request, identifier, store);
4225
+ }
4226
+ const promise = next(context.request).then(document => {
4227
+ return handleFetchSuccess(store, context, options, document);
4228
+ }, error => {
4229
+ return handleFetchError(store, context, options, error);
4230
+ });
4231
+ if (!isMut) {
4232
+ return promise;
4233
+ }
4234
+
4235
+ // for mutations we need to enqueue the promise with the requestStateService
4236
+ // TODO should we enque a request per record in records?
4237
+ const record = context.request.data?.record || context.request.records?.[0];
4238
+ return store._requestCache._enqueue(promise, {
4239
+ data: [{
4240
+ op: 'saveRecord',
4241
+ recordIdentifier: record,
4242
+ options: undefined
4243
+ }]
4244
+ });
2629
4245
  }
2630
- defineSignal(RecordReference.prototype, '_ref');
2631
4246
 
2632
4247
  /**
2633
4248
  A `ManyArray` is a `MutableArray` that represents the contents of a has-many
@@ -3385,14 +5000,41 @@ function getBelongsToField(context) {
3385
5000
  }
3386
5001
  function setBelongsToField(context) {
3387
5002
  const {
5003
+ field,
5004
+ resourceKey,
3388
5005
  store
3389
5006
  } = context;
3390
5007
  const {
3391
- schema
5008
+ schema,
5009
+ cache
3392
5010
  } = store;
5011
+
5012
+ /**
5013
+ * We currently only allow editing sync belongsTo relationships
5014
+ * in PolarisMode
5015
+ */
5016
+ const isValidPolarisConfig = field.options.linksMode && !field.options.async;
5017
+ if (!context.legacy && isValidPolarisConfig) {
5018
+ const name = context.path.at(-1);
5019
+ cache.mutate({
5020
+ op: 'replaceRelatedRecord',
5021
+ record: resourceKey,
5022
+ field: name,
5023
+ value: getResourceKey(context.value)
5024
+ },
5025
+ // @ts-expect-error
5026
+ true);
5027
+ return true;
5028
+ }
3393
5029
  schema._kind('@legacy', 'belongsTo').set(store, context.record, context.resourceKey, context.field, context.value);
3394
5030
  return true;
3395
5031
  }
5032
+ function getResourceKey(record) {
5033
+ if (!record) {
5034
+ return null;
5035
+ }
5036
+ return recordIdentifierFor(record);
5037
+ }
3396
5038
  function getCollectionField(context) {
3397
5039
  entangleSignal(context.signals, context.record, context.path.at(-1), null);
3398
5040
  }
@@ -4024,6 +5666,17 @@ function setSchemaObjectField(context) {
4024
5666
  // }
4025
5667
  return true;
4026
5668
  }
5669
+
5670
+ /**
5671
+ * A ReactiveResource context object passed to
5672
+ * the get/set functions of a field kind implementation.
5673
+ *
5674
+ * This provides all the necessary information for
5675
+ * the field kind to perform its operations.
5676
+ *
5677
+ * @internal
5678
+ */
5679
+
4027
5680
  const DefaultMode = {
4028
5681
  '@hash': {
4029
5682
  get: getHashField,
@@ -4692,6 +6345,29 @@ function checkout(resource) {
4692
6345
  function commit(record) {
4693
6346
  return record[Commit]();
4694
6347
  }
6348
+ function instantiateRecord(store, identifier, createArgs) {
6349
+ const schema = store.schema;
6350
+ const resourceSchema = schema.resource(identifier);
6351
+ const legacy = resourceSchema?.legacy ?? false;
6352
+ const editable = legacy;
6353
+ const record = new ReactiveResource({
6354
+ store,
6355
+ resourceKey: identifier,
6356
+ modeName: legacy ? 'legacy' : 'polaris',
6357
+ legacy: legacy,
6358
+ editable: editable,
6359
+ path: null,
6360
+ field: null,
6361
+ value: null
6362
+ });
6363
+ if (createArgs && editable) {
6364
+ Object.assign(record, createArgs);
6365
+ }
6366
+ return record;
6367
+ }
6368
+ function teardownRecord(record) {
6369
+ record[Destroy]();
6370
+ }
4695
6371
  const Support = getOrSetGlobal('Support', new WeakMap());
4696
6372
  const ConstructorField = {
4697
6373
  type: '@constructor',
@@ -4706,6 +6382,14 @@ const TypeField = {
4706
6382
  key: 'type'
4707
6383
  }
4708
6384
  };
6385
+ const IdentifierField = {
6386
+ type: '@identity',
6387
+ name: '$key',
6388
+ kind: 'derived',
6389
+ options: {
6390
+ key: '^'
6391
+ }
6392
+ };
4709
6393
  const DefaultIdentityField = {
4710
6394
  name: 'id',
4711
6395
  kind: '@id'
@@ -4874,6 +6558,7 @@ function withDefaults(schema) {
4874
6558
  // we add TypeField to the beginning so that it will
4875
6559
  // appear right next to the identity field
4876
6560
  schema.fields.unshift(TypeField);
6561
+ schema.fields.unshift(IdentifierField);
4877
6562
  schema.fields.push(ConstructorField);
4878
6563
  return schema;
4879
6564
  }
@@ -5234,4 +6919,484 @@ function mergeMap(base, toApply) {
5234
6919
  base.set(key, value);
5235
6920
  }
5236
6921
  }
5237
- export { CacheKeyManager as C, InstanceCache as I, RecordArrayManager as R, SchemaService as S, _clearCaches as _, recordIdentifierFor as a, setIdentifierGenerationMethod as b, setIdentifierUpdateMethod as c, setIdentifierForgetMethod as d, setIdentifierResetMethod as e, setKeyInfoForResource as f, coerceId as g, isRequestKey as h, isResourceKey as i, ensureStringId as j, fastPush as k, assertPrivateCapabilities as l, setRecordIdentifier as m, StoreMap as n, normalizeModelName as o, createLegacyManyArray as p, getNewRecord as q, registerDerivations as r, storeFor as s, peekResourceKey as t, createReactiveDocument as u, ReactiveResource as v, checkout as w, withDefaults as x, fromIdentity as y, commit as z };
6922
+
6923
+ // Lazily close over fetch to avoid breaking Mirage
6924
+ const _fetch = typeof fetch !== 'undefined' ? (...args) => fetch(...args) : typeof FastBoot !== 'undefined' ? (...args) => FastBoot.require('node-fetch')(...args) : () => {
6925
+ throw new Error('No Fetch Implementation Found');
6926
+ };
6927
+
6928
+ // clones a response in a way that should still
6929
+ // allow it to stream
6930
+ function cloneResponse(response, overrides) {
6931
+ const props = cloneResponseProperties(response);
6932
+ return new Response(response.body, Object.assign(props, overrides));
6933
+ }
6934
+ function withDateHeader(response) {
6935
+ const headers = new Headers(response.headers);
6936
+ headers.set('date', new Date().toUTCString());
6937
+ return cloneResponse(response, {
6938
+ headers
6939
+ });
6940
+ }
6941
+ const MUTATION_OPS = new Set(['updateRecord', 'createRecord', 'deleteRecord']);
6942
+ 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']]);
6943
+
6944
+ /**
6945
+ * ```ts
6946
+ * import { Fetch } from '@warp-drive/core';
6947
+ * ```
6948
+ *
6949
+ * A basic Fetch Handler which converts a request into a
6950
+ * `fetch` call presuming the response to be `json`.
6951
+ *
6952
+ * ```ts
6953
+ * import { RequestManager, Fetch } from '@warp-drive/core';
6954
+ *
6955
+ * const manager = new RequestManager()
6956
+ * .use([Fetch]);
6957
+ * ```
6958
+ *
6959
+ * @public
6960
+ */
6961
+ const Fetch = {
6962
+ async request(context) {
6963
+ let response;
6964
+ try {
6965
+ ({});
6966
+ response = await _fetch(context.request.url, context.request);
6967
+ } catch (e) {
6968
+ if (e instanceof DOMException && e.name === 'AbortError') {
6969
+ e.statusText = 'Aborted';
6970
+ e.status = 20;
6971
+ e.isRequestError = true;
6972
+ } else {
6973
+ e.statusText = 'Unknown Network Error';
6974
+ e.status = 0;
6975
+ if (!(e instanceof DOMException)) {
6976
+ e.code = 0;
6977
+ }
6978
+ e.isRequestError = true;
6979
+ }
6980
+ throw e;
6981
+ }
6982
+ const isError = !response.ok || response.status >= 400;
6983
+ const op = context.request.op;
6984
+ const isMutationOp = Boolean(op && MUTATION_OPS.has(op));
6985
+ if (!isError && !isMutationOp && response.status !== 204 && !response.headers.has('date')) {
6986
+ {
6987
+ response = withDateHeader(response);
6988
+ }
6989
+ }
6990
+ context.setResponse(response);
6991
+ if (response.status === 204 || context.request.method === 'HEAD') {
6992
+ return null;
6993
+ }
6994
+ let text = '';
6995
+ // if we are in a mirage context, we cannot support streaming
6996
+ {
6997
+ const reader = response.body.getReader();
6998
+ const decoder = new TextDecoder();
6999
+ let isStreaming = context.hasRequestedStream;
7000
+ let stream = isStreaming ? new TransformStream() : null;
7001
+ let writer = stream?.writable.getWriter();
7002
+ if (isStreaming) {
7003
+ // Listen for the abort event on the AbortSignal
7004
+ context.request.signal?.addEventListener('abort', () => {
7005
+ if (!isStreaming) {
7006
+ return;
7007
+ }
7008
+ void stream.writable.abort('Request Aborted');
7009
+ void stream.readable.cancel('Request Aborted');
7010
+ });
7011
+ context.setStream(stream.readable);
7012
+ }
7013
+ while (true) {
7014
+ // we manually read the stream instead of using `response.json()`
7015
+ // or `response.text()` because if we need to stream the body
7016
+ // we need to be able to pass the stream along efficiently.
7017
+ const {
7018
+ done,
7019
+ value
7020
+ } = await reader.read();
7021
+ if (done) {
7022
+ if (isStreaming) {
7023
+ isStreaming = false;
7024
+ await writer.ready;
7025
+ await writer.close();
7026
+ }
7027
+ break;
7028
+ }
7029
+ text += decoder.decode(value, {
7030
+ stream: true
7031
+ });
7032
+
7033
+ // if we are streaming, we want to pass the stream along
7034
+ if (isStreaming) {
7035
+ await writer.ready;
7036
+ await writer.write(value);
7037
+ } else if (context.hasRequestedStream) {
7038
+ const encode = new TextEncoder();
7039
+ isStreaming = true;
7040
+ stream = new TransformStream();
7041
+ // Listen for the abort event on the AbortSignal
7042
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
7043
+ context.request.signal?.addEventListener('abort', () => {
7044
+ if (!isStreaming) {
7045
+ return;
7046
+ }
7047
+ void stream.writable.abort('Request Aborted');
7048
+ void stream.readable.cancel('Request Aborted');
7049
+ });
7050
+ context.setStream(stream.readable);
7051
+ writer = stream.writable.getWriter();
7052
+ await writer.ready;
7053
+ await writer.write(encode.encode(text));
7054
+ await writer.ready;
7055
+ await writer.write(value);
7056
+ }
7057
+ }
7058
+ if (isStreaming) {
7059
+ isStreaming = false;
7060
+ await writer.ready;
7061
+ await writer.close();
7062
+ }
7063
+ }
7064
+ // if we are an error, we will want to throw
7065
+ if (isError) {
7066
+ let errorPayload;
7067
+ try {
7068
+ errorPayload = JSON.parse(text);
7069
+ } catch {
7070
+ // void;
7071
+ }
7072
+ // attempt errors discovery
7073
+ const errors = Array.isArray(errorPayload) ? errorPayload : isDict(errorPayload) && Array.isArray(errorPayload.errors) ? errorPayload.errors : null;
7074
+ const statusText = response.statusText || ERROR_STATUS_CODE_FOR.get(response.status) || 'Unknown Request Error';
7075
+ const msg = `[${response.status} ${statusText}] ${context.request.method ?? 'GET'} (${response.type}) - ${response.url}`;
7076
+ const error = errors ? new AggregateError(errors, msg) : new Error(msg);
7077
+ error.status = response.status;
7078
+ error.statusText = statusText;
7079
+ error.isRequestError = true;
7080
+ error.code = error.status;
7081
+ error.name = error.statusText.replaceAll(' ', '') + 'Error';
7082
+ error.content = errorPayload;
7083
+ throw error;
7084
+ } else {
7085
+ return JSON.parse(text);
7086
+ }
7087
+ }
7088
+ };
7089
+ function isDict(v) {
7090
+ return v !== null && typeof v === 'object';
7091
+ }
7092
+
7093
+ /**
7094
+ * ## Import
7095
+ *
7096
+ * ```js
7097
+ * import { RequestManager } from '@warp-drive/core';
7098
+ * ```
7099
+ *
7100
+ * For complete usage guide see the [RequestManager Documentation](/guides/).
7101
+ *
7102
+ * ## How It Works
7103
+ *
7104
+ * ```ts
7105
+ * interface RequestManager {
7106
+ * request<T>(req: RequestInfo): Future<T>;
7107
+ * }
7108
+ * ```
7109
+ *
7110
+ * A RequestManager provides a request/response flow in which configured
7111
+ * handlers are successively given the opportunity to handle, modify, or
7112
+ * pass-along a request.
7113
+ *
7114
+ * <img src="/images/handlers-all-labeled.gif" alt="RequestManager Flow Animation" width="100%" />
7115
+ *
7116
+ * For example:
7117
+ *
7118
+ * ::: code-group
7119
+ *
7120
+ * ```ts [Setup.ts]
7121
+ * import { RequestManager, Fetch } from '@warp-drive/core';
7122
+ * import { AutoCompress } from '@warp-drive/utilities/handlers';
7123
+ * import Auth from 'ember-simple-auth/handler';
7124
+ *
7125
+ * // ... create manager
7126
+ * const manager = new RequestManager()
7127
+ * .use([Auth, new AutoCompress(), Fetch]); // [!code focus]
7128
+ * ```
7129
+ *
7130
+ * ```ts [Usage.ts]
7131
+ * import Config from './config';
7132
+ *
7133
+ * const { apiUrl } = Config;
7134
+ *
7135
+ * // ... execute a request
7136
+ * const response = await manager.request({
7137
+ * url: `${apiUrl}/users`
7138
+ * });
7139
+ * ```
7140
+ *
7141
+ * :::
7142
+ *
7143
+ * ### Futures
7144
+ *
7145
+ * The return value of `manager.request` is a `Future`, which allows
7146
+ * access to limited information about the request while it is still
7147
+ * pending and fulfills with the final state when the request completes.
7148
+ *
7149
+ * A `Future` is cancellable via `abort`.
7150
+ *
7151
+ * Handlers may optionally expose a `ReadableStream` to the `Future` for
7152
+ * streaming data; however, when doing so the future should not resolve
7153
+ * until the response stream is fully read.
7154
+ *
7155
+ * ```ts
7156
+ * interface Future<T> extends Promise<StructuredDocument<T>> {
7157
+ * abort(): void;
7158
+ *
7159
+ * async getStream(): ReadableStream | null;
7160
+ * }
7161
+ * ```
7162
+ *
7163
+ * ### StructuredDocuments
7164
+ *
7165
+ * A Future resolves with a `StructuredDataDocument` or rejects with a `StructuredErrorDocument`.
7166
+ *
7167
+ * ```ts
7168
+ * interface StructuredDataDocument<T> {
7169
+ * request: ImmutableRequestInfo;
7170
+ * response: ImmutableResponseInfo;
7171
+ * content: T;
7172
+ * }
7173
+ * interface StructuredErrorDocument extends Error {
7174
+ * request: ImmutableRequestInfo;
7175
+ * response: ImmutableResponseInfo;
7176
+ * error: string | object;
7177
+ * }
7178
+ * type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
7179
+ * ```
7180
+ *
7181
+ * @public
7182
+ */
7183
+ class RequestManager {
7184
+ /** @internal */
7185
+
7186
+ /** @internal */
7187
+
7188
+ /**
7189
+ * A map of pending requests from request.id to their
7190
+ * associated CacheHandler promise.
7191
+ *
7192
+ * This queue is managed by the CacheHandler
7193
+ *
7194
+ * @internal
7195
+ */
7196
+
7197
+ /** @internal */
7198
+
7199
+ constructor(options) {
7200
+ Object.assign(this, options);
7201
+ this._pending = new Map();
7202
+ this._deduped = new Map();
7203
+ this._handlers = [];
7204
+ }
7205
+
7206
+ /**
7207
+ * Register a handler to use for primary cache intercept.
7208
+ *
7209
+ * Only one such handler may exist. If using the same
7210
+ * RequestManager as the Store instance the Store
7211
+ * registers itself as a Cache handler.
7212
+ *
7213
+ * @public
7214
+ */
7215
+ useCache(cacheHandler) {
7216
+ cacheHandler[IS_CACHE_HANDLER] = true;
7217
+ this._handlers.unshift(cacheHandler);
7218
+ return this;
7219
+ }
7220
+
7221
+ /**
7222
+ * Register handler(s) to use when a request is issued.
7223
+ *
7224
+ * Handlers will be invoked in the order they are registered.
7225
+ * Each Handler is given the opportunity to handle the request,
7226
+ * curry the request, or pass along a modified request.
7227
+ *
7228
+ * @public
7229
+ */
7230
+ use(newHandlers) {
7231
+ const handlers = this._handlers;
7232
+ handlers.push(...newHandlers);
7233
+ return this;
7234
+ }
7235
+
7236
+ /**
7237
+ * Issue a Request.
7238
+ *
7239
+ * Returns a Future that fulfills with a StructuredDocument
7240
+ *
7241
+ * @public
7242
+ */
7243
+ request(request) {
7244
+ const handlers = this._handlers;
7245
+ const controller = request.controller || new AbortController();
7246
+ if (request.controller) {
7247
+ delete request.controller;
7248
+ }
7249
+ const requestId = peekUniversalTransient('REQ_ID') ?? 0;
7250
+ setUniversalTransient('REQ_ID', requestId + 1);
7251
+ const context = {
7252
+ controller,
7253
+ response: null,
7254
+ stream: null,
7255
+ hasRequestedStream: false,
7256
+ id: requestId,
7257
+ identifier: null,
7258
+ requester: request[EnableHydration] && request.store ? request.store : this
7259
+ };
7260
+ const promise = executeNextHandler(handlers, request, 0, context);
7261
+
7262
+ // the cache handler will set the result of the request synchronously
7263
+ // if it is able to fulfill the request from the cache
7264
+ const cacheResult = getRequestResult(requestId);
7265
+ // const promise1 = store.request(myRequest);
7266
+ // const promise2 = store.request(myRequest);
7267
+ // promise1 === promise2; // false
7268
+ // either we need to make promise1 === promise2, or we need to make sure that
7269
+ // we need to have a way to key from request to result
7270
+ // such that we can lookup the result here and return it if it exists
7271
+ const finalPromise = upgradePromise(promise.then(result => {
7272
+ setPromiseResult(finalPromise, {
7273
+ isError: false,
7274
+ result
7275
+ });
7276
+ clearRequestResult(requestId);
7277
+ return result;
7278
+ }, error => {
7279
+ setPromiseResult(finalPromise, {
7280
+ isError: true,
7281
+ result: error
7282
+ });
7283
+ clearRequestResult(requestId);
7284
+ throw error;
7285
+ }), promise);
7286
+ if (cacheResult) {
7287
+ setPromiseResult(finalPromise, cacheResult);
7288
+ }
7289
+ return finalPromise;
7290
+ }
7291
+
7292
+ /**
7293
+ * This method exists so that the RequestManager can be created
7294
+ * can be created by container/factory systems that expect to
7295
+ * call a static `create` method to instantiate the class.
7296
+ *
7297
+ * Using `new RequestManager()` directly is preferred.
7298
+ *
7299
+ * @private
7300
+ */
7301
+ static create(options) {
7302
+ return new this(options);
7303
+ }
7304
+ }
7305
+
7306
+ /**
7307
+ * This type exists for internal use only for
7308
+ * where intimate contracts still exist either for
7309
+ * the Test Suite or for Legacy code.
7310
+ *
7311
+ * @private
7312
+ */
7313
+
7314
+ /**
7315
+ * @module
7316
+ * @mergeModuleWith <project>
7317
+ */
7318
+
7319
+ // Mirage (or another Pretender-based fetch mock) may be used outside of a
7320
+ // `TESTING` build (e.g. `ember serve` in dev mode), so this override is
7321
+ // exposed unconditionally rather than gated to the `TESTING` env.
7322
+ // @ts-expect-error adding to globalThis
7323
+ globalThis.setWarpDriveIsMaybeMirage = setIsMaybeMirage;
7324
+
7325
+ /**
7326
+ * Options for setting up a Store instance with `useRecommendedStore`.
7327
+ */
7328
+
7329
+ /**
7330
+ * Creates a configured Store class with recommended defaults
7331
+ * for schema handling, reactivity, caching, and request management.
7332
+ *
7333
+ * ```ts
7334
+ * import { useRecommendedStore } from '@warp-drive/core';
7335
+ * import { JSONAPICache } from '@warp-drive/json-api';
7336
+ *
7337
+ * export const Store = useRecommendedStore({
7338
+ * cache: JSONAPICache,
7339
+ * schemas: [],
7340
+ * });
7341
+ * ```
7342
+ */
7343
+ function useRecommendedStore(options, StoreKlass = Store) {
7344
+ return class AppStore extends StoreKlass {
7345
+ requestManager = new RequestManager().use([...(options.handlers ?? []), Fetch]).useCache(CacheHandler);
7346
+ lifetimes = options.policy ?? new DefaultCachePolicy({
7347
+ apiCacheHardExpires: 15 * 60 * 1000,
7348
+ // 15 minutes
7349
+ apiCacheSoftExpires: 1 * 30 * 1000,
7350
+ // 30 seconds
7351
+ constraints: {
7352
+ headers: {
7353
+ 'X-WarpDrive-Expires': true,
7354
+ 'Cache-Control': true,
7355
+ Expires: true
7356
+ }
7357
+ }
7358
+ });
7359
+ createSchemaService() {
7360
+ const schema = new SchemaService();
7361
+ registerDerivations(schema);
7362
+ if (options.schemas) schema.registerResources(options.schemas);
7363
+ if (options.traits) {
7364
+ for (const trait of options.traits) {
7365
+ schema.registerTrait(trait);
7366
+ }
7367
+ }
7368
+ if (options.derivations) {
7369
+ for (const derivation of options.derivations) {
7370
+ schema.registerDerivation(derivation);
7371
+ }
7372
+ }
7373
+ if (options.transformations) {
7374
+ for (const transformation of options.transformations) {
7375
+ schema.registerTransformation(transformation);
7376
+ }
7377
+ }
7378
+ if (options.hashFns) {
7379
+ for (const hashFn of options.hashFns) {
7380
+ schema.registerHashFn(hashFn);
7381
+ }
7382
+ }
7383
+ if (options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7384
+ for (const extension of options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7385
+ schema.CAUTION_MEGA_DANGER_ZONE_registerExtension(extension);
7386
+ }
7387
+ }
7388
+ return schema;
7389
+ }
7390
+ createCache(capabilities) {
7391
+ // eslint-disable-next-line new-cap
7392
+ return new options.cache(capabilities);
7393
+ }
7394
+ instantiateRecord(key, createArgs) {
7395
+ return instantiateRecord(this, key, createArgs);
7396
+ }
7397
+ teardownRecord(record) {
7398
+ return teardownRecord(record);
7399
+ }
7400
+ };
7401
+ }
7402
+ export { setIdentifierForgetMethod as A, setIdentifierResetMethod as B, CacheHandler as C, setKeyInfoForResource as D, Fetch as F, RecordArrayManager as R, Store as S, _clearCaches as _, isRequestKey as a, assertPrivateStore as b, coerceId as c, isPrivateStore as d, ensureStringId as e, fastPush as f, assertPrivateCapabilities as g, setRecordIdentifier as h, isResourceKey as i, StoreMap as j, createLegacyManyArray as k, checkout as l, instantiateRecord as m, normalizeModelName as n, SchemaService as o, fromIdentity as p, registerDerivations as q, recordIdentifierFor as r, storeFor as s, teardownRecord as t, commit as u, RequestManager as v, withDefaults as w, useRecommendedStore as x, setIdentifierGenerationMethod as y, setIdentifierUpdateMethod as z };