@warp-drive/core 5.9.0-alpha.17 → 5.9.0-alpha.19

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 (57) hide show
  1. package/declarations/graph/-private/-state.d.ts +1 -0
  2. package/declarations/index.d.ts +10 -1
  3. package/declarations/reactive/-private/default-mode.d.ts +22 -9
  4. package/declarations/reactive/-private/kind/belongs-to-field.d.ts +3 -0
  5. package/declarations/types/runtime.d.ts +16 -0
  6. package/declarations/types/schema/fields.d.ts +40 -12
  7. package/dist/graph/-private.js +67 -52
  8. package/dist/{index-CFwnjldF.js → index-LI2ckwf9.js} +332 -10
  9. package/dist/index.js +1 -1
  10. package/dist/reactive.js +1 -1
  11. package/dist/store/-private.js +1 -1
  12. package/dist/types/-private.js +1 -1
  13. package/dist/types/runtime.js +20 -1
  14. package/dist/unpkg/dev/graph/-private.js +67 -52
  15. package/dist/unpkg/dev/{index-BZRBBa34.js → index-DU_HLpg-.js} +332 -10
  16. package/dist/unpkg/dev/index.js +1 -1
  17. package/dist/unpkg/dev/reactive.js +1 -1
  18. package/dist/unpkg/dev/store/-private.js +2 -2
  19. package/dist/unpkg/dev/types/-private.js +1 -1
  20. package/dist/unpkg/dev/types/runtime.js +20 -1
  21. package/dist/unpkg/dev-deprecated/graph/-private.js +67 -52
  22. package/dist/unpkg/dev-deprecated/{index-Bu52tQ9I.js → index-BYFUHVPd.js} +332 -10
  23. package/dist/unpkg/dev-deprecated/index.js +1 -1
  24. package/dist/unpkg/dev-deprecated/reactive.js +1 -1
  25. package/dist/unpkg/dev-deprecated/store/-private.js +1 -1
  26. package/dist/unpkg/dev-deprecated/types/-private.js +1 -1
  27. package/dist/unpkg/dev-deprecated/types/runtime.js +20 -1
  28. package/dist/unpkg/prod/{-leaked-BHLmJyaw.js → -leaked-DgjQ5X55.js} +2 -2
  29. package/dist/unpkg/prod/configure.js +1 -1
  30. package/dist/unpkg/prod/graph/-private.js +17 -25
  31. package/dist/unpkg/prod/{schema-BdC3DirM.js → index-C-AhVafS.js} +2383 -73
  32. package/dist/unpkg/prod/index.js +5 -483
  33. package/dist/unpkg/prod/{promise-cache-DIT8Ypjq.js → promise-cache-DUblkX-U.js} +1 -1
  34. package/dist/unpkg/prod/reactive.js +3 -29
  35. package/dist/unpkg/prod/{request-BrJSCG6r.js → request-eb7Zm-oS.js} +1 -1
  36. package/dist/unpkg/prod/request.js +2 -2
  37. package/dist/unpkg/prod/signals/-leaked.js +1 -1
  38. package/dist/unpkg/prod/store/-private.js +2 -3
  39. package/dist/unpkg/prod/types/-private.js +1 -1
  40. package/dist/unpkg/prod/types/runtime.js +20 -1
  41. package/dist/unpkg/prod-deprecated/{-leaked-D_5Yrs5c.js → -leaked-BNVttukY.js} +1 -1
  42. package/dist/unpkg/prod-deprecated/configure.js +1 -1
  43. package/dist/unpkg/prod-deprecated/graph/-private.js +17 -25
  44. package/dist/unpkg/prod-deprecated/{schema-h0nuUTCj.js → index-CplZl2hv.js} +1035 -7
  45. package/dist/unpkg/prod-deprecated/index.js +5 -483
  46. package/dist/unpkg/prod-deprecated/{promise-cache-DIT8Ypjq.js → promise-cache-DUblkX-U.js} +1 -1
  47. package/dist/unpkg/prod-deprecated/reactive.js +2 -3
  48. package/dist/unpkg/prod-deprecated/{request-BrJSCG6r.js → request-eb7Zm-oS.js} +1 -1
  49. package/dist/unpkg/prod-deprecated/request.js +2 -2
  50. package/dist/unpkg/prod-deprecated/signals/-leaked.js +1 -1
  51. package/dist/unpkg/prod-deprecated/store/-private.js +1 -2
  52. package/dist/unpkg/prod-deprecated/types/-private.js +1 -1
  53. package/dist/unpkg/prod-deprecated/types/runtime.js +20 -1
  54. package/package.json +3 -3
  55. package/dist/unpkg/prod/handler-BJogFbfS.js +0 -1619
  56. package/dist/unpkg/prod-deprecated/handler-kGR8zguj.js +0 -334
  57. package/dist/unpkg/prod-deprecated/hooks-wJdveHPi.js +0 -26
@@ -1,12 +1,14 @@
1
- import { d as defineGate, w as withSignalStore, n as notifyInternalSignal, p as peekInternalSignal, b as willSyncFlushWatchers, e as getOrCreateInternalSignal, f as consumeInternalSignal, h as createInternalSignal, A as ARRAY_SIGNAL, S as Signals, i as createSignalDescriptor, j as defineSignal, k as entangleSignal, l as entangleInitiallyStaleSignal, O as OBJECT_SIGNAL, m as createInternalMemo } from "./-leaked-D_5Yrs5c.js";
1
+ import { d as defineGate, w as withSignalStore, n as notifyInternalSignal, p as peekInternalSignal, b as willSyncFlushWatchers, e as getOrCreateInternalSignal, f as consumeInternalSignal, h as createInternalSignal, A as ARRAY_SIGNAL, S as Signals, i as createSignalDescriptor, j as defineSignal, k as entangleSignal, l as entangleInitiallyStaleSignal, O as OBJECT_SIGNAL, m as createInternalMemo } from "./-leaked-BNVttukY.js";
2
2
  import { EnableHydration, SkipCache, STRUCTURED } from './types/request.js';
3
- import { peekTransient, setTransient, getOrSetGlobal } from './types/-private.js';
4
- import { isResourceSchema } from './types/schema/fields.js';
3
+ import { peekTransient, setTransient, getOrSetGlobal, peekUniversalTransient, setUniversalTransient } from './types/-private.js';
5
4
  import { D as Destroy, a as Context, S as SOURCE, C as Checkout, b as Commit } from "./-private-3C1OkYtZ.js";
6
- import { w as withBrand } from "./request-BrJSCG6r.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';
7
9
  import { CACHE_OWNER } from './types/identifier.js';
8
10
  import { dasherize } from './utils/string.js';
