@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,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,
@@ -6388,6 +6759,29 @@ function checkout(resource) {
6388
6759
  function commit(record) {
6389
6760
  return record[Commit]();
6390
6761
  }
6762
+ function instantiateRecord(store, identifier, createArgs) {
6763
+ const schema = store.schema;
6764
+ const resourceSchema = schema.resource(identifier);
6765
+ const legacy = resourceSchema?.legacy ?? false;
6766
+ const editable = legacy;
6767
+ const record = new ReactiveResource({
6768
+ store,
6769
+ resourceKey: identifier,
6770
+ modeName: legacy ? 'legacy' : 'polaris',
6771
+ legacy: legacy,
6772
+ editable: editable,
6773
+ path: null,
6774
+ field: null,
6775
+ value: null
6776
+ });
6777
+ if (createArgs && editable) {
6778
+ Object.assign(record, createArgs);
6779
+ }
6780
+ return record;
6781
+ }
6782
+ function teardownRecord(record) {
6783
+ record[Destroy]();
6784
+ }
6391
6785
  const Support = getOrSetGlobal('Support', new WeakMap());
6392
6786
  const ConstructorField = {
6393
6787
  type: '@constructor',
@@ -6402,6 +6796,14 @@ const TypeField = {
6402
6796
  key: 'type'
6403
6797
  }
6404
6798
  };
6799
+ const IdentifierField = {
6800
+ type: '@identity',
6801
+ name: '$key',
6802
+ kind: 'derived',
6803
+ options: {
6804
+ key: '^'
6805
+ }
6806
+ };
6405
6807
  const DefaultIdentityField = {
6406
6808
  name: 'id',
6407
6809
  kind: '@id'
@@ -6570,6 +6972,7 @@ function withDefaults(schema) {
6570
6972
  // we add TypeField to the beginning so that it will
6571
6973
  // appear right next to the identity field
6572
6974
  schema.fields.unshift(TypeField);
6975
+ schema.fields.unshift(IdentifierField);
6573
6976
  schema.fields.push(ConstructorField);
6574
6977
  return schema;
6575
6978
  }
@@ -6954,4 +7357,484 @@ function mergeMap(base, toApply) {
6954
7357
  base.set(key, value);
6955
7358
  }
6956
7359
  }
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 };
7360
+
7361
+ // Lazily close over fetch to avoid breaking Mirage
7362
+ const _fetch = typeof fetch !== 'undefined' ? (...args) => fetch(...args) : typeof FastBoot !== 'undefined' ? (...args) => FastBoot.require('node-fetch')(...args) : () => {
7363
+ throw new Error('No Fetch Implementation Found');
7364
+ };
7365
+
7366
+ // clones a response in a way that should still
7367
+ // allow it to stream
7368
+ function cloneResponse(response, overrides) {
7369
+ const props = cloneResponseProperties(response);
7370
+ return new Response(response.body, Object.assign(props, overrides));
7371
+ }
7372
+ function withDateHeader(response) {
7373
+ const headers = new Headers(response.headers);
7374
+ headers.set('date', new Date().toUTCString());
7375
+ return cloneResponse(response, {
7376
+ headers
7377
+ });
7378
+ }
7379
+ const MUTATION_OPS = new Set(['updateRecord', 'createRecord', 'deleteRecord']);
7380
+ 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']]);
7381
+
7382
+ /**
7383
+ * ```ts
7384
+ * import { Fetch } from '@warp-drive/core';
7385
+ * ```
7386
+ *
7387
+ * A basic Fetch Handler which converts a request into a
7388
+ * `fetch` call presuming the response to be `json`.
7389
+ *
7390
+ * ```ts
7391
+ * import { RequestManager, Fetch } from '@warp-drive/core';
7392
+ *
7393
+ * const manager = new RequestManager()
7394
+ * .use([Fetch]);
7395
+ * ```
7396
+ *
7397
+ * @public
7398
+ */
7399
+ const Fetch = {
7400
+ async request(context) {
7401
+ let response;
7402
+ try {
7403
+ ({});
7404
+ response = await _fetch(context.request.url, context.request);
7405
+ } catch (e) {
7406
+ if (e instanceof DOMException && e.name === 'AbortError') {
7407
+ e.statusText = 'Aborted';
7408
+ e.status = 20;
7409
+ e.isRequestError = true;
7410
+ } else {
7411
+ e.statusText = 'Unknown Network Error';
7412
+ e.status = 0;
7413
+ if (!(e instanceof DOMException)) {
7414
+ e.code = 0;
7415
+ }
7416
+ e.isRequestError = true;
7417
+ }
7418
+ throw e;
7419
+ }
7420
+ const isError = !response.ok || response.status >= 400;
7421
+ const op = context.request.op;
7422
+ const isMutationOp = Boolean(op && MUTATION_OPS.has(op));
7423
+ if (!isError && !isMutationOp && response.status !== 204 && !response.headers.has('date')) {
7424
+ {
7425
+ response = withDateHeader(response);
7426
+ }
7427
+ }
7428
+ context.setResponse(response);
7429
+ if (response.status === 204 || context.request.method === 'HEAD') {
7430
+ return null;
7431
+ }
7432
+ let text = '';
7433
+ // if we are in a mirage context, we cannot support streaming
7434
+ {
7435
+ const reader = response.body.getReader();
7436
+ const decoder = new TextDecoder();
7437
+ let isStreaming = context.hasRequestedStream;
7438
+ let stream = isStreaming ? new TransformStream() : null;
7439
+ let writer = stream?.writable.getWriter();
7440
+ if (isStreaming) {
7441
+ // Listen for the abort event on the AbortSignal
7442
+ context.request.signal?.addEventListener('abort', () => {
7443
+ if (!isStreaming) {
7444
+ return;
7445
+ }
7446
+ void stream.writable.abort('Request Aborted');
7447
+ void stream.readable.cancel('Request Aborted');
7448
+ });
7449
+ context.setStream(stream.readable);
7450
+ }
7451
+ while (true) {
7452
+ // we manually read the stream instead of using `response.json()`
7453
+ // or `response.text()` because if we need to stream the body
7454
+ // we need to be able to pass the stream along efficiently.
7455
+ const {
7456
+ done,
7457
+ value
7458
+ } = await reader.read();
7459
+ if (done) {
7460
+ if (isStreaming) {
7461
+ isStreaming = false;
7462
+ await writer.ready;
7463
+ await writer.close();
7464
+ }
7465
+ break;
7466
+ }
7467
+ text += decoder.decode(value, {
7468
+ stream: true
7469
+ });
7470
+
7471
+ // if we are streaming, we want to pass the stream along
7472
+ if (isStreaming) {
7473
+ await writer.ready;
7474
+ await writer.write(value);
7475
+ } else if (context.hasRequestedStream) {
7476
+ const encode = new TextEncoder();
7477
+ isStreaming = true;
7478
+ stream = new TransformStream();
7479
+ // Listen for the abort event on the AbortSignal
7480
+ // eslint-disable-next-line @typescript-eslint/no-loop-func
7481
+ context.request.signal?.addEventListener('abort', () => {
7482
+ if (!isStreaming) {
7483
+ return;
7484
+ }
7485
+ void stream.writable.abort('Request Aborted');
7486
+ void stream.readable.cancel('Request Aborted');
7487
+ });
7488
+ context.setStream(stream.readable);
7489
+ writer = stream.writable.getWriter();
7490
+ await writer.ready;
7491
+ await writer.write(encode.encode(text));
7492
+ await writer.ready;
7493
+ await writer.write(value);
7494
+ }
7495
+ }
7496
+ if (isStreaming) {
7497
+ isStreaming = false;
7498
+ await writer.ready;
7499
+ await writer.close();
7500
+ }
7501
+ }
7502
+ // if we are an error, we will want to throw
7503
+ if (isError) {
7504
+ let errorPayload;
7505
+ try {
7506
+ errorPayload = JSON.parse(text);
7507
+ } catch {
7508
+ // void;
7509
+ }
7510
+ // attempt errors discovery
7511
+ const errors = Array.isArray(errorPayload) ? errorPayload : isDict(errorPayload) && Array.isArray(errorPayload.errors) ? errorPayload.errors : null;
7512
+ const statusText = response.statusText || ERROR_STATUS_CODE_FOR.get(response.status) || 'Unknown Request Error';
7513
+ const msg = `[${response.status} ${statusText}] ${context.request.method ?? 'GET'} (${response.type}) - ${response.url}`;
7514
+ const error = errors ? new AggregateError(errors, msg) : new Error(msg);
7515
+ error.status = response.status;
7516
+ error.statusText = statusText;
7517
+ error.isRequestError = true;
7518
+ error.code = error.status;
7519
+ error.name = error.statusText.replaceAll(' ', '') + 'Error';
7520
+ error.content = errorPayload;
7521
+ throw error;
7522
+ } else {
7523
+ return JSON.parse(text);
7524
+ }
7525
+ }
7526
+ };
7527
+ function isDict(v) {
7528
+ return v !== null && typeof v === 'object';
7529
+ }
7530
+
7531
+ /**
7532
+ * ## Import
7533
+ *
7534
+ * ```js
7535
+ * import { RequestManager } from '@warp-drive/core';
7536
+ * ```
7537
+ *
7538
+ * For complete usage guide see the [RequestManager Documentation](/guides/).
7539
+ *
7540
+ * ## How It Works
7541
+ *
7542
+ * ```ts
7543
+ * interface RequestManager {
7544
+ * request<T>(req: RequestInfo): Future<T>;
7545
+ * }
7546
+ * ```
7547
+ *
7548
+ * A RequestManager provides a request/response flow in which configured
7549
+ * handlers are successively given the opportunity to handle, modify, or
7550
+ * pass-along a request.
7551
+ *
7552
+ * <img src="/images/handlers-all-labeled.gif" alt="RequestManager Flow Animation" width="100%" />
7553
+ *
7554
+ * For example:
7555
+ *
7556
+ * ::: code-group
7557
+ *
7558
+ * ```ts [Setup.ts]
7559
+ * import { RequestManager, Fetch } from '@warp-drive/core';
7560
+ * import { AutoCompress } from '@warp-drive/utilities/handlers';
7561
+ * import Auth from 'ember-simple-auth/handler';
7562
+ *
7563
+ * // ... create manager
7564
+ * const manager = new RequestManager()
7565
+ * .use([Auth, new AutoCompress(), Fetch]); // [!code focus]
7566
+ * ```
7567
+ *
7568
+ * ```ts [Usage.ts]
7569
+ * import Config from './config';
7570
+ *
7571
+ * const { apiUrl } = Config;
7572
+ *
7573
+ * // ... execute a request
7574
+ * const response = await manager.request({
7575
+ * url: `${apiUrl}/users`
7576
+ * });
7577
+ * ```
7578
+ *
7579
+ * :::
7580
+ *
7581
+ * ### Futures
7582
+ *
7583
+ * The return value of `manager.request` is a `Future`, which allows
7584
+ * access to limited information about the request while it is still
7585
+ * pending and fulfills with the final state when the request completes.
7586
+ *
7587
+ * A `Future` is cancellable via `abort`.
7588
+ *
7589
+ * Handlers may optionally expose a `ReadableStream` to the `Future` for
7590
+ * streaming data; however, when doing so the future should not resolve
7591
+ * until the response stream is fully read.
7592
+ *
7593
+ * ```ts
7594
+ * interface Future<T> extends Promise<StructuredDocument<T>> {
7595
+ * abort(): void;
7596
+ *
7597
+ * async getStream(): ReadableStream | null;
7598
+ * }
7599
+ * ```
7600
+ *
7601
+ * ### StructuredDocuments
7602
+ *
7603
+ * A Future resolves with a `StructuredDataDocument` or rejects with a `StructuredErrorDocument`.
7604
+ *
7605
+ * ```ts
7606
+ * interface StructuredDataDocument<T> {
7607
+ * request: ImmutableRequestInfo;
7608
+ * response: ImmutableResponseInfo;
7609
+ * content: T;
7610
+ * }
7611
+ * interface StructuredErrorDocument extends Error {
7612
+ * request: ImmutableRequestInfo;
7613
+ * response: ImmutableResponseInfo;
7614
+ * error: string | object;
7615
+ * }
7616
+ * type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
7617
+ * ```
7618
+ *
7619
+ * @public
7620
+ */
7621
+ class RequestManager {
7622
+ /** @internal */
7623
+
7624
+ /** @internal */
7625
+
7626
+ /**
7627
+ * A map of pending requests from request.id to their
7628
+ * associated CacheHandler promise.
7629
+ *
7630
+ * This queue is managed by the CacheHandler
7631
+ *
7632
+ * @internal
7633
+ */
7634
+
7635
+ /** @internal */
7636
+
7637
+ constructor(options) {
7638
+ Object.assign(this, options);
7639
+ this._pending = new Map();
7640
+ this._deduped = new Map();
7641
+ this._handlers = [];
7642
+ }
7643
+
7644
+ /**
7645
+ * Register a handler to use for primary cache intercept.
7646
+ *
7647
+ * Only one such handler may exist. If using the same
7648
+ * RequestManager as the Store instance the Store
7649
+ * registers itself as a Cache handler.
7650
+ *
7651
+ * @public
7652
+ */
7653
+ useCache(cacheHandler) {
7654
+ cacheHandler[IS_CACHE_HANDLER] = true;
7655
+ this._handlers.unshift(cacheHandler);
7656
+ return this;
7657
+ }
7658
+
7659
+ /**
7660
+ * Register handler(s) to use when a request is issued.
7661
+ *
7662
+ * Handlers will be invoked in the order they are registered.
7663
+ * Each Handler is given the opportunity to handle the request,
7664
+ * curry the request, or pass along a modified request.
7665
+ *
7666
+ * @public
7667
+ */
7668
+ use(newHandlers) {
7669
+ const handlers = this._handlers;
7670
+ handlers.push(...newHandlers);
7671
+ return this;
7672
+ }
7673
+
7674
+ /**
7675
+ * Issue a Request.
7676
+ *
7677
+ * Returns a Future that fulfills with a StructuredDocument
7678
+ *
7679
+ * @public
7680
+ */
7681
+ request(request) {
7682
+ const handlers = this._handlers;
7683
+ const controller = request.controller || new AbortController();
7684
+ if (request.controller) {
7685
+ delete request.controller;
7686
+ }
7687
+ const requestId = peekUniversalTransient('REQ_ID') ?? 0;
7688
+ setUniversalTransient('REQ_ID', requestId + 1);
7689
+ const context = {
7690
+ controller,
7691
+ response: null,
7692
+ stream: null,
7693
+ hasRequestedStream: false,
7694
+ id: requestId,
7695
+ identifier: null,
7696
+ requester: request[EnableHydration] && request.store ? request.store : this
7697
+ };
7698
+ const promise = executeNextHandler(handlers, request, 0, context);
7699
+
7700
+ // the cache handler will set the result of the request synchronously
7701
+ // if it is able to fulfill the request from the cache
7702
+ const cacheResult = getRequestResult(requestId);
7703
+ // const promise1 = store.request(myRequest);
7704
+ // const promise2 = store.request(myRequest);
7705
+ // promise1 === promise2; // false
7706
+ // either we need to make promise1 === promise2, or we need to make sure that
7707
+ // we need to have a way to key from request to result
7708
+ // such that we can lookup the result here and return it if it exists
7709
+ const finalPromise = upgradePromise(promise.then(result => {
7710
+ setPromiseResult(finalPromise, {
7711
+ isError: false,
7712
+ result
7713
+ });
7714
+ clearRequestResult(requestId);
7715
+ return result;
7716
+ }, error => {
7717
+ setPromiseResult(finalPromise, {
7718
+ isError: true,
7719
+ result: error
7720
+ });
7721
+ clearRequestResult(requestId);
7722
+ throw error;
7723
+ }), promise);
7724
+ if (cacheResult) {
7725
+ setPromiseResult(finalPromise, cacheResult);
7726
+ }
7727
+ return finalPromise;
7728
+ }
7729
+
7730
+ /**
7731
+ * This method exists so that the RequestManager can be created
7732
+ * can be created by container/factory systems that expect to
7733
+ * call a static `create` method to instantiate the class.
7734
+ *
7735
+ * Using `new RequestManager()` directly is preferred.
7736
+ *
7737
+ * @private
7738
+ */
7739
+ static create(options) {
7740
+ return new this(options);
7741
+ }
7742
+ }
7743
+
7744
+ /**
7745
+ * This type exists for internal use only for
7746
+ * where intimate contracts still exist either for
7747
+ * the Test Suite or for Legacy code.
7748
+ *
7749
+ * @private
7750
+ */
7751
+
7752
+ /**
7753
+ * @module
7754
+ * @mergeModuleWith <project>
7755
+ */
7756
+
7757
+ // Mirage (or another Pretender-based fetch mock) may be used outside of a
7758
+ // `TESTING` build (e.g. `ember serve` in dev mode), so this override is
7759
+ // exposed unconditionally rather than gated to the `TESTING` env.
7760
+ // @ts-expect-error adding to globalThis
7761
+ globalThis.setWarpDriveIsMaybeMirage = setIsMaybeMirage;
7762
+
7763
+ /**
7764
+ * Options for setting up a Store instance with `useRecommendedStore`.
7765
+ */
7766
+
7767
+ /**
7768
+ * Creates a configured Store class with recommended defaults
7769
+ * for schema handling, reactivity, caching, and request management.
7770
+ *
7771
+ * ```ts
7772
+ * import { useRecommendedStore } from '@warp-drive/core';
7773
+ * import { JSONAPICache } from '@warp-drive/json-api';
7774
+ *
7775
+ * export const Store = useRecommendedStore({
7776
+ * cache: JSONAPICache,
7777
+ * schemas: [],
7778
+ * });
7779
+ * ```
7780
+ */
7781
+ function useRecommendedStore(options, StoreKlass = Store) {
7782
+ return class AppStore extends StoreKlass {
7783
+ requestManager = new RequestManager().use([...(options.handlers ?? []), Fetch]).useCache(CacheHandler);
7784
+ lifetimes = options.policy ?? new DefaultCachePolicy({
7785
+ apiCacheHardExpires: 15 * 60 * 1000,
7786
+ // 15 minutes
7787
+ apiCacheSoftExpires: 1 * 30 * 1000,
7788
+ // 30 seconds
7789
+ constraints: {
7790
+ headers: {
7791
+ 'X-WarpDrive-Expires': true,
7792
+ 'Cache-Control': true,
7793
+ Expires: true
7794
+ }
7795
+ }
7796
+ });
7797
+ createSchemaService() {
7798
+ const schema = new SchemaService();
7799
+ registerDerivations(schema);
7800
+ if (options.schemas) schema.registerResources(options.schemas);
7801
+ if (options.traits) {
7802
+ for (const trait of options.traits) {
7803
+ schema.registerTrait(trait);
7804
+ }
7805
+ }
7806
+ if (options.derivations) {
7807
+ for (const derivation of options.derivations) {
7808
+ schema.registerDerivation(derivation);
7809
+ }
7810
+ }
7811
+ if (options.transformations) {
7812
+ for (const transformation of options.transformations) {
7813
+ schema.registerTransformation(transformation);
7814
+ }
7815
+ }
7816
+ if (options.hashFns) {
7817
+ for (const hashFn of options.hashFns) {
7818
+ schema.registerHashFn(hashFn);
7819
+ }
7820
+ }
7821
+ if (options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7822
+ for (const extension of options.CAUTION_MEGA_DANGER_ZONE_extensions) {
7823
+ schema.CAUTION_MEGA_DANGER_ZONE_registerExtension(extension);
7824
+ }
7825
+ }
7826
+ return schema;
7827
+ }
7828
+ createCache(capabilities) {
7829
+ // eslint-disable-next-line new-cap
7830
+ return new options.cache(capabilities);
7831
+ }
7832
+ instantiateRecord(key, createArgs) {
7833
+ return instantiateRecord(this, key, createArgs);
7834
+ }
7835
+ teardownRecord(record) {
7836
+ return teardownRecord(record);
7837
+ }
7838
+ };
7839
+ }
7840
+ 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 };