@warp-drive/utilities 5.10.0-alpha.12 → 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.
- package/README.md +9 -0
- package/cjs-dist/string.cjs +19 -0
- package/cjs-dist/string.cjs.map +1 -1
- package/dist/active-record.d.ts +21 -0
- package/dist/active-record.d.ts.map +1 -1
- package/dist/active-record.js +13 -1
- package/dist/active-record.js.map +1 -1
- package/dist/derivations.d.ts +2 -0
- package/dist/derivations.d.ts.map +1 -1
- package/dist/derivations.js +6 -0
- package/dist/derivations.js.map +1 -1
- package/dist/handlers.d.ts +15 -0
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +15 -0
- package/dist/handlers.js.map +1 -1
- package/dist/{index-D6QIwzqm.d.ts → index-Ds06tKwC.d.ts} +39 -1
- package/dist/index-Ds06tKwC.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -1
- package/dist/json-api.d.ts +36 -1
- package/dist/json-api.d.ts.map +1 -1
- package/dist/json-api.js +23 -1
- package/dist/json-api.js.map +1 -1
- package/dist/rest.d.ts +20 -0
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +13 -1
- package/dist/rest.js.map +1 -1
- package/dist/{string-CWmAluxh.js → string-BZACNuLp.js} +20 -1
- package/dist/string-BZACNuLp.js.map +1 -0
- package/dist/string.d.ts +19 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +1 -1
- package/dist/unpkg/dev/active-record.js +13 -1
- package/dist/unpkg/dev/active-record.js.map +1 -1
- package/dist/unpkg/dev/derivations.js +6 -0
- package/dist/unpkg/dev/derivations.js.map +1 -1
- package/dist/unpkg/dev/handlers.js +15 -0
- package/dist/unpkg/dev/handlers.js.map +1 -1
- package/dist/unpkg/dev/index.js +39 -0
- package/dist/unpkg/dev/index.js.map +1 -1
- package/dist/unpkg/dev/json-api.js +23 -1
- package/dist/unpkg/dev/json-api.js.map +1 -1
- package/dist/unpkg/dev/rest.js +13 -1
- package/dist/unpkg/dev/rest.js.map +1 -1
- package/dist/unpkg/dev/{string-j4ur-TDs.js → string-I8KHJXV7.js} +20 -1
- package/dist/unpkg/dev/string-I8KHJXV7.js.map +1 -0
- package/dist/unpkg/dev/string.js +1 -1
- package/dist/unpkg/dev-deprecated/active-record.js +13 -1
- package/dist/unpkg/dev-deprecated/active-record.js.map +1 -1
- package/dist/unpkg/dev-deprecated/derivations.js +6 -0
- package/dist/unpkg/dev-deprecated/derivations.js.map +1 -1
- package/dist/unpkg/dev-deprecated/handlers.js +15 -0
- package/dist/unpkg/dev-deprecated/handlers.js.map +1 -1
- package/dist/unpkg/dev-deprecated/index.js +39 -0
- package/dist/unpkg/dev-deprecated/index.js.map +1 -1
- package/dist/unpkg/dev-deprecated/json-api.js +23 -1
- package/dist/unpkg/dev-deprecated/json-api.js.map +1 -1
- package/dist/unpkg/dev-deprecated/rest.js +13 -1
- package/dist/unpkg/dev-deprecated/rest.js.map +1 -1
- package/dist/unpkg/dev-deprecated/{string-j4ur-TDs.js → string-I8KHJXV7.js} +20 -1
- package/dist/unpkg/dev-deprecated/string-I8KHJXV7.js.map +1 -0
- package/dist/unpkg/dev-deprecated/string.js +1 -1
- package/dist/unpkg/prod/active-record.js +13 -1
- package/dist/unpkg/prod/active-record.js.map +1 -1
- package/dist/unpkg/prod/derivations.js +6 -0
- package/dist/unpkg/prod/derivations.js.map +1 -1
- package/dist/unpkg/prod/handlers.js +15 -0
- package/dist/unpkg/prod/handlers.js.map +1 -1
- package/dist/unpkg/prod/index.js +39 -0
- package/dist/unpkg/prod/index.js.map +1 -1
- package/dist/unpkg/prod/json-api.js +23 -1
- package/dist/unpkg/prod/json-api.js.map +1 -1
- package/dist/unpkg/prod/rest.js +13 -1
- package/dist/unpkg/prod/rest.js.map +1 -1
- package/dist/unpkg/prod/string-D8BEYx2W.js +302 -0
- package/dist/unpkg/prod/string-D8BEYx2W.js.map +1 -0
- package/dist/unpkg/prod/string.js +1 -1
- package/dist/unpkg/prod-deprecated/active-record.js +13 -1
- package/dist/unpkg/prod-deprecated/active-record.js.map +1 -1
- package/dist/unpkg/prod-deprecated/derivations.js +6 -0
- package/dist/unpkg/prod-deprecated/derivations.js.map +1 -1
- package/dist/unpkg/prod-deprecated/handlers.js +15 -0
- package/dist/unpkg/prod-deprecated/handlers.js.map +1 -1
- package/dist/unpkg/prod-deprecated/index.js +39 -0
- package/dist/unpkg/prod-deprecated/index.js.map +1 -1
- package/dist/unpkg/prod-deprecated/json-api.js +23 -1
- package/dist/unpkg/prod-deprecated/json-api.js.map +1 -1
- package/dist/unpkg/prod-deprecated/rest.js +13 -1
- package/dist/unpkg/prod-deprecated/rest.js.map +1 -1
- package/dist/unpkg/prod-deprecated/{string-T1ahjLdj.js → string-D8BEYx2W.js} +20 -1
- package/dist/unpkg/prod-deprecated/string-D8BEYx2W.js.map +1 -0
- package/dist/unpkg/prod-deprecated/string.js +1 -1
- package/package.json +6 -6
- package/dist/index-D6QIwzqm.d.ts.map +0 -1
- package/dist/string-CWmAluxh.js.map +0 -1
- package/dist/unpkg/dev/string-j4ur-TDs.js.map +0 -1
- package/dist/unpkg/dev-deprecated/string-j4ur-TDs.js.map +0 -1
- package/dist/unpkg/prod/string-T1ahjLdj.js +0 -283
- package/dist/unpkg/prod/string-T1ahjLdj.js.map +0 -1
- package/dist/unpkg/prod-deprecated/string-T1ahjLdj.js.map +0 -1
|
@@ -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","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"],"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","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"],"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":";AAIA,SAASA,qBAAqBC,QAA8B;CAC1D,OAAOA,WAAW,UAAUA,WAAW,SAASA,WAAW,WAAWA,WAAW;AACnF;;;;;;;;;AAUA,MAAaC,gCAAyC;CACpD,IAAIC,iBAAiB;CAErB,MAAMC,iBAAiB,IAAIC,QAAQ,IAAI;EACrCC,MAAM,IAAIC,eAAe;EACzBN,QAAQ;EAER,IAAIO,SAAS;GACXL,iBAAiB;GACjB,OAAO;EACT;CACF,CAAC,CAAC,CAACM,QAAQC,IAAI,cAAc;CAE7B,OAAOP,kBAAkB,CAACC;AAC5B,EAAC,CAAE;;;;;AAwHH,MAAMO,sBAAsB;CAC1BC,MAAM;CACNC,aAAa;CACbC,YAAY;CACZC,UAAU;CACVC,QAAQ;AACV;AACA,MAAMF,aAAaG,OAAOC,eAAeC,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCnD,IAAaC,eAAb,MAA6C;;;;;CAY3CC,YAAYC,UAA8B,CAAC,GAAG;EAC5C,MAAMC,OAAO;GACXC,QAAQF,QAAQE,UAAU;GAC1BC,aAAaR,OAAOS,OAAO,CAAC,GAAGf,qBAAqBW,QAAQG,WAAW;GACvEE,gBAAgBL,QAAQK,kBAAkB;GAC1CC,gBAAgBN,QAAQM,kBAAkB;EAC5C;EACA,KAAKN,UAAUC;CACjB;CAEAM,QAAW,EAAEA,WAA2BC,MAAyC;EAC/E,MAAM,EAAEL,gBAAgB,KAAKH;EAC7B,MAAM,EAAEhB,SAASuB;EAejB,IAAI,EAZF,CAAC,CAACvB,QACFN,qBAAqB6B,QAAQ5B,MAAM,KACnC4B,QAAQP,SAASU,aAAa,UAE7BH,QAAQP,SAASU,WAAW,OAC3B,OAAO1B,SAAS,YAAYA,gBAAgBU,SAASiB,YAAY,UAAUR,aAAanB,KAAK4B,MAAM,IACnG5B,gBAAgBM,OAAOqB,YAAY,QAAQR,aAAanB,KAAK6B,IAAI,IACjE7B,gBAAgBO,cAAcoB,YAAY,eAAeR,aAAanB,KAAK8B,UAAU,IACrF9B,gBAAgBS,WAAWkB,YAAY,YAAYR,aAAanB,KAAK8B,UAAU,IAC/E9B,gBAAgBQ,aAAamB,YAAY,cAAcR,aAAanB,KAAK8B,UAAU,IACnF,SAEiB,OAAON,KAAKD,OAAO;EAKxC,MAAMU,SAASF,IADMC,SAAST,QAAQvB,IACvB+B,CAAQ,CAAC/B,MAAMkC,YAAY,IAAIC,kBAAkB,KAAKnB,QAAQE,MAAM,CAAC;EACpF,MAAMf,UAAU,IAAIiC,QAAQb,QAAQpB,OAAO;EAC3CA,QAAQkC,IAAI,oBAAoBC,kBAAkB,KAAKtB,QAAQE,MAAM,CAAC;EAOtE,MAAMI,iBAAiBC,QAAQP,SAASM,kBAAkB,KAAKN,QAAQM;EACvE,MAAMD,iBAAiBE,QAAQP,SAASK,kBAAkB,KAAKL,QAAQK;EACvE,IAAIC,kBAAmB1B,0BAA0ByB,gBAAiB;GAChE,MAAMkB,MAAM5B,OAAOS,OAAO,CAAC,GAAGG,SAAS;IACrCvB,MAAMiC;IACN9B;GACF,CAAC;GACD,IAAIP,wBAEF2C,IAAIrC,SAAS;GAGf,OAAOsB,KAAKe,GAAG;EAMjB;EAIA,OAAOC,IADUR,SAASC,MACnBO,CAAI,CAACC,KAAK,CAAC,CAACC,MAAMD,SAAS;GAKhC,OAAOjB,KAJKb,OAAOS,OAAO,CAAC,GAAGG,SAAS;IACrCvB,MAAMyC;IACNtC;GACF,CACYoC,CAAG;EACjB,CAAC;CACH;AACF;AAEA,SAASZ,YAAYgB,MAAyBxB,aAAoCU,MAAuB;CAEvG,IAAIV,YAAYwB,UAAU,GAAG,OAAO;CACpC,IAAIxB,YAAYwB,UAAU,IAAI,OAAO;CACrC,OAAOd,QAAQV,YAAYwB;AAC7B;AAEA,SAASL,kBAAkBpB,QAAmC;CAC5D,QAAQA,QAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK,eACH,OAAOA;EACT,SAGE,OAAOA;CACX;AACF;;;;;;;;;;;;;;;ACpRA,IAAa0B,OAAb,MAAqC;;;;;;;CAUnCC,YAAYC,SAAkBC,SAAkB;EAC9C,KAAKD,UAAUA;EACf,KAAKC,UAAUA;CACjB;CAEAC,QAAqBC,SAAyBC,MAAqE;EACjH,IAAI,KAAKH,QAAQE,OAAO,GACtB,OAAO,KAAKH,QAAQE,QAAQC,SAASC,IAAI;EAE3C,OAAOA,KAAKD,QAAQD,OAAO;CAC7B;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACaA,MAAa,iBAA0B,EACrC,QAAW,SAA8B,MAAsE;CAC7G,IAAI,CAAC,QAAQ,QAAQ,SAAS,eAC5B,OAAO,KAAK,QAAQ,OAAO;CAG7B,OAAO,KAAK,QAAQ,OAAO,CAAC,CAAC,MAAM,aAAa;EAC9C,OAAO,gBAAmB,QAAQ;CACpC,CAAC;AACH,EACF;AAEA,SAAS,gBAAmB,UAAwC;CAClE,OAAO,EACL,MAAM,SAAS,QACjB;AACF;;;;AChEA,IAAI,OAAOG,aAAa,aACtBC,WAAWC,iBAAiB,gBAAgB,WAAY;CACtDC,eAAeC,QAAQ,cAAc,MAAM;AAC7C,CAAC;AAGH,SAASC,WAAW;CAClB,IAAI,OAAOF,mBAAmB,aAC5B,OAAOG,OAAOC,WAAW;CAG3B,MAAMC,QAAQL,eAAeM,QAAQ,QAAQ;CAC7C,IAAID,OAEF;MADkBL,eAAeM,QAAQ,YACrCC,MAAc,QAChB,OAAOF;CACT;CAKF,MAAMG,WAAWL,OAAOC,WAAW;CACnCJ,eAAeC,QAAQ,UAAUO,QAAQ;CACzC,OAAOA;AACT;;;;;;;;;;AAWA,MAAaC,SAAiBP,SAAS;;;;;;;AAOvC,MAAaQ,eAAuBC,KAAKC,MAAMC,KAAKC,IAAI,IAAI,GAAI;;;;;;;;;;;;;;AAehE,SAAgBC,eAAeC,SAA2B;CACxD,MAAMF,MAAMH,KAAKC,MAAMC,KAAKC,IAAI,IAAI,GAAI;CACxCE,QAAQC,IAAI,mBAAmB,UAAUH,IAAG,GAAIX,OAAOC,WAAW,EAAC,WAAYM,aAAY,GAAID,QAAQ;CAEvG,OAAOO;AACT;;;;;;;;AASA,MAAaE,iBAAiB;;;;;;;;;;AAW9B,SAAgBC,uBAAuBC,KAA+B,CAKtE"}
|
|
@@ -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) {
|
|
@@ -57,47 +64,59 @@ function setBuildURLConfig(config) {
|
|
|
57
64
|
/**
|
|
58
65
|
* {@link buildBaseURL} options for a `findRecord` request.
|
|
59
66
|
*
|
|
67
|
+
* @summary Options passed to `buildBaseURL` to build the URL for fetching one resource by type and id.
|
|
60
68
|
* @public
|
|
61
69
|
*/
|
|
62
70
|
/**
|
|
63
71
|
* {@link buildBaseURL} options for a `query` request.
|
|
64
72
|
*
|
|
73
|
+
* @summary Options passed to `buildBaseURL` to build the URL for querying a collection of one resource type.
|
|
65
74
|
* @public
|
|
66
75
|
*/
|
|
67
76
|
/**
|
|
68
77
|
* {@link buildBaseURL} options for a `findMany` request.
|
|
69
78
|
*
|
|
79
|
+
* @summary Options passed to `buildBaseURL` to build the URL for fetching several resources, pathed by the first
|
|
80
|
+
* identifier's type.
|
|
70
81
|
* @public
|
|
71
82
|
*/
|
|
72
83
|
/**
|
|
73
84
|
* {@link buildBaseURL} options for a `findRelatedCollection` request.
|
|
74
85
|
*
|
|
86
|
+
* @summary Options passed to `buildBaseURL` to build the URL for a record's has-many relationship, ending in the
|
|
87
|
+
* field name.
|
|
75
88
|
* @public
|
|
76
89
|
*/
|
|
77
90
|
/**
|
|
78
91
|
* {@link buildBaseURL} options for a `findRelatedRecord` request.
|
|
79
92
|
*
|
|
93
|
+
* @summary Options passed to `buildBaseURL` to build the URL for a record's belongs-to relationship, ending in the
|
|
94
|
+
* field name.
|
|
80
95
|
* @public
|
|
81
96
|
*/
|
|
82
97
|
/**
|
|
83
98
|
* {@link buildBaseURL} options for a `createRecord` request.
|
|
84
99
|
*
|
|
100
|
+
* @summary Options passed to `buildBaseURL` to build the collection URL a new record of a given type is saved to.
|
|
85
101
|
* @public
|
|
86
102
|
*/
|
|
87
103
|
/**
|
|
88
104
|
* {@link buildBaseURL} options for an `updateRecord` request.
|
|
89
105
|
*
|
|
106
|
+
* @summary Options passed to `buildBaseURL` to build the URL for saving changes to an existing record by type and id.
|
|
90
107
|
* @public
|
|
91
108
|
*/
|
|
92
109
|
/**
|
|
93
110
|
* {@link buildBaseURL} options for a `deleteRecord` request.
|
|
94
111
|
*
|
|
112
|
+
* @summary Options passed to `buildBaseURL` to build the URL for deleting an existing record by type and id.
|
|
95
113
|
* @public
|
|
96
114
|
*/
|
|
97
115
|
/**
|
|
98
116
|
* {@link buildBaseURL} options for building a URL directly from a `resourcePath`
|
|
99
117
|
* without an associated request operation.
|
|
100
118
|
*
|
|
119
|
+
* @summary Options passed to `buildBaseURL` to build a URL from an explicit `resourcePath` with no request `op`.
|
|
101
120
|
* @public
|
|
102
121
|
*/
|
|
103
122
|
/**
|
|
@@ -113,6 +132,7 @@ function setBuildURLConfig(config) {
|
|
|
113
132
|
* - {@link DeleteRecordUrlOptions}
|
|
114
133
|
* - {@link GenericUrlOptions}
|
|
115
134
|
*
|
|
135
|
+
* @summary Any of the option shapes `buildBaseURL` accepts, one per request `op` plus a generic `resourcePath` form.
|
|
116
136
|
* @public
|
|
117
137
|
*/
|
|
118
138
|
const OPERATIONS_WITH_PRIMARY_RECORDS = /* @__PURE__ */ new Set([
|
|
@@ -133,6 +153,9 @@ function resourcePathForType(options) {
|
|
|
133
153
|
* Does not include support for building query params (see `buildQueryParams`)
|
|
134
154
|
* so that it may be composed cleanly with other query-params strategies.
|
|
135
155
|
*
|
|
156
|
+
* See the [Builders](/guides/the-manual/requests/builders) guide for writing your own request
|
|
157
|
+
* builders.
|
|
158
|
+
*
|
|
136
159
|
* Usage:
|
|
137
160
|
*
|
|
138
161
|
* ```ts
|
|
@@ -162,6 +185,7 @@ function resourcePathForType(options) {
|
|
|
162
185
|
* - 'findRecord' 'query' 'findMany' 'findRelatedCollection' 'findRelatedRecord'` 'createRecord' 'updateRecord' 'deleteRecord'
|
|
163
186
|
* - Depending on the value of `op`, `identifier` or `identifiers` will be required.
|
|
164
187
|
*
|
|
188
|
+
* @summary Joins host, namespace, resource path, id, and relationship field into a request URL without query params.
|
|
165
189
|
* @public
|
|
166
190
|
*/
|
|
167
191
|
function buildBaseURL(urlOptions) {
|
|
@@ -191,6 +215,11 @@ function handleInclude(include) {
|
|
|
191
215
|
* filter out keys of an object that have falsy values or point to empty arrays
|
|
192
216
|
* returning a new object with only those keys that have truthy values / non-empty arrays
|
|
193
217
|
*
|
|
218
|
+
* See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
|
|
219
|
+
* why builders need stable query params.
|
|
220
|
+
*
|
|
221
|
+
* @summary Returns a copy of an object without keys whose values are `undefined`, `null`, empty strings, or empty
|
|
222
|
+
* arrays.
|
|
194
223
|
* @public
|
|
195
224
|
* @param source object to filter keys with empty values from
|
|
196
225
|
* @return A new object with the keys that contained empty values removed
|
|
@@ -209,6 +238,9 @@ function filterEmpty(source) {
|
|
|
209
238
|
* Sorts query params by both key and value returning a new URLSearchParams
|
|
210
239
|
* object with the keys inserted in sorted order.
|
|
211
240
|
*
|
|
241
|
+
* See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
|
|
242
|
+
* why builders need stable query params.
|
|
243
|
+
*
|
|
212
244
|
* Treats `included` specially, splicing it into an array if it is a string and sorting the array.
|
|
213
245
|
*
|
|
214
246
|
* The `params` given are never mutated: neither the object itself nor any array
|
|
@@ -223,6 +255,8 @@ function filterEmpty(source) {
|
|
|
223
255
|
* 'repeat': appends the key for every value e.g. `&ids=1&ids=2`
|
|
224
256
|
* 'comma' (default): appends the key once with a comma separated list of values e.g. `&ids=1,2`
|
|
225
257
|
*
|
|
258
|
+
* @summary Builds a `URLSearchParams` with keys and array values sorted, so equivalent queries produce identical
|
|
259
|
+
* params.
|
|
226
260
|
* @public
|
|
227
261
|
* @return A {@link URLSearchParams} with keys inserted in sorted order
|
|
228
262
|
*/
|
|
@@ -271,6 +305,9 @@ function sortQueryParams(params, options) {
|
|
|
271
305
|
/**
|
|
272
306
|
* Sorts query params by both key and value, returning a query params string
|
|
273
307
|
*
|
|
308
|
+
* See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for
|
|
309
|
+
* why builders need stable query params.
|
|
310
|
+
*
|
|
274
311
|
* Treats `included` specially, splicing it into an array if it is a string and sorting the array.
|
|
275
312
|
*
|
|
276
313
|
* The `params` given are never mutated: neither the object itself nor any array
|
|
@@ -285,6 +322,8 @@ function sortQueryParams(params, options) {
|
|
|
285
322
|
* 'repeat': appends the key for every value e.g. `ids=1&ids=2`
|
|
286
323
|
* 'comma' (default): appends the key once with a comma separated list of values e.g. `ids=1,2`
|
|
287
324
|
*
|
|
325
|
+
* @summary Serializes query params into a string with keys and array values sorted, so equivalent queries share one
|
|
326
|
+
* URL.
|
|
288
327
|
* @public
|
|
289
328
|
* @return A sorted query params string without the leading `?`
|
|
290
329
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["getOrSetGlobal","CONFIG","host","namespace","setBuildURLConfig","config","OPERATIONS_WITH_PRIMARY_RECORDS","Set","isOperationWithPrimaryRecord","options","has","op","hasResourcePath","resourcePath","length","resourcePathForType","identifiers","type","identifier","buildBaseURL","urlOptions","Object","assign","idPath","encodeURIComponent","id","fieldPath","hasHost","url","filter","Boolean","join","DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS","arrayFormat","handleInclude","include","split","filterEmpty","source","result","key","value","undefined","Array","isArray","sortQueryParams","params","opts","paramsIsObject","URLSearchParams","urlParams","dictionaryParams","forEach","hasExisting","existingValue","push","sortedKeys","keys","sort","sortedValue","toSorted","v","i","append","String","buildQueryParams","toString"],"sources":["../../../src/index.ts"],"sourcesContent":["/**\n * @module\n * @mergeModuleWith <project>\n */\n\nimport { assert } from '@warp-drive/core/build-config/macros';\nimport { getOrSetGlobal } from '@warp-drive/core/types/-private';\nimport type { QueryParamsSerializationOptions, QueryParamsSource, Serializable } from '@warp-drive/core/types/params';\n\n// prevents the final constructed object from needing to add\n// host and namespace which are provided by the final consuming\n// class to the prototype which can result in overwrite errors\n\n/**\n * The global configuration used by {@link buildBaseURL} when a call does\n * not provide its own `host`/`namespace`. Set via {@link setBuildURLConfig}.\n *\n * @public\n */\nexport interface BuildURLConfig {\n /**\n * The scheme, domain and port (if any) to prefix built URLs with, e.g. `'https://api.example.com'`.\n */\n host: string | null;\n /**\n * The path segment to insert between `host` and the resource path, e.g. `'api/v1'`.\n */\n namespace: string | null;\n}\n\nconst CONFIG: BuildURLConfig = getOrSetGlobal('CONFIG', {\n host: '',\n namespace: '',\n});\n\n/**\n * Sets the global configuration for `buildBaseURL`\n * for host and namespace values for the application.\n *\n * These values may still be overridden by passing\n * them to buildBaseURL directly.\n *\n * This method may be called as many times as needed.\n * host values of `''` or `'/'` are equivalent.\n *\n * Except for the value of `/` as host, host should not\n * end with `/`.\n *\n * namespace should not start or end with a `/`.\n *\n * ```ts\n * type BuildURLConfig = {\n * host: string;\n * namespace: string'\n * }\n * ```\n *\n * Example:\n *\n * ```ts\n * import { setBuildURLConfig } from '@warp-drive/utilities';\n *\n * setBuildURLConfig({\n * host: 'https://api.example.com',\n * namespace: 'api/v1'\n * });\n * ```\n *\n * @public\n */\nexport function setBuildURLConfig(config: BuildURLConfig): void {\n assert(`setBuildURLConfig: You must pass a config object`, config);\n assert(\n `setBuildURLConfig: You must pass a config object with a 'host' or 'namespace' property`,\n 'host' in config || 'namespace' in config\n );\n\n CONFIG.host = config.host || '';\n CONFIG.namespace = config.namespace || '';\n\n assert(\n `buildBaseURL: host must NOT end with '/', received '${CONFIG.host}'`,\n CONFIG.host === '/' || !CONFIG.host.endsWith('/')\n );\n assert(\n `buildBaseURL: namespace must NOT start with '/', received '${CONFIG.namespace}'`,\n !CONFIG.namespace.startsWith('/')\n );\n assert(\n `buildBaseURL: namespace must NOT end with '/', received '${CONFIG.namespace}'`,\n !CONFIG.namespace.endsWith('/')\n );\n}\n\n/**\n * {@link buildBaseURL} options for a `findRecord` request.\n *\n * @public\n */\nexport interface FindRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRecord';\n /**\n * The type and id of the record to find.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `query` request.\n *\n * @public\n */\nexport interface QueryUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'query';\n /**\n * The type of the records to query.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `findMany` request.\n *\n * @public\n */\nexport interface FindManyUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findMany';\n /**\n * The type and id of each record to find.\n */\n identifiers: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n }[];\n /**\n * The path segment for the resource, defaults to the first identifier's `type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n/**\n * {@link buildBaseURL} options for a `findRelatedCollection` request.\n *\n * @public\n */\nexport interface FindRelatedCollectionUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRelatedCollection';\n /**\n * The type and id of the record whose relationship is being fetched.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The relationship field name, appended to the resource path.\n */\n fieldPath: string;\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `findRelatedRecord` request.\n *\n * @public\n */\nexport interface FindRelatedResourceUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRelatedRecord';\n /**\n * The type and id of the record whose relationship is being fetched.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The relationship field name, appended to the resource path.\n */\n fieldPath: string;\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `createRecord` request.\n *\n * @public\n */\nexport interface CreateRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'createRecord';\n /**\n * The type of the record being created.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for an `updateRecord` request.\n *\n * @public\n */\nexport interface UpdateRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'updateRecord';\n /**\n * The type and id of the record being updated.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `deleteRecord` request.\n *\n * @public\n */\nexport interface DeleteRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'deleteRecord';\n /**\n * The type and id of the record being deleted.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for building a URL directly from a `resourcePath`\n * without an associated request operation.\n *\n * @public\n */\nexport interface GenericUrlOptions {\n /**\n * The path segment for the resource.\n */\n resourcePath: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * The union of all `op`-specific option shapes accepted by {@link buildBaseURL}, one of:\n *\n * - {@link FindRecordUrlOptions}\n * - {@link QueryUrlOptions}\n * - {@link FindManyUrlOptions}\n * - {@link FindRelatedCollectionUrlOptions}\n * - {@link FindRelatedResourceUrlOptions}\n * - {@link CreateRecordUrlOptions}\n * - {@link UpdateRecordUrlOptions}\n * - {@link DeleteRecordUrlOptions}\n * - {@link GenericUrlOptions}\n *\n * @public\n */\nexport type UrlOptions =\n | FindRecordUrlOptions\n | QueryUrlOptions\n | FindManyUrlOptions\n | FindRelatedCollectionUrlOptions\n | FindRelatedResourceUrlOptions\n | CreateRecordUrlOptions\n | UpdateRecordUrlOptions\n | DeleteRecordUrlOptions\n | GenericUrlOptions;\n\nconst OPERATIONS_WITH_PRIMARY_RECORDS = new Set([\n 'findRecord',\n 'findRelatedRecord',\n 'findRelatedCollection',\n 'updateRecord',\n 'deleteRecord',\n]);\n\nfunction isOperationWithPrimaryRecord(\n options: UrlOptions\n): options is\n | FindRecordUrlOptions\n | FindRelatedCollectionUrlOptions\n | FindRelatedResourceUrlOptions\n | UpdateRecordUrlOptions\n | DeleteRecordUrlOptions {\n return 'op' in options && OPERATIONS_WITH_PRIMARY_RECORDS.has(options.op);\n}\n\nfunction hasResourcePath(options: UrlOptions): options is GenericUrlOptions {\n return 'resourcePath' in options && typeof options.resourcePath === 'string' && options.resourcePath.length > 0;\n}\n\nfunction resourcePathForType(options: UrlOptions): string {\n assert(\n `resourcePathForType: You must pass a valid op as part of options`,\n 'op' in options && typeof options.op === 'string'\n );\n return options.op === 'findMany' ? options.identifiers[0].type : options.identifier.type;\n}\n\n/**\n * Builds a URL for a request based on the provided options.\n * Does not include support for building query params (see `buildQueryParams`)\n * so that it may be composed cleanly with other query-params strategies.\n *\n * Usage:\n *\n * ```ts\n * import { buildBaseURL } from '@warp-drive/utilities';\n *\n * const url = buildBaseURL({\n * host: 'https://api.example.com',\n * namespace: 'api/v1',\n * resourcePath: 'emberDevelopers',\n * op: 'query',\n * identifier: { type: 'ember-developer' }\n * });\n *\n * // => 'https://api.example.com/api/v1/emberDevelopers'\n * ```\n *\n * On the surface this may seem like a lot of work to do something simple, but\n * it is designed to be composable with other utilities and interfaces that the\n * average product engineer will never need to see or use.\n *\n * A few notes:\n *\n * - `resourcePath` is optional, but if it is not provided, `identifier.type` will be used.\n * - `host` and `namespace` are optional, but if they are not provided, the values globally\n * configured via `setBuildURLConfig` will be used.\n * - `op` is required and must be one of the following:\n * - 'findRecord' 'query' 'findMany' 'findRelatedCollection' 'findRelatedRecord'` 'createRecord' 'updateRecord' 'deleteRecord'\n * - Depending on the value of `op`, `identifier` or `identifiers` will be required.\n *\n * @public\n */\nexport function buildBaseURL(urlOptions: UrlOptions): string {\n const options = Object.assign(\n {\n host: CONFIG.host,\n namespace: CONFIG.namespace,\n },\n urlOptions\n );\n assert(\n `buildBaseURL: You must pass \\`op\\` as part of options`,\n hasResourcePath(options) || (typeof options.op === 'string' && options.op.length > 0)\n );\n assert(\n `buildBaseURL: You must pass \\`identifier\\` as part of options`,\n hasResourcePath(options) ||\n options.op === 'findMany' ||\n (options.identifier && typeof options.identifier === 'object')\n );\n assert(\n `buildBaseURL: You must pass \\`identifiers\\` as part of options`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n (options.identifiers &&\n Array.isArray(options.identifiers) &&\n options.identifiers.length > 0 &&\n options.identifiers.every((i) => i && typeof i === 'object'))\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifier\\` as part of options, expected 'id'`,\n hasResourcePath(options) ||\n !isOperationWithPrimaryRecord(options) ||\n (typeof options.identifier.id === 'string' && options.identifier.id.length > 0)\n );\n assert(\n `buildBaseURL: You must pass \\`identifiers\\` as part of options`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n options.identifiers.every((i) => typeof i.id === 'string' && i.id.length > 0)\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifier\\` as part of options, expected 'type'`,\n hasResourcePath(options) ||\n options.op === 'findMany' ||\n (typeof options.identifier.type === 'string' && options.identifier.type.length > 0)\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifiers\\` as part of options, expected 'type'`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n (typeof options.identifiers[0].type === 'string' && options.identifiers[0].type.length > 0)\n );\n\n // prettier-ignore\n const idPath: string =\n isOperationWithPrimaryRecord(options) ? encodeURIComponent(options.identifier.id)\n : '';\n const resourcePath = options.resourcePath || resourcePathForType(options);\n const { host, namespace } = options;\n const fieldPath = 'fieldPath' in options ? options.fieldPath : '';\n\n assert(\n `buildBaseURL: You tried to build a url for a ${String(\n 'op' in options ? options.op + ' ' : ''\n )}request to ${resourcePath} but resourcePath must be set or op must be one of \"${[\n 'findRecord',\n 'findRelatedRecord',\n 'findRelatedCollection',\n 'updateRecord',\n 'deleteRecord',\n 'createRecord',\n 'query',\n 'findMany',\n ].join('\",\"')}\".`,\n hasResourcePath(options) ||\n [\n 'findRecord',\n 'query',\n 'findMany',\n 'findRelatedCollection',\n 'findRelatedRecord',\n 'createRecord',\n 'updateRecord',\n 'deleteRecord',\n ].includes(options.op)\n );\n\n assert(`buildBaseURL: host must NOT end with '/', received '${host}'`, host === '/' || !host.endsWith('/'));\n assert(`buildBaseURL: namespace must NOT start with '/', received '${namespace}'`, !namespace.startsWith('/'));\n assert(`buildBaseURL: namespace must NOT end with '/', received '${namespace}'`, !namespace.endsWith('/'));\n assert(\n `buildBaseURL: resourcePath must NOT start with '/', received '${resourcePath}'`,\n !resourcePath.startsWith('/')\n );\n assert(`buildBaseURL: resourcePath must NOT end with '/', received '${resourcePath}'`, !resourcePath.endsWith('/'));\n assert(`buildBaseURL: fieldPath must NOT start with '/', received '${fieldPath}'`, !fieldPath.startsWith('/'));\n assert(`buildBaseURL: fieldPath must NOT end with '/', received '${fieldPath}'`, !fieldPath.endsWith('/'));\n assert(`buildBaseURL: idPath must NOT start with '/', received '${idPath}'`, !idPath.startsWith('/'));\n assert(`buildBaseURL: idPath must NOT end with '/', received '${idPath}'`, !idPath.endsWith('/'));\n\n const hasHost = host !== '' && host !== '/';\n const url = [hasHost ? host : '', namespace, resourcePath, idPath, fieldPath].filter(Boolean).join('/');\n return hasHost ? url : `/${url}`;\n}\n\nconst DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS: QueryParamsSerializationOptions = {\n arrayFormat: 'comma',\n};\n\nfunction handleInclude(include: string | string[]): string[] {\n assert(\n `Expected include to be a string or array, got ${typeof include}`,\n typeof include === 'string' || Array.isArray(include)\n );\n return typeof include === 'string' ? include.split(',') : include;\n}\n\n/**\n * filter out keys of an object that have falsy values or point to empty arrays\n * returning a new object with only those keys that have truthy values / non-empty arrays\n *\n * @public\n * @param source object to filter keys with empty values from\n * @return A new object with the keys that contained empty values removed\n */\nexport function filterEmpty(source: Record<string, Serializable>): Record<string, Serializable> {\n const result: Record<string, Serializable> = {};\n for (const key in source) {\n const value = source[key];\n // Allow `0` and `false` but filter falsy values that indicate \"empty\"\n if (value !== undefined && value !== null && value !== '') {\n if (!Array.isArray(value) || value.length > 0) {\n result[key] = source[key];\n }\n }\n }\n return result;\n}\n\n/**\n * Sorts query params by both key and value returning a new URLSearchParams\n * object with the keys inserted in sorted order.\n *\n * Treats `included` specially, splicing it into an array if it is a string and sorting the array.\n *\n * The `params` given are never mutated: neither the object itself nor any array\n * value it holds is reordered or replaced, so passing state that is also\n * rendered (a tracked array of filter values, for instance) is safe.\n *\n * Options:\n * - arrayFormat: 'bracket' | 'indices' | 'repeat' | 'comma'\n *\n * 'bracket': appends [] to the key for every value e.g. `&ids[]=1&ids[]=2`\n * 'indices': appends [i] to the key for every value e.g. `&ids[0]=1&ids[1]=2`\n * 'repeat': appends the key for every value e.g. `&ids=1&ids=2`\n * 'comma' (default): appends the key once with a comma separated list of values e.g. `&ids=1,2`\n *\n * @public\n * @return A {@link URLSearchParams} with keys inserted in sorted order\n */\nexport function sortQueryParams(params: QueryParamsSource, options?: QueryParamsSerializationOptions): URLSearchParams {\n const opts = Object.assign({}, DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS, options);\n const paramsIsObject = !(params instanceof URLSearchParams);\n const urlParams = new URLSearchParams();\n // shallow copy so that normalizing `include` below does not write back onto\n // the caller's object. Array values are still shared with the caller, so\n // everything downstream must sort copies rather than sorting in place.\n const dictionaryParams: Record<string, Serializable> = paramsIsObject ? Object.assign({}, params) : {};\n\n if (!paramsIsObject) {\n params.forEach((value, key) => {\n const hasExisting = key in dictionaryParams;\n if (!hasExisting) {\n dictionaryParams[key] = value;\n } else {\n const existingValue = dictionaryParams[key];\n if (Array.isArray(existingValue)) {\n existingValue.push(value);\n } else {\n dictionaryParams[key] = [existingValue, value];\n }\n }\n });\n }\n\n if ('include' in dictionaryParams) {\n dictionaryParams.include = handleInclude(dictionaryParams.include as string | string[]);\n }\n\n const sortedKeys = Object.keys(dictionaryParams).sort();\n sortedKeys.forEach((key) => {\n const value = dictionaryParams[key];\n if (Array.isArray(value)) {\n const sortedValue = value.toSorted();\n switch (opts.arrayFormat) {\n case 'indices':\n sortedValue.forEach((v, i) => {\n urlParams.append(`${key}[${i}]`, String(v));\n });\n return;\n case 'bracket':\n sortedValue.forEach((v) => {\n urlParams.append(`${key}[]`, String(v));\n });\n return;\n case 'repeat':\n sortedValue.forEach((v) => {\n urlParams.append(key, String(v));\n });\n return;\n case 'comma':\n default:\n urlParams.append(key, sortedValue.join(','));\n return;\n }\n } else {\n urlParams.append(key, String(value));\n }\n });\n\n return urlParams;\n}\n\n/**\n * Sorts query params by both key and value, returning a query params string\n *\n * Treats `included` specially, splicing it into an array if it is a string and sorting the array.\n *\n * The `params` given are never mutated: neither the object itself nor any array\n * value it holds is reordered or replaced, so passing state that is also\n * rendered (a tracked array of filter values, for instance) is safe.\n *\n * Options:\n * - arrayFormat: 'bracket' | 'indices' | 'repeat' | 'comma'\n *\n * 'bracket': appends [] to the key for every value e.g. `ids[]=1&ids[]=2`\n * 'indices': appends [i] to the key for every value e.g. `ids[0]=1&ids[1]=2`\n * 'repeat': appends the key for every value e.g. `ids=1&ids=2`\n * 'comma' (default): appends the key once with a comma separated list of values e.g. `ids=1,2`\n *\n * @public\n * @return A sorted query params string without the leading `?`\n */\nexport function buildQueryParams(params: QueryParamsSource, options?: QueryParamsSerializationOptions): string {\n return sortQueryParams(params, options).toString();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["getOrSetGlobal","CONFIG","host","namespace","setBuildURLConfig","config","OPERATIONS_WITH_PRIMARY_RECORDS","Set","isOperationWithPrimaryRecord","options","has","op","hasResourcePath","resourcePath","length","resourcePathForType","identifiers","type","identifier","buildBaseURL","urlOptions","Object","assign","idPath","encodeURIComponent","id","fieldPath","hasHost","url","filter","Boolean","join","DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS","arrayFormat","handleInclude","include","split","filterEmpty","source","result","key","value","undefined","Array","isArray","sortQueryParams","params","opts","paramsIsObject","URLSearchParams","urlParams","dictionaryParams","forEach","hasExisting","existingValue","push","sortedKeys","keys","sort","sortedValue","toSorted","v","i","append","String","buildQueryParams","toString"],"sources":["../../../src/index.ts"],"sourcesContent":["/**\n * @summary Helpers for building request URLs and sorted query strings, with a configurable global host and namespace.\n * @module\n * @mergeModuleWith <project>\n */\n\nimport { assert } from '@warp-drive/core/build-config/macros';\nimport { getOrSetGlobal } from '@warp-drive/core/types/-private';\nimport type { QueryParamsSerializationOptions, QueryParamsSource, Serializable } from '@warp-drive/core/types/params';\n\n// prevents the final constructed object from needing to add\n// host and namespace which are provided by the final consuming\n// class to the prototype which can result in overwrite errors\n\n/**\n * The global configuration used by {@link buildBaseURL} when a call does\n * not provide its own `host`/`namespace`. Set via {@link setBuildURLConfig}.\n *\n * @summary The global `host` and `namespace` that `buildBaseURL` prefixes onto URLs when a call does not pass its own.\n * @public\n */\nexport interface BuildURLConfig {\n /**\n * The scheme, domain and port (if any) to prefix built URLs with, e.g. `'https://api.example.com'`.\n */\n host: string | null;\n /**\n * The path segment to insert between `host` and the resource path, e.g. `'api/v1'`.\n */\n namespace: string | null;\n}\n\nconst CONFIG: BuildURLConfig = getOrSetGlobal('CONFIG', {\n host: '',\n namespace: '',\n});\n\n/**\n * Sets the global configuration for `buildBaseURL`\n * for host and namespace values for the application.\n *\n * These values may still be overridden by passing\n * them to buildBaseURL directly.\n *\n * The [Basic Usage](/guides/the-manual/cookbook/basic-usage#step-2-configure-some-request-defaults)\n * guide sets these defaults through the `@warp-drive/utilities/json-api` version of this function,\n * which also sets this global configuration.\n *\n * This method may be called as many times as needed.\n * host values of `''` or `'/'` are equivalent.\n *\n * Except for the value of `/` as host, host should not\n * end with `/`.\n *\n * namespace should not start or end with a `/`.\n *\n * ```ts\n * type BuildURLConfig = {\n * host: string;\n * namespace: string'\n * }\n * ```\n *\n * Example:\n *\n * ```ts\n * import { setBuildURLConfig } from '@warp-drive/utilities';\n *\n * setBuildURLConfig({\n * host: 'https://api.example.com',\n * namespace: 'api/v1'\n * });\n * ```\n *\n * @summary Sets the app-wide default `host` and `namespace` used by `buildBaseURL` and the request builders.\n * @public\n */\nexport function setBuildURLConfig(config: BuildURLConfig): void {\n assert(`setBuildURLConfig: You must pass a config object`, config);\n assert(\n `setBuildURLConfig: You must pass a config object with a 'host' or 'namespace' property`,\n 'host' in config || 'namespace' in config\n );\n\n CONFIG.host = config.host || '';\n CONFIG.namespace = config.namespace || '';\n\n assert(\n `buildBaseURL: host must NOT end with '/', received '${CONFIG.host}'`,\n CONFIG.host === '/' || !CONFIG.host.endsWith('/')\n );\n assert(\n `buildBaseURL: namespace must NOT start with '/', received '${CONFIG.namespace}'`,\n !CONFIG.namespace.startsWith('/')\n );\n assert(\n `buildBaseURL: namespace must NOT end with '/', received '${CONFIG.namespace}'`,\n !CONFIG.namespace.endsWith('/')\n );\n}\n\n/**\n * {@link buildBaseURL} options for a `findRecord` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for fetching one resource by type and id.\n * @public\n */\nexport interface FindRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRecord';\n /**\n * The type and id of the record to find.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `query` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for querying a collection of one resource type.\n * @public\n */\nexport interface QueryUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'query';\n /**\n * The type of the records to query.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `findMany` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for fetching several resources, pathed by the first\n * identifier's type.\n * @public\n */\nexport interface FindManyUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findMany';\n /**\n * The type and id of each record to find.\n */\n identifiers: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n }[];\n /**\n * The path segment for the resource, defaults to the first identifier's `type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n/**\n * {@link buildBaseURL} options for a `findRelatedCollection` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for a record's has-many relationship, ending in the\n * field name.\n * @public\n */\nexport interface FindRelatedCollectionUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRelatedCollection';\n /**\n * The type and id of the record whose relationship is being fetched.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The relationship field name, appended to the resource path.\n */\n fieldPath: string;\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `findRelatedRecord` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for a record's belongs-to relationship, ending in the\n * field name.\n * @public\n */\nexport interface FindRelatedResourceUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'findRelatedRecord';\n /**\n * The type and id of the record whose relationship is being fetched.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The relationship field name, appended to the resource path.\n */\n fieldPath: string;\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `createRecord` request.\n *\n * @summary Options passed to `buildBaseURL` to build the collection URL a new record of a given type is saved to.\n * @public\n */\nexport interface CreateRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'createRecord';\n /**\n * The type of the record being created.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for an `updateRecord` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for saving changes to an existing record by type and id.\n * @public\n */\nexport interface UpdateRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'updateRecord';\n /**\n * The type and id of the record being updated.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for a `deleteRecord` request.\n *\n * @summary Options passed to `buildBaseURL` to build the URL for deleting an existing record by type and id.\n * @public\n */\nexport interface DeleteRecordUrlOptions {\n /**\n * The request operation this URL is for.\n */\n op: 'deleteRecord';\n /**\n * The type and id of the record being deleted.\n */\n identifier: {\n /**\n * The resource type.\n */\n type: string;\n /**\n * The resource id.\n */\n id: string;\n };\n /**\n * The path segment for the resource, defaults to `identifier.type` if not provided.\n */\n resourcePath?: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * {@link buildBaseURL} options for building a URL directly from a `resourcePath`\n * without an associated request operation.\n *\n * @summary Options passed to `buildBaseURL` to build a URL from an explicit `resourcePath` with no request `op`.\n * @public\n */\nexport interface GenericUrlOptions {\n /**\n * The path segment for the resource.\n */\n resourcePath: string;\n /**\n * Overrides the globally configured host for this call only.\n */\n host?: string;\n /**\n * Overrides the globally configured namespace for this call only.\n */\n namespace?: string;\n}\n\n/**\n * The union of all `op`-specific option shapes accepted by {@link buildBaseURL}, one of:\n *\n * - {@link FindRecordUrlOptions}\n * - {@link QueryUrlOptions}\n * - {@link FindManyUrlOptions}\n * - {@link FindRelatedCollectionUrlOptions}\n * - {@link FindRelatedResourceUrlOptions}\n * - {@link CreateRecordUrlOptions}\n * - {@link UpdateRecordUrlOptions}\n * - {@link DeleteRecordUrlOptions}\n * - {@link GenericUrlOptions}\n *\n * @summary Any of the option shapes `buildBaseURL` accepts, one per request `op` plus a generic `resourcePath` form.\n * @public\n */\nexport type UrlOptions =\n | FindRecordUrlOptions\n | QueryUrlOptions\n | FindManyUrlOptions\n | FindRelatedCollectionUrlOptions\n | FindRelatedResourceUrlOptions\n | CreateRecordUrlOptions\n | UpdateRecordUrlOptions\n | DeleteRecordUrlOptions\n | GenericUrlOptions;\n\nconst OPERATIONS_WITH_PRIMARY_RECORDS = new Set([\n 'findRecord',\n 'findRelatedRecord',\n 'findRelatedCollection',\n 'updateRecord',\n 'deleteRecord',\n]);\n\nfunction isOperationWithPrimaryRecord(\n options: UrlOptions\n): options is\n | FindRecordUrlOptions\n | FindRelatedCollectionUrlOptions\n | FindRelatedResourceUrlOptions\n | UpdateRecordUrlOptions\n | DeleteRecordUrlOptions {\n return 'op' in options && OPERATIONS_WITH_PRIMARY_RECORDS.has(options.op);\n}\n\nfunction hasResourcePath(options: UrlOptions): options is GenericUrlOptions {\n return 'resourcePath' in options && typeof options.resourcePath === 'string' && options.resourcePath.length > 0;\n}\n\nfunction resourcePathForType(options: UrlOptions): string {\n assert(\n `resourcePathForType: You must pass a valid op as part of options`,\n 'op' in options && typeof options.op === 'string'\n );\n return options.op === 'findMany' ? options.identifiers[0].type : options.identifier.type;\n}\n\n/**\n * Builds a URL for a request based on the provided options.\n * Does not include support for building query params (see `buildQueryParams`)\n * so that it may be composed cleanly with other query-params strategies.\n *\n * See the [Builders](/guides/the-manual/requests/builders) guide for writing your own request\n * builders.\n *\n * Usage:\n *\n * ```ts\n * import { buildBaseURL } from '@warp-drive/utilities';\n *\n * const url = buildBaseURL({\n * host: 'https://api.example.com',\n * namespace: 'api/v1',\n * resourcePath: 'emberDevelopers',\n * op: 'query',\n * identifier: { type: 'ember-developer' }\n * });\n *\n * // => 'https://api.example.com/api/v1/emberDevelopers'\n * ```\n *\n * On the surface this may seem like a lot of work to do something simple, but\n * it is designed to be composable with other utilities and interfaces that the\n * average product engineer will never need to see or use.\n *\n * A few notes:\n *\n * - `resourcePath` is optional, but if it is not provided, `identifier.type` will be used.\n * - `host` and `namespace` are optional, but if they are not provided, the values globally\n * configured via `setBuildURLConfig` will be used.\n * - `op` is required and must be one of the following:\n * - 'findRecord' 'query' 'findMany' 'findRelatedCollection' 'findRelatedRecord'` 'createRecord' 'updateRecord' 'deleteRecord'\n * - Depending on the value of `op`, `identifier` or `identifiers` will be required.\n *\n * @summary Joins host, namespace, resource path, id, and relationship field into a request URL without query params.\n * @public\n */\nexport function buildBaseURL(urlOptions: UrlOptions): string {\n const options = Object.assign(\n {\n host: CONFIG.host,\n namespace: CONFIG.namespace,\n },\n urlOptions\n );\n assert(\n `buildBaseURL: You must pass \\`op\\` as part of options`,\n hasResourcePath(options) || (typeof options.op === 'string' && options.op.length > 0)\n );\n assert(\n `buildBaseURL: You must pass \\`identifier\\` as part of options`,\n hasResourcePath(options) ||\n options.op === 'findMany' ||\n (options.identifier && typeof options.identifier === 'object')\n );\n assert(\n `buildBaseURL: You must pass \\`identifiers\\` as part of options`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n (options.identifiers &&\n Array.isArray(options.identifiers) &&\n options.identifiers.length > 0 &&\n options.identifiers.every((i) => i && typeof i === 'object'))\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifier\\` as part of options, expected 'id'`,\n hasResourcePath(options) ||\n !isOperationWithPrimaryRecord(options) ||\n (typeof options.identifier.id === 'string' && options.identifier.id.length > 0)\n );\n assert(\n `buildBaseURL: You must pass \\`identifiers\\` as part of options`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n options.identifiers.every((i) => typeof i.id === 'string' && i.id.length > 0)\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifier\\` as part of options, expected 'type'`,\n hasResourcePath(options) ||\n options.op === 'findMany' ||\n (typeof options.identifier.type === 'string' && options.identifier.type.length > 0)\n );\n assert(\n `buildBaseURL: You must pass valid \\`identifiers\\` as part of options, expected 'type'`,\n hasResourcePath(options) ||\n options.op !== 'findMany' ||\n (typeof options.identifiers[0].type === 'string' && options.identifiers[0].type.length > 0)\n );\n\n // prettier-ignore\n const idPath: string =\n isOperationWithPrimaryRecord(options) ? encodeURIComponent(options.identifier.id)\n : '';\n const resourcePath = options.resourcePath || resourcePathForType(options);\n const { host, namespace } = options;\n const fieldPath = 'fieldPath' in options ? options.fieldPath : '';\n\n assert(\n `buildBaseURL: You tried to build a url for a ${String(\n 'op' in options ? options.op + ' ' : ''\n )}request to ${resourcePath} but resourcePath must be set or op must be one of \"${[\n 'findRecord',\n 'findRelatedRecord',\n 'findRelatedCollection',\n 'updateRecord',\n 'deleteRecord',\n 'createRecord',\n 'query',\n 'findMany',\n ].join('\",\"')}\".`,\n hasResourcePath(options) ||\n [\n 'findRecord',\n 'query',\n 'findMany',\n 'findRelatedCollection',\n 'findRelatedRecord',\n 'createRecord',\n 'updateRecord',\n 'deleteRecord',\n ].includes(options.op)\n );\n\n assert(`buildBaseURL: host must NOT end with '/', received '${host}'`, host === '/' || !host.endsWith('/'));\n assert(`buildBaseURL: namespace must NOT start with '/', received '${namespace}'`, !namespace.startsWith('/'));\n assert(`buildBaseURL: namespace must NOT end with '/', received '${namespace}'`, !namespace.endsWith('/'));\n assert(\n `buildBaseURL: resourcePath must NOT start with '/', received '${resourcePath}'`,\n !resourcePath.startsWith('/')\n );\n assert(`buildBaseURL: resourcePath must NOT end with '/', received '${resourcePath}'`, !resourcePath.endsWith('/'));\n assert(`buildBaseURL: fieldPath must NOT start with '/', received '${fieldPath}'`, !fieldPath.startsWith('/'));\n assert(`buildBaseURL: fieldPath must NOT end with '/', received '${fieldPath}'`, !fieldPath.endsWith('/'));\n assert(`buildBaseURL: idPath must NOT start with '/', received '${idPath}'`, !idPath.startsWith('/'));\n assert(`buildBaseURL: idPath must NOT end with '/', received '${idPath}'`, !idPath.endsWith('/'));\n\n const hasHost = host !== '' && host !== '/';\n const url = [hasHost ? host : '', namespace, resourcePath, idPath, fieldPath].filter(Boolean).join('/');\n return hasHost ? url : `/${url}`;\n}\n\nconst DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS: QueryParamsSerializationOptions = {\n arrayFormat: 'comma',\n};\n\nfunction handleInclude(include: string | string[]): string[] {\n assert(\n `Expected include to be a string or array, got ${typeof include}`,\n typeof include === 'string' || Array.isArray(include)\n );\n return typeof include === 'string' ? include.split(',') : include;\n}\n\n/**\n * filter out keys of an object that have falsy values or point to empty arrays\n * returning a new object with only those keys that have truthy values / non-empty arrays\n *\n * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for\n * why builders need stable query params.\n *\n * @summary Returns a copy of an object without keys whose values are `undefined`, `null`, empty strings, or empty\n * arrays.\n * @public\n * @param source object to filter keys with empty values from\n * @return A new object with the keys that contained empty values removed\n */\nexport function filterEmpty(source: Record<string, Serializable>): Record<string, Serializable> {\n const result: Record<string, Serializable> = {};\n for (const key in source) {\n const value = source[key];\n // Allow `0` and `false` but filter falsy values that indicate \"empty\"\n if (value !== undefined && value !== null && value !== '') {\n if (!Array.isArray(value) || value.length > 0) {\n result[key] = source[key];\n }\n }\n }\n return result;\n}\n\n/**\n * Sorts query params by both key and value returning a new URLSearchParams\n * object with the keys inserted in sorted order.\n *\n * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for\n * why builders need stable query params.\n *\n * Treats `included` specially, splicing it into an array if it is a string and sorting the array.\n *\n * The `params` given are never mutated: neither the object itself nor any array\n * value it holds is reordered or replaced, so passing state that is also\n * rendered (a tracked array of filter values, for instance) is safe.\n *\n * Options:\n * - arrayFormat: 'bracket' | 'indices' | 'repeat' | 'comma'\n *\n * 'bracket': appends [] to the key for every value e.g. `&ids[]=1&ids[]=2`\n * 'indices': appends [i] to the key for every value e.g. `&ids[0]=1&ids[1]=2`\n * 'repeat': appends the key for every value e.g. `&ids=1&ids=2`\n * 'comma' (default): appends the key once with a comma separated list of values e.g. `&ids=1,2`\n *\n * @summary Builds a `URLSearchParams` with keys and array values sorted, so equivalent queries produce identical\n * params.\n * @public\n * @return A {@link URLSearchParams} with keys inserted in sorted order\n */\nexport function sortQueryParams(params: QueryParamsSource, options?: QueryParamsSerializationOptions): URLSearchParams {\n const opts = Object.assign({}, DEFAULT_QUERY_PARAMS_SERIALIZATION_OPTIONS, options);\n const paramsIsObject = !(params instanceof URLSearchParams);\n const urlParams = new URLSearchParams();\n // shallow copy so that normalizing `include` below does not write back onto\n // the caller's object. Array values are still shared with the caller, so\n // everything downstream must sort copies rather than sorting in place.\n const dictionaryParams: Record<string, Serializable> = paramsIsObject ? Object.assign({}, params) : {};\n\n if (!paramsIsObject) {\n params.forEach((value, key) => {\n const hasExisting = key in dictionaryParams;\n if (!hasExisting) {\n dictionaryParams[key] = value;\n } else {\n const existingValue = dictionaryParams[key];\n if (Array.isArray(existingValue)) {\n existingValue.push(value);\n } else {\n dictionaryParams[key] = [existingValue, value];\n }\n }\n });\n }\n\n if ('include' in dictionaryParams) {\n dictionaryParams.include = handleInclude(dictionaryParams.include as string | string[]);\n }\n\n const sortedKeys = Object.keys(dictionaryParams).sort();\n sortedKeys.forEach((key) => {\n const value = dictionaryParams[key];\n if (Array.isArray(value)) {\n const sortedValue = value.toSorted();\n switch (opts.arrayFormat) {\n case 'indices':\n sortedValue.forEach((v, i) => {\n urlParams.append(`${key}[${i}]`, String(v));\n });\n return;\n case 'bracket':\n sortedValue.forEach((v) => {\n urlParams.append(`${key}[]`, String(v));\n });\n return;\n case 'repeat':\n sortedValue.forEach((v) => {\n urlParams.append(key, String(v));\n });\n return;\n case 'comma':\n default:\n urlParams.append(key, sortedValue.join(','));\n return;\n }\n } else {\n urlParams.append(key, String(value));\n }\n });\n\n return urlParams;\n}\n\n/**\n * Sorts query params by both key and value, returning a query params string\n *\n * See [Cache Keys for Requests](/guides/the-manual/requests/builders#cache-keys-for-requests) for\n * why builders need stable query params.\n *\n * Treats `included` specially, splicing it into an array if it is a string and sorting the array.\n *\n * The `params` given are never mutated: neither the object itself nor any array\n * value it holds is reordered or replaced, so passing state that is also\n * rendered (a tracked array of filter values, for instance) is safe.\n *\n * Options:\n * - arrayFormat: 'bracket' | 'indices' | 'repeat' | 'comma'\n *\n * 'bracket': appends [] to the key for every value e.g. `ids[]=1&ids[]=2`\n * 'indices': appends [i] to the key for every value e.g. `ids[0]=1&ids[1]=2`\n * 'repeat': appends the key for every value e.g. `ids=1&ids=2`\n * 'comma' (default): appends the key once with a comma separated list of values e.g. `ids=1,2`\n *\n * @summary Serializes query params into a string with keys and array values sorted, so equivalent queries share one\n * URL.\n * @public\n * @return A sorted query params string without the leading `?`\n */\nexport function buildQueryParams(params: QueryParamsSource, options?: QueryParamsSerializationOptions): string {\n return sortQueryParams(params, options).toString();\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildBaseURL, buildQueryParams, setBuildURLConfig as setBuildURLConfig$1 } from "./index.js";
|
|
2
|
-
import { s as pluralize } from "./string-
|
|
2
|
+
import { s as pluralize } from "./string-D8BEYx2W.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
|
|
|
@@ -17,6 +17,9 @@ let ACCEPT_HEADER_VALUE = "application/vnd.api+json";
|
|
|
17
17
|
* Extensions and profiles are keyed by their namespace with the value being
|
|
18
18
|
* their URI.
|
|
19
19
|
*
|
|
20
|
+
* The [Basic Usage](/guides/the-manual/cookbook/basic-usage#step-2-configure-some-request-defaults)
|
|
21
|
+
* guide uses it to configure a host, a namespace and a pagination profile.
|
|
22
|
+
*
|
|
20
23
|
* Example:
|
|
21
24
|
*
|
|
22
25
|
* ```ts
|
|
@@ -46,6 +49,8 @@ let ACCEPT_HEADER_VALUE = "application/vnd.api+json";
|
|
|
46
49
|
* }
|
|
47
50
|
* ```
|
|
48
51
|
*
|
|
52
|
+
* @summary Sets the JSON:API extensions and profiles sent in the `Accept` header, plus the global URL host and
|
|
53
|
+
* namespace.
|
|
49
54
|
* @public
|
|
50
55
|
* @param {BuildURLConfig} config
|
|
51
56
|
* @return {void}
|
|
@@ -151,6 +156,9 @@ function postQuery(type, query = {}, options = {}) {
|
|
|
151
156
|
* Builds request options to delete record for resources,
|
|
152
157
|
* configured for the url, method and header expectations of most JSON:API APIs.
|
|
153
158
|
*
|
|
159
|
+
* See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
|
|
160
|
+
* when to use one.
|
|
161
|
+
*
|
|
154
162
|
* **Basic Usage**
|
|
155
163
|
*
|
|
156
164
|
* ```ts
|
|
@@ -192,6 +200,7 @@ function postQuery(type, query = {}, options = {}) {
|
|
|
192
200
|
* const data = await store.request(options);
|
|
193
201
|
* ```
|
|
194
202
|
*
|
|
203
|
+
* @summary Builds a JSON:API `DELETE` request that persists deletion of an existing record.
|
|
195
204
|
* @public
|
|
196
205
|
* @param record
|
|
197
206
|
* @param options
|
|
@@ -225,6 +234,9 @@ function deleteRecord(record, options = {}) {
|
|
|
225
234
|
* Builds request options to create new record for resources,
|
|
226
235
|
* configured for the url, method and header expectations of most JSON:API APIs.
|
|
227
236
|
*
|
|
237
|
+
* See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
|
|
238
|
+
* when to use one.
|
|
239
|
+
*
|
|
228
240
|
* **Basic Usage**
|
|
229
241
|
*
|
|
230
242
|
* ```ts
|
|
@@ -266,6 +278,7 @@ function deleteRecord(record, options = {}) {
|
|
|
266
278
|
* const data = await store.request(options);
|
|
267
279
|
* ```
|
|
268
280
|
*
|
|
281
|
+
* @summary Builds a JSON:API `POST` request for saving a new record; the app must set the request body itself.
|
|
269
282
|
* @public
|
|
270
283
|
* @param record
|
|
271
284
|
* @param options
|
|
@@ -299,6 +312,9 @@ function createRecord(record, options = {}) {
|
|
|
299
312
|
* Builds request options to update existing record for resources,
|
|
300
313
|
* configured for the url, method and header expectations of most JSON:API APIs.
|
|
301
314
|
*
|
|
315
|
+
* See the [Builders](/guides/the-manual/requests/builders) guide for what request builders do and
|
|
316
|
+
* when to use one.
|
|
317
|
+
*
|
|
302
318
|
* **Example Usage**
|
|
303
319
|
*
|
|
304
320
|
* ```ts
|
|
@@ -343,6 +359,8 @@ function createRecord(record, options = {}) {
|
|
|
343
359
|
* const data = await store.request(options);
|
|
344
360
|
* ```
|
|
345
361
|
*
|
|
362
|
+
* @summary Builds a JSON:API `PUT` (or `PATCH`) request for saving an existing record; the app must set the request
|
|
363
|
+
* body itself.
|
|
346
364
|
* @public
|
|
347
365
|
* @param record
|
|
348
366
|
* @param options
|
|
@@ -380,6 +398,8 @@ function updateRecord(record, options = {}) {
|
|
|
380
398
|
*
|
|
381
399
|
* Serializes the current state of a resource or array of resources for use with POST or PUT requests.
|
|
382
400
|
*
|
|
401
|
+
* @summary Copies the cached state of one or more resources into a JSON:API-style `{ data }` object, a starting point
|
|
402
|
+
* for request bodies.
|
|
383
403
|
* @public
|
|
384
404
|
* @param cache - the cache to serialize the resource(s) from
|
|
385
405
|
* @param identifiers - the resource(s) to serialize
|
|
@@ -439,6 +459,8 @@ function _serializeResource(cache, identifier) {
|
|
|
439
459
|
* const relationshipDiffMap = cache.changedRelationships(identifier);
|
|
440
460
|
* ```
|
|
441
461
|
*
|
|
462
|
+
* @summary Serializes only a resource's changed attributes and relationships from the cache into a JSON:API-style
|
|
463
|
+
* `{ data }` patch.
|
|
442
464
|
* @public
|
|
443
465
|
* @param cache - the cache to serialize the resource's changes from
|
|
444
466
|
* @param identifier - the resource whose changes should be serialized
|