9
- import './types/runtime.js';
11
+ import { isResourceSchema } from './types/schema/fields.js';
10
12
  import { RecordStore, Type } from './types/symbols.js';
11
13
  import * as _importSync20 from '@ember/object';
12
14
  function urlFromLink(link) {
@@ -4242,6 +4244,337 @@ function upgradeInstanceCaches(cache) {
4242
4244
  }
4243
4245
  return withReferences;
4244
4246
  }
4247
+ const MUTATION_OPS$1 = new Set(['createRecord', 'updateRecord', 'deleteRecord']);
4248
+ function calcShouldFetch(store, request, hasCachedValue, identifier) {
4249
+ const {
4250
+ cacheOptions
4251
+ } = request;
4252
+ return request.op && MUTATION_OPS$1.has(request.op) || cacheOptions?.reload || !hasCachedValue || (store.lifetimes && identifier ? store.lifetimes.isHardExpired(identifier, store) : false);
4253
+ }
4254
+ function calcShouldBackgroundFetch(store, request, willFetch, identifier) {
4255
+ const {
4256
+ cacheOptions
4257
+ } = request;
4258
+ return cacheOptions?.backgroundReload || (store.lifetimes && identifier ? store.lifetimes.isSoftExpired(identifier, store) : false);
4259
+ }
4260
+ function isMutation(request) {
4261
+ return Boolean(request.op && MUTATION_OPS$1.has(request.op));
4262
+ }
4263
+ function isCacheAffecting(document) {
4264
+ if (!isMutation(document.request)) {
4265
+ return true;
4266
+ }
4267
+ // a mutation combined with a 204 has no cache impact when no known records were involved
4268
+ // a createRecord with a 201 with an empty response and no known records should similarly
4269
+ // have no cache impact
4270
+
4271
+ if (document.request.op === 'createRecord' && document.response?.status === 201) {
4272
+ return document.content ? Object.keys(document.content).length > 0 : false;
4273
+ }
4274
+ return document.response?.status !== 204;
4275
+ }
4276
+ function isAggregateError(error) {
4277
+ return error instanceof AggregateError || error.name === 'AggregateError' && Array.isArray(error.errors);
4278
+ }
4279
+ // TODO @runspired, consider if we should deep freeze errors (potentially only in debug) vs cloning them
4280
+ function cloneError(error) {
4281
+ const isAggregate = isAggregateError(error);
4282
+ const cloned = isAggregate ? new AggregateError(structuredClone(error.errors), error.message) : new Error(error.message);
4283
+ cloned.stack = error.stack;
4284
+ cloned.error = error.error;
4285
+
4286
+ // copy over enumerable properties
4287
+ Object.assign(cloned, error);
4288
+ return cloned;
4289
+ }
4290
+ function getPriority(identifier, deduped, priority) {
4291
+ if (identifier) {
4292
+ const existing = deduped.get(identifier);
4293
+ if (existing) {
4294
+ return existing.priority;
4295
+ }
4296
+ }
4297
+ return priority;
4298
+ }
4299
+
4300
+ /**
4301
+ * A CacheHandler that adds support for using an WarpDrive Cache with a RequestManager.
4302
+ *
4303
+ * This handler will only run when a request has supplied a `store` instance. Requests
4304
+ * issued by the store via `store.request()` will automatically have the `store` instance
4305
+ * attached to the request.
4306
+ *
4307
+ * ```ts
4308
+ * requestManager.request({
4309
+ * store: store,
4310
+ * url: '/api/posts',
4311
+ * method: 'GET'
4312
+ * });
4313
+ * ```
4314
+ *
4315
+ * When this handler elects to handle a request, it will return the raw `StructuredDocument`
4316
+ * unless the request has `[EnableHydration]` set to `true`. In this case, the handler will
4317
+ * return a `Document` instance that will automatically update the UI when the cache is updated
4318
+ * in the future and will hydrate any identifiers in the StructuredDocument into Record instances.
4319
+ *
4320
+ * When issuing a request via the store, [EnableHydration] is automatically set to `true`. This
4321
+ * means that if desired you can issue requests that utilize the cache without needing to also
4322
+ * utilize Record instances if desired.
4323
+ *
4324
+ * Said differently, you could elect to issue all requests via a RequestManager, without ever using
4325
+ * the store directly, by setting [EnableHydration] to `true` and providing a store instance. Not
4326
+ * necessarily the most useful thing, but the decoupled nature of the RequestManager and incremental-feature
4327
+ * approach of WarpDrive allows for this flexibility.
4328
+ *
4329
+ * ```ts
4330
+ * import { EnableHydration } from '@warp-drive/core/types/request';
4331
+ *
4332
+ * requestManager.request({
4333
+ * store: store,
4334
+ * url: '/api/posts',
4335
+ * method: 'GET',
4336
+ * [EnableHydration]: true
4337
+ * });
4338
+ *
4339
+ */
4340
+ const CacheHandler = {
4341
+ request(context, next) {
4342
+ // if we have no cache or no cache-key skip cache handling
4343
+ if (!context.request.store || context.request.cacheOptions?.[SkipCache]) {
4344
+ return next(context.request);
4345
+ }
4346
+ const {
4347
+ store
4348
+ } = context.request;
4349
+ const identifier = store.cacheKeyManager.getOrCreateDocumentIdentifier(context.request);
4350
+ if (identifier) {
4351
+ context.setIdentifier(identifier);
4352
+ }
4353
+
4354
+ // used to dedupe existing requests that match
4355
+ const DEDUPE = store.requestManager._deduped;
4356
+ const activeRequest = identifier && DEDUPE.get(identifier);
4357
+ const peeked = identifier ? store.cache.peekRequest(identifier) : null;
4358
+
4359
+ // determine if we should skip cache
4360
+ if (calcShouldFetch(store, context.request, !!peeked, identifier)) {
4361
+ if (activeRequest) {
4362
+ activeRequest.priority = {
4363
+ blocking: true
4364
+ };
4365
+ return activeRequest.promise;
4366
+ }
4367
+ let promise = fetchContentAndHydrate(next, context, identifier, {
4368
+ blocking: true
4369
+ });
4370
+ if (identifier) {
4371
+ promise = promise.finally(() => {
4372
+ DEDUPE.delete(identifier);
4373
+ store.notifications.notify(identifier, 'state', null);
4374
+ });
4375
+ DEDUPE.set(identifier, {
4376
+ priority: {
4377
+ blocking: true
4378
+ },
4379
+ promise
4380
+ });
4381
+ queueMicrotask(() => {
4382
+ store.notifications.notify(identifier, 'state', null);
4383
+ });
4384
+ }
4385
+ store.requestManager._pending.set(context.id, promise);
4386
+ return promise;
4387
+ }
4388
+
4389
+ // if we have not skipped cache, determine if we should update behind the scenes
4390
+ if (calcShouldBackgroundFetch(store, context.request, false, identifier)) {
4391
+ let promise = activeRequest?.promise || fetchContentAndHydrate(next, context, identifier, {
4392
+ blocking: false
4393
+ });
4394
+ if (identifier && !activeRequest) {
4395
+ promise = promise.finally(() => {
4396
+ DEDUPE.delete(identifier);
4397
+ store.notifications.notify(identifier, 'state', null);
4398
+ });
4399
+ DEDUPE.set(identifier, {
4400
+ priority: {
4401
+ blocking: false
4402
+ },
4403
+ promise
4404
+ });
4405
+ queueMicrotask(() => {
4406
+ store.notifications.notify(identifier, 'state', null);
4407
+ });
4408
+ }
4409
+ store.requestManager._pending.set(context.id, promise);
4410
+ }
4411
+ const shouldHydrate = context.request[EnableHydration] || false;
4412
+ context.setResponse(peeked.response);
4413
+ if ('error' in peeked) {
4414
+ const content = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
4415
+ shouldHydrate,
4416
+ identifier
4417
+ }, peeked.content) : peeked.content;
4418
+ const newError = cloneError(peeked);
4419
+ newError.content = content;
4420
+ throw newError;
4421
+ }
4422
+ const result = shouldHydrate ? maybeUpdateUiObjects(store, context.request, {
4423
+ shouldHydrate,
4424
+ identifier
4425
+ }, peeked.content) : peeked.content;
4426
+ return result;
4427
+ }
4428
+ };
4429
+ function maybeUpdateUiObjects(store, request, options, document) {
4430
+ const {
4431
+ identifier
4432
+ } = options;
4433
+ if (!document || !options.shouldHydrate) {
4434
+ return document ?? null;
4435
+ }
4436
+ if (identifier) {
4437
+ return store._instanceCache.getDocument(identifier);
4438
+ }
4439
+
4440
+ // if we don't have an identifier, we give the document
4441
+ // its own local cache
4442
+ return createReactiveDocument(store, null, {
4443
+ request,
4444
+ document
4445
+ });
4446
+ }
4447
+ function updateCacheForSuccess(store, request, options, document) {
4448
+ let response = null;
4449
+ if (isMutation(request)) {
4450
+ if (Array.isArray(request.records)) {
4451
+ response = store.cache.didCommit(request.records, document);
4452
+ } else if (request.data?.record) {
4453
+ // legacy fallback, the data option should no longer be used for this
4454
+ response = store.cache.didCommit(request.data.record, document);
4455
+
4456
+ // a mutation combined with a 204 has no cache impact when no known records were involved
4457
+ // a createRecord with a 201 with an empty response and no known records should similarly
4458
+ // have no cache impact
4459
+ } else if (isCacheAffecting(document)) {
4460
+ response = store.cache.put(document);
4461
+ }
4462
+ } else {
4463
+ response = store.cache.put(document);
4464
+ }
4465
+ return maybeUpdateUiObjects(store, request, options, response);
4466
+ }
4467
+ function handleFetchSuccess(store, context, options, document) {
4468
+ const {
4469
+ request
4470
+ } = context;
4471
+ store.requestManager._pending.delete(context.id);
4472
+ store._enableAsyncFlush = true;
4473
+ let response;
4474
+ store._join(() => {
4475
+ response = updateCacheForSuccess(store, request, options, document);
4476
+ });
4477
+ store._enableAsyncFlush = null;
4478
+ if (store.lifetimes?.didRequest) {
4479
+ store.lifetimes.didRequest(context.request, document.response, options.identifier, store);
4480
+ }
4481
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
4482
+ if (finalPriority.blocking) {
4483
+ return response;
4484
+ } else {
4485
+ store.notifications._flush();
4486
+ }
4487
+ }
4488
+ function updateCacheForError(store, context, options, error) {
4489
+ let response;
4490
+ if (isMutation(context.request)) {
4491
+ // TODO similar to didCommit we should spec this to be similar to cache.put for handling full response
4492
+ // currently we let the response remain undefiend.
4493
+ const errors = error && error.content && typeof error.content === 'object' && 'errors' in error.content && Array.isArray(error.content.errors) ? error.content.errors : undefined;
4494
+ if (Array.isArray(context.request.records)) {
4495
+ store.cache.commitWasRejected(context.request.records, errors);
4496
+ } else if (context.request.data?.record) {
4497
+ // legacy fallback, the data option should no longer be used for this
4498
+ store.cache.commitWasRejected(context.request.data.record, errors);
4499
+ } else {
4500
+ store.cache.put(error);
4501
+ }
4502
+ } else {
4503
+ response = store.cache.put(error);
4504
+ return maybeUpdateUiObjects(store, context.request, options, response);
4505
+ }
4506
+ }
4507
+ function handleFetchError(store, context, options, error) {
4508
+ store.requestManager._pending.delete(context.id);
4509
+ if (context.request.signal?.aborted) {
4510
+ throw error;
4511
+ }
4512
+ store._enableAsyncFlush = true;
4513
+ let response;
4514
+ store._join(() => {
4515
+ response = updateCacheForError(store, context, options, error);
4516
+ });
4517
+ store._enableAsyncFlush = null;
4518
+ if (options.identifier && store.lifetimes?.didRequest) {
4519
+ store.lifetimes.didRequest(context.request, error.response, options.identifier, store);
4520
+ }
4521
+ if (isMutation(context.request)) {
4522
+ throw error;
4523
+ }
4524
+ const finalPriority = getPriority(options.identifier, store.requestManager._deduped, options.priority);
4525
+ if (finalPriority.blocking) {
4526
+ const newError = cloneError(error);
4527
+ newError.content = response;
4528
+ throw newError;
4529
+ } else {
4530
+ store.notifications._flush();
4531
+ }
4532
+ }
4533
+ function fetchContentAndHydrate(next, context, identifier, priority) {
4534
+ const {
4535
+ store
4536
+ } = context.request;
4537
+ const shouldHydrate = context.request[EnableHydration] || false;
4538
+ const options = {
4539
+ shouldHydrate,
4540
+ identifier,
4541
+ priority
4542
+ };
4543
+ let isMut = false;
4544
+ if (isMutation(context.request)) {
4545
+ isMut = true;
4546
+ if (Array.isArray(context.request.records)) {
4547
+ context.request.records.forEach(record => {
4548
+ store.cache.willCommit(record, context);
4549
+ });
4550
+ } else if (context.request.data?.record) {
4551
+ // legacy fallback, the data option should no longer be used for this
4552
+ store.cache.willCommit(context.request.data.record, context);
4553
+ }
4554
+ }
4555
+ if (store.lifetimes?.willRequest) {
4556
+ store.lifetimes.willRequest(context.request, identifier, store);
4557
+ }
4558
+ const promise = next(context.request).then(document => {
4559
+ return handleFetchSuccess(store, context, options, document);
4560
+ }, error => {
4561
+ return handleFetchError(store, context, options, error);
4562
+ });
4563
+ if (!isMut) {
4564
+ return promise;
4565
+ }
4566
+
4567
+ // for mutations we need to enqueue the promise with the requestStateService
4568
+ // TODO should we enque a request per record in records?
4569
+ const record = context.request.data?.record || context.request.records?.[0];
4570
+ return store._requestCache._enqueue(promise, {
4571
+ data: [{
4572
+ op: 'saveRecord',
4573
+ recordIdentifier: record,
4574
+ options: undefined
4575
+ }]
4576
+ });
4577
+ }
4245
4578
  function isNonEmptyString(str) {
4246
4579
  return Boolean(str && typeof str === 'string');
4247
4580
  }
@@ -5081,14 +5414,41 @@ function getBelongsToField(context) {
5081
5414
  }
5082
5415
  function setBelongsToField(context) {
5083
5416
  const {
5417
+ field,
5418
+ resourceKey,
5084
5419
  store
5085
5420
  } = context;
5086
5421
  const {
5087
- schema
5422
+ schema,
5423
+ cache
5088
5424
  } = store;
5425
+
5426
+ /**
5427
+ * We currently only allow editing sync belongsTo relationships
5428
+ * in PolarisMode
5429
+ */
5430
+ const isValidPolarisConfig = field.options.linksMode && !field.options.async;
5431
+ if (!context.legacy && isValidPolarisConfig) {
5432
+ const name = context.path.at(-1);
5433
+ cache.mutate({
5434
+ op: 'replaceRelatedRecord',
5435
+ record: resourceKey,
5436
+ field: name,
5437
+ value: getResourceKey(context.value)
5438
+ },
5439
+ // @ts-expect-error
5440
+ true);
5441
+ return true;
5442
+ }
5089
5443
  schema._kind('@legacy', 'belongsTo').set(store, context.record, context.resourceKey, context.field, context.value);
5090
5444
  return true;
5091
5445
  }
5446
+ function getResourceKey(record) {
5447
+ if (!record) {
5448
+ return null;
5449
+ }
5450
+ return recordIdentifierFor(record);
5451
+ }
5092
5452
  function getCollectionField(context) {
5093
5453
  entangleSignal(context.signals, context.record, context.path.at(-1), null);
5094
5454
  }
@@ -5720,6 +6080,17 @@ function setSchemaObjectField(context) {
5720
6080
  // }
5721
6081
  return true;
5722
6082
  }
6083
+
6084
+ /**
6085
+ * A ReactiveResource context object passed to
6086
+ * the get/set functions of a field kind implementation.
6087
+ *
6088
+ * This provides all the necessary information for
6089
+ * the field kind to perform its operations.
6090
+ *
6091
+ * @internal
6092
+ */
6093
+
5723
6094
  const DefaultMode = {
5724
6095
  '@hash': {
5725
6096
  get: getHashField,
@@ -5990,6 +6361,9 @@ class ReactiveResource {
5990
6361
  if (prop === Destroy || prop === Checkout) {
5991
6362
  return true;
5992
6363
  }
6364
+ if (prop === identityField?.name) {
6365
+ return true;
6366
+ }
5993
6367
  return fields.has(prop);
5994
6368
  },
5995
6369
  getOwnPropertyDescriptor(target, prop) {
@@ -6388,6 +6762,29 @@ function checkout(resource) {
6388
6762
  function commit(record) {
6389
6763
  return record[Commit]();
6390
6764
  }
6765
+ function instantiateRecord(store, identifier, createArgs) {
6766
+ const schema = store.schema;
6767
+ const resourceSchema = schema.resource(identifier);
6768
+ const legacy = resourceSchema?.legacy ?? false;
6769
+ const editable = legacy;
6770
+ const record = new ReactiveResource({
6771
+ store,
6772
+ resourceKey: identifier,
6773
+ modeName: legacy ? 'legacy' : 'polaris',
6774
+ legacy: legacy,
6775
+ editable: editable,
6776
+ path: null,
6777
+ field: null,
6778
+ value: null
6779
+ });
6780
+ if (createArgs && editable) {
6781
+ Object.assign(record, createArgs);
6782
+ }
6783
+ return record;
6784
+ }
6785
+ function teardownRecord(record) {
6786
+ record[Destroy]();
6787
+ }
6391
6788
  const Support = getOrSetGlobal('Support', new WeakMap());
6392
6789
  const ConstructorField = {
6393
6790
  type: '@constructor',
@@ -6402,6 +6799,14 @@ const TypeField = {
6402
6799
  key: 'type'
6403
6800
  }
6404
6801
  };
6802
+ const IdentifierField = {
6803
+ type: '@identity',
6804
+ name: '$key',
6805
+ kind: 'derived',
6806
+ options: {
6807
+ key: '^'
6808
+ }
6809
+ };
6405
6810
  const DefaultIdentityField = {
6406
6811
  name: 'id',
6407
6812
  kind: '@id'
@@ -6570,6 +6975,7 @@ function withDefaults(schema) {
6570
6975
  // we add TypeField to the beginning so that it will
6571
6976
  // appear right next to the identity field
6572
6977
  schema.fields.unshift(TypeField);
6978
+ schema.fields.unshift(IdentifierField);
6573
6979
  schema.fields.push(ConstructorField);
6574
6980
  return schema;
6575
6981
  }
@@ -6654,6 +7060,18 @@ class SchemaService {
6654
7060
 
6655
7061
  /** @internal */
6656
7062
 
7063
+ /**
7064
+ * Tracks, per abstract type, the relationship fields that concrete
7065
+ * implementers have contributed via `options.as`, along with the type
7066
+ * that contributed each one (for assertion messages). This is
7067
+ * independent of whatever schema (synthesized or user-registered)
7068
+ * currently occupies `_schemas` for that type, so that these fields
7069
+ * survive regardless of the order in which the abstract type's
7070
+ * implementers and its own (optional) concrete schema are registered.
7071
+ *
7072
+ * @internal
7073
+ */
7074
+
6657
7075
  /** @internal */
6658
7076
 
6659
7077
  /** @internal */
@@ -6665,6 +7083,7 @@ class SchemaService {
6665
7083
  this._derivations = new Map();
6666
7084
  this._traits = new Map();
6667
7085
  this._modes = new Map();
7086
+ this._abstractImplementerFields = new Map();
6668
7087
  this._extensions = {
6669
7088
  object: new Map(),
6670
7089
  array: new Map()
@@ -6710,12 +7129,33 @@ class SchemaService {
6710
7129
  const fields = new Map();
6711
7130
  const relationships = {};
6712
7131
  const attributes = {};
7132
+ const abstractImplementations = [];
6713
7133
  for (const field of schema.fields) {
6714
7134
  fields.set(field.name, field);
6715
7135
  if (field.kind === 'attribute') {
6716
7136
  attributes[field.name] = field;
6717
7137
  } else if (field.kind === 'belongsTo' || field.kind === 'hasMany') {
6718
7138
  relationships[field.name] = field;
7139
+ if (field.options?.as) {
7140
+ abstractImplementations.push(field);
7141
+ }
7142
+ }
7143
+ }
7144
+
7145
+ // This type may already be known as an abstract polymorphic type,
7146
+ // implemented by other concrete types via `options.as`, from before it
7147
+ // ever had a schema of its own (see `_registerAbstractTypeImplementation`).
7148
+ // Carry those previously-contributed fields forward so that registering
7149
+ // a "real" schema for the type - whenever that happens to occur - never
7150
+ // erases the relationships its implementers depend on.
7151
+ const implementerFields = this._abstractImplementerFields.get(schema.type);
7152
+ if (implementerFields) {
7153
+ for (const [name, contribution] of implementerFields) {
7154
+ const ownField = fields.get(name);
7155
+ if (!ownField) {
7156
+ fields.set(name, contribution.field);
7157
+ relationships[name] = contribution.field;
7158
+ }
6719
7159
  }
6720
7160
  }
6721
7161
  const cacheFields = null;
@@ -6734,6 +7174,92 @@ class SchemaService {
6734
7174
  internalSchema.cacheFields = getCacheFields(internalSchema);
6735
7175
  }
6736
7176
  this._schemas.set(schema.type, internalSchema);
7177
+
7178
+ // A relationship field's `as` option marks it as a valid concrete
7179
+ // implementer of an abstract polymorphic type (e.g. `as: 'commentable'`).
7180
+ // That abstract type may never be given its own schema by the user - it
7181
+ // may exist only to be implemented by concrete types like this one - or
7182
+ // it may already have (or later receive) a schema of its own, e.g. if it
7183
+ // turns out to also be a real, directly-resolvable resource. Either way,
7184
+ // ensure it has a schema with this field present, so that it behaves
7185
+ // like any other registered resource (`hasResource`, `fields`, etc.)
7186
+ // instead of requiring special-casing wherever abstract relationship
7187
+ // types are resolved.
7188
+ for (const field of abstractImplementations) {
7189
+ this._registerAbstractTypeImplementation(field, schema.type);
7190
+ }
7191
+ }
7192
+
7193
+ /** @internal */
7194
+ _registerAbstractTypeImplementation(field, implementer) {
7195
+ const abstractType = field.options.as;
7196
+ let implementerFields = this._abstractImplementerFields.get(abstractType);
7197
+ if (!implementerFields) {
7198
+ implementerFields = new Map();
7199
+ this._abstractImplementerFields.set(abstractType, implementerFields);
7200
+ }
7201
+
7202
+ // Unlike the original approach this replaced, `as` is *not* stripped here:
7203
+ // the field as synthesized onto the abstract type's own schema keeps
7204
+ // `options.as === abstractType`, redundant/self-referential as that is.
7205
+ // This keeps every contributor - implementers and the abstract type's
7206
+ // own schema alike - declaring the same thing, which is what
7207
+ // `assertConsistentAbstractFieldShape` checks, and it is also what lets
7208
+ // `assertPolymorphicType` (which reads a field's declared `as` off
7209
+ // whatever `schema.fields()` serves for its type) correctly permit the
7210
+ // abstract type itself to be pushed directly into this relationship.
7211
+ const abstractField = {
7212
+ ...field
7213
+ };
7214
+ const contribution = {
7215
+ field: abstractField,
7216
+ source: implementer
7217
+ };
7218
+
7219
+ // all concrete implementations of an abstract type are required to
7220
+ // share the same shape for the field that implements it, so the first
7221
+ // one registered is as good a canonical source as any - but a later,
7222
+ // differently-shaped one is almost certainly a mistake rather than an
7223
+ // intentional override, so we catch it rather than silently ignoring it.
7224
+ const existingImplementer = implementerFields.get(field.name);
7225
+ if (existingImplementer) {
7226
+ return;
7227
+ }
7228
+ implementerFields.set(field.name, contribution);
7229
+ let abstractSchema = this._schemas.get(abstractType);
7230
+ if (!abstractSchema) {
7231
+ abstractSchema = {
7232
+ original: {
7233
+ legacy: true,
7234
+ identity: {
7235
+ kind: '@id',
7236
+ name: 'id'
7237
+ },
7238
+ type: abstractType,
7239
+ fields: []
7240
+ },
7241
+ finalized: true,
7242
+ fields: new Map(),
7243
+ cacheFields: new Map(),
7244
+ relationships: {},
7245
+ attributes: {},
7246
+ traits: new Set()
7247
+ };
7248
+ this._schemas.set(abstractType, abstractSchema);
7249
+ }
7250
+ const existingAbstractField = abstractSchema.fields.get(field.name);
7251
+ if (existingAbstractField) {
7252
+ return;
7253
+ }
7254
+ abstractSchema.fields.set(field.name, abstractField);
7255
+ abstractSchema.relationships[field.name] = abstractField;
7256
+
7257
+ // If the schema is mid-finalization (traits pending), finalizeResource
7258
+ // will recompute cacheFields from the current `fields` map anyway; avoid
7259
+ // doing so prematurely from a partially-resolved set of fields.
7260
+ if (abstractSchema.finalized) {
7261
+ abstractSchema.cacheFields = getCacheFields(abstractSchema);
7262
+ }
6737
7263
  }
6738
7264
 
6739
7265
  /**
@@ -6954,4 +7480,506 @@ function mergeMap(base, toApply) {
6954
7480
  base.set(key, value);
6955
7481
  }
6956
7482
  }
6957
- export { setIdentifierResetMethod as A, setKeyInfoForResource as B, RecordArrayManager as R, SchemaService as S, _clearCaches as _, commit as a, Store as b, checkout as c, recordIdentifierFor as d, isRequestKey as e, fromIdentity as f, constructResource as g, coerceId as h, isResourceKey as i, ensureStringId as j, fastPush as k, assertPrivateStore as l, isPrivateStore as m, assertPrivateCapabilities as n, setRecordIdentifier as o, StoreMap as p, normalizeModelName as q, registerDerivations as r, storeFor as s, createLegacyManyArray as t, ReactiveResource as u, createReactiveDocument as v, withDefaults as w, setIdentifierGenerationMethod as x, setIdentifierUpdateMethod as y, setIdentifierForgetMethod as z };
7483
+
7484
+ // Lazily close over fetch to avoid breaking Mirage
7485
+ const _fetch = typeof fetch !== 'undefined' ? (...args) => fetch(...args) : typeof FastBoot !== 'undefined' ? (...args) => FastBoot.require('node-fetch')(...args) : () => {
7486
+ throw new Error('No Fetch Implementation Found');
7487
+ };
7488
+
7489
+ // clones a response in a way that should still
7490
+ // allow it to stream
7491
+ function cloneResponse(response, overrides) {
7492
+ const props = cloneResponseProperties(response);
7493
+ return new Response(response.body, Object.assign(props, overrides));
7494
+ }
7495
+ function withDateHeader(response) {
7496
+ const headers = new Headers(response.headers);
7497
+ headers.set('date', new Date().toUTCString());
7498
+ return cloneResponse(response, {
7499
+ headers
7500
+ });
7501
+ }
7502
+ const MUTATION_OPS = new Set(['updateRecord', 'createRecord', 'deleteRecord']);
7503
+ 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']]);
7504
+
7505
+ /**
7506
+ * ```ts
7507
+ * import { Fetch } from '@warp-drive/core';
7508
+ * ```
7509
+ *
7510
+ * A basic Fetch Handler which converts a request into a
7511
+ * `fetch` call presuming the response to be `json`.
7512
+ *
7513
+ * ```ts
7514
+ * import { RequestManager, Fetch } from '@warp-drive/core';
7515
+ *
7516
+ * const manager = new RequestManager()
7517
+ * .use([Fetch]);
7518
+ * ```
7519
+ *
7520
+ * @public
7521
+ */
7522
+ const Fetch = {
7523
+ async request(context) {
7524
+ let response;
7525
+ try {
7526
+ ({});
7527
+ response = await _fetch(context.request.url, context.request);
7528
+ } catch (e) {
7529
+ if (e instanceof DOMException && e.name === 'AbortError') {
7530
+ e.statusText = 'Aborted';
7531
+ e.status = 20;
7532
+ e.isRequestError = true;
7533
+ } else {
7534
+ e.statusText = 'Unknown Network Error';
7535
+ e.status = 0;
7536
+ if (!(e instanceof DOMException)) {
7537
+ e.code = 0;
7538
+ }
7539
+ e.isRequestError = true;
7540
+ }
7541
+ throw e;
7542
+ }
7543
+ const isError = !response.ok || response.status >= 400;
7544
+ const op = context.request.op;
7545
+ const isMutationOp = Boolean(op && MUTATION_OPS.has(op));
7546
+ if (!isError && !isMutationOp && response.status !== 204 && !response.headers.has('date')) {
7547
+ {
7548
+ response = withDateHeader(response);
7549
+ }
7550
+ }
7551
+ context.setResponse(response);
7552
+ if (response.status === 204 || context.request.method === 'HEAD') {
7553
+ return null;
7554
+ }
7555
+ let text = '';
7556
+ // if we are in a mirage context, we cannot support streaming
7557
+ {
7558
+ const reader = response.body.getReader();
7559
+ const decoder = new TextDecoder();
7560
+ let isStreaming = context.hasRequestedStream;
7561
+ let stream = isStreaming ? new TransformStream() : null;
7562
+ let writer = stream?.writable.getWriter();
7563
+ if (isStreaming) {
7564
+ // Listen for the abort event on the AbortSignal
7565
+ context.request.signal?.addEventListener('abort', () => {
7566
+ if (!isStreaming) {
7567
+ return;
7568
+ }
7569
+ void stream.writable.abort('Request Aborted');
7570
+ void stream.readable.cancel('Request Aborted');
7571
+ });
7572
+ context.setStream(stream.readable);
7573
+ }
7574
+ while (true) {
7575
+ // we manually read the stream instead of using `response.json()`
7576
+ // or `response.text()` because if we need to stream the body
7577
+ // we need to be able to pass the stream along efficiently.
7578
+ const {
7579
+ done,
7580
+ value
7581
+ } = await reader.read();
7582
+ if (done) {
7583
+ if (isStreaming) {
7584
+ isStreaming = false;
7585
+ await writer.ready;
7586
+ await writer.close();
7587
+ }
7588
+ break;
7589
+ }
7590
+ text += decoder.decode(value, {
7591
+ stream: true
7592
+ });
7593
+
7594
+ // if we are streaming, we want to pass the stream along
7595
+ if (isStreaming) {
7596
+ await writer.ready;
7597
+ await writer.write(value);
7598
+ } else if (context.hasRequestedStream) {
7599
+ const encode = new TextEncoder();
7600
+ isStreaming = true;
7601
+ stream = new TransformStream();
7602
+ // Listen for the abort event on the AbortSignal
7603
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
7604
+ context.request.signal?.addEventListener('abort', () => {
7605
+ if (!isStreaming) {
7606
+ return;
7607
+ }
7608
+ void stream.writable.abort('Request Aborted');
7609
+ void stream.readable.cancel('Request Aborted');
7610
+ });
7611
+ context.setStream(stream.readable);
7612
+ writer = stream.writable.getWriter();
7613
+ await writer.ready;
7614
+ await writer.write(encode.encode(text));
7615
+ await writer.ready;
7616
+ await writer.write(value);
7617
+ }
7618
+ }
7619
+ if (isStreaming) {
7620
+ isStreaming = false;
7621
+ await writer.ready;
7622
+ await writer.close();
7623
+ }
7624
+ }
7625
+ // if we are an error, we will want to throw
7626
+ if (isError) {
7627
+ let errorPayload;
7628
+ try {
7629
+ errorPayload = JSON.parse(text);
7630
+ } catch {
7631
+ // void;
7632
+ }
7633
+ // attempt errors discovery
7634
+ const errors = Array.isArray(errorPayload) ? errorPayload : isDict(errorPayload) && Array.isArray(errorPayload.errors) ? errorPayload.errors : null;
7635
+ const statusText = response.statusText || ERROR_STATUS_CODE_FOR.get(response.status) || 'Unknown Request Error';
7636
+ const msg = `[${response.status} ${statusText}] ${context.request.method ?? 'GET'} (${response.type}) - ${response.url}`;
7637
+ const error = errors ? new AggregateError(errors, msg) : new Error(msg);
7638
+ error.status = response.status;
7639
+ error.statusText = statusText;
7640
+ error.isRequestError = true;
7641
+ error.code = error.status;
7642
+ error.name = error.statusText.replaceAll(' ', '') + 'Error';
7643
+ error.content = errorPayload;
7644
+ throw error;
7645
+ } else {
7646
+ return JSON.parse(text);
7647
+ }
7648
+ }
7649
+ };
7650
+ function isDict(v) {
7651
+ return v !== null && typeof v === 'object';
7652
+ }
7653
+
7654
+ /**
7655
+ * ## Import
7656
+ *
7657
+ * ```js
7658
+ * import { RequestManager } from '@warp-drive/core';
7659
+ * ```
7660
+ *
7661
+ * For complete usage guide see the [RequestManager Documentation](/guides/).
7662
+ *
7663
+ * ## How It Works
7664
+ *
7665
+ * ```ts
7666
+ * interface RequestManager {
7667
+ * request<T>(req: RequestInfo): Future<T>;
7668
+ * }
7669
+ * ```
7670
+ *
7671
+ * A RequestManager provides a request/response flow in which configured
7672
+ * handlers are successively given the opportunity to handle, modify, or
7673
+ * pass-along a request.
7674
+ *
7675
+ * <img src="/images/handlers-all-labeled.gif" alt="RequestManager Flow Animation" width="100%" />
7676
+ *
7677
+ * For example:
7678
+ *
7679
+ * ::: code-group
7680
+ *
7681
+ * ```ts [Setup.ts]
7682
+ * import { RequestManager, Fetch } from '@warp-drive/core';
7683
+ * import { AutoCompress } from '@warp-drive/utilities/handlers';
7684
+ * import Auth from 'ember-simple-auth/handler';
7685
+ *
7686
+ * // ... create manager
7687
+ * const manager = new RequestManager()
7688
+ * .use([Auth, new AutoCompress(), Fetch]); // [!code focus]
7689
+ * ```
7690
+ *
7691
+ * ```ts [Usage.ts]
7692
+ * import Config from './config';
7693
+ *
7694
+ * const { apiUrl } = Config;
7695
+ *
7696
+ * // ... execute a request
7697
+ * const response = await manager.request({
7698
+ * url: `${apiUrl}/users`
7699
+ * });
7700
+ * ```
7701
+ *
7702
+ * :::
7703
+ *
7704
+ * ### Futures
7705
+ *
7706
+ * The return value of `manager.request` is a `Future`, which allows
7707
+ * access to limited information about the request while it is still
7708
+ * pending and fulfills with the final state when the request completes.
7709
+ *
7710
+ * A `Future` is cancellable via `abort`.
7711
+ *
7712
+ * Handlers may optionally expose a `ReadableStream` to the `Future` for
7713
+ * streaming data; however, when doing so the future should not resolve
7714
+ * until the response stream is fully read.
7715
+ *
7716
+ * ```ts
7717
+ * interface Future<T> extends Promise<StructuredDocument<T>> {
7718
+ * abort(): void;
7719
+ *
7720
+ * async getStream(): ReadableStream | null;
7721
+ * }
7722
+ * ```
7723
+ *
7724
+ * ### StructuredDocuments
7725
+ *
7726
+ * A Future resolves with a `StructuredDataDocument` or rejects with a `StructuredErrorDocument`.
7727
+ *
7728
+ * ```ts
7729
+ * interface StructuredDataDocument<T> {
7730
+ * request: ImmutableRequestInfo;
7731
+ * response: ImmutableResponseInfo;
7732
+ * content: T;
7733
+ * }
7734
+ * interface StructuredErrorDocument extends Error {
7735
+ * request: ImmutableRequestInfo;
7736
+ * response: ImmutableResponseInfo;
7737
+ * error: string | object;
7738
+ * }
7739
+ * type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
7740
+ * ```
7741
+ *
7742
+ * @public
7743
+ */
7744
+ class RequestManager {
7745
+ /** @internal */
7746
+
7747
+ /** @internal */
7748
+
7749
+ /**
7750
+ * A map of pending requests from request.id to their
7751
+ * associated CacheHandler promise.
7752
+ *
7753
+ * This queue is managed by the CacheHandler
7754
+ *
7755
+ * @internal
7756
+ */
7757
+
7758
+ /** @internal */
7759
+
7760
+ constructor(options) {
7761
+ Object.assign(this, options);
7762
+ this._pending = new Map();
7763
+ this._deduped = new Map();
7764
+ this._handlers = [];
7765
+ }
7766
+
7767
+ /**
7768
+ * Register a handler to use for primary cache intercept.
7769
+ *
7770
+ * Only one such handler may exist. If using the same
7771
+ * RequestManager as the Store instance the Store
7772
+ * registers itself as a Cache handler.
7773
+ *
7774
+ * @public
7775
+ */
7776
+ useCache(cacheHandler) {
7777
+ cacheHandler[IS_CACHE_HANDLER] = true;
7778
+ this._handlers.unshift(cacheHandler);
7779
+ return this;
7780
+ }
7781
+
7782
+ /**
7783
+ * Register handler(s) to use when a request is issued.
7784
+ *
7785
+ * Handlers will be invoked in the order they are registered.
7786
+ * Each Handler is given the opportunity to handle the request,
7787
+ * curry the request, or pass along a modified request.
7788
+ *
7789
+ * @public
7790
+ */
7791
+ use(newHandlers) {
7792
+ const handlers = this._handlers;
7793
+ handlers.push(...newHandlers);
7794
+ return this;
7795
+ }
7796
+
7797
+ /**
7798
+ * Issue a Request.
7799
+ *
7800
+ * Returns a Future that fulfills with a StructuredDocument
7801
+ *
7802
+ * @public
7803
+ */
7804
+ request(request) {
7805
+ const handlers = this._handlers;
7806
+ const controller = request.controller || new AbortController();
7807
+ if (request.controller) {
7808
+ delete request.controller;
7809
+ }
7810
+ const requestId = peekUniversalTransient('REQ_ID') ?? 0;
7811
+ setUniversalTransient('REQ_ID', requestId + 1);
7812
+ const context = {
7813
+ controller,
7814
+ response: null,
7815
+ stream: null,
7816
+ hasRequestedStream: false,
7817
+ id: requestId,
7818
+ identifier: null,
7819
+ requester: request[EnableHydration] && request.store ? request.store : this
7820
+ };
7821
+ const promise = executeNextHandler(handlers, request, 0, context);
7822
+
7823
+ // the cache handler will set the result of the request synchronously
7824
+ // if it is able to fulfill the request from the cache
7825
+ const cacheResult = getRequestResult(requestId);
7826
+ // const promise1 = store.request(myRequest);
7827
+ // const promise2 = store.request(myRequest);
7828
+ // promise1 === promise2; // false
7829
+ // either we need to make promise1 === promise2, or we need to make sure that
7830
+ // we need to have a way to key from request to result
7831
+ // such that we can lookup the result here and return it if it exists
7832
+ const finalPromise = upgradePromise(promise.then(result => {
7833
+ setPromiseResult(finalPromise, {
7834
+ isError: false,
7835
+ result
7836
+ });
7837
+ clearRequestResult(requestId);
7838
+ return result;
7839
+ }, error => {
7840
+ setPromiseResult(finalPromise, {
7841
+ isError: true,
7842
+ result: error
7843
+ });
7844
+ clearRequestResult(requestId);
7845
+ throw error;
7846
+ }), promise);
7847
+ if (cacheResult) {
7848
+ setPromiseResult(finalPromise, cacheResult);
7849
+ }
7850
+ return finalPromise;
7851
+ }
7852
+
7853
+ /**
7854
+ * This method exists so that the RequestManager can be created
7855
+ * can be created by container/factory systems that expect to
7856
+ * call a static `create` method to instantiate the class.
7857
+ *
7858
+ * Using `new RequestManager()` directly is preferred.
7859
+ *
7860
+ * @private
7861
+ */
7862
+ static create(options) {
7863
+ return new this(options);
7864
+ }
7865
+ }
7866
+
7867
+ /**
7868
+ * This type exists for internal use only for
7869
+ * where intimate contracts still exist either for
7870
+ * the Test Suite or for Legacy code.
7871
+ *
7872
+ * @private
7873
+ */
7874
+
7875
+ /**
7876
+ * @module
7877
+ * @mergeModuleWith <project>
7878
+ */
7879
+
7880
+ // Mirage (or another Pretender-based fetch mock) may be used outside of a
7881
+ // `TESTING` build (e.g. `ember serve` in dev mode), so this override is
7882
+ // exposed unconditionally rather than gated to the `TESTING` env.
7883
+ // @ts-expect-error adding to globalThis
7884
+ globalThis.setWarpDriveIsMaybeMirage = setIsMaybeMirage;
7885
+
7886
+ /**
7887
+ * Options for setting up a Store instance with `useRecommendedStore`.
7888
+ */
7889
+
7890
+ /**
7891
+ * Creates a configured Store class with recommended defaults
7892
+ * for schema handling, reactivity, caching, and request management.
7893
+ *
7894
+ * ```ts
7895
+ * import { useRecommendedStore } from '@warp-drive/core';
7896
+ * import { JSONAPICache } from '@warp-drive/json-api';
7897
+ *
7898
+ * export const Store = useRecommendedStore({
7899
+ * cache: JSONAPICache,
7900
+ * schemas: [],
7901
+ * });
7902
+ * ```
7903
+ */
7904
+ function useRecommendedStore(options, StoreKlass = Store) {
7905
+ return class AppStore extends StoreKlass {
7906
+ constructor(createArgs) {
7907
+ super(createArgs);
7908
+ // installed via defineProperty (rather than a class field/accessor) so that
7909
+ // this lazy override of the inherited `requestManager` field does not
7910
+ // conflict with the documented pattern of assigning it directly on
7911
+ // consumer-authored Store subclasses. The setter preserves the ability
7912
+ // to replace `requestManager` outright after construction.
7913
+ let requestManager;
7914
+ Object.defineProperty(this, 'requestManager', {
7915
+ configurable: true,
7916
+ enumerable: true,
7917
+ get: () => {
7918
+ if (!requestManager) {
7919
+ const handlers = typeof options.handlers === 'function' ? options.handlers(this) : options.handlers ?? [];
7920
+ requestManager = new RequestManager().use([...handlers, Fetch]).useCache(CacheHandler);
7921
+ }
7922
+ return requestManager;
7923
+ },
7924
+ set: value => {
7925
+ requestManager = value;
7926
+ }
7927
+ });
7928
+ }
7929
+ lifetimes = options.policy ?? new DefaultCachePolicy({
7930
+ apiCacheHardExpires: 15 * 60 * 1000,
7931
+ // 15 minutes
7932
+ apiCacheSoftExpires: 1 * 30 * 1000,
7933
+ // 30 seconds
7934
+ constraints: {
7935
+ headers: {
7936
+ 'X-WarpDrive-Expires': true,
7937
+ 'Cache-Control': true,
7938
+ Expires: true
7939
+ }
7940
+ }
7941
+ });
7942
+ createSchemaService() {
7943
+ const schema = new SchemaService();
7944
+ registerDerivations(schema);
7945
+ if (options.schemas) schema.registerResources(options.schemas);
7946
+ if (options.traits) {
7947
+ for (const trait of options.traits) {
7948
+ schema.registerTrait(trait);
7949
+ }
7950
+ }
7951
+ if (options.derivations) {
7952
+ for (const derivation of options.derivations) {
7953
+ schema.registerDerivation(derivation);
7954
+ }
7955
+ }
7956
+ if (options.transformations) {
7957
+ for (const transformation of options.transformations) {
7958
+ schema.registerTransformation(transformation);
7959
+ }
7960
+ }
7961
+ if (options.hashFns) {
7962
+ for (const hashFn of options.hashFns) {
7963
+ schema.registerHashFn(hashFn);
7964
+ }
7965
+ }
7966
+ if (options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7967
+ for (const extension of options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7968
+ schema.CAUTION_MEGA_DANGER_ZONE_registerExtension(extension);
7969
+ }
7970
+ }
7971
+ return schema;
7972
+ }
7973
+ createCache(capabilities) {
7974
+ // eslint-disable-next-line new-cap
7975
+ return new options.cache(capabilities);
7976
+ }
7977
+ instantiateRecord(key, createArgs) {
7978
+ return instantiateRecord(this, key, createArgs);
7979
+ }
7980
+ teardownRecord(record) {
7981
+ return teardownRecord(record);
7982
+ }
7983
+ };
7984
+ }
7985
+ export { setIdentifierUpdateMethod as A, setIdentifierForgetMethod as B, CacheHandler as C, setIdentifierResetMethod as D, setKeyInfoForResource as E, Fetch as F, RecordArrayManager as R, SchemaService as S, _clearCaches as _, commit as a, Store as b, checkout as c, recordIdentifierFor as d, isResourceKey as e, fromIdentity as f, isRequestKey as g, constructResource as h, instantiateRecord as i, coerceId as j, ensureStringId as k, fastPush as l, assertPrivateStore as m, isPrivateStore as n, assertPrivateCapabilities as o, setRecordIdentifier as p, StoreMap as q, registerDerivations as r, storeFor as s, teardownRecord as t, normalizeModelName as u, createLegacyManyArray as v, withDefaults as w, RequestManager as x, useRecommendedStore as y, setIdentifierGenerationMethod as z };