@estoreclub/api 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/bridgeClient.d.ts +10 -0
  2. package/dist/bridgeClient.js +10 -0
  3. package/dist/bridgeClient.js.map +7 -0
  4. package/dist/chunks/chunk-2554RPSS.js +45 -0
  5. package/dist/chunks/chunk-2554RPSS.js.map +7 -0
  6. package/dist/chunks/chunk-2BH4QPV2.js +27 -0
  7. package/dist/chunks/chunk-2BH4QPV2.js.map +7 -0
  8. package/dist/chunks/chunk-3KEEGSMP.js +86 -0
  9. package/dist/chunks/chunk-3KEEGSMP.js.map +7 -0
  10. package/dist/chunks/chunk-5DN7L6LX.js +861 -0
  11. package/dist/chunks/chunk-5DN7L6LX.js.map +7 -0
  12. package/dist/chunks/chunk-DUVULZ6N.js +111 -0
  13. package/dist/chunks/chunk-DUVULZ6N.js.map +7 -0
  14. package/dist/chunks/chunk-GJZ7EZEF.js +25 -0
  15. package/dist/chunks/chunk-GJZ7EZEF.js.map +7 -0
  16. package/dist/chunks/chunk-HFEQWXG6.js +24 -0
  17. package/dist/chunks/chunk-HFEQWXG6.js.map +7 -0
  18. package/dist/chunks/chunk-OFFTL6RC.js +24 -0
  19. package/dist/chunks/chunk-OFFTL6RC.js.map +7 -0
  20. package/dist/chunks/chunk-TSERNR4R.js +15 -0
  21. package/dist/chunks/chunk-TSERNR4R.js.map +7 -0
  22. package/dist/chunks/chunk-ZITPZDI7.js +280 -0
  23. package/dist/chunks/chunk-ZITPZDI7.js.map +7 -0
  24. package/dist/client.d.ts +407 -0
  25. package/dist/client.js +14 -0
  26. package/dist/client.js.map +7 -0
  27. package/dist/companyApiKey.d.ts +9 -0
  28. package/dist/companyApiKey.js +9 -0
  29. package/dist/companyApiKey.js.map +7 -0
  30. package/dist/customerToken.d.ts +18 -0
  31. package/dist/customerToken.js +11 -0
  32. package/dist/customerToken.js.map +7 -0
  33. package/dist/errors.d.ts +41 -0
  34. package/dist/errors.js +9 -0
  35. package/dist/errors.js.map +7 -0
  36. package/dist/index.d.ts +82 -0
  37. package/dist/index.js +166 -0
  38. package/dist/index.js.map +7 -0
  39. package/dist/language.d.ts +27 -0
  40. package/dist/language.js +11 -0
  41. package/dist/language.js.map +7 -0
  42. package/dist/params.d.ts +58 -0
  43. package/dist/params.js +27 -0
  44. package/dist/params.js.map +7 -0
  45. package/dist/requestCache.d.ts +46 -0
  46. package/dist/requestCache.js +9 -0
  47. package/dist/requestCache.js.map +7 -0
  48. package/dist/siteData.d.ts +237 -0
  49. package/dist/siteData.js +132 -0
  50. package/dist/siteData.js.map +7 -0
  51. package/dist/types.d.ts +2123 -0
  52. package/dist/types.js +1 -0
  53. package/dist/types.js.map +7 -0
  54. package/dist/visitorCountry.d.ts +25 -0
  55. package/dist/visitorCountry.js +15 -0
  56. package/dist/visitorCountry.js.map +7 -0
  57. package/package.json +64 -4
  58. package/README.md +0 -3
@@ -0,0 +1,10 @@
1
+ import type { PublicApiClient } from "./client";
2
+ /**
3
+ * A `PublicApiClient` that forwards every method over the bridge.
4
+ *
5
+ * Same surface as `createPublicApiClient`, so `siteData` can return either one and page code
6
+ * cannot tell which it got.
7
+ */
8
+ export declare function createBridgeClient(): PublicApiClient;
9
+ /** Is a data bridge installed in this global? Then no key belongs here — use the bridge. */
10
+ export declare function hasServiceBridge(): boolean;
@@ -0,0 +1,10 @@
1
+ import {
2
+ createBridgeClient,
3
+ hasServiceBridge
4
+ } from "./chunks/chunk-DUVULZ6N.js";
5
+ import "./chunks/chunk-GJZ7EZEF.js";
6
+ export {
7
+ createBridgeClient,
8
+ hasServiceBridge
9
+ };
10
+ //# sourceMappingURL=bridgeClient.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,45 @@
1
+ // packages/api/src/visitorCountry.ts
2
+ var resolver = null;
3
+ function setVisitorCountryResolver(next) {
4
+ resolver = next;
5
+ }
6
+ function getVisitorCountryResolver() {
7
+ return resolver;
8
+ }
9
+ function resolveVisitorCountry() {
10
+ if (!resolver) return null;
11
+ try {
12
+ return normalCountry(resolver());
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ function normalCountry(value) {
18
+ if (typeof value !== "string") return null;
19
+ const code = value.trim().toUpperCase();
20
+ return /^[A-Z]{2}$/.test(code) && code !== "XX" ? code : null;
21
+ }
22
+ function countryFromHeaders(header) {
23
+ for (const name of [
24
+ "cloudfront-viewer-country",
25
+ "cf-ipcountry",
26
+ "x-vercel-ip-country"
27
+ ]) {
28
+ const country = normalCountry(header(name));
29
+ if (country) return country;
30
+ }
31
+ for (const part of (header("accept-language") ?? "").split(",")) {
32
+ const region = /^\s*[a-z]{2,3}(?:-[a-z]{4})?-([a-z]{2})\b/i.exec(part);
33
+ if (region) return region[1].toUpperCase();
34
+ }
35
+ return null;
36
+ }
37
+
38
+ export {
39
+ setVisitorCountryResolver,
40
+ getVisitorCountryResolver,
41
+ resolveVisitorCountry,
42
+ normalCountry,
43
+ countryFromHeaders
44
+ };
45
+ //# sourceMappingURL=chunk-2554RPSS.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../packages/api/src/visitorCountry.ts"],
4
+ "sourcesContent": ["/**\n * The country the visitor a render is made for is in, for `getSiteInfo().visitorCountry`.\n *\n * Only the request knows it - the CDN's country header, else the region of the language the\n * browser asks for - and a page's reads are made on the server, per request. So the render tier\n * reads it from the request and sets a resolver here for the length of one render, the same way\n * it sets the language (`language.ts`) and the customer (`customerToken.ts`). A site then writes\n * its own dollar as a bare \"$\" for a visitor at home, and says whose it is to anyone else.\n *\n * Nothing set - a static build, no request - means unknown: null.\n */\nexport type VisitorCountryResolver = () => string | null | undefined;\n\nlet resolver: VisitorCountryResolver | null = null;\n\nexport function setVisitorCountryResolver(\n next: VisitorCountryResolver | null,\n): void {\n resolver = next;\n}\n\n/** The resolver in force, so the render tier can put back exactly what was there. */\nexport function getVisitorCountryResolver(): VisitorCountryResolver | null {\n return resolver;\n}\n\n/** \"NZ\", or null when nobody said. Never throws. */\nexport function resolveVisitorCountry(): string | null {\n if (!resolver) return null;\n\n try {\n return normalCountry(resolver());\n } catch {\n return null;\n }\n}\n\n/** Two letters, upper case - or null for anything else (\"XX\", \"T1\" and the like included). */\nexport function normalCountry(value: unknown): string | null {\n if (typeof value !== \"string\") return null;\n\n const code = value.trim().toUpperCase();\n\n return /^[A-Z]{2}$/.test(code) && code !== \"XX\" ? code : null;\n}\n\n/**\n * The visitor's country from a request's headers: the CDN's own (`cloudfront-viewer-country`,\n * `cf-ipcountry`, `x-vercel-ip-country`), else the region of the first language the browser asks\n * for (\"en-NZ\" \u2192 \"NZ\"). For the render tiers, which hold the request.\n */\nexport function countryFromHeaders(\n header: (name: string) => string | null | undefined,\n): string | null {\n for (const name of [\n \"cloudfront-viewer-country\",\n \"cf-ipcountry\",\n \"x-vercel-ip-country\",\n ]) {\n const country = normalCountry(header(name));\n\n if (country) return country;\n }\n\n for (const part of (header(\"accept-language\") ?? \"\").split(\",\")) {\n const region = /^\\s*[a-z]{2,3}(?:-[a-z]{4})?-([a-z]{2})\\b/i.exec(part);\n\n if (region) return region[1].toUpperCase();\n }\n\n return null;\n}\n"],
5
+ "mappings": ";AAaA,IAAI,WAA0C;AAEvC,SAAS,0BACd,MACM;AACN,aAAW;AACb;AAGO,SAAS,4BAA2D;AACzE,SAAO;AACT;AAGO,SAAS,wBAAuC;AACrD,MAAI,CAAC,SAAU,QAAO;AAEtB,MAAI;AACF,WAAO,cAAc,SAAS,CAAC;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,cAAc,OAA+B;AAC3D,MAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,QAAM,OAAO,MAAM,KAAK,EAAE,YAAY;AAEtC,SAAO,aAAa,KAAK,IAAI,KAAK,SAAS,OAAO,OAAO;AAC3D;AAOO,SAAS,mBACd,QACe;AACf,aAAW,QAAQ;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAG;AACD,UAAM,UAAU,cAAc,OAAO,IAAI,CAAC;AAE1C,QAAI,QAAS,QAAO;AAAA,EACtB;AAEA,aAAW,SAAS,OAAO,iBAAiB,KAAK,IAAI,MAAM,GAAG,GAAG;AAC/D,UAAM,SAAS,6CAA6C,KAAK,IAAI;AAErE,QAAI,OAAQ,QAAO,OAAO,CAAC,EAAE,YAAY;AAAA,EAC3C;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,27 @@
1
+ // packages/api/src/requestCache.ts
2
+ var store = null;
3
+ async function runWithRequestCache(render) {
4
+ const previous = store;
5
+ store = /* @__PURE__ */ new Map();
6
+ try {
7
+ return await render();
8
+ } finally {
9
+ store = previous;
10
+ }
11
+ }
12
+ function memoGet(key, compute) {
13
+ if (!store) return compute();
14
+ const hit = store.get(key);
15
+ if (hit) return hit;
16
+ const pending = compute();
17
+ pending.catch(() => {
18
+ });
19
+ store.set(key, pending);
20
+ return pending;
21
+ }
22
+
23
+ export {
24
+ runWithRequestCache,
25
+ memoGet
26
+ };
27
+ //# sourceMappingURL=chunk-2BH4QPV2.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../packages/api/src/requestCache.ts"],
4
+ "sourcesContent": ["/**\n * A per-render dedup cache for GET reads \u2014 alive ONLY for the duration of one render.\n *\n * `React.cache()` \u2014 which the site's data readers rely on to share one fetch across the many\n * components that need it \u2014 does NOTHING under `react-dom/static.prerender` (it is an RSC-only\n * feature with no cache scope here). Without a replacement, one page render fired the SAME\n * `getDictionary` / `getSiteInfo` / `getProducts` request dozens of times.\n *\n * This is that replacement, at the one place every read passes through. The SSR bootstrap wraps a\n * render in `runWithRequestCache`, which turns the cache ON for exactly that render and OFF again\n * when it settles (success OR error). Outside a render \u2014 the browser, a standalone script, the\n * long-lived multi-tenant RPC server \u2014 the cache is simply absent (`store === null`), so `memoGet`\n * is a pure passthrough and no request state is ever held between renders or shared between them.\n *\n * SAFETY (why a module-level Map does not leak across tenants or parallel calls):\n * - Scope: the Map exists only while a render runs and is dropped in `finally`, so nothing lingers\n * after a render and nothing exists when no render is running.\n * - Tenant: the memo KEY includes the company key + product-sample dataset + language, not just the\n * URL (see `request()` in `client.ts`) \u2014 `getSiteInfo` is `/company` for every tenant, so a\n * URL-only key WOULD collide. Two tenants can never read each other's entry even if a `store`\n * were momentarily shared.\n * - Concurrency: the platform renders each page in its own sandbox process (one render at a time)\n * and the dev server serialises renders, so the module `store` belongs to one render. Even if two\n * renders ever overlapped in one process, `runWithRequestCache` save/restores the previous store\n * and the tenant-scoped keys mean the worst case is a few extra fetches \u2014 never wrong data.\n * - Only reads are memoised (GETs, and the few POSTs that only read - see `READ_POSTS` in\n * `client.ts`); mutations never reach here.\n * - A failed read IS remembered, for this render only: every component asking again would get\n * the same answer - a shop over its daily allowance gets 429 - and each retry is one more\n * counted call. The next render asks afresh.\n *\n * Deliberately dependency-free (no `async_hooks` \u2014 a Node builtin the platform's render sandbox\n * cannot import).\n */\n\nlet store: Map<string, Promise<unknown>> | null = null;\n\n/**\n * Run one render with GET dedup enabled, then tear the cache down.\n *\n * `finally` guarantees the cache is dropped even if the render throws. `previous` is restored\n * rather than forced to `null` so a nested or overlapping scope leaves things as it found them.\n */\nexport async function runWithRequestCache<T>(\n render: () => Promise<T>,\n): Promise<T> {\n const previous = store;\n\n store = new Map();\n\n try {\n return await render();\n } finally {\n store = previous;\n }\n}\n\n/**\n * Return the memoised result for `key`, or compute and store it. When no render scope is active\n * (`store === null`), it just runs `compute` \u2014 no caching, no state, no staleness.\n */\nexport function memoGet<T>(key: string, compute: () => Promise<T>): Promise<T> {\n if (!store) return compute();\n\n const hit = store.get(key) as Promise<T> | undefined;\n\n if (hit) return hit;\n\n const pending = compute();\n\n // Kept even when it fails: within one render a retry gets the same refusal, and costs a call.\n // Marked handled here so a rejection nobody else awaits yet is not reported as unhandled;\n // every caller still receives it.\n pending.catch(() => {});\n store.set(key, pending);\n\n return pending;\n}\n"],
5
+ "mappings": ";AAmCA,IAAI,QAA8C;AAQlD,eAAsB,oBACpB,QACY;AACZ,QAAM,WAAW;AAEjB,UAAQ,oBAAI,IAAI;AAEhB,MAAI;AACF,WAAO,MAAM,OAAO;AAAA,EACtB,UAAE;AACA,YAAQ;AAAA,EACV;AACF;AAMO,SAAS,QAAW,KAAa,SAAuC;AAC7E,MAAI,CAAC,MAAO,QAAO,QAAQ;AAE3B,QAAM,MAAM,MAAM,IAAI,GAAG;AAEzB,MAAI,IAAK,QAAO;AAEhB,QAAM,UAAU,QAAQ;AAKxB,UAAQ,MAAM,MAAM;AAAA,EAAC,CAAC;AACtB,QAAM,IAAI,KAAK,OAAO;AAEtB,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,86 @@
1
+ // packages/api/src/params.ts
2
+ var MAX_LIMIT = 200;
3
+ function segment(value, name) {
4
+ const raw = typeof value === "number" && Number.isInteger(value) && value > 0 ? String(value) : typeof value === "string" ? value : "";
5
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(raw)) {
6
+ throw new Error(`invalid ${name}`);
7
+ }
8
+ return raw;
9
+ }
10
+ function label(value, name) {
11
+ const raw = (typeof value === "string" ? value : "").trim();
12
+ if (!raw || raw.length > 128 || /[\u0000-\u001f\u007f]/.test(raw)) {
13
+ throw new Error(`invalid ${name}`);
14
+ }
15
+ return raw;
16
+ }
17
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18
+ function idOrName(params) {
19
+ const { id, name, identifier } = params ?? {};
20
+ const hasId = id !== void 0 && id !== null && id !== "";
21
+ const hasName = name !== void 0 && name !== null && name !== "";
22
+ const hasIdentifier = identifier !== void 0 && identifier !== null && identifier !== "";
23
+ if (hasId && hasName) throw new Error("pass id or name, not both");
24
+ if (hasIdentifier && (hasId || hasName)) {
25
+ throw new Error("pass one of identifier, id or name");
26
+ }
27
+ if (hasIdentifier) return { identifier: label(identifier, "identifier") };
28
+ if (hasId) {
29
+ if (typeof id !== "string" || !UUID.test(id)) throw new Error("invalid id");
30
+ return { id };
31
+ }
32
+ return { name: label(name, "name") };
33
+ }
34
+ function include(value, allowed) {
35
+ if (value === void 0) return { include: allowed.join(",") };
36
+ if (!Array.isArray(value)) throw new Error("invalid include");
37
+ const wanted = [...new Set(value)];
38
+ if (wanted.some((part) => !allowed.includes(part))) {
39
+ throw new Error("invalid include");
40
+ }
41
+ return wanted.length ? { include: wanted.join(",") } : {};
42
+ }
43
+ function paging(params) {
44
+ const out = {};
45
+ const limit = Number(params.limit);
46
+ const page = Number(params.page);
47
+ if (Number.isFinite(limit) && limit > 0) {
48
+ out.limit = String(Math.min(Math.floor(limit), MAX_LIMIT));
49
+ }
50
+ if (Number.isFinite(page) && page > 0) out.page = String(Math.floor(page));
51
+ return out;
52
+ }
53
+ function text(key, value) {
54
+ return typeof value === "string" && value ? { [key]: value } : {};
55
+ }
56
+ function oneOf(key, value, allowed) {
57
+ return typeof value === "string" && allowed.includes(value) ? { [key]: value } : {};
58
+ }
59
+ function whole(key, value, min, max) {
60
+ const number = Number(value);
61
+ if (value === void 0 || value === null || !Number.isFinite(number)) {
62
+ return {};
63
+ }
64
+ return { [key]: String(Math.min(max, Math.max(min, Math.floor(number)))) };
65
+ }
66
+ function flag(key, value) {
67
+ return value === true ? { [key]: "true" } : {};
68
+ }
69
+ function trimSlash(value) {
70
+ return value.replace(/\/+$/, "");
71
+ }
72
+
73
+ export {
74
+ MAX_LIMIT,
75
+ segment,
76
+ label,
77
+ idOrName,
78
+ include,
79
+ paging,
80
+ text,
81
+ oneOf,
82
+ whole,
83
+ flag,
84
+ trimSlash
85
+ };
86
+ //# sourceMappingURL=chunk-3KEEGSMP.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../packages/api/src/params.ts"],
4
+ "sourcesContent": ["/**\n * Turning a caller's parameters into a query string, defensively.\n *\n * Written for the worst caller there is: page code inside the sandbox, which can send any\n * JSON at all. Every value is checked here rather than trusted, because the check that\n * matters is the one on this side of the boundary.\n */\n\n/** The largest page the API will be asked for, whatever a caller requests. */\nexport const MAX_LIMIT = 200;\n\n/**\n * A path segment supplied by a caller goes into a URL, so it is validated rather than\n * escaped: an id is a slug, a UUID or an entity's number, and anything else is a mistake or an attempt to reach a\n * different endpoint by writing `../`.\n */\nexport function segment(value: unknown, name: string): string {\n // A number is an entity's own number (a location's `2`, a booking's `12`): the API takes it\n // wherever it takes the id.\n const raw =\n typeof value === \"number\" && Number.isInteger(value) && value > 0\n ? String(value)\n : typeof value === \"string\"\n ? value\n : \"\";\n\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(raw)) {\n throw new Error(`invalid ${name}`);\n }\n\n return raw;\n}\n\n/**\n * A NAME a caller looks something up by \u2014 a category, a product, one of the shop's lists.\n *\n * `segment` is for ids, which are `[A-Za-z0-9_-]` by construction. A name is not: it is free\n * text a shop typed, in whatever language it writes in, and \"Pizza sizes\", \"A\u00F1adidos\" and\n * \"\u041F\u0438\u0446\u0446\u044B\" are all ordinary ones. Refusing them is how a page asking for a real category got\n * \"invalid name\" thrown at it.\n *\n * These names travel in the QUERY STRING, where `URLSearchParams` does the encoding \u2014 so this\n * only has to refuse what is not a name at all: nothing, something absurdly long, or a\n * control character.\n */\nexport function label(value: unknown, name: string): string {\n const raw = (typeof value === \"string\" ? value : \"\").trim();\n\n if (!raw || raw.length > 128 || /[\\u0000-\\u001f\\u007f]/.test(raw)) {\n throw new Error(`invalid ${name}`);\n }\n\n return raw;\n}\n\n/** An id a caller looks something up by. The API's ids are UUIDs, and nothing else is one. */\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * `{ id }` or `{ name }` as query parameters \u2014 exactly one of them.\n *\n * Refused here rather than left to the server's 400: a page that passes both has a bug the\n * server would report as \"data unavailable (400)\", which reads like an outage.\n */\nexport function idOrName(params: unknown): Record<string, string> {\n const { id, name, identifier } = (params ?? {}) as {\n id?: unknown;\n name?: unknown;\n identifier?: unknown;\n };\n const hasId = id !== undefined && id !== null && id !== \"\";\n const hasName = name !== undefined && name !== null && name !== \"\";\n const hasIdentifier =\n identifier !== undefined && identifier !== null && identifier !== \"\";\n\n if (hasId && hasName) throw new Error(\"pass id or name, not both\");\n\n if (hasIdentifier && (hasId || hasName)) {\n throw new Error(\"pass one of identifier, id or name\");\n }\n\n // First, because it is the one a page should be written against \u2014 see `GetDictionaryParams`.\n // Only lists are read this way; the helper is shared, and the other rows have no identifier,\n // so anything passing one here is asking for a list.\n if (hasIdentifier) return { identifier: label(identifier, \"identifier\") };\n\n if (hasId) {\n if (typeof id !== \"string\" || !UUID.test(id)) throw new Error(\"invalid id\");\n\n return { id };\n }\n\n return { name: label(name, \"name\") };\n}\n\n/**\n * The `include` parameter: which optional parts of a response to carry.\n *\n * `undefined` means EVERY part in `allowed` \u2014 the shape a page written before `include`\n * existed relies on. An empty list means none, and sends nothing (the API's own default).\n * Anything outside `allowed` is refused rather than dropped, so a typo cannot silently cost\n * a page its images.\n */\nexport function include<T extends string>(\n value: readonly T[] | undefined,\n allowed: readonly T[],\n): Record<string, string> {\n if (value === undefined) return { include: allowed.join(\",\") };\n\n if (!Array.isArray(value)) throw new Error(\"invalid include\");\n\n const wanted = [...new Set(value)];\n\n if (wanted.some((part) => !allowed.includes(part))) {\n throw new Error(\"invalid include\");\n }\n\n return wanted.length ? { include: wanted.join(\",\") } : {};\n}\n\n/** Bound the paging so one call cannot ask for an entire catalogue. */\nexport function paging(params: {\n page?: unknown;\n limit?: unknown;\n}): Record<string, string> {\n const out: Record<string, string> = {};\n const limit = Number(params.limit);\n const page = Number(params.page);\n\n if (Number.isFinite(limit) && limit > 0) {\n out.limit = String(Math.min(Math.floor(limit), MAX_LIMIT));\n }\n\n if (Number.isFinite(page) && page > 0) out.page = String(Math.floor(page));\n\n return out;\n}\n\n/** A string parameter, included only when it is one. */\nexport function text(key: string, value: unknown): Record<string, string> {\n return typeof value === \"string\" && value ? { [key]: value } : {};\n}\n\n/** One of a fixed set of words, included only when it is one of them. */\nexport function oneOf(\n key: string,\n value: unknown,\n allowed: readonly string[],\n): Record<string, string> {\n return typeof value === \"string\" && allowed.includes(value)\n ? { [key]: value }\n : {};\n}\n\n/** A whole number within bounds, included only when it is one \u2014 clamped, not refused. */\nexport function whole(\n key: string,\n value: unknown,\n min: number,\n max: number,\n): Record<string, string> {\n const number = Number(value);\n\n if (value === undefined || value === null || !Number.isFinite(number)) {\n return {};\n }\n\n return { [key]: String(Math.min(max, Math.max(min, Math.floor(number)))) };\n}\n\n/** A boolean parameter, included only when it is explicitly true. */\nexport function flag(key: string, value: unknown): Record<string, string> {\n return value === true ? { [key]: \"true\" } : {};\n}\n\nexport function trimSlash(value: string): string {\n return value.replace(/\\/+$/, \"\");\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }