@warp-drive/utilities 5.10.0-alpha.10 → 5.10.0-alpha.13

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 (101) hide show
  1. package/README.md +92 -7
  2. package/cjs-dist/string.cjs +19 -0
  3. package/cjs-dist/string.cjs.map +1 -1
  4. package/dist/active-record.d.ts +21 -0
  5. package/dist/active-record.d.ts.map +1 -1
  6. package/dist/active-record.js +13 -1
  7. package/dist/active-record.js.map +1 -1
  8. package/dist/derivations.d.ts +2 -0
  9. package/dist/derivations.d.ts.map +1 -1
  10. package/dist/derivations.js +6 -0
  11. package/dist/derivations.js.map +1 -1
  12. package/dist/handlers.d.ts +15 -0
  13. package/dist/handlers.d.ts.map +1 -1
  14. package/dist/handlers.js +15 -0
  15. package/dist/handlers.js.map +1 -1
  16. package/dist/{index-D6QIwzqm.d.ts → index-Ds06tKwC.d.ts} +39 -1
  17. package/dist/index-Ds06tKwC.d.ts.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +39 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/json-api.d.ts +36 -1
  22. package/dist/json-api.d.ts.map +1 -1
  23. package/dist/json-api.js +23 -1
  24. package/dist/json-api.js.map +1 -1
  25. package/dist/rest.d.ts +20 -0
  26. package/dist/rest.d.ts.map +1 -1
  27. package/dist/rest.js +13 -1
  28. package/dist/rest.js.map +1 -1
  29. package/dist/{string-CWmAluxh.js → string-BZACNuLp.js} +20 -1
  30. package/dist/string-BZACNuLp.js.map +1 -0
  31. package/dist/string.d.ts +19 -0
  32. package/dist/string.d.ts.map +1 -1
  33. package/dist/string.js +1 -1
  34. package/dist/unpkg/dev/active-record.js +13 -1
  35. package/dist/unpkg/dev/active-record.js.map +1 -1
  36. package/dist/unpkg/dev/derivations.js +6 -0
  37. package/dist/unpkg/dev/derivations.js.map +1 -1
  38. package/dist/unpkg/dev/handlers.js +15 -0
  39. package/dist/unpkg/dev/handlers.js.map +1 -1
  40. package/dist/unpkg/dev/index.js +39 -0
  41. package/dist/unpkg/dev/index.js.map +1 -1
  42. package/dist/unpkg/dev/json-api.js +23 -1
  43. package/dist/unpkg/dev/json-api.js.map +1 -1
  44. package/dist/unpkg/dev/rest.js +13 -1
  45. package/dist/unpkg/dev/rest.js.map +1 -1
  46. package/dist/unpkg/dev/{string-j4ur-TDs.js → string-I8KHJXV7.js} +20 -1
  47. package/dist/unpkg/dev/string-I8KHJXV7.js.map +1 -0
  48. package/dist/unpkg/dev/string.js +1 -1
  49. package/dist/unpkg/dev-deprecated/active-record.js +13 -1
  50. package/dist/unpkg/dev-deprecated/active-record.js.map +1 -1
  51. package/dist/unpkg/dev-deprecated/derivations.js +6 -0
  52. package/dist/unpkg/dev-deprecated/derivations.js.map +1 -1
  53. package/dist/unpkg/dev-deprecated/handlers.js +15 -0
  54. package/dist/unpkg/dev-deprecated/handlers.js.map +1 -1
  55. package/dist/unpkg/dev-deprecated/index.js +39 -0
  56. package/dist/unpkg/dev-deprecated/index.js.map +1 -1
  57. package/dist/unpkg/dev-deprecated/json-api.js +23 -1
  58. package/dist/unpkg/dev-deprecated/json-api.js.map +1 -1
  59. package/dist/unpkg/dev-deprecated/rest.js +13 -1
  60. package/dist/unpkg/dev-deprecated/rest.js.map +1 -1
  61. package/dist/unpkg/dev-deprecated/{string-j4ur-TDs.js → string-I8KHJXV7.js} +20 -1
  62. package/dist/unpkg/dev-deprecated/string-I8KHJXV7.js.map +1 -0
  63. package/dist/unpkg/dev-deprecated/string.js +1 -1
  64. package/dist/unpkg/prod/active-record.js +13 -1
  65. package/dist/unpkg/prod/active-record.js.map +1 -1
  66. package/dist/unpkg/prod/derivations.js +6 -0
  67. package/dist/unpkg/prod/derivations.js.map +1 -1
  68. package/dist/unpkg/prod/handlers.js +15 -0
  69. package/dist/unpkg/prod/handlers.js.map +1 -1
  70. package/dist/unpkg/prod/index.js +39 -0
  71. package/dist/unpkg/prod/index.js.map +1 -1
  72. package/dist/unpkg/prod/json-api.js +23 -1
  73. package/dist/unpkg/prod/json-api.js.map +1 -1
  74. package/dist/unpkg/prod/rest.js +13 -1
  75. package/dist/unpkg/prod/rest.js.map +1 -1
  76. package/dist/unpkg/prod/string-D8BEYx2W.js +302 -0
  77. package/dist/unpkg/prod/string-D8BEYx2W.js.map +1 -0
  78. package/dist/unpkg/prod/string.js +1 -1
  79. package/dist/unpkg/prod-deprecated/active-record.js +13 -1
  80. package/dist/unpkg/prod-deprecated/active-record.js.map +1 -1
  81. package/dist/unpkg/prod-deprecated/derivations.js +6 -0
  82. package/dist/unpkg/prod-deprecated/derivations.js.map +1 -1
  83. package/dist/unpkg/prod-deprecated/handlers.js +15 -0
  84. package/dist/unpkg/prod-deprecated/handlers.js.map +1 -1
  85. package/dist/unpkg/prod-deprecated/index.js +39 -0
  86. package/dist/unpkg/prod-deprecated/index.js.map +1 -1
  87. package/dist/unpkg/prod-deprecated/json-api.js +23 -1
  88. package/dist/unpkg/prod-deprecated/json-api.js.map +1 -1
  89. package/dist/unpkg/prod-deprecated/rest.js +13 -1
  90. package/dist/unpkg/prod-deprecated/rest.js.map +1 -1
  91. package/dist/unpkg/prod-deprecated/{string-T1ahjLdj.js → string-D8BEYx2W.js} +20 -1
  92. package/dist/unpkg/prod-deprecated/string-D8BEYx2W.js.map +1 -0
  93. package/dist/unpkg/prod-deprecated/string.js +1 -1
  94. package/package.json +6 -6
  95. package/dist/index-D6QIwzqm.d.ts.map +0 -1
  96. package/dist/string-CWmAluxh.js.map +0 -1
  97. package/dist/unpkg/dev/string-j4ur-TDs.js.map +0 -1
  98. package/dist/unpkg/dev-deprecated/string-j4ur-TDs.js.map +0 -1
  99. package/dist/unpkg/prod/string-T1ahjLdj.js +0 -283
  100. package/dist/unpkg/prod/string-T1ahjLdj.js.map +0 -1
  101. package/dist/unpkg/prod-deprecated/string-T1ahjLdj.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { buildBaseURL, buildQueryParams } from "./index.js";
2
- import { g as underscore, s as pluralize } from "./string-j4ur-TDs.js";
2
+ import { g as underscore, s as pluralize } from "./string-I8KHJXV7.js";
3
3
  import { n as extractCacheOptions, t as copyForwardUrlOptions } from "./builder-utils-BkXwUT0x.js";
4
4
  import { recordIdentifierFor } from "@warp-drive/core";
5
5
 
@@ -62,6 +62,9 @@ function isExisting(identifier) {
62
62
  * Builds request options to delete record for resources,
63
63
  * configured for the url, method and header expectations of ActiveRecord APIs.
64
64
  *
65
+ * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
66
+ * when to use one.
67
+ *
65
68
  * **Basic Usage**
66
69
  *
67
70
  * ```ts
@@ -103,6 +106,7 @@ function isExisting(identifier) {
103
106
  * const data = await store.request(options);
104
107
  * ```
105
108
  *
109
+ * @summary Builds a `DELETE` request that persists deletion of an existing record, using ActiveRecord-style URLs.
106
110
  * @public
107
111
  * @param record
108
112
  * @param options
@@ -137,6 +141,9 @@ function deleteRecord(record, options = {}) {
137
141
  * Builds request options to create new record for resources,
138
142
  * configured for the url, method and header expectations of most ActiveRecord APIs.
139
143
  *
144
+ * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
145
+ * when to use one.
146
+ *
140
147
  * **Basic Usage**
141
148
  *
142
149
  * ```ts
@@ -168,6 +175,7 @@ function deleteRecord(record, options = {}) {
168
175
  * const data = await store.request(options);
169
176
  * ```
170
177
  *
178
+ * @summary Builds a `POST` request that saves a newly created record, using ActiveRecord-style URLs.
171
179
  * @public
172
180
  * @param record
173
181
  * @param options
@@ -199,6 +207,9 @@ function createRecord(record, options = {}) {
199
207
  * Builds request options to update existing record for resources,
200
208
  * configured for the url, method and header expectations of most ActiveRecord APIs.
201
209
  *
210
+ * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
211
+ * when to use one.
212
+ *
202
213
  * **Basic Usage**
203
214
  *
204
215
  * ```ts
@@ -233,6 +244,7 @@ function createRecord(record, options = {}) {
233
244
  * const data = await store.request(options);
234
245
  * ```
235
246
  *
247
+ * @summary Builds a `PUT` (or `PATCH`) request that saves changes to an existing record, using ActiveRecord-style URLs.
236
248
  * @public
237
249
  * @param record
238
250
  * @param options
@@ -1 +1 @@
1
- {"version":3,"file":"active-record.js","names":["recordIdentifierFor","buildBaseURL","pluralize","underscore","copyForwardUrlOptions","isExisting","identifier","id","type","deleteRecord","record","options","test","Error","urlOptions","op","resourcePath","url","headers","Headers","append","method","data","records","createRecord","updateRecord","patch"],"sources":["../../../src/-private/active-record/find-record.ts","../../../src/-private/active-record/query.ts","../../../src/-private/active-record/save-record.ts"],"sourcesContent":["import type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { TypeFromInstance } from '@warp-drive/core/types/record';\nimport type {\n FindRecordOptions,\n FindRecordRequestOptions,\n RemotelyAccessibleIdentifier,\n} from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport { buildBaseURL, buildQueryParams, type FindRecordUrlOptions } from '../../index.ts';\nimport { pluralize, underscore } from '../../string.ts';\nimport { copyForwardUrlOptions, extractCacheOptions } from '../builder-utils.ts';\n\n/**\n * Builds request options to fetch a single resource by a known id or identifier\n * configured for the url and header expectations of most ActiveRecord APIs.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const data = await store.request(findRecord('person', '1'));\n * ```\n *\n * **With Options**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord('person', '1', { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **With an Identifier**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord({ type: 'person', id: '1' }, { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing and underscoring the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord('person', '1', { include: ['pets', 'friends'] }, { namespace: 'api/v2' });\n * const data = await store.request(options);\n * ```\n *\n * @public\n * @param identifier\n * @param options\n */\nexport function findRecord<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n identifier: RemotelyAccessibleIdentifier<TypeFromInstance<T>>,\n options?: FindRecordOptions\n): FindRecordRequestOptions<ReactiveDataDocument<T, M, E>, T>;\nexport function findRecord(\n identifier: RemotelyAccessibleIdentifier,\n options?: FindRecordOptions\n): FindRecordRequestOptions;\nexport function findRecord<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n type: TypeFromInstance<T>,\n id: string,\n options?: FindRecordOptions\n): FindRecordRequestOptions<ReactiveDataDocument<T, M, E>, T>;\nexport function findRecord(type: string, id: string, options?: FindRecordOptions): FindRecordRequestOptions;\nexport function findRecord(\n arg1: string | RemotelyAccessibleIdentifier,\n arg2: string | FindRecordOptions | undefined,\n arg3?: FindRecordOptions\n): FindRecordRequestOptions {\n const identifier: RemotelyAccessibleIdentifier = typeof arg1 === 'string' ? { type: arg1, id: arg2 as string } : arg1;\n const options = ((typeof arg1 === 'string' ? arg3 : arg2) || {}) as FindRecordOptions;\n const cacheOptions = extractCacheOptions(options);\n const urlOptions: FindRecordUrlOptions = {\n identifier,\n op: 'findRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url: options.include?.length\n ? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}`\n : url,\n method: 'GET',\n headers,\n cacheOptions,\n op: 'findRecord',\n records: [identifier],\n };\n}\n\n/** @deprecated use {@link ReactiveDataDocument} instead */\nexport type FindRecordResultDocument<\n T,\n M extends Meta | undefined = Meta | undefined,\n E extends object = object,\n> = ReactiveDataDocument<T, M, E>;\n","import type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { QueryParamsSource } from '@warp-drive/core/types/params';\nimport type { TypeFromInstance } from '@warp-drive/core/types/record';\nimport type { ConstrainedRequestOptions, QueryRequestOptions } from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport { buildBaseURL, buildQueryParams, type QueryUrlOptions } from '../../index.ts';\nimport { pluralize, underscore } from '../../string';\nimport { copyForwardUrlOptions, extractCacheOptions } from '../builder-utils.ts';\n\n/**\n * Builds request options to query for resources, usually by a primary\n * type, configured for the url and header expectations of most ActiveRecord APIs.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const data = await store.request(query('person'));\n * ```\n *\n * **With Query Params**\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const options = query('person', { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing and underscoring the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const options = query('person', { include: ['pets', 'friends'] }, { reload: true });\n * const data = await store.request(options);\n * ```\n *\n * @public\n * @param identifier\n * @param query\n * @param options\n */\nexport function query<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n type: TypeFromInstance<T>,\n query?: QueryParamsSource,\n options?: ConstrainedRequestOptions\n): QueryRequestOptions<ReactiveDataDocument<T[], M, E>>;\nexport function query(\n type: string,\n query?: QueryParamsSource,\n options?: ConstrainedRequestOptions\n): QueryRequestOptions;\nexport function query(\n type: string,\n // oxlint-disable-next-line no-shadow\n query: QueryParamsSource = {},\n options: ConstrainedRequestOptions = {}\n): QueryRequestOptions {\n const cacheOptions = extractCacheOptions(options);\n const urlOptions: QueryUrlOptions = {\n identifier: { type },\n op: 'query',\n resourcePath: pluralize(underscore(type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n const queryString = buildQueryParams(query, options.urlParamsSettings);\n\n return {\n url: queryString ? `${url}?${queryString}` : url,\n method: 'GET',\n headers,\n cacheOptions,\n op: 'query',\n };\n}\n","import { recordIdentifierFor } from '@warp-drive/core';\nimport { assert } from '@warp-drive/core/build-config/macros';\nimport type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { PersistedResourceKey, ResourceKey } from '@warp-drive/core/types/identifier';\nimport type { TypedRecordInstance } from '@warp-drive/core/types/record';\nimport type {\n ConstrainedRequestOptions,\n CreateRequestOptions,\n DeleteRequestOptions,\n UpdateRequestOptions,\n} from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport {\n buildBaseURL,\n type CreateRecordUrlOptions,\n type DeleteRecordUrlOptions,\n type UpdateRecordUrlOptions,\n} from '../../index.ts';\nimport { pluralize, underscore } from '../../string';\nimport { copyForwardUrlOptions } from '../builder-utils.ts';\n\nfunction isExisting(identifier: ResourceKey): identifier is PersistedResourceKey {\n return 'id' in identifier && identifier.id !== null && 'type' in identifier && identifier.type !== null;\n}\n\n/**\n * Builds request options to delete record for resources,\n * configured for the url, method and header expectations of ActiveRecord APIs.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { deleteRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n *\n * // mark record as deleted\n * store.deleteRecord(person);\n *\n * // persist deletion\n * const data = await store.request(deleteRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { deleteRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n *\n * // mark record as deleted\n * store.deleteRecord(person);\n *\n * // persist deletion\n * const options = deleteRecord(person, { namespace: 'api/v1' });\n * const data = await store.request(options);\n * ```\n *\n * @public\n * @param record\n * @param options\n */\nexport function deleteRecord<T>(record: T, options?: ConstrainedRequestOptions): DeleteRequestOptions<T>;\nexport function deleteRecord(record: unknown, options?: ConstrainedRequestOptions): DeleteRequestOptions;\nexport function deleteRecord(record: unknown, options: ConstrainedRequestOptions = {}): DeleteRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));\n\n const urlOptions: DeleteRecordUrlOptions = {\n identifier: identifier,\n op: 'deleteRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: 'DELETE',\n headers,\n op: 'deleteRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n\n/**\n * Builds request options to create new record for resources,\n * configured for the url, method and header expectations of most ActiveRecord APIs.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { createRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.createRecord('person', { name: 'Ted' });\n * const data = await store.request(createRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { createRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.createRecord('person', { name: 'Ted' });\n * const options = createRecord(person, { namespace: 'api/v1' });\n * const data = await store.request(options);\n * ```\n *\n * @public\n * @param record\n * @param options\n */\nexport function createRecord<T>(record: T, options?: ConstrainedRequestOptions): CreateRequestOptions<T>;\nexport function createRecord(record: unknown, options?: ConstrainedRequestOptions): CreateRequestOptions;\nexport function createRecord(record: unknown, options: ConstrainedRequestOptions = {}): CreateRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n\n const urlOptions: CreateRecordUrlOptions = {\n identifier: identifier,\n op: 'createRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: 'POST',\n headers,\n op: 'createRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n\n/**\n * Builds request options to update existing record for resources,\n * configured for the url, method and header expectations of most ActiveRecord APIs.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { updateRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n * person.name = 'Chris';\n * const data = await store.request(updateRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `patch` - Allows caller to specify whether to use a PATCH request instead of a PUT request, defaults to `false`.\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { updateRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n * person.name = 'Chris';\n * const options = updateRecord(person, { patch: true });\n * const data = await store.request(options);\n * ```\n *\n * @public\n * @param record\n * @param options\n */\nexport function updateRecord<\n T extends TypedRecordInstance,\n RT extends TypedRecordInstance = T,\n M extends Meta | undefined = Meta | undefined,\n E extends object = object,\n>(\n record: T,\n options?: ConstrainedRequestOptions & { patch?: boolean }\n): UpdateRequestOptions<ReactiveDataDocument<RT, M, E>, T>;\nexport function updateRecord(\n record: unknown,\n options?: ConstrainedRequestOptions & { patch?: boolean }\n): UpdateRequestOptions;\nexport function updateRecord(\n record: unknown,\n options: ConstrainedRequestOptions & { patch?: boolean } = {}\n): UpdateRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n assert(`Cannot update a record that does not have an associated type and id.`, isExisting(identifier));\n\n const urlOptions: UpdateRecordUrlOptions = {\n identifier: identifier,\n op: 'updateRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: options.patch ? 'PATCH' : 'PUT',\n headers,\n op: 'updateRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"active-record.js","names":["recordIdentifierFor","buildBaseURL","pluralize","underscore","copyForwardUrlOptions","isExisting","identifier","id","type","deleteRecord","record","options","test","Error","urlOptions","op","resourcePath","url","headers","Headers","append","method","data","records","createRecord","updateRecord","patch"],"sources":["../../../src/-private/active-record/find-record.ts","../../../src/-private/active-record/query.ts","../../../src/-private/active-record/save-record.ts"],"sourcesContent":["import type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { TypeFromInstance } from '@warp-drive/core/types/record';\nimport type {\n FindRecordOptions,\n FindRecordRequestOptions,\n RemotelyAccessibleIdentifier,\n} from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport { buildBaseURL, buildQueryParams, type FindRecordUrlOptions } from '../../index.ts';\nimport { pluralize, underscore } from '../../string.ts';\nimport { copyForwardUrlOptions, extractCacheOptions } from '../builder-utils.ts';\n\n/**\n * Builds request options to fetch a single resource by a known id or identifier\n * configured for the url and header expectations of most ActiveRecord APIs.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and\n * when to use one.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const data = await store.request(findRecord('person', '1'));\n * ```\n *\n * **With Options**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord('person', '1', { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **With an Identifier**\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord({ type: 'person', id: '1' }, { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing and underscoring the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { findRecord } from '@warp-drive/utilities/active-record';\n *\n * const options = findRecord('person', '1', { include: ['pets', 'friends'] }, { namespace: 'api/v2' });\n * const data = await store.request(options);\n * ```\n *\n * @summary Builds a `GET` request for one resource by type and id, using ActiveRecord-style pluralized, underscored\n * URLs.\n * @public\n * @param identifier\n * @param options\n */\nexport function findRecord<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n identifier: RemotelyAccessibleIdentifier<TypeFromInstance<T>>,\n options?: FindRecordOptions\n): FindRecordRequestOptions<ReactiveDataDocument<T, M, E>, T>;\nexport function findRecord(\n identifier: RemotelyAccessibleIdentifier,\n options?: FindRecordOptions\n): FindRecordRequestOptions;\nexport function findRecord<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n type: TypeFromInstance<T>,\n id: string,\n options?: FindRecordOptions\n): FindRecordRequestOptions<ReactiveDataDocument<T, M, E>, T>;\nexport function findRecord(type: string, id: string, options?: FindRecordOptions): FindRecordRequestOptions;\nexport function findRecord(\n arg1: string | RemotelyAccessibleIdentifier,\n arg2: string | FindRecordOptions | undefined,\n arg3?: FindRecordOptions\n): FindRecordRequestOptions {\n const identifier: RemotelyAccessibleIdentifier = typeof arg1 === 'string' ? { type: arg1, id: arg2 as string } : arg1;\n const options = ((typeof arg1 === 'string' ? arg3 : arg2) || {}) as FindRecordOptions;\n const cacheOptions = extractCacheOptions(options);\n const urlOptions: FindRecordUrlOptions = {\n identifier,\n op: 'findRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url: options.include?.length\n ? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}`\n : url,\n method: 'GET',\n headers,\n cacheOptions,\n op: 'findRecord',\n records: [identifier],\n };\n}\n\n/** @deprecated use {@link ReactiveDataDocument} instead */\nexport type FindRecordResultDocument<\n T,\n M extends Meta | undefined = Meta | undefined,\n E extends object = object,\n> = ReactiveDataDocument<T, M, E>;\n","import type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { QueryParamsSource } from '@warp-drive/core/types/params';\nimport type { TypeFromInstance } from '@warp-drive/core/types/record';\nimport type { ConstrainedRequestOptions, QueryRequestOptions } from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport { buildBaseURL, buildQueryParams, type QueryUrlOptions } from '../../index.ts';\nimport { pluralize, underscore } from '../../string';\nimport { copyForwardUrlOptions, extractCacheOptions } from '../builder-utils.ts';\n\n/**\n * Builds request options to query for resources, usually by a primary\n * type, configured for the url and header expectations of most ActiveRecord APIs.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and\n * when to use one.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const data = await store.request(query('person'));\n * ```\n *\n * **With Query Params**\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const options = query('person', { include: ['pets', 'friends'] });\n * const data = await store.request(options);\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing and underscoring the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { query } from '@warp-drive/utilities/active-record';\n *\n * const options = query('person', { include: ['pets', 'friends'] }, { reload: true });\n * const data = await store.request(options);\n * ```\n *\n * @summary Builds a `GET` request that queries a resource type with query params, using ActiveRecord-style URLs.\n * @public\n * @param identifier\n * @param query\n * @param options\n */\nexport function query<T, M extends Meta | undefined = Meta | undefined, E extends object = object>(\n type: TypeFromInstance<T>,\n query?: QueryParamsSource,\n options?: ConstrainedRequestOptions\n): QueryRequestOptions<ReactiveDataDocument<T[], M, E>>;\nexport function query(\n type: string,\n query?: QueryParamsSource,\n options?: ConstrainedRequestOptions\n): QueryRequestOptions;\nexport function query(\n type: string,\n // oxlint-disable-next-line no-shadow\n query: QueryParamsSource = {},\n options: ConstrainedRequestOptions = {}\n): QueryRequestOptions {\n const cacheOptions = extractCacheOptions(options);\n const urlOptions: QueryUrlOptions = {\n identifier: { type },\n op: 'query',\n resourcePath: pluralize(underscore(type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n const queryString = buildQueryParams(query, options.urlParamsSettings);\n\n return {\n url: queryString ? `${url}?${queryString}` : url,\n method: 'GET',\n headers,\n cacheOptions,\n op: 'query',\n };\n}\n","import { recordIdentifierFor } from '@warp-drive/core';\nimport { assert } from '@warp-drive/core/build-config/macros';\nimport type { ReactiveDataDocument } from '@warp-drive/core/reactive';\nimport type { PersistedResourceKey, ResourceKey } from '@warp-drive/core/types/identifier';\nimport type { TypedRecordInstance } from '@warp-drive/core/types/record';\nimport type {\n ConstrainedRequestOptions,\n CreateRequestOptions,\n DeleteRequestOptions,\n UpdateRequestOptions,\n} from '@warp-drive/core/types/request';\nimport type { Meta } from '@warp-drive/core/types/spec/json-api-raw';\n\nimport {\n buildBaseURL,\n type CreateRecordUrlOptions,\n type DeleteRecordUrlOptions,\n type UpdateRecordUrlOptions,\n} from '../../index.ts';\nimport { pluralize, underscore } from '../../string';\nimport { copyForwardUrlOptions } from '../builder-utils.ts';\n\nfunction isExisting(identifier: ResourceKey): identifier is PersistedResourceKey {\n return 'id' in identifier && identifier.id !== null && 'type' in identifier && identifier.type !== null;\n}\n\n/**\n * Builds request options to delete record for resources,\n * configured for the url, method and header expectations of ActiveRecord APIs.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and\n * when to use one.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { deleteRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n *\n * // mark record as deleted\n * store.deleteRecord(person);\n *\n * // persist deletion\n * const data = await store.request(deleteRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { deleteRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n *\n * // mark record as deleted\n * store.deleteRecord(person);\n *\n * // persist deletion\n * const options = deleteRecord(person, { namespace: 'api/v1' });\n * const data = await store.request(options);\n * ```\n *\n * @summary Builds a `DELETE` request that persists deletion of an existing record, using ActiveRecord-style URLs.\n * @public\n * @param record\n * @param options\n */\nexport function deleteRecord<T>(record: T, options?: ConstrainedRequestOptions): DeleteRequestOptions<T>;\nexport function deleteRecord(record: unknown, options?: ConstrainedRequestOptions): DeleteRequestOptions;\nexport function deleteRecord(record: unknown, options: ConstrainedRequestOptions = {}): DeleteRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n assert(`Cannot delete a record that does not have an associated type and id.`, isExisting(identifier));\n\n const urlOptions: DeleteRecordUrlOptions = {\n identifier: identifier,\n op: 'deleteRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: 'DELETE',\n headers,\n op: 'deleteRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n\n/**\n * Builds request options to create new record for resources,\n * configured for the url, method and header expectations of most ActiveRecord APIs.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and\n * when to use one.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { createRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.createRecord('person', { name: 'Ted' });\n * const data = await store.request(createRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { createRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.createRecord('person', { name: 'Ted' });\n * const options = createRecord(person, { namespace: 'api/v1' });\n * const data = await store.request(options);\n * ```\n *\n * @summary Builds a `POST` request that saves a newly created record, using ActiveRecord-style URLs.\n * @public\n * @param record\n * @param options\n */\nexport function createRecord<T>(record: T, options?: ConstrainedRequestOptions): CreateRequestOptions<T>;\nexport function createRecord(record: unknown, options?: ConstrainedRequestOptions): CreateRequestOptions;\nexport function createRecord(record: unknown, options: ConstrainedRequestOptions = {}): CreateRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n\n const urlOptions: CreateRecordUrlOptions = {\n identifier: identifier,\n op: 'createRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: 'POST',\n headers,\n op: 'createRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n\n/**\n * Builds request options to update existing record for resources,\n * configured for the url, method and header expectations of most ActiveRecord APIs.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and\n * when to use one.\n *\n * **Basic Usage**\n *\n * ```ts\n * import { updateRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n * person.name = 'Chris';\n * const data = await store.request(updateRecord(person));\n * ```\n *\n * **Supplying Options to Modify the Request Behavior**\n *\n * The following options are supported:\n *\n * - `patch` - Allows caller to specify whether to use a PATCH request instead of a PUT request, defaults to `false`.\n * - `host` - The host to use for the request, defaults to the `host` configured with `setBuildURLConfig`.\n * - `namespace` - The namespace to use for the request, defaults to the `namespace` configured with `setBuildURLConfig`.\n * - `resourcePath` - The resource path to use for the request, defaults to pluralizing the supplied type\n * - `reload` - Whether to forcibly reload the request if it is already in the store, not supplying this\n * option will delegate to the store's CachePolicy, defaulting to `false` if none is configured.\n * - `backgroundReload` - Whether to reload the request if it is already in the store, but to also resolve the\n * promise with the cached value, not supplying this option will delegate to the store's CachePolicy,\n * defaulting to `false` if none is configured.\n * - `urlParamsSetting` - an object containing options for how to serialize the query params (see `buildQueryParams`)\n *\n * ```ts\n * import { updateRecord } from '@warp-drive/utilities/active-record';\n *\n * const person = store.peekRecord('person', '1');\n * person.name = 'Chris';\n * const options = updateRecord(person, { patch: true });\n * const data = await store.request(options);\n * ```\n *\n * @summary Builds a `PUT` (or `PATCH`) request that saves changes to an existing record, using ActiveRecord-style URLs.\n * @public\n * @param record\n * @param options\n */\nexport function updateRecord<\n T extends TypedRecordInstance,\n RT extends TypedRecordInstance = T,\n M extends Meta | undefined = Meta | undefined,\n E extends object = object,\n>(\n record: T,\n options?: ConstrainedRequestOptions & { patch?: boolean }\n): UpdateRequestOptions<ReactiveDataDocument<RT, M, E>, T>;\nexport function updateRecord(\n record: unknown,\n options?: ConstrainedRequestOptions & { patch?: boolean }\n): UpdateRequestOptions;\nexport function updateRecord(\n record: unknown,\n options: ConstrainedRequestOptions & { patch?: boolean } = {}\n): UpdateRequestOptions {\n const identifier = recordIdentifierFor(record);\n assert(`Expected to be given a record instance`, identifier);\n assert(`Cannot update a record that does not have an associated type and id.`, isExisting(identifier));\n\n const urlOptions: UpdateRecordUrlOptions = {\n identifier: identifier,\n op: 'updateRecord',\n resourcePath: pluralize(underscore(identifier.type)),\n };\n\n copyForwardUrlOptions(urlOptions, options);\n\n const url = buildBaseURL(urlOptions);\n const headers = new Headers();\n headers.append('Accept', 'application/json;charset=utf-8');\n\n return {\n url,\n method: options.patch ? 'PATCH' : 'PUT',\n headers,\n op: 'updateRecord',\n data: {\n record: identifier,\n },\n records: [identifier],\n };\n}\n"],"mappings":"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"}
@@ -2,11 +2,17 @@ import { Type } from "@warp-drive/core/types/symbols";
2
2
 
3
3
  //#region src/derivations.ts
4
4
  /**
5
+ * @module
6
+ * @summary The `concat` derivation, which joins a record's listed fields into one string with an optional separator.
7
+ */
8
+ /**
5
9
  * A derivation for use by {@link ReactiveResource} that joins the given fields
6
10
  * with the optional separator (or '' if no separator is provided).
7
11
  *
8
12
  * Generally you should not need to import and use this function directly.
9
13
  *
14
+ * @summary Derivation registered as `concat` that joins a record's listed `fields` into one string with an optional
15
+ * `separator`.
10
16
  * @example
11
17
  * {
12
18
  * name: 'fullName',
@@ -1 +1 @@
1
- {"version":3,"file":"derivations.js","names":["Type","concat","record","options","_prop","Error","opts","fields","map","field","join","separator"],"sources":["../../../src/derivations.ts"],"sourcesContent":["import type { ReactiveResource } from '@warp-drive/core/reactive';\nimport { Type } from '@warp-drive/core/types/symbols';\n\ninterface ConcatDerivation {\n (\n record: ReactiveResource & { [key: string]: unknown },\n options: Record<string, unknown> | null,\n _prop: string\n ): string;\n [Type]: 'concat';\n}\n\n/**\n * A derivation for use by {@link ReactiveResource} that joins the given fields\n * with the optional separator (or '' if no separator is provided).\n *\n * Generally you should not need to import and use this function directly.\n *\n * @example\n * {\n * name: 'fullName',\n * kind: 'derived',\n * type: 'concat',\n * options: {\n * fields: ['firstName', 'lastName'],\n * separator: ' ',\n * },\n * }\n */\nexport const concat: ConcatDerivation = (\n record: ReactiveResource & { [key: string]: unknown },\n options: Record<string, unknown> | null,\n _prop: string\n) => {\n if (!options) {\n throw new Error(`options is required`);\n }\n // SAFETY: we cast internally to a more specific type, for our own use\n // SAFETY: but provide the more general signature to the schema service\n const opts = options as { fields: string[]; separator?: string };\n return opts.fields.map((field) => record[field]).join(opts.separator ?? '');\n};\nconcat[Type] = 'concat';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA6BA,MAAaC,UACXC,QACAC,SACAC,UACG;CACH,IAAI,CAACD,SACH,MAAM,IAAIE,MAAM,qBAAqB;CAIvC,MAAMC,OAAOH;CACb,OAAOG,KAAKC,OAAOC,KAAKC,UAAUP,OAAOO,MAAM,CAAC,CAACC,KAAKJ,KAAKK,aAAa,EAAE;AAC5E;AACAV,OAAOD,QAAQ"}
1
+ {"version":3,"file":"derivations.js","names":["Type","concat","record","options","_prop","Error","opts","fields","map","field","join","separator"],"sources":["../../../src/derivations.ts"],"sourcesContent":["/**\n * @module\n * @summary The `concat` derivation, which joins a record's listed fields into one string with an optional separator.\n */\n\nimport type { ReactiveResource } from '@warp-drive/core/reactive';\nimport { Type } from '@warp-drive/core/types/symbols';\n\ninterface ConcatDerivation {\n (\n record: ReactiveResource & { [key: string]: unknown },\n options: Record<string, unknown> | null,\n _prop: string\n ): string;\n [Type]: 'concat';\n}\n\n/**\n * A derivation for use by {@link ReactiveResource} that joins the given fields\n * with the optional separator (or '' if no separator is provided).\n *\n * Generally you should not need to import and use this function directly.\n *\n * @summary Derivation registered as `concat` that joins a record's listed `fields` into one string with an optional\n * `separator`.\n * @example\n * {\n * name: 'fullName',\n * kind: 'derived',\n * type: 'concat',\n * options: {\n * fields: ['firstName', 'lastName'],\n * separator: ' ',\n * },\n * }\n */\nexport const concat: ConcatDerivation = (\n record: ReactiveResource & { [key: string]: unknown },\n options: Record<string, unknown> | null,\n _prop: string\n) => {\n if (!options) {\n throw new Error(`options is required`);\n }\n // SAFETY: we cast internally to a more specific type, for our own use\n // SAFETY: but provide the more general signature to the schema service\n const opts = options as { fields: string[]; separator?: string };\n return opts.fields.map((field) => record[field]).join(opts.separator ?? '');\n};\nconcat[Type] = 'concat';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,MAAaC,UACXC,QACAC,SACAC,UACG;CACH,IAAI,CAACD,SACH,MAAM,IAAIE,MAAM,qBAAqB;CAIvC,MAAMC,OAAOH;CACb,OAAOG,KAAKC,OAAOC,KAAKC,UAAUP,OAAOO,MAAM,CAAC,CAACC,KAAKJ,KAAKK,aAAa,EAAE;AAC5E;AACAV,OAAOD,QAAQ"}
@@ -6,6 +6,8 @@ function isCompressibleMethod(method) {
6
6
  * Whether the browser supports `ReadableStream` as a request body
7
7
  * in a `POST` request.
8
8
  *
9
+ * @summary Whether the current browser can send a `ReadableStream` as a `fetch` request body, detected once at
10
+ * module load.
9
11
  * @group Constants
10
12
  */
11
13
  const SupportsRequestStreams = (() => {
@@ -56,6 +58,8 @@ const TypedArray = Object.getPrototypeOf(Uint8Array);
56
58
  * }
57
59
  * ```
58
60
  *
61
+ * @summary Request handler that compresses large `POST`, `PUT`, `PATCH`, and `DELETE` bodies with `CompressionStream`,
62
+ * optionally streaming them.
59
63
  * @group Handlers
60
64
  * @public
61
65
  * @since 5.5.0
@@ -125,6 +129,8 @@ function encodingForFormat(format) {
125
129
  */
126
130
  /**
127
131
  *
132
+ * @summary Request handler that wraps another handler and runs it only when a check function returns true for the
133
+ * request.
128
134
  * @group Handlers
129
135
  * @public
130
136
  */
@@ -190,6 +196,7 @@ var Gate = class {
190
196
  * Errors are not processed by this handler, so if the request fails and the error response
191
197
  * is not in {json:api} format additional processing may be needed.
192
198
  *
199
+ * @summary Request handler that wraps the whole response body in `meta` for requests with `options.isMetaRequest` set.
193
200
  * @group Handlers
194
201
  */
195
202
  const MetaDocHandler = { request(context, next) {
@@ -222,12 +229,15 @@ function getTabId() {
222
229
  * useful for observability/tracing and deduping
223
230
  * across multiple tabs.
224
231
  *
232
+ * @summary Random UUID identifying the current browser tab, kept in `sessionStorage` across reloads, for request
233
+ * tracing.
225
234
  * @group Constants
226
235
  */
227
236
  const TAB_ID = getTabId();
228
237
  /**
229
238
  * The epoch seconds at which the tab id was generated
230
239
  *
240
+ * @summary Epoch seconds recorded when this module loaded, paired with `TAB_ID` in trace headers.
231
241
  * @group Constants
232
242
  */
233
243
  const TAB_ASSIGNED = Math.floor(Date.now() / 1e3);
@@ -240,6 +250,8 @@ const TAB_ASSIGNED = Math.floor(Date.now() / 1e3);
240
250
  *
241
251
  * Follows the template: `Root=1-${now}-${uuidv4};TabId=1-${epochSeconds}-${tab-uuid}`
242
252
  *
253
+ * @summary Sets an `X-Amzn-Trace-Id` header carrying a per-request id and the tab id, so requests can be traced to
254
+ * their browser tab.
243
255
  * @group Utility Functions
244
256
  */
245
257
  function addTraceHeader(headers) {
@@ -251,6 +263,7 @@ function addTraceHeader(headers) {
251
263
  * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html
252
264
  * As of 2024-12-05 the maximum URL length is 8192 bytes.
253
265
  *
266
+ * @summary The 8192-byte URL length limit, taken from AWS CloudFront, that `assertInvalidUrlLength` checks against.
254
267
  * @group Constants
255
268
  */
256
269
  const MAX_URL_LENGTH = 8192;
@@ -259,6 +272,8 @@ const MAX_URL_LENGTH = 8192;
259
272
  *
260
273
  * See also {@link MAX_URL_LENGTH}
261
274
  *
275
+ * @summary Dev-mode assertion that throws when a URL is longer than `MAX_URL_LENGTH`, suggesting a `POST` or `QUERY`
276
+ * request instead.
262
277
  * @group Utility Functions
263
278
  */
264
279
  function assertInvalidUrlLength(url) {
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.js","names":["isCompressibleMethod","method","SupportsRequestStreams","duplexAccessed","hasContentType","Request","body","ReadableStream","duplex","headers","has","DEFAULT_CONSTRAINTS","Blob","ArrayBuffer","TypedArray","DataView","String","Object","getPrototypeOf","Uint8Array","AutoCompress","constructor","options","opts","format","constraints","assign","allowStreaming","forceStreaming","request","next","shouldCompress","compress","canCompress","length","size","byteLength","response","Response","stream","pipeThrough","CompressionStream","Headers","set","encodingForFormat","req","resp","blob","then","type","test","Error","Gate","constructor","handler","checkFn","request","context","next","FastBoot","globalThis","addEventListener","sessionStorage","setItem","getTabId","crypto","randomUUID","tabId","getItem","tabClosed","newTabId","TAB_ID","TAB_ASSIGNED","Math","floor","Date","now","addTraceHeader","headers","set","MAX_URL_LENGTH","assertInvalidUrlLength","url","test","Error","length"],"sources":["../../../src/-private/handlers/auto-compress.ts","../../../src/-private/handlers/gated.ts","../../../src/-private/handlers/meta-doc.ts","../../../src/-private/handlers/utils.ts"],"sourcesContent":["import { assert } from '@warp-drive/core/build-config/macros';\nimport type { Future, Handler, NextFn } from '@warp-drive/core/request';\nimport type { HTTPMethod, RequestContext } from '@warp-drive/core/types/request';\n\nfunction isCompressibleMethod(method?: HTTPMethod): boolean {\n return method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';\n}\n\n/**\n * Whether the browser supports `ReadableStream` as a request body\n * in a `POST` request.\n *\n * @group Constants\n */\nexport const SupportsRequestStreams: boolean = (() => {\n let duplexAccessed = false;\n\n const hasContentType = new Request('', {\n body: new ReadableStream(),\n method: 'POST',\n // @ts-expect-error untyped\n get duplex() {\n duplexAccessed = true;\n return 'half';\n },\n }).headers.has('Content-Type');\n\n return duplexAccessed && !hasContentType;\n})();\n\ninterface Constraints {\n /**\n * The minimum size at which to compress blobs\n *\n * @default 1000\n */\n Blob?: number;\n /**\n * The minimum size at which to compress array buffers\n *\n * @default 1000\n */\n ArrayBuffer?: number;\n /**\n * The minimum size at which to compress typed arrays\n *\n * @default 1000\n */\n TypedArray?: number;\n /**\n * The minimum size at which to compress data views\n *\n * @default 1000\n */\n DataView?: number;\n /**\n * The minimum size at which to compress strings\n *\n * @default 1000\n */\n String?: number;\n}\n\n/**\n * Options for configuring the AutoCompress handler.\n *\n */\ninterface CompressionOptions {\n /**\n * The compression format to use. Must be a valid\n * compression format supported by [CompressionStream](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)\n *\n * The default is `gzip`.\n *\n */\n format?: CompressionFormat;\n\n /**\n * Some browsers support `ReadableStream` as a request body. This option\n * enables passing the compression stream as the request body instead of\n * the final compressed body when the browser supports doing so.\n *\n * This comes with several caveats:\n *\n * - the request will be put into `duplex: 'half'` mode. This should be\n * transparent to you, but it is worth noting.\n * - the request mode cannot be `no-cors` as requests with a `ReadableStream`\n * have no content length and thus are a new form of request that triggers\n * cors requirements and a preflight request.\n * - http/1.x is not supported.\n *\n * For additional reading about the restrictions of using `ReadableStream`\n * as a request body, see the [Chromium Documentation](https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests#restrictions)\n *\n * Streaming can be enabled per-request in browsers which support it by\n * setting `request.options.allowStreaming` to `true`.\n *\n * Streaming can be forced even when the browser does not support it by setting\n * `request.options.forceStreaming` to `true`. This is useful if later handlers\n * in the chain can handle the request body as a stream.\n *\n * @default false\n */\n allowStreaming?: boolean;\n\n /**\n * If `true`, the request will be forced into streaming mode even\n * if the browser does not support it. This is useful if later handlers\n * in the chain can handle the request body as a stream.\n *\n * @default false\n */\n forceStreaming?: boolean;\n\n /**\n * The constraints for the request body. This is used to determine\n * whether to compress the request body or not.\n *\n * The defaults are:\n *\n * ```ts\n * {\n * Blob: 1000, // blob.size\n * ArrayBuffer: 1000, // buffer.byteLength\n * TypedArray: 1000, // array.byteLength\n * DataView: 1000, // view.byteLength\n * String: 1000, // string.length\n * }\n * ```\n *\n * The following body types are never compressed unless explicitly\n * configured by the request:\n * - `FormData`\n * - `URLSearchParams`\n * - `ReadableStream`\n *\n * A request.options.compress value of `false` will disable\n * compression for a request body of any type. While a value of\n * `true` will enable compression for the request.\n *\n * An undefined value will use the default, a value of `0` will\n * enable compression for all values, and a value of `-1` will\n * disable compression.\n *\n */\n constraints?: Constraints;\n}\n\nconst DEFAULT_CONSTRAINTS = {\n Blob: 1000,\n ArrayBuffer: 1000,\n TypedArray: 1000,\n DataView: 1000,\n String: 1000,\n};\nconst TypedArray = Object.getPrototypeOf(Uint8Array) as typeof Uint8Array;\n\n/**\n * A request handler that automatically compresses the request body\n * if the request body is a string, array buffer, blob, or form data.\n *\n * This uses the [CompressionStream API](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)\n *\n * The compression format as well as the kinds of data to compress can be\n * configured using the `format` and `constraints` options.\n *\n * ```diff\n * +import { AutoCompress } from '@ember-data/request-utils/handlers';\n * import Fetch from '@ember-data/request/fetch';\n * import RequestManager from '@ember-data/request';\n * import Store from '@ember-data/store';\n *\n * class AppStore extends Store {\n * requestManager = new RequestManager()\n * .use([\n * + new AutoCompress(),\n * Fetch\n * ]);\n * }\n * ```\n *\n * @group Handlers\n * @public\n * @since 5.5.0\n */\nexport class AutoCompress implements Handler {\n /**\n * The resolved options this handler was configured with, with all\n * defaults (including `constraints` defaults) applied.\n */\n declare options: Required<CompressionOptions> & {\n /**\n * The resolved size constraints used to decide whether to compress a given request body.\n */\n constraints: Required<Constraints>;\n };\n\n constructor(options: CompressionOptions = {}) {\n const opts = {\n format: options.format ?? 'gzip',\n constraints: Object.assign({}, DEFAULT_CONSTRAINTS, options.constraints),\n allowStreaming: options.allowStreaming ?? false,\n forceStreaming: options.forceStreaming ?? false,\n };\n this.options = opts;\n }\n\n request<T>({ request }: RequestContext, next: NextFn<T>): Promise<T> | Future<T> {\n const { constraints } = this.options;\n const { body } = request;\n\n const shouldCompress =\n !!body &&\n isCompressibleMethod(request.method) &&\n request.options?.compress !== false &&\n // prettier-ignore\n (request.options?.compress ? true\n : typeof body === 'string' || body instanceof String ? canCompress('String', constraints, body.length)\n : body instanceof Blob ? canCompress('Blob', constraints, body.size)\n : body instanceof ArrayBuffer ? canCompress('ArrayBuffer', constraints, body.byteLength)\n : body instanceof DataView ? canCompress('DataView', constraints, body.byteLength)\n : body instanceof TypedArray ? canCompress('TypedArray', constraints, body.byteLength)\n : false);\n\n if (!shouldCompress) return next(request);\n\n // A convenient way to convert all of the supported body types to a readable\n // stream is to use a `Response` object body\n const response = new Response(request.body);\n const stream = response.body?.pipeThrough(new CompressionStream(this.options.format));\n const headers = new Headers(request.headers);\n headers.set('Content-Encoding', encodingForFormat(this.options.format));\n\n //\n // For browsers that support it, `fetch` can receive a `ReadableStream` as\n // the body, so all we need to do is to create a new `ReadableStream` and\n // compress it on the fly\n //\n const forceStreaming = request.options?.forceStreaming ?? this.options.forceStreaming;\n const allowStreaming = request.options?.allowStreaming ?? this.options.allowStreaming;\n if (forceStreaming || (SupportsRequestStreams && allowStreaming)) {\n const req = Object.assign({}, request, {\n body: stream,\n headers,\n });\n if (SupportsRequestStreams) {\n // @ts-expect-error untyped\n req.duplex = 'half';\n }\n\n return next(req);\n\n //\n // For non-chromium browsers, we have to \"pull\" the stream to get the final\n // bytes and supply the final byte array as the new request body.\n //\n }\n\n // we need to pull the stream to get the final bytes\n const resp = new Response(stream);\n return resp.blob().then((blob) => {\n const req = Object.assign({}, request, {\n body: blob,\n headers,\n });\n return next(req);\n }) as Promise<T>;\n }\n}\n\nfunction canCompress(type: keyof Constraints, constraints: Required<Constraints>, size: number): boolean {\n // if we have a value of 0, we can compress anything\n if (constraints[type] === 0) return true;\n if (constraints[type] === -1) return false;\n return size >= constraints[type];\n}\n\nfunction encodingForFormat(format: CompressionFormat): string {\n switch (format) {\n case 'gzip':\n case 'deflate':\n case 'deflate-raw':\n return format;\n default:\n assert(`Unsupported compression format: ${format as unknown as string}`);\n // @ts-expect-error - unreachable code is reachable in production\n return format;\n }\n}\n","import type { Future, Handler, NextFn } from '@warp-drive/core/request';\nimport type { RequestContext, StructuredDataDocument } from '@warp-drive/core/types/request';\n\n/**\n * If CheckFn returns true, the wrapped handler will be used.\n * If CheckFn returns false, the wrapped handler will be skipped.\n */\ntype CheckFn = (context: RequestContext) => boolean;\n\n/**\n *\n * @group Handlers\n * @public\n */\nexport class Gate implements Handler {\n /**\n * The wrapped handler to invoke when {@link Gate.checkFn | checkFn} returns `true`.\n */\n declare handler: Handler;\n /**\n * The predicate used to decide whether {@link Gate.handler | handler} should run for a given request.\n */\n declare checkFn: CheckFn;\n\n constructor(handler: Handler, checkFn: CheckFn) {\n this.handler = handler;\n this.checkFn = checkFn;\n }\n\n request<T = unknown>(context: RequestContext, next: NextFn<T>): Promise<T | StructuredDataDocument<T>> | Future<T> {\n if (this.checkFn(context)) {\n return this.handler.request(context, next);\n }\n return next(context.request);\n }\n}\n","import type { StoreRequestContext } from '@warp-drive/core';\nimport type { Handler, NextFn } from '@warp-drive/core/request';\nimport type { StructuredDataDocument } from '@warp-drive/core/types/request';\n\n/**\n * MetaDocHandler processes requests that are marked as meta requests.\n *\n * It treats the response body as \"entirely meta\" transforming\n *\n * ```ts\n * {\n * some: \"key\",\n * another: \"thing\"\n * }\n * ```\n *\n * into\n *\n * ```ts\n * {\n * \t meta: {\n * some: \"key\",\n * another: \"thing\"\n * }\n * }\n * ```\n *\n * To activate this handler, a request should specify\n *\n * ```ts\n * options.isMetaRequest = true\n * ```\n *\n * For instance\n *\n * ```ts\n * store.request({\n * url: '/example',\n * options: {\n * isMetaRequest: true\n * }\n * });\n * ```\n *\n * Errors are not processed by this handler, so if the request fails and the error response\n * is not in {json:api} format additional processing may be needed.\n *\n * @group Handlers\n */\nexport const MetaDocHandler: Handler = {\n request<T>(context: StoreRequestContext, next: NextFn<T>): Promise<T | StructuredDataDocument<T> | Awaited<T>> {\n if (!context.request.options?.isMetaRequest) {\n return next(context.request);\n }\n\n return next(context.request).then((response) => {\n return processResponse<T>(response);\n });\n },\n};\n\nfunction processResponse<T>(response: StructuredDataDocument<T>): T {\n return {\n meta: response.content,\n } as T;\n}\n","import { assert } from '@warp-drive/core/build-config/macros';\n\nif (typeof FastBoot === 'undefined') {\n globalThis.addEventListener('beforeunload', function () {\n sessionStorage.setItem('tab-closed', 'true');\n });\n}\n\nfunction getTabId() {\n if (typeof sessionStorage === 'undefined') {\n return crypto.randomUUID();\n }\n\n const tabId = sessionStorage.getItem('tab-id');\n if (tabId) {\n const tabClosed = sessionStorage.getItem('tab-closed');\n if (tabClosed === 'true') {\n return tabId;\n }\n\n // fall through to generate a new tab id\n }\n\n const newTabId = crypto.randomUUID();\n sessionStorage.setItem('tab-id', newTabId);\n return newTabId;\n}\n\n/**\n * A unique identifier for the current browser tab\n * useful for observability/tracing and deduping\n * across multiple tabs.\n *\n * @group Constants\n */\nexport const TAB_ID: string = getTabId();\n/**\n * The epoch seconds at which the tab id was generated\n *\n * @group Constants\n */\nexport const TAB_ASSIGNED: number = Math.floor(Date.now() / 1000);\n\n/**\n * Adds the `X-Amzn-Trace-Id` header to support observability\n * tooling around request routing.\n *\n * This makes use of the {@link TAB_ID} and {@link TAB_ASSIGNED}\n * to enable tracking the browser tab of origin across multiple requests.\n *\n * Follows the template: `Root=1-${now}-${uuidv4};TabId=1-${epochSeconds}-${tab-uuid}`\n *\n * @group Utility Functions\n */\nexport function addTraceHeader(headers: Headers): Headers {\n const now = Math.floor(Date.now() / 1000);\n headers.set('X-Amzn-Trace-Id', `Root=1-${now}-${crypto.randomUUID()};TabId=1-${TAB_ASSIGNED}-${TAB_ID}`);\n\n return headers;\n}\n\n/**\n * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html\n * As of 2024-12-05 the maximum URL length is 8192 bytes.\n *\n * @group Constants\n */\nexport const MAX_URL_LENGTH = 8192;\n\n/**\n * This assertion takes a URL and throws an error if the URL is longer than the maximum URL length.\n *\n * See also {@link MAX_URL_LENGTH}\n *\n * @group Utility Functions\n */\nexport function assertInvalidUrlLength(url: string | undefined): void {\n assert(\n `URL length ${url?.length} exceeds the maximum URL length of ${MAX_URL_LENGTH} bytes.\\n\\nConsider converting this request query a \\`/query\\` endpoint instead of a GET, or upgrade the current endpoint to be able to receive a POST request directly (ideally specifying the header HTTP-Method-Override: QUERY)\\n\\nThe Invalid URL is:\\n\\n${url}`,\n !url || url.length <= MAX_URL_LENGTH\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"handlers.js","names":["isCompressibleMethod","method","SupportsRequestStreams","duplexAccessed","hasContentType","Request","body","ReadableStream","duplex","headers","has","DEFAULT_CONSTRAINTS","Blob","ArrayBuffer","TypedArray","DataView","String","Object","getPrototypeOf","Uint8Array","AutoCompress","constructor","options","opts","format","constraints","assign","allowStreaming","forceStreaming","request","next","shouldCompress","compress","canCompress","length","size","byteLength","response","Response","stream","pipeThrough","CompressionStream","Headers","set","encodingForFormat","req","resp","blob","then","type","test","Error","Gate","constructor","handler","checkFn","request","context","next","FastBoot","globalThis","addEventListener","sessionStorage","setItem","getTabId","crypto","randomUUID","tabId","getItem","tabClosed","newTabId","TAB_ID","TAB_ASSIGNED","Math","floor","Date","now","addTraceHeader","headers","set","MAX_URL_LENGTH","assertInvalidUrlLength","url","test","Error","length"],"sources":["../../../src/-private/handlers/auto-compress.ts","../../../src/-private/handlers/gated.ts","../../../src/-private/handlers/meta-doc.ts","../../../src/-private/handlers/utils.ts"],"sourcesContent":["import { assert } from '@warp-drive/core/build-config/macros';\nimport type { Future, Handler, NextFn } from '@warp-drive/core/request';\nimport type { HTTPMethod, RequestContext } from '@warp-drive/core/types/request';\n\nfunction isCompressibleMethod(method?: HTTPMethod): boolean {\n return method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE';\n}\n\n/**\n * Whether the browser supports `ReadableStream` as a request body\n * in a `POST` request.\n *\n * @summary Whether the current browser can send a `ReadableStream` as a `fetch` request body, detected once at\n * module load.\n * @group Constants\n */\nexport const SupportsRequestStreams: boolean = (() => {\n let duplexAccessed = false;\n\n const hasContentType = new Request('', {\n body: new ReadableStream(),\n method: 'POST',\n // @ts-expect-error untyped\n get duplex() {\n duplexAccessed = true;\n return 'half';\n },\n }).headers.has('Content-Type');\n\n return duplexAccessed && !hasContentType;\n})();\n\ninterface Constraints {\n /**\n * The minimum size at which to compress blobs\n *\n * @default 1000\n */\n Blob?: number;\n /**\n * The minimum size at which to compress array buffers\n *\n * @default 1000\n */\n ArrayBuffer?: number;\n /**\n * The minimum size at which to compress typed arrays\n *\n * @default 1000\n */\n TypedArray?: number;\n /**\n * The minimum size at which to compress data views\n *\n * @default 1000\n */\n DataView?: number;\n /**\n * The minimum size at which to compress strings\n *\n * @default 1000\n */\n String?: number;\n}\n\n/**\n * Options for configuring the AutoCompress handler.\n *\n */\ninterface CompressionOptions {\n /**\n * The compression format to use. Must be a valid\n * compression format supported by [CompressionStream](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)\n *\n * The default is `gzip`.\n *\n */\n format?: CompressionFormat;\n\n /**\n * Some browsers support `ReadableStream` as a request body. This option\n * enables passing the compression stream as the request body instead of\n * the final compressed body when the browser supports doing so.\n *\n * This comes with several caveats:\n *\n * - the request will be put into `duplex: 'half'` mode. This should be\n * transparent to you, but it is worth noting.\n * - the request mode cannot be `no-cors` as requests with a `ReadableStream`\n * have no content length and thus are a new form of request that triggers\n * cors requirements and a preflight request.\n * - http/1.x is not supported.\n *\n * For additional reading about the restrictions of using `ReadableStream`\n * as a request body, see the [Chromium Documentation](https://developer.chrome.com/docs/capabilities/web-apis/fetch-streaming-requests#restrictions)\n *\n * Streaming can be enabled per-request in browsers which support it by\n * setting `request.options.allowStreaming` to `true`.\n *\n * Streaming can be forced even when the browser does not support it by setting\n * `request.options.forceStreaming` to `true`. This is useful if later handlers\n * in the chain can handle the request body as a stream.\n *\n * @default false\n */\n allowStreaming?: boolean;\n\n /**\n * If `true`, the request will be forced into streaming mode even\n * if the browser does not support it. This is useful if later handlers\n * in the chain can handle the request body as a stream.\n *\n * @default false\n */\n forceStreaming?: boolean;\n\n /**\n * The constraints for the request body. This is used to determine\n * whether to compress the request body or not.\n *\n * The defaults are:\n *\n * ```ts\n * {\n * Blob: 1000, // blob.size\n * ArrayBuffer: 1000, // buffer.byteLength\n * TypedArray: 1000, // array.byteLength\n * DataView: 1000, // view.byteLength\n * String: 1000, // string.length\n * }\n * ```\n *\n * The following body types are never compressed unless explicitly\n * configured by the request:\n * - `FormData`\n * - `URLSearchParams`\n * - `ReadableStream`\n *\n * A request.options.compress value of `false` will disable\n * compression for a request body of any type. While a value of\n * `true` will enable compression for the request.\n *\n * An undefined value will use the default, a value of `0` will\n * enable compression for all values, and a value of `-1` will\n * disable compression.\n *\n */\n constraints?: Constraints;\n}\n\nconst DEFAULT_CONSTRAINTS = {\n Blob: 1000,\n ArrayBuffer: 1000,\n TypedArray: 1000,\n DataView: 1000,\n String: 1000,\n};\nconst TypedArray = Object.getPrototypeOf(Uint8Array) as typeof Uint8Array;\n\n/**\n * A request handler that automatically compresses the request body\n * if the request body is a string, array buffer, blob, or form data.\n *\n * This uses the [CompressionStream API](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)\n *\n * The compression format as well as the kinds of data to compress can be\n * configured using the `format` and `constraints` options.\n *\n * ```diff\n * +import { AutoCompress } from '@ember-data/request-utils/handlers';\n * import Fetch from '@ember-data/request/fetch';\n * import RequestManager from '@ember-data/request';\n * import Store from '@ember-data/store';\n *\n * class AppStore extends Store {\n * requestManager = new RequestManager()\n * .use([\n * + new AutoCompress(),\n * Fetch\n * ]);\n * }\n * ```\n *\n * @summary Request handler that compresses large `POST`, `PUT`, `PATCH`, and `DELETE` bodies with `CompressionStream`,\n * optionally streaming them.\n * @group Handlers\n * @public\n * @since 5.5.0\n */\nexport class AutoCompress implements Handler {\n /**\n * The resolved options this handler was configured with, with all\n * defaults (including `constraints` defaults) applied.\n */\n declare options: Required<CompressionOptions> & {\n /**\n * The resolved size constraints used to decide whether to compress a given request body.\n */\n constraints: Required<Constraints>;\n };\n\n constructor(options: CompressionOptions = {}) {\n const opts = {\n format: options.format ?? 'gzip',\n constraints: Object.assign({}, DEFAULT_CONSTRAINTS, options.constraints),\n allowStreaming: options.allowStreaming ?? false,\n forceStreaming: options.forceStreaming ?? false,\n };\n this.options = opts;\n }\n\n request<T>({ request }: RequestContext, next: NextFn<T>): Promise<T> | Future<T> {\n const { constraints } = this.options;\n const { body } = request;\n\n const shouldCompress =\n !!body &&\n isCompressibleMethod(request.method) &&\n request.options?.compress !== false &&\n // prettier-ignore\n (request.options?.compress ? true\n : typeof body === 'string' || body instanceof String ? canCompress('String', constraints, body.length)\n : body instanceof Blob ? canCompress('Blob', constraints, body.size)\n : body instanceof ArrayBuffer ? canCompress('ArrayBuffer', constraints, body.byteLength)\n : body instanceof DataView ? canCompress('DataView', constraints, body.byteLength)\n : body instanceof TypedArray ? canCompress('TypedArray', constraints, body.byteLength)\n : false);\n\n if (!shouldCompress) return next(request);\n\n // A convenient way to convert all of the supported body types to a readable\n // stream is to use a `Response` object body\n const response = new Response(request.body);\n const stream = response.body?.pipeThrough(new CompressionStream(this.options.format));\n const headers = new Headers(request.headers);\n headers.set('Content-Encoding', encodingForFormat(this.options.format));\n\n //\n // For browsers that support it, `fetch` can receive a `ReadableStream` as\n // the body, so all we need to do is to create a new `ReadableStream` and\n // compress it on the fly\n //\n const forceStreaming = request.options?.forceStreaming ?? this.options.forceStreaming;\n const allowStreaming = request.options?.allowStreaming ?? this.options.allowStreaming;\n if (forceStreaming || (SupportsRequestStreams && allowStreaming)) {\n const req = Object.assign({}, request, {\n body: stream,\n headers,\n });\n if (SupportsRequestStreams) {\n // @ts-expect-error untyped\n req.duplex = 'half';\n }\n\n return next(req);\n\n //\n // For non-chromium browsers, we have to \"pull\" the stream to get the final\n // bytes and supply the final byte array as the new request body.\n //\n }\n\n // we need to pull the stream to get the final bytes\n const resp = new Response(stream);\n return resp.blob().then((blob) => {\n const req = Object.assign({}, request, {\n body: blob,\n headers,\n });\n return next(req);\n }) as Promise<T>;\n }\n}\n\nfunction canCompress(type: keyof Constraints, constraints: Required<Constraints>, size: number): boolean {\n // if we have a value of 0, we can compress anything\n if (constraints[type] === 0) return true;\n if (constraints[type] === -1) return false;\n return size >= constraints[type];\n}\n\nfunction encodingForFormat(format: CompressionFormat): string {\n switch (format) {\n case 'gzip':\n case 'deflate':\n case 'deflate-raw':\n return format;\n default:\n assert(`Unsupported compression format: ${format as unknown as string}`);\n // @ts-expect-error - unreachable code is reachable in production\n return format;\n }\n}\n","import type { Future, Handler, NextFn } from '@warp-drive/core/request';\nimport type { RequestContext, StructuredDataDocument } from '@warp-drive/core/types/request';\n\n/**\n * If CheckFn returns true, the wrapped handler will be used.\n * If CheckFn returns false, the wrapped handler will be skipped.\n */\ntype CheckFn = (context: RequestContext) => boolean;\n\n/**\n *\n * @summary Request handler that wraps another handler and runs it only when a check function returns true for the\n * request.\n * @group Handlers\n * @public\n */\nexport class Gate implements Handler {\n /**\n * The wrapped handler to invoke when {@link Gate.checkFn | checkFn} returns `true`.\n */\n declare handler: Handler;\n /**\n * The predicate used to decide whether {@link Gate.handler | handler} should run for a given request.\n */\n declare checkFn: CheckFn;\n\n constructor(handler: Handler, checkFn: CheckFn) {\n this.handler = handler;\n this.checkFn = checkFn;\n }\n\n request<T = unknown>(context: RequestContext, next: NextFn<T>): Promise<T | StructuredDataDocument<T>> | Future<T> {\n if (this.checkFn(context)) {\n return this.handler.request(context, next);\n }\n return next(context.request);\n }\n}\n","import type { StoreRequestContext } from '@warp-drive/core';\nimport type { Handler, NextFn } from '@warp-drive/core/request';\nimport type { StructuredDataDocument } from '@warp-drive/core/types/request';\n\n/**\n * MetaDocHandler processes requests that are marked as meta requests.\n *\n * It treats the response body as \"entirely meta\" transforming\n *\n * ```ts\n * {\n * some: \"key\",\n * another: \"thing\"\n * }\n * ```\n *\n * into\n *\n * ```ts\n * {\n * \t meta: {\n * some: \"key\",\n * another: \"thing\"\n * }\n * }\n * ```\n *\n * To activate this handler, a request should specify\n *\n * ```ts\n * options.isMetaRequest = true\n * ```\n *\n * For instance\n *\n * ```ts\n * store.request({\n * url: '/example',\n * options: {\n * isMetaRequest: true\n * }\n * });\n * ```\n *\n * Errors are not processed by this handler, so if the request fails and the error response\n * is not in {json:api} format additional processing may be needed.\n *\n * @summary Request handler that wraps the whole response body in `meta` for requests with `options.isMetaRequest` set.\n * @group Handlers\n */\nexport const MetaDocHandler: Handler = {\n request<T>(context: StoreRequestContext, next: NextFn<T>): Promise<T | StructuredDataDocument<T> | Awaited<T>> {\n if (!context.request.options?.isMetaRequest) {\n return next(context.request);\n }\n\n return next(context.request).then((response) => {\n return processResponse<T>(response);\n });\n },\n};\n\nfunction processResponse<T>(response: StructuredDataDocument<T>): T {\n return {\n meta: response.content,\n } as T;\n}\n","import { assert } from '@warp-drive/core/build-config/macros';\n\nif (typeof FastBoot === 'undefined') {\n globalThis.addEventListener('beforeunload', function () {\n sessionStorage.setItem('tab-closed', 'true');\n });\n}\n\nfunction getTabId() {\n if (typeof sessionStorage === 'undefined') {\n return crypto.randomUUID();\n }\n\n const tabId = sessionStorage.getItem('tab-id');\n if (tabId) {\n const tabClosed = sessionStorage.getItem('tab-closed');\n if (tabClosed === 'true') {\n return tabId;\n }\n\n // fall through to generate a new tab id\n }\n\n const newTabId = crypto.randomUUID();\n sessionStorage.setItem('tab-id', newTabId);\n return newTabId;\n}\n\n/**\n * A unique identifier for the current browser tab\n * useful for observability/tracing and deduping\n * across multiple tabs.\n *\n * @summary Random UUID identifying the current browser tab, kept in `sessionStorage` across reloads, for request\n * tracing.\n * @group Constants\n */\nexport const TAB_ID: string = getTabId();\n/**\n * The epoch seconds at which the tab id was generated\n *\n * @summary Epoch seconds recorded when this module loaded, paired with `TAB_ID` in trace headers.\n * @group Constants\n */\nexport const TAB_ASSIGNED: number = Math.floor(Date.now() / 1000);\n\n/**\n * Adds the `X-Amzn-Trace-Id` header to support observability\n * tooling around request routing.\n *\n * This makes use of the {@link TAB_ID} and {@link TAB_ASSIGNED}\n * to enable tracking the browser tab of origin across multiple requests.\n *\n * Follows the template: `Root=1-${now}-${uuidv4};TabId=1-${epochSeconds}-${tab-uuid}`\n *\n * @summary Sets an `X-Amzn-Trace-Id` header carrying a per-request id and the tab id, so requests can be traced to\n * their browser tab.\n * @group Utility Functions\n */\nexport function addTraceHeader(headers: Headers): Headers {\n const now = Math.floor(Date.now() / 1000);\n headers.set('X-Amzn-Trace-Id', `Root=1-${now}-${crypto.randomUUID()};TabId=1-${TAB_ASSIGNED}-${TAB_ID}`);\n\n return headers;\n}\n\n/**\n * Source: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html\n * As of 2024-12-05 the maximum URL length is 8192 bytes.\n *\n * @summary The 8192-byte URL length limit, taken from AWS CloudFront, that `assertInvalidUrlLength` checks against.\n * @group Constants\n */\nexport const MAX_URL_LENGTH = 8192;\n\n/**\n * This assertion takes a URL and throws an error if the URL is longer than the maximum URL length.\n *\n * See also {@link MAX_URL_LENGTH}\n *\n * @summary Dev-mode assertion that throws when a URL is longer than `MAX_URL_LENGTH`, suggesting a `POST` or `QUERY`\n * request instead.\n * @group Utility Functions\n */\nexport function assertInvalidUrlLength(url: string | undefined): void {\n assert(\n `URL length ${url?.length} exceeds the maximum URL length of ${MAX_URL_LENGTH} bytes.\\n\\nConsider converting this request query a \\`/query\\` endpoint instead of a GET, or upgrade the current endpoint to be able to receive a POST request directly (ideally specifying the header HTTP-Method-Override: QUERY)\\n\\nThe Invalid URL is:\\n\\n${url}`,\n !url || url.length <= MAX_URL_LENGTH\n );\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@ import { getOrSetGlobal } from "@warp-drive/core/types/-private";
2
2
 
3
3
  //#region src/index.ts
4
4
  /**
5
+ * @summary Helpers for building request URLs and sorted query strings, with a configurable global host and namespace.
5
6
  * @module
6
7
  * @mergeModuleWith <project>
7
8
  */
@@ -9,6 +10,7 @@ import { getOrSetGlobal } from "@warp-drive/core/types/-private";
9
10
  * The global configuration used by {@link buildBaseURL} when a call does
10
11
  * not provide its own `host`/`namespace`. Set via {@link setBuildURLConfig}.
11
12
  *
13
+ * @summary The global `host` and `namespace` that `buildBaseURL` prefixes onto URLs when a call does not pass its own.
12
14
  * @public
13
15
  */
14
16
  const CONFIG = getOrSetGlobal("CONFIG", {
@@ -22,6 +24,10 @@ const CONFIG = getOrSetGlobal("CONFIG", {
22
24
  * These values may still be overridden by passing
23
25
  * them to buildBaseURL directly.
24
26
  *
27
+ * The [Basic Usage](/guides/the-manual/cookbook/basic-usage#step-2-configure-some-request-defaults)
28
+ * guide sets these defaults through the `@warp-drive/utilities/json-api` version of this function,
29
+ * which also sets this global configuration.
30
+ *
25
31
  * This method may be called as many times as needed.
26
32
  * host values of `''` or `'/'` are equivalent.
27
33
  *
@@ -48,6 +54,7 @@ const CONFIG = getOrSetGlobal("CONFIG", {
48
54
  * });
49
55
  * ```
50
56
  *
57
+ * @summary Sets the app-wide default `host` and `namespace` used by `buildBaseURL` and the request builders.
51
58
  * @public
52
59
  */
53
60
  function setBuildURLConfig(config) {
@@ -72,47 +79,59 @@ function setBuildURLConfig(config) {
72
79
  /**
73
80
  * {@link buildBaseURL} options for a `findRecord` request.
74
81
  *
82
+ * @summary Options passed to `buildBaseURL` to build the URL for fetching one resource by type and id.
75
83
  * @public
76
84
  */
77
85
  /**
78
86
  * {@link buildBaseURL} options for a `query` request.
79
87
  *
88
+ * @summary Options passed to `buildBaseURL` to build the URL for querying a collection of one resource type.
80
89
  * @public
81
90
  */
82
91
  /**
83
92
  * {@link buildBaseURL} options for a `findMany` request.
84
93
  *
94
+ * @summary Options passed to `buildBaseURL` to build the URL for fetching several resources, pathed by the first
95
+ * identifier's type.
85
96
  * @public
86
97
  */
87
98
  /**
88
99
  * {@link buildBaseURL} options for a `findRelatedCollection` request.
89
100
  *
101
+ * @summary Options passed to `buildBaseURL` to build the URL for a record's has-many relationship, ending in the
102
+ * field name.
90
103
  * @public
91
104
  */
92
105
  /**
93
106
  * {@link buildBaseURL} options for a `findRelatedRecord` request.
94
107
  *
108
+ * @summary Options passed to `buildBaseURL` to build the URL for a record's belongs-to relationship, ending in the
109
+ * field name.
95
110
  * @public
96
111
  */
97
112
  /**
98
113
  * {@link buildBaseURL} options for a `createRecord` request.
99
114
  *
115
+ * @summary Options passed to `buildBaseURL` to build the collection URL a new record of a given type is saved to.
100
116
  * @public
101
117
  */
102
118
  /**
103
119
  * {@link buildBaseURL} options for an `updateRecord` request.
104
120
  *
121
+ * @summary Options passed to `buildBaseURL` to build the URL for saving changes to an existing record by type and id.
105
122
  * @public
106
123
  */
107
124
  /**
108
125
  * {@link buildBaseURL} options for a `deleteRecord` request.
109
126
  *
127
+ * @summary Options passed to `buildBaseURL` to build the URL for deleting an existing record by type and id.
110
128
  * @public
111
129
  */
112
130
  /**
113
131
  * {@link buildBaseURL} options for building a URL directly from a `resourcePath`
114
132
  * without an associated request operation.
115
133
  *
134
+ * @summary Options passed to `buildBaseURL` to build a URL from an explicit `resourcePath` with no request `op`.
116
135
  * @public
117
136
  */
118
137
  /**
@@ -128,6 +147,7 @@ function setBuildURLConfig(config) {
128
147
  * - {@link DeleteRecordUrlOptions}
129
148
  * - {@link GenericUrlOptions}
130
149
  *
150
+ * @summary Any of the option shapes `buildBaseURL` accepts, one per request `op` plus a generic `resourcePath` form.
131
151
  * @public
132
152
  */
133
153
  const OPERATIONS_WITH_PRIMARY_RECORDS = /* @__PURE__ */ new Set([
@@ -154,6 +174,9 @@ function resourcePathForType(options) {
154
174
  * Does not include support for building query params (see `buildQueryParams`)
155
175
  * so that it may be composed cleanly with other query-params strategies.
156
176
  *
177
+ * See the [Builders](/guides/the-manual/requests/builders) guide for writing your own request
178
+ * builders.
179
+ *
157
180
  * Usage:
158
181
  *
159
182
  * ```ts
@@ -183,6 +206,7 @@ function resourcePathForType(options) {
183
206
  * - 'findRecord' 'query' 'findMany' 'findRelatedCollection' 'findRelatedRecord'` 'createRecord' 'updateRecord' 'deleteRecord'
184
207
  * - Depending on the value of `op`, `identifier` or `identifiers` will be required.
185
208
  *
209
+ * @summary Joins host, namespace, resource path, id, and relationship field into a request URL without query params.
186
210
  * @public
187
211
  */
188
212
  function buildBaseURL(urlOptions) {
@@ -284,6 +308,11 @@ function handleInclude(include) {
284
308
  * filter out keys of an object that have falsy values or point to empty arrays
285
309
  * returning a new object with only those keys that have truthy values / non-empty arrays
286
310
  *
311
+ * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
312
+ * why builders need stable query params.
313
+ *
314
+ * @summary Returns a copy of an object without keys whose values are `undefined`, `null`, empty strings, or empty
315
+ * arrays.
287
316
  * @public
288
317
  * @param source object to filter keys with empty values from
289
318
  * @return A new object with the keys that contained empty values removed
@@ -302,6 +331,9 @@ function filterEmpty(source) {
302
331
  * Sorts query params by both key and value returning a new URLSearchParams
303
332
  * object with the keys inserted in sorted order.
304
333
  *
334
+ * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
335
+ * why builders need stable query params.
336
+ *
305
337
  * Treats `included` specially, splicing it into an array if it is a string and sorting the array.
306
338
  *
307
339
  * The `params` given are never mutated: neither the object itself nor any array
@@ -316,6 +348,8 @@ function filterEmpty(source) {
316
348
  * 'repeat': appends the key for every value e.g. `&ids=1&ids=2`
317
349
  * 'comma' (default): appends the key once with a comma separated list of values e.g. `&ids=1,2`
318
350
  *
351
+ * @summary Builds a `URLSearchParams` with keys and array values sorted, so equivalent queries produce identical
352
+ * params.
319
353
  * @public
320
354
  * @return A {@link URLSearchParams} with keys inserted in sorted order
321
355
  */
@@ -364,6 +398,9 @@ function sortQueryParams(params, options) {
364
398
  /**
365
399
  * Sorts query params by both key and value, returning a query params string
366
400
  *
401
+ * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
402
+ * why builders need stable query params.
403
+ *
367
404
  * Treats `included` specially, splicing it into an array if it is a string and sorting the array.
368
405
  *
369
406
  * The `params` given are never mutated: neither the object itself nor any array
@@ -378,6 +415,8 @@ function sortQueryParams(params, options) {
378
415
  * 'repeat': appends the key for every value e.g. `ids=1&ids=2`
379
416
  * 'comma' (default): appends the key once with a comma separated list of values e.g. `ids=1,2`
380
417
  *
418
+ * @summary Serializes query params into a string with keys and array values sorted, so equivalent queries share one
419
+ * URL.
381
420
  * @public
382
421
  * @return A sorted query params string without the leading `?`
383
422
  */