@craftainer/crud-sdk 0.3.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/entry.ts","../../src/cache/keys.ts","../../src/cache/scope.ts","../../src/cache/middleware.ts","../../src/cache/memory.ts","../../node_modules/.pnpm/idb@8.0.4/node_modules/idb/build/index.js","../../src/cache/idb.ts","../../src/cache/wire.ts"],"sourcesContent":["/** Turning responses into `CacheEntry` values and back. */\nimport type { CacheEntry } from \"../interfaces.ts\";\n\n/** How the cache answered a read: see `cacheStatus`. */\nexport type CacheStatus = \"hit\" | \"stale\" | \"revalidated\" | \"miss\";\n\nconst statuses = new WeakMap<Response, CacheStatus>();\n\n/**\n * How the cache answered `response`: `hit` (fresh, no network), `stale`\n * (inside the stale-while-revalidate window, refreshed in the background),\n * `revalidated` (a `304` rebuilt from the stored entry) or `miss`.\n * `undefined` when the cache did not handle the request.\n */\nexport function cacheStatus(response: Response): CacheStatus | undefined {\n return statuses.get(response);\n}\n\n/** Tag `response` with how the cache answered. */\nexport function tagStatus(response: Response, status: CacheStatus): Response {\n statuses.set(response, status);\n return response;\n}\n\n// Describe the transfer, not the representation: the stored body is decoded.\nconst TRANSFER_HEADERS: ReadonlySet<string> = new Set([\n \"content-length\",\n \"content-encoding\",\n \"transfer-encoding\",\n \"connection\",\n \"keep-alive\",\n]);\nconst VARY_OK: ReadonlySet<string> = new Set([\n \"authorization\",\n \"accept\",\n \"accept-encoding\",\n]);\n\n/** The lower-cased directive names of the `Cache-Control` header. */\nexport function cacheControl(headers: Headers): ReadonlySet<string> {\n const names = (headers.get(\"cache-control\") ?? \"\")\n .split(\",\")\n .map((part) => (part.split(\"=\", 1)[0] as string).trim().toLowerCase())\n .filter((name) => name !== \"\");\n return new Set(names);\n}\n\n/** Whether the media type is JSON (`application/json` or `+json`). */\nexport function isJson(response: Response): boolean {\n const media = (response.headers.get(\"content-type\") ?? \"\")\n .split(\";\", 1)[0]!\n .trim()\n .toLowerCase();\n return media === \"application/json\" || media.endsWith(\"+json\");\n}\n\n/** `Vary` may only name headers the key already accounts for; `*` never caches. */\nexport function varyAllowsCaching(response: Response): boolean {\n const names = (response.headers.get(\"vary\") ?? \"\")\n .split(\",\")\n .map((part) => part.trim().toLowerCase())\n .filter((name) => name !== \"\");\n return names.every((name) => VARY_OK.has(name));\n}\n\nfunction storableHeaders(headers: Headers): Array<readonly [string, string]> {\n const items: Array<readonly [string, string]> = [];\n for (const [name, value] of headers) {\n if (!TRANSFER_HEADERS.has(name)) {\n items.push([name, value]);\n }\n }\n return items;\n}\n\nexport interface EntryTimes {\n readonly now: number;\n /** Milliseconds fresh. */\n readonly fresh: number;\n /** Milliseconds of stale-while-revalidate after `fresh`. */\n readonly stale: number;\n}\n\n/** Snapshot a read response whose body text was already read. */\nexport function buildEntry(\n response: Response,\n body: string,\n times: EntryTimes,\n keepEtag: boolean,\n): CacheEntry {\n const etag = keepEtag ? response.headers.get(\"etag\") : null;\n const base = {\n status: response.status,\n headers: storableHeaders(response.headers),\n body,\n storedAt: times.now,\n freshUntil: times.now + times.fresh,\n staleUntil: times.now + times.fresh + times.stale,\n };\n return etag === null ? base : { ...base, etag };\n}\n\n/** RFC 9111 4.3.4: headers of a `304` replace the stored ones of the same name. */\nexport function mergeHeaders(\n stored: CacheEntry[\"headers\"],\n update: Headers,\n): CacheEntry[\"headers\"] {\n const fresh = storableHeaders(update);\n const replaced = new Set(fresh.map(([name]) => name));\n return [...stored.filter(([name]) => !replaced.has(name)), ...fresh];\n}\n\n/** Rebuild a response from an entry, tagged with how the cache produced it. */\nexport function toResponse(entry: CacheEntry, status: CacheStatus): Response {\n const headers = new Headers();\n for (const [name, value] of entry.headers) {\n headers.append(name, value);\n }\n return tagStatus(\n new Response(entry.body, { status: entry.status, headers }),\n status,\n );\n}\n","/**\n * Cache keys and the invalidation prefixes.\n *\n * Keys are slash-separated:\n * `v1/{resource}/{kind}/{subject}/{apiVersion}/{scope}/{canonicalQuery}`.\n * Versions of one resource never share an entry, but they share one event\n * stream; because the version comes after kind and subject, one prefix such\n * as `v1/{resource}/record/{id}/` clears an entry in every version and for\n * every caller at once.\n */\nimport type { Op } from \"../interfaces.ts\";\n\n/** The ops that read and are cached (`manifest` has its own key). */\nexport type CacheKind =\n \"list\" | \"record\" | \"stats\" | \"predict\" | \"revisions\" | \"filters\";\n\nconst KINDS: ReadonlySet<string> = new Set<CacheKind>([\n \"list\",\n \"record\",\n \"stats\",\n \"predict\",\n \"revisions\",\n \"filters\",\n]);\nconst STATS_KINDS: ReadonlySet<string> = new Set([\"stats\", \"predict\"]);\nconst COLLECTION_KINDS = [\"list\", \"stats\", \"predict\"] as const;\nconst PER_RECORD_KINDS = [\"record\", \"revisions\"] as const;\n\n/** `true` when the op is a cacheable read kind. */\nexport function isCacheKind(op: Op): op is CacheKind {\n return KINDS.has(op);\n}\n\n/** `true` for `stats` and `predict`, which `stats_ttl` bounds. */\nexport function isStatsKind(kind: string): boolean {\n return STATS_KINDS.has(kind);\n}\n\n/**\n * Sorts the query pairs the client sent and drops `id` (it is the subject).\n * Values are kept verbatim: the client already emits canonical,\n * percent-encoded text, so nothing is decoded or re-encoded here.\n */\nexport function canonicalQuery(query: string): string {\n const text = query.startsWith(\"?\") ? query.slice(1) : query;\n const pairs = text\n .split(\"&\")\n .filter((part) => part !== \"\" && part.split(\"=\", 1)[0] !== \"id\")\n .map((part) => {\n const at = part.indexOf(\"=\");\n return at < 0\n ? ([part, \"\"] as const)\n : ([part.slice(0, at), part.slice(at)] as const);\n });\n pairs.sort(([an, av], [bn, bv]) =>\n an === bn ? (av < bv ? -1 : av > bv ? 1 : 0) : an < bn ? -1 : 1,\n );\n return pairs.map(([name, rest]) => name + rest).join(\"&\");\n}\n\nexport interface KeyParts {\n readonly resource: string;\n readonly kind: CacheKind;\n readonly recordId?: number | undefined;\n readonly scope: string;\n readonly query?: string;\n /** `null` (no version) renders as `_`. */\n readonly apiVersion: string | null;\n}\n\n/** The key of one cached response. */\nexport function buildKey(parts: KeyParts): string {\n const subject =\n (parts.kind === \"record\" || parts.kind === \"revisions\") &&\n parts.recordId !== undefined\n ? String(parts.recordId)\n : \"_\";\n const version = parts.apiVersion ?? \"_\";\n return `v1/${parts.resource}/${parts.kind}/${subject}/${version}/${parts.scope}/${canonicalQuery(parts.query ?? \"\")}`;\n}\n\n/** Key of the manifest document for one caller scope. */\nexport function manifestKey(scope: string): string {\n return `v1/_manifest/${scope}`;\n}\n\n/** Prefix of all entries of one kind for `resource` (every scope and subject). */\nexport function kindPrefix(resource: string, kind: CacheKind): string {\n return `v1/${resource}/${kind}/`;\n}\n\n/** Prefix of the `record` entries of one record (every version, scope, query). */\nexport function recordPrefix(resource: string, recordId: number): string {\n return `v1/${resource}/record/${recordId}/`;\n}\n\n/** Prefix of the `revisions` entries of one record. */\nexport function revisionsPrefix(resource: string, recordId: number): string {\n return `v1/${resource}/revisions/${recordId}/`;\n}\n\n/** The list, stats and predict prefixes of `resource`. */\nexport function collectionPrefixes(resource: string): string[] {\n return COLLECTION_KINDS.map((kind) => kindPrefix(resource, kind));\n}\n\n/** Everything cached for `resource` except `filters` (a reconnect or an unknown change). */\nexport function resourcePrefixes(resource: string): string[] {\n return [...COLLECTION_KINDS, ...PER_RECORD_KINDS].map((kind) =>\n kindPrefix(resource, kind),\n );\n}\n\n/**\n * Prefixes an SSE event clears, per the spec's table: `create` clears the\n * collection kinds; `update`/`delete`/`restore` also clear the record and its\n * revisions; an unknown action or a missing record id clears the resource.\n */\nexport function eventPrefixes(\n resource: string,\n action: string,\n recordId: number | undefined,\n): string[] {\n if (action === \"create\") {\n return collectionPrefixes(resource);\n }\n if (\n (action === \"update\" || action === \"delete\" || action === \"restore\") &&\n recordId !== undefined\n ) {\n return [\n ...collectionPrefixes(resource),\n recordPrefix(resource, recordId),\n revisionsPrefix(resource, recordId),\n ];\n }\n return resourcePrefixes(resource);\n}\n","import { ANON, cacheScope } from \"../auth.ts\";\n\n/**\n * `\"anon\"` when the request has no Authorization header; otherwise the first\n * 32 hex characters of the SHA-256 of the whole credential, the same digest as\n * the client's `cacheScope(token)` (a `Bearer ` prefix is dropped, any other value is hashed as it is). It is read from the\n * request the cache sees, so it must be the innermost middleware that matters:\n * whatever sets Authorization has to run before the cache. The raw credential\n * never enters a key or a log, and the unverified `sub` claim is never used.\n */\nexport async function credentialScope(headers: Headers): Promise<string> {\n const value = headers.get(\"authorization\");\n if (value === null) {\n return ANON;\n }\n const credential = /^bearer\\s+(.+)$/i.exec(value)?.[1] ?? value;\n return cacheScope(credential);\n}\n","/**\n * The caching middleware (`createCache`): an I3 `Middleware` plus the control\n * surface that events and the client use.\n *\n * It reads the resource, op, record id, API version and caller scope from\n * `RequestContext` and never touches the Authorization header. A read is\n * answered in one of four ways, see `cacheStatus`: `hit` (fresh, no\n * network), `stale` (inside the stale-while-revalidate window, refreshed in\n * the background), `revalidated` (a `304` rebuilt from the stored entry with\n * merged headers) or `miss`.\n *\n * Freshness beyond \"revalidate every time\" is granted only while the\n * resource's SSE stream is up (`setEventsActive`): events are the only thing\n * that can end a fresh window early. `stats` and `predict` are bounded by\n * `statsTtl` instead, as the server caches those bodies the same way. A\n * per-resource generation counter stops a response that was in flight when\n * an invalidation landed from being stored fresh.\n */\nimport type {\n CacheEntry,\n MaybePromise,\n CacheStore,\n Middleware,\n Next,\n RequestContext,\n} from \"../interfaces.ts\";\nimport {\n buildEntry,\n cacheControl,\n isJson,\n mergeHeaders,\n tagStatus,\n toResponse,\n varyAllowsCaching,\n} from \"./entry.ts\";\nimport {\n buildKey,\n collectionPrefixes,\n eventPrefixes,\n isCacheKind,\n isStatsKind,\n kindPrefix,\n manifestKey,\n recordPrefix,\n resourcePrefixes,\n revisionsPrefix,\n} from \"./keys.ts\";\nimport type { CacheKind } from \"./keys.ts\";\nimport { cachePolicyFromManifest } from \"./policy.ts\";\nimport { credentialScope } from \"./scope.ts\";\nimport type { CachePolicy } from \"./policy.ts\";\n\n/**\n * A store may clear several prefixes in one pass (one walk, one transaction);\n * the cache uses it when present and falls back to `clear` per prefix.\n */\nexport interface CacheStoreMany extends CacheStore {\n clearMany?(prefixes: readonly string[]): MaybePromise<void>;\n}\n\nexport interface CacheOptions {\n readonly store: CacheStoreMany;\n /**\n * Policy per manifest resource: `name@apiVersion` (built from that version's\n * manifest entry) or, for every version, plain `name`. A resource without\n * one is not cached. The event stream and the freshness gate are per name.\n */\n readonly policies: Readonly<Record<string, CachePolicy>>;\n /** Cache the manifest document per caller scope. Default `true`. */\n readonly cacheManifest?: boolean;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\nexport interface Cache {\n /** Put it in the client's middleware chain. */\n readonly middleware: Middleware;\n /** Record whether the SSE stream of `resource` is connected (gates fresh hits). */\n setEventsActive(resource: string, active: boolean): void;\n eventsActive(resource: string): boolean;\n /** Clear what an SSE event makes stale, in every API version. */\n invalidateEvent(\n resource: string,\n action: string,\n recordId: number | undefined,\n ): Promise<void>;\n /** Clear everything cached for `resource` except `filters` (after a reconnect). */\n clearResource(resource: string): Promise<void>;\n /** Clear keys under `prefix` (everything the store owns when omitted). */\n clear(prefix?: string): Promise<void>;\n /**\n * Adds `If-Match` from the cached record ETag to `PATCH`/`DELETE ?id=` that\n * have none. Put it innermost, next to `middleware`, so it sees the same\n * Authorization (and so scope) the request is sent with.\n */\n readonly autoIfMatch: Middleware;\n /** The stored ETag of a record of one API version, for `auto_if_match`. */\n cachedEtag(\n resource: string,\n recordId: number,\n scope: string,\n apiVersion: string | null,\n ): Promise<string | undefined>;\n /** Abort background revalidations and wait for them. */\n close(): Promise<void>;\n}\n\ntype Kind = CacheKind | \"manifest\";\ntype Decision = \"hit\" | \"stale\" | \"revalidate\" | \"miss\";\ntype StoreOp =\n | { readonly type: \"clear\"; readonly prefixes: readonly string[] }\n | {\n readonly type: \"set\";\n readonly key: string;\n readonly entry: CacheEntry;\n readonly ttlMs: number;\n }\n | { readonly type: \"delete\"; readonly key: string };\n\n/** What `begin` learned about one request. */\ninterface State {\n readonly ctx: RequestContext;\n readonly policy: CachePolicy;\n /** The cache kind of a read; `undefined` for a write. */\n readonly kind: Kind | undefined;\n readonly key: string | undefined;\n /** The resource's invalidation count when the request was made. */\n readonly generation: number;\n /** From the Authorization header of the request as the cache sees it. */\n readonly scope: string;\n readonly forceRevalidate: boolean;\n entry?: CacheEntry | undefined;\n /** Whether we put `If-None-Match` on the request. */\n validated?: boolean;\n}\n\nconst MIN_RETENTION_MS = 3_600_000;\nconst MANIFEST_POLICY: CachePolicy = cachePolicyFromManifest({});\nconst WRITE_METHODS: ReadonlySet<string> = new Set([\n \"POST\",\n \"PATCH\",\n \"PUT\",\n \"DELETE\",\n]);\nconst BULK_OPS: ReadonlySet<string> = new Set([\"bulk_update\", \"bulk_delete\"]);\nconst RECORD_WRITES: ReadonlySet<string> = new Set([\n \"delete\",\n \"publish\",\n \"restore\",\n]);\nconst CREATES: ReadonlySet<string> = new Set([\"create\", \"clone\", \"draft\"]);\n\nfunction isStorable(response: Response): boolean {\n return (\n isJson(response) &&\n !cacheControl(response.headers).has(\"no-store\") &&\n varyAllowsCaching(response)\n );\n}\n\nfunction retentionMs(freshMs: number, staleMs: number): number {\n return Math.max(freshMs + staleMs, MIN_RETENTION_MS);\n}\n\n/** The ids of a bulk response (`{matched, ids}`), or `undefined` if unreadable. */\nfunction idsFromBody(body: string): number[] | undefined {\n try {\n const ids = (JSON.parse(body) as { ids?: unknown }).ids;\n return Array.isArray(ids) && ids.every((id) => Number.isInteger(id))\n ? (ids as number[])\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The resource names in a manifest document, or none if unreadable. */\nfunction manifestResources(body: string): string[] {\n try {\n const resources = (JSON.parse(body) as { resources?: unknown }).resources;\n return Array.isArray(resources)\n ? resources\n .map((item: { name?: unknown }) => item.name)\n .filter((name): name is string => typeof name === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nasync function safe<T>(call: () => T | Promise<T>): Promise<T | undefined> {\n try {\n return await call();\n } catch {\n return undefined; // a failing store is a miss, never a failed request\n }\n}\n\nexport function createCache(options: CacheOptions): Cache {\n const { store } = options;\n const policies = new Map(Object.entries(options.policies));\n const policyFor = (\n resource: string | undefined,\n apiVersion: string | null,\n ): CachePolicy | undefined =>\n resource === undefined\n ? undefined\n : (policies.get(`${resource}@${apiVersion}`) ?? policies.get(resource));\n const cacheManifest = options.cacheManifest ?? true;\n const now = options.now ?? Date.now;\n const eventsActive = new Set<string>();\n const generations = new Map<string, number>();\n const inflight = new Set<string>();\n const tasks = new Set<Promise<void>>();\n const background = new AbortController();\n\n let globalGeneration = 0; // bumped by `clear`, which may span resources\n const generation = (resource: string | undefined): number =>\n (generations.get(resource ?? \"\") ?? 0) + globalGeneration;\n const bump = (resource: string): void => {\n generations.set(resource, generation(resource) + 1);\n };\n\n // -- freshness ---------------------------------------------------------------\n\n /**\n * Whether an entry of `kind` may be served without the network right now:\n * only while the stream is up, except `stats`/`predict`, which `stats_ttl`\n * bounds on its own. Never `filters` (events never clear them) or the manifest.\n */\n const gateOpen = (\n policy: CachePolicy,\n resource: string | undefined,\n kind: Kind,\n ): boolean =>\n !policy.revalidateOnly &&\n kind !== \"manifest\" &&\n kind !== \"filters\" &&\n (isStatsKind(kind) || eventsActive.has(resource as string));\n\n /** Stale-while-revalidate needs the stream for every kind, stats and predict included. */\n const staleOpen = (\n policy: CachePolicy,\n resource: string | undefined,\n kind: Kind,\n ): boolean =>\n gateOpen(policy, resource, kind) && eventsActive.has(resource as string);\n\n /** Milliseconds fresh and of stale-while-revalidate for a new entry; none if the response may predate an invalidation. */\n const entryTimes = (\n state: State,\n kind: Kind,\n ): { fresh: number; stale: number } => {\n const resource = state.ctx.resource;\n if (\n generation(resource) !== state.generation ||\n !gateOpen(state.policy, resource, kind)\n ) {\n return { fresh: 0, stale: 0 };\n }\n const fresh = isStatsKind(kind)\n ? state.policy.statsTtl\n : state.policy.maxAge;\n return {\n fresh: fresh * 1000,\n stale: staleOpen(state.policy, resource, kind)\n ? state.policy.staleWhileRevalidate * 1000\n : 0,\n };\n };\n\n const decide = (state: State, entry: CacheEntry | undefined): Decision => {\n if (entry === undefined) {\n return \"miss\";\n }\n if (\n !state.forceRevalidate &&\n gateOpen(state.policy, state.ctx.resource, state.kind as Kind)\n ) {\n const at = now();\n if (at < entry.freshUntil) {\n return \"hit\";\n }\n if (\n at < entry.staleUntil &&\n staleOpen(state.policy, state.ctx.resource, state.kind as Kind)\n ) {\n return \"stale\";\n }\n }\n return entry.etag !== undefined && state.policy.etag\n ? \"revalidate\"\n : \"miss\";\n };\n\n // -- request side ------------------------------------------------------------\n\n const begin = async (ctx: RequestContext): Promise<State | undefined> => {\n const { request, resource } = ctx;\n const scope = await credentialScope(request.headers);\n const generationNow = generation(resource);\n if (request.method !== \"GET\") {\n if (!WRITE_METHODS.has(request.method)) {\n return undefined; // HEAD, OPTIONS and the like change nothing\n }\n const policy = policyFor(resource, ctx.apiVersion);\n return policy?.enabled === true\n ? {\n ctx,\n policy,\n kind: undefined,\n key: undefined,\n generation: generationNow,\n scope,\n forceRevalidate: false,\n }\n : undefined;\n }\n let policy: CachePolicy | undefined;\n let kind: Kind;\n let key: string;\n if (ctx.op === \"manifest\") {\n if (!cacheManifest) {\n return undefined;\n }\n policy = MANIFEST_POLICY;\n kind = \"manifest\";\n key = manifestKey(scope);\n } else {\n policy = policyFor(resource, ctx.apiVersion);\n if (\n resource === undefined ||\n policy?.enabled !== true ||\n !isCacheKind(ctx.op)\n ) {\n return undefined;\n }\n kind = ctx.op;\n key = buildKey({\n resource,\n kind,\n recordId: ctx.recordId,\n scope,\n query: new URL(request.url).search,\n apiVersion: ctx.apiVersion,\n });\n }\n const control = cacheControl(request.headers);\n if (control.has(\"no-store\") || request.headers.has(\"if-none-match\")) {\n return undefined; // the caller forbids storing or manages validators itself\n }\n return {\n ctx,\n policy,\n kind,\n key,\n generation: generationNow,\n scope,\n forceRevalidate: control.has(\"no-cache\"),\n };\n };\n\n /** A copy of `ctx` whose request carries our validator (a `GET` has no body to lose). */\n const withValidator = (ctx: RequestContext, etag: string): RequestContext => {\n const headers = new Headers(ctx.request.headers);\n headers.set(\"if-none-match\", etag);\n return { ...ctx, request: new Request(ctx.request, { headers }) };\n };\n\n // -- response side -----------------------------------------------------------\n\n const readBody = async (\n state: State,\n response: Response,\n ): Promise<string | undefined> => {\n const { status } = response;\n const { op, recordId } = state.ctx;\n let wanted: boolean;\n if (state.kind !== undefined) {\n wanted = status === 200 && isStorable(response);\n } else if (op === \"update\") {\n wanted = status === 200 && recordId !== undefined && isStorable(response);\n } else {\n wanted =\n status >= 200 &&\n status < 300 &&\n (BULK_OPS.has(op) || (op === \"restore\" && recordId === undefined));\n }\n return wanted ? response.clone().text() : undefined;\n };\n\n /** One clear op for all `prefixes`: the store clears them in a single pass. */\n const clears = (prefixes: readonly string[]): StoreOp[] => [\n { type: \"clear\", prefixes },\n ];\n\n const manifestOps = (\n old: CacheEntry | undefined,\n entry: CacheEntry,\n ): StoreOp[] => {\n if (\n old === undefined ||\n (old.etag !== undefined && old.etag === entry.etag)\n ) {\n return [];\n }\n const names = new Set([\n ...[...policies.keys()].map((key) => key.replace(/@[^@]*$/, \"\")),\n ...manifestResources(old.body),\n ...manifestResources(entry.body),\n ]);\n return clears([...names].sort().map((name) => kindPrefix(name, \"filters\")));\n };\n\n const readPlan = (\n state: State,\n response: Response,\n body: string | undefined,\n ): { result: Response; ops: StoreOp[] } => {\n const key = state.key as string;\n const kind = state.kind as Kind;\n const { status } = response;\n const at = now();\n const { fresh, stale } = entryTimes(state, kind);\n const old = state.entry;\n if (status === 304 && state.validated === true && old !== undefined) {\n // `validated` means the request carried `old.etag`.\n const updated: CacheEntry = {\n ...old,\n headers: mergeHeaders(old.headers, response.headers),\n storedAt: at,\n freshUntil: at + fresh,\n staleUntil: at + fresh + stale,\n etag: response.headers.get(\"etag\") ?? (old.etag as string),\n };\n return {\n result: toResponse(updated, \"revalidated\"),\n ops: [\n {\n type: \"set\",\n key,\n entry: updated,\n ttlMs: retentionMs(fresh, stale),\n },\n ],\n };\n }\n if (status === 200 && body !== undefined) {\n tagStatus(response, \"miss\");\n const entry = buildEntry(\n response,\n body,\n { now: at, fresh, stale },\n state.policy.etag,\n );\n if (entry.etag === undefined && fresh <= 0) {\n // No validator and never fresh: drop the old entry.\n return { result: response, ops: [{ type: \"delete\", key }] };\n }\n const ops: StoreOp[] = [\n { type: \"set\", key, entry, ttlMs: retentionMs(fresh, stale) },\n ];\n if (kind === \"manifest\") {\n ops.push(...manifestOps(old, entry));\n }\n return { result: response, ops };\n }\n if (status === 200 || status === 404 || status === 410) {\n return { result: response, ops: [{ type: \"delete\", key }] };\n }\n return { result: response, ops: [] };\n };\n\n /** Store the PATCH response as the record's entry (it is the read view plus ETag). */\n const writeThrough = (\n state: State,\n response: Response,\n body: string | undefined,\n resource: string,\n recordId: number,\n ): StoreOp[] => {\n if (body === undefined) {\n return [];\n }\n const { fresh, stale } = entryTimes(state, \"record\");\n const entry = buildEntry(\n response,\n body,\n { now: now(), fresh, stale },\n state.policy.etag,\n );\n if (entry.etag === undefined && fresh <= 0) {\n return [];\n }\n const key = buildKey({\n resource,\n kind: \"record\",\n recordId,\n scope: state.scope,\n apiVersion: state.ctx.apiVersion,\n });\n return [{ type: \"set\", key, entry, ttlMs: retentionMs(fresh, stale) }];\n };\n\n const writeOps = (\n state: State,\n response: Response,\n body: string | undefined,\n ): StoreOp[] => {\n const { op, recordId } = state.ctx;\n const resource = state.ctx.resource as string;\n const { status } = response;\n if (status === 412) {\n return recordId === undefined\n ? []\n : clears([recordPrefix(resource, recordId)]);\n }\n if (status < 200 || status >= 300) {\n return [];\n }\n const collections = collectionPrefixes(resource);\n if (CREATES.has(op)) {\n return clears(collections);\n }\n if (recordId !== undefined && (RECORD_WRITES.has(op) || op === \"update\")) {\n return [\n ...clears([\n ...collections,\n revisionsPrefix(resource, recordId),\n recordPrefix(resource, recordId),\n ]),\n ...(op === \"update\"\n ? writeThrough(state, response, body, resource, recordId)\n : []),\n ];\n }\n if (BULK_OPS.has(op) || op === \"restore\") {\n // Bulk ops and a bulk restore always have their body read (see `readBody`).\n const ids = idsFromBody(body as string);\n if (ids !== undefined) {\n return clears([\n ...collections,\n ...ids.flatMap((id) => [\n recordPrefix(resource, id),\n revisionsPrefix(resource, id),\n ]),\n ]);\n }\n }\n return clears(resourcePrefixes(resource));\n };\n\n /** One pass when the store can, else one `clear` per prefix; a failure is a miss. */\n const clearPrefixes = async (prefixes: readonly string[]): Promise<void> => {\n if (store.clearMany !== undefined) {\n await safe(() => store.clearMany?.(prefixes));\n return;\n }\n for (const prefix of prefixes) {\n await safe(() => store.clear(prefix));\n }\n };\n\n const apply = async (op: StoreOp): Promise<void> => {\n await safe(() =>\n op.type === \"clear\"\n ? clearPrefixes(op.prefixes)\n : op.type === \"set\"\n ? store.set(op.key, op.entry, op.ttlMs)\n : store.delete(op.key),\n );\n };\n\n /** Plan, then apply, the store changes a response calls for. */\n const finish = async (\n state: State,\n response: Response,\n ): Promise<Response> => {\n const body = await readBody(state, response);\n const { result, ops } =\n state.kind === undefined\n ? { result: response, ops: writeOps(state, response, body) }\n : readPlan(state, response, body);\n if (state.kind === undefined && ops.some((op) => op.type === \"clear\")) {\n bump(state.ctx.resource as string); // before applying: reads now in flight must not store fresh\n }\n const expected = generation(state.ctx.resource);\n for (const op of ops) {\n await apply(op);\n }\n if (generation(state.ctx.resource) !== expected) {\n // An invalidation landed while storing: drop what was just stored.\n for (const op of ops.filter((candidate) => candidate.type === \"set\")) {\n await apply({ type: \"delete\", key: op.key });\n }\n }\n return result;\n };\n\n // -- stale-while-revalidate --------------------------------------------------\n\n const schedule = (state: State, next: Next): void => {\n const key = state.key as string;\n if (background.signal.aborted || inflight.has(key)) {\n return;\n }\n inflight.add(key);\n const entry = state.entry as CacheEntry;\n const task = (async (): Promise<void> => {\n try {\n const signal = background.signal;\n const headers = new Headers(state.ctx.request.headers);\n if (entry.etag !== undefined) {\n headers.set(\"if-none-match\", entry.etag);\n }\n // Detached from the caller's signal: this outlives their request.\n const request = new Request(state.ctx.request.url, {\n method: \"GET\",\n headers,\n signal,\n });\n const fresh: State = {\n ...state,\n validated: entry.etag !== undefined,\n };\n const response = await next({ ...state.ctx, request, signal });\n const result = await finish(fresh, response);\n await result.body?.cancel();\n } catch {\n // Nothing to report to: the caller already has the stale answer.\n } finally {\n inflight.delete(key);\n }\n })();\n tasks.add(task);\n void task.then(() => tasks.delete(task));\n };\n\n // -- the middleware ----------------------------------------------------------\n\n const middleware: Middleware = async (ctx, next) => {\n const state = await begin(ctx);\n if (state === undefined) {\n return next(ctx);\n }\n if (state.kind === undefined) {\n return finish(state, await next(ctx));\n }\n state.entry = await safe(() => store.get(state.key as string));\n const decision = decide(state, state.entry);\n const entry = state.entry as CacheEntry;\n if (decision === \"hit\") {\n return toResponse(entry, \"hit\");\n }\n if (decision === \"stale\") {\n schedule(state, next);\n return toResponse(entry, \"stale\");\n }\n let sent = ctx;\n if (decision === \"revalidate\") {\n sent = withValidator(ctx, entry.etag as string);\n state.validated = true;\n }\n return finish(state, await next(sent));\n };\n\n // -- control surface ---------------------------------------------------------\n\n const autoIfMatch: Middleware = async (ctx, next) => {\n const { request, resource, recordId } = ctx;\n if (\n (ctx.op === \"update\" || ctx.op === \"delete\") &&\n recordId !== undefined &&\n resource !== undefined &&\n policyFor(resource, ctx.apiVersion)?.enabled === true &&\n !request.headers.has(\"if-match\")\n ) {\n const etag = await lookupEtag(\n resource,\n recordId,\n await credentialScope(request.headers),\n ctx.apiVersion,\n );\n if (etag !== undefined) {\n const headers = new Headers(request.headers);\n headers.set(\"if-match\", etag);\n return next({ ...ctx, request: new Request(request, { headers }) });\n }\n }\n return next(ctx);\n };\n\n const lookupEtag = async (\n resource: string,\n recordId: number,\n scope: string,\n apiVersion: string | null,\n ): Promise<string | undefined> => {\n const entry = await safe(() =>\n store.get(\n buildKey({ resource, kind: \"record\", recordId, scope, apiVersion }),\n ),\n );\n return entry?.etag;\n };\n\n return {\n middleware,\n autoIfMatch,\n setEventsActive(resource, active) {\n if (active) {\n eventsActive.add(resource);\n } else {\n eventsActive.delete(resource);\n }\n },\n eventsActive: (resource) => eventsActive.has(resource),\n async invalidateEvent(resource, action, recordId) {\n bump(resource);\n await clearPrefixes(eventPrefixes(resource, action, recordId));\n },\n async clearResource(resource) {\n bump(resource);\n await clearPrefixes(resourcePrefixes(resource));\n },\n async clear(prefix) {\n globalGeneration += 1; // reads in flight must not store fresh over the clear\n await safe(() => store.clear(prefix));\n },\n cachedEtag: lookupEtag,\n async close() {\n background.abort();\n await Promise.allSettled(tasks);\n },\n };\n}\n","/** In-memory cache store: LRU with a per-entry TTL. */\nimport type { CacheEntry, CacheStore } from \"../interfaces.ts\";\n\n/** How long a store keeps an entry when the caller gives no ttl (the spec's retention floor). */\nexport const DEFAULT_TTL_MS = 3_600_000;\n\nexport interface MemoryStoreOptions {\n /** Default 1024. */\n readonly maxEntries?: number;\n readonly defaultTtlMs?: number;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\n/** LRU store (default 1024 entries) that also honours each entry's ttl. */\nexport class MemoryStore implements CacheStore {\n private readonly data = new Map<\n string,\n { readonly expiresAt: number; readonly entry: CacheEntry }\n >();\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly now: () => number;\n\n constructor(options: MemoryStoreOptions = {}) {\n const maxEntries = options.maxEntries ?? 1024;\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new RangeError(\"maxEntries must be an integer of at least 1\");\n }\n this.maxEntries = maxEntries;\n this.defaultTtlMs = options.defaultTtlMs ?? DEFAULT_TTL_MS;\n this.now = options.now ?? Date.now;\n }\n\n /** Number of stored entries (expired ones included until touched). */\n get size(): number {\n return this.data.size;\n }\n\n get(key: string): CacheEntry | undefined {\n const item = this.data.get(key);\n if (item === undefined) {\n return undefined;\n }\n if (item.expiresAt <= this.now()) {\n this.data.delete(key);\n return undefined;\n }\n this.data.delete(key); // re-insert: Map order is the recency order\n this.data.set(key, item);\n return item.entry;\n }\n\n set(key: string, entry: CacheEntry, ttlMs?: number): void {\n this.data.delete(key);\n this.data.set(key, {\n expiresAt: this.now() + (ttlMs ?? this.defaultTtlMs),\n entry,\n });\n while (this.data.size > this.maxEntries) {\n // The first key is the least recently used.\n this.data.delete(this.data.keys().next().value as string);\n }\n }\n\n delete(key: string): void {\n this.data.delete(key);\n }\n\n /** Remove every key under any of `prefixes` in one walk. */\n clearMany(prefixes: readonly string[]): void {\n for (const key of this.data.keys()) {\n if (prefixes.some((prefix) => key.startsWith(prefix))) {\n this.data.delete(key);\n }\n }\n }\n\n clear(prefix?: string): void {\n for (const key of this.data.keys()) {\n if (prefix === undefined || key.startsWith(prefix)) {\n this.data.delete(key);\n }\n }\n }\n}\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBIndex,\n IDBObjectStore,\n IDBCursor,\n IDBDatabase,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = ['advance', 'continue', 'continuePrimaryKey'].map((method) => IDBCursor.prototype[method])));\n}\nconst transactionDoneMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(this.request);\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (!Object.is(newValue, value)) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = {};\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods[prop])\n return cachedMethods[prop];\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n return (cachedMethods[prop] = method);\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nconst advanceMethodProps = ['advance', 'continue', 'continuePrimaryKey'];\nconst methodMap = {};\nconst advanceResults = new WeakMap();\nconst ittrProxiedCursorToOriginalProxy = new WeakMap();\nconst cursorIteratorTraps = {\n get(target, prop) {\n if (!advanceMethodProps.includes(prop))\n return target[prop];\n let cachedFunc = methodMap[prop];\n if (!cachedFunc) {\n cachedFunc = methodMap[prop] = function (...args) {\n advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));\n };\n }\n return cachedFunc;\n },\n};\nasync function* iterate(...args) {\n // tslint:disable-next-line:no-this-assignment\n let cursor = this;\n if (!(cursor instanceof IDBCursor)) {\n cursor = await cursor.openCursor(...args);\n }\n if (!cursor)\n return;\n cursor = cursor;\n const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);\n ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);\n // Map this double-proxy back to the original, so other cursor methods work.\n reverseTransformCache.set(proxiedCursor, unwrap(cursor));\n while (cursor) {\n yield proxiedCursor;\n // If one of the advancing methods was not called, call continue().\n cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());\n advanceResults.delete(proxiedCursor);\n }\n}\nfunction isIteratorProp(target, prop) {\n return ((prop === Symbol.asyncIterator &&\n instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||\n (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get(target, prop, receiver) {\n if (isIteratorProp(target, prop))\n return iterate;\n return oldTraps.get(target, prop, receiver);\n },\n has(target, prop) {\n return isIteratorProp(target, prop) || oldTraps.has(target, prop);\n },\n}));\n\nexport { deleteDB, openDB, unwrap, wrap };\n","/**\n * IndexedDB cache store (one object store, out-of-line string keys).\n *\n * Every call is wrapped: a blocked database, private mode or a missing\n * `indexedDB` is a miss (or a no-op), never a failed request. `clear(prefix)`\n * is one `IDBKeyRange.bound(prefix, prefix + \"\\uffff\")` delete.\n */\nimport { openDB } from \"idb\";\nimport type { IDBPDatabase } from \"idb\";\nimport type { CacheEntry, CacheStore } from \"../interfaces.ts\";\nimport { DEFAULT_TTL_MS } from \"./memory.ts\";\n\n// The package compiles without the DOM lib; this is the one global it needs.\ndeclare const IDBKeyRange: {\n bound(lower: string, upper: string): unknown;\n};\n\nconst STORE = \"entries\";\n\ninterface Stored {\n readonly expiresAt: number;\n readonly entry: CacheEntry;\n}\n\nexport interface IndexedDbStoreOptions {\n /** Database name; default `craftainer-crud-cache`. */\n readonly name?: string;\n readonly defaultTtlMs?: number;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\nexport class IndexedDbStore implements CacheStore {\n private readonly name: string;\n private readonly defaultTtlMs: number;\n private readonly now: () => number;\n private opening: Promise<IDBPDatabase> | undefined;\n\n constructor(options: IndexedDbStoreOptions = {}) {\n this.name = options.name ?? \"craftainer-crud-cache\";\n if (this.name === \"\") {\n // The name is the per-origin namespace of the store.\n throw new RangeError(\"IndexedDbStore: name must not be empty\");\n }\n this.defaultTtlMs = options.defaultTtlMs ?? DEFAULT_TTL_MS;\n this.now = options.now ?? Date.now;\n }\n\n private open(): Promise<IDBPDatabase> {\n this.opening ??= openDB(this.name, 1, {\n upgrade(db) {\n db.createObjectStore(STORE);\n },\n });\n return this.opening;\n }\n\n /** Run `work` on the database; a failure drops the handle (so a later call retries) and yields `fallback`. */\n private async run<T>(\n fallback: T,\n work: (db: IDBPDatabase) => Promise<T>,\n ): Promise<T> {\n // The handle this call runs on: a concurrent call may already have replaced it.\n let used: Promise<IDBPDatabase> | undefined;\n try {\n used = this.open(); // may throw at once when indexedDB is unusable\n return await work(await used);\n } catch {\n // Drop only our own handle, and close it: an orphaned connection can\n // block an upgrade, while a newer handle belongs to other calls.\n if (this.opening === used) {\n this.opening = undefined;\n }\n try {\n (await used)?.close();\n } catch {\n // It never opened.\n }\n return fallback;\n }\n }\n\n get(key: string): Promise<CacheEntry | undefined> {\n return this.run<CacheEntry | undefined>(undefined, async (db) => {\n const stored = (await db.get(STORE, key)) as Stored | undefined;\n if (stored === undefined) {\n return undefined;\n }\n if (stored.expiresAt <= this.now()) {\n await db.delete(STORE, key);\n return undefined;\n }\n return stored.entry;\n });\n }\n\n set(key: string, entry: CacheEntry, ttlMs?: number): Promise<void> {\n return this.run(undefined, async (db) => {\n const stored: Stored = {\n expiresAt: this.now() + (ttlMs ?? this.defaultTtlMs),\n entry,\n };\n await db.put(STORE, stored, key);\n });\n }\n\n delete(key: string): Promise<void> {\n return this.run(undefined, (db) => db.delete(STORE, key));\n }\n\n clear(prefix?: string): Promise<void> {\n return this.run(undefined, (db) =>\n prefix === undefined\n ? db.clear(STORE)\n : db.delete(STORE, IDBKeyRange.bound(prefix, `${prefix}\\uffff`)),\n );\n }\n\n /** Clear several prefixes in one transaction (one bound range each). */\n clearMany(prefixes: readonly string[]): Promise<void> {\n return this.run(undefined, async (db) => {\n const tx = db.transaction(STORE, \"readwrite\");\n await Promise.all([\n ...prefixes.map((prefix) =>\n tx.store.delete(IDBKeyRange.bound(prefix, `${prefix}\\uffff`)),\n ),\n tx.done,\n ]);\n });\n }\n\n /** Close the database handle; a later call reopens it. */\n async close(): Promise<void> {\n const opening = this.opening;\n this.opening = undefined;\n try {\n (await opening)?.close();\n } catch {\n // Never opened.\n }\n }\n}\n","/** Wiring an event hub to a cache. */\nimport type { EventHub } from \"../events.ts\";\nimport type { Cache } from \"./middleware.ts\";\n\n/** The subscriptions that currently see a connected stream, per cache and resource. */\nconst connected = new WeakMap<Cache, Map<string, Set<object>>>();\n\n/**\n * Subscribe `cache` to `hub` for the manifest resource `resource`. Events\n * clear the spec's prefixes, a reconnect clears the resource, and fresh hits\n * are allowed only while the stream is connected. Returns the unsubscribe\n * (it also closes the freshness gate).\n */\nexport function invalidateOnEvents(\n cache: Cache,\n hub: EventHub,\n resource: string,\n): () => void {\n const byResource = connected.get(cache) ?? new Map<string, Set<object>>();\n connected.set(cache, byResource);\n const live = byResource.get(resource) ?? new Set<object>();\n byResource.set(resource, live);\n const self = {}; // this subscription's identity\n // The gate is open while any subscription sees a connected stream.\n const mark = (up: boolean): void => {\n if (up) {\n live.add(self);\n } else {\n live.delete(self);\n }\n cache.setEventsActive(resource, live.size > 0);\n };\n const unsubscribe = hub.subscribe({\n onEvent: (event) =>\n cache.invalidateEvent(resource, event.action, event.recordId),\n onReset: () => cache.clearResource(resource),\n onStatus: mark,\n });\n return () => {\n try {\n unsubscribe();\n } finally {\n mark(false); // idempotent; the gate stays open while another subscription is connected\n }\n };\n}\n"],"ignoreList":[5],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/cache/entry.ts","../../src/cache/keys.ts","../../src/cache/scope.ts","../../src/cache/middleware.ts","../../src/cache/memory.ts","../../node_modules/.pnpm/idb@8.0.4/node_modules/idb/build/index.js","../../src/cache/idb.ts","../../src/cache/wire.ts"],"sourcesContent":["/** Turning responses into `CacheEntry` values and back. */\nimport type { CacheEntry } from \"../interfaces.ts\";\n\n/** How the cache answered a read: see `cacheStatus`. */\nexport type CacheStatus = \"hit\" | \"stale\" | \"revalidated\" | \"miss\";\n\nconst statuses = new WeakMap<Response, CacheStatus>();\n\n/**\n * How the cache answered `response`: `hit` (fresh, no network), `stale`\n * (inside the stale-while-revalidate window, refreshed in the background),\n * `revalidated` (a `304` rebuilt from the stored entry) or `miss`.\n * `undefined` when the cache did not handle the request.\n */\nexport function cacheStatus(response: Response): CacheStatus | undefined {\n return statuses.get(response);\n}\n\n/** Tag `response` with how the cache answered. */\nexport function tagStatus(response: Response, status: CacheStatus): Response {\n statuses.set(response, status);\n return response;\n}\n\n// Describe the transfer, not the representation: the stored body is decoded.\nconst TRANSFER_HEADERS: ReadonlySet<string> = new Set([\n \"content-length\",\n \"content-encoding\",\n \"transfer-encoding\",\n \"connection\",\n \"keep-alive\",\n]);\nconst VARY_OK: ReadonlySet<string> = new Set([\n \"authorization\",\n \"accept\",\n \"accept-encoding\",\n]);\n\n/** The lower-cased directive names of the `Cache-Control` header. */\nexport function cacheControl(headers: Headers): ReadonlySet<string> {\n const names = (headers.get(\"cache-control\") ?? \"\")\n .split(\",\")\n .map((part) => (part.split(\"=\", 1)[0] as string).trim().toLowerCase())\n .filter((name) => name !== \"\");\n return new Set(names);\n}\n\n/** Whether the media type is JSON (`application/json` or `+json`). */\nexport function isJson(response: Response): boolean {\n const media = (response.headers.get(\"content-type\") ?? \"\")\n .split(\";\", 1)[0]!\n .trim()\n .toLowerCase();\n return media === \"application/json\" || media.endsWith(\"+json\");\n}\n\n/** `Vary` may only name headers the key already accounts for; `*` never caches. */\nexport function varyAllowsCaching(response: Response): boolean {\n const names = (response.headers.get(\"vary\") ?? \"\")\n .split(\",\")\n .map((part) => part.trim().toLowerCase())\n .filter((name) => name !== \"\");\n return names.every((name) => VARY_OK.has(name));\n}\n\nfunction storableHeaders(headers: Headers): Array<readonly [string, string]> {\n const items: Array<readonly [string, string]> = [];\n for (const [name, value] of headers) {\n if (!TRANSFER_HEADERS.has(name)) {\n items.push([name, value]);\n }\n }\n return items;\n}\n\nexport interface EntryTimes {\n readonly now: number;\n /** Milliseconds fresh. */\n readonly fresh: number;\n /** Milliseconds of stale-while-revalidate after `fresh`. */\n readonly stale: number;\n}\n\n/** Snapshot a read response whose body text was already read. */\nexport function buildEntry(\n response: Response,\n body: string,\n times: EntryTimes,\n keepEtag: boolean,\n): CacheEntry {\n const etag = keepEtag ? response.headers.get(\"etag\") : null;\n const base = {\n status: response.status,\n headers: storableHeaders(response.headers),\n body,\n storedAt: times.now,\n freshUntil: times.now + times.fresh,\n staleUntil: times.now + times.fresh + times.stale,\n };\n return etag === null ? base : { ...base, etag };\n}\n\n/** RFC 9111 4.3.4: headers of a `304` replace the stored ones of the same name. */\nexport function mergeHeaders(\n stored: CacheEntry[\"headers\"],\n update: Headers,\n): CacheEntry[\"headers\"] {\n const fresh = storableHeaders(update);\n const replaced = new Set(fresh.map(([name]) => name));\n return [...stored.filter(([name]) => !replaced.has(name)), ...fresh];\n}\n\n/** Rebuild a response from an entry, tagged with how the cache produced it. */\nexport function toResponse(entry: CacheEntry, status: CacheStatus): Response {\n const headers = new Headers();\n for (const [name, value] of entry.headers) {\n headers.append(name, value);\n }\n return tagStatus(\n new Response(entry.body, { status: entry.status, headers }),\n status,\n );\n}\n","/**\n * Cache keys and the invalidation prefixes.\n *\n * Keys are slash-separated:\n * `v1/{resource}/{kind}/{subject}/{apiVersion}/{scope}/{canonicalQuery}`.\n * Versions of one resource never share an entry, but they share one event\n * stream; because the version comes after kind and subject, one prefix such\n * as `v1/{resource}/record/{id}/` clears an entry in every version and for\n * every caller at once.\n */\nimport type { Op } from \"../interfaces.ts\";\n\n/** The ops that read and are cached (`manifest` has its own key). */\nexport type CacheKind =\n \"list\" | \"record\" | \"stats\" | \"predict\" | \"revisions\" | \"filters\";\n\nconst KINDS: ReadonlySet<string> = new Set<CacheKind>([\n \"list\",\n \"record\",\n \"stats\",\n \"predict\",\n \"revisions\",\n \"filters\",\n]);\nconst STATS_KINDS: ReadonlySet<string> = new Set([\"stats\", \"predict\"]);\nconst COLLECTION_KINDS = [\"list\", \"stats\", \"predict\"] as const;\nconst PER_RECORD_KINDS = [\"record\", \"revisions\"] as const;\n\n/** `true` when the op is a cacheable read kind. */\nexport function isCacheKind(op: Op): op is CacheKind {\n return KINDS.has(op);\n}\n\n/** `true` for `stats` and `predict`, which `stats_ttl` bounds. */\nexport function isStatsKind(kind: string): boolean {\n return STATS_KINDS.has(kind);\n}\n\n/**\n * Sorts the query pairs the client sent and drops `id` (it is the subject).\n * Values are kept verbatim: the client already emits canonical,\n * percent-encoded text, so nothing is decoded or re-encoded here.\n */\nexport function canonicalQuery(query: string): string {\n const text = query.startsWith(\"?\") ? query.slice(1) : query;\n const pairs = text\n .split(\"&\")\n .filter((part) => part !== \"\" && part.split(\"=\", 1)[0] !== \"id\")\n .map((part) => {\n const at = part.indexOf(\"=\");\n return at < 0\n ? ([part, \"\"] as const)\n : ([part.slice(0, at), part.slice(at)] as const);\n });\n pairs.sort(([an, av], [bn, bv]) =>\n an === bn ? (av < bv ? -1 : av > bv ? 1 : 0) : an < bn ? -1 : 1,\n );\n return pairs.map(([name, rest]) => name + rest).join(\"&\");\n}\n\nexport interface KeyParts {\n readonly resource: string;\n readonly kind: CacheKind;\n readonly recordId?: number | undefined;\n readonly scope: string;\n readonly query?: string;\n /** `null` (no version) renders as `_`. */\n readonly apiVersion: string | null;\n}\n\n/** The key of one cached response. */\nexport function buildKey(parts: KeyParts): string {\n const subject =\n (parts.kind === \"record\" || parts.kind === \"revisions\") &&\n parts.recordId !== undefined\n ? String(parts.recordId)\n : \"_\";\n const version = parts.apiVersion ?? \"_\";\n return `v1/${parts.resource}/${parts.kind}/${subject}/${version}/${parts.scope}/${canonicalQuery(parts.query ?? \"\")}`;\n}\n\n/** Key of the manifest document for one caller scope. */\nexport function manifestKey(scope: string): string {\n return `v1/_manifest/${scope}`;\n}\n\n/** Prefix of all entries of one kind for `resource` (every scope and subject). */\nexport function kindPrefix(resource: string, kind: CacheKind): string {\n return `v1/${resource}/${kind}/`;\n}\n\n/** Prefix of the `record` entries of one record (every version, scope, query). */\nexport function recordPrefix(resource: string, recordId: number): string {\n return `v1/${resource}/record/${recordId}/`;\n}\n\n/** Prefix of the `revisions` entries of one record. */\nexport function revisionsPrefix(resource: string, recordId: number): string {\n return `v1/${resource}/revisions/${recordId}/`;\n}\n\n/** The list, stats and predict prefixes of `resource`. */\nexport function collectionPrefixes(resource: string): string[] {\n return COLLECTION_KINDS.map((kind) => kindPrefix(resource, kind));\n}\n\n/** Everything cached for `resource` except `filters` (a reconnect or an unknown change). */\nexport function resourcePrefixes(resource: string): string[] {\n return [...COLLECTION_KINDS, ...PER_RECORD_KINDS].map((kind) =>\n kindPrefix(resource, kind),\n );\n}\n\n/**\n * Prefixes an SSE event clears, per the spec's table: `create` clears the\n * collection kinds; `update`/`delete`/`restore` also clear the record and its\n * revisions; an unknown action or a missing record id clears the resource.\n */\nexport function eventPrefixes(\n resource: string,\n action: string,\n recordId: number | undefined,\n): string[] {\n if (action === \"create\") {\n return collectionPrefixes(resource);\n }\n if (\n (action === \"update\" || action === \"delete\" || action === \"restore\") &&\n recordId !== undefined\n ) {\n return [\n ...collectionPrefixes(resource),\n recordPrefix(resource, recordId),\n revisionsPrefix(resource, recordId),\n ];\n }\n return resourcePrefixes(resource);\n}\n","import { ANON, cacheScope } from \"../auth.ts\";\n\n/**\n * `\"anon\"` when the request has no Authorization header; otherwise the first\n * 32 hex characters of the SHA-256 of the whole credential, the same digest as\n * the client's `cacheScope(token)` (a `Bearer ` prefix is dropped, any other value is hashed as it is). It is read from the\n * request the cache sees, so it must be the innermost middleware that matters:\n * whatever sets Authorization has to run before the cache. The raw credential\n * never enters a key or a log, and the unverified `sub` claim is never used.\n */\nexport async function credentialScope(headers: Headers): Promise<string> {\n const value = headers.get(\"authorization\");\n if (value === null) {\n return ANON;\n }\n const credential = /^bearer\\s+(.+)$/i.exec(value)?.[1] ?? value;\n return cacheScope(credential);\n}\n","/**\n * The caching middleware (`createCache`): an I3 `Middleware` plus the control\n * surface that events and the client use.\n *\n * It reads the resource, op, record id, API version and caller scope from\n * `RequestContext` and never touches the Authorization header. A read is\n * answered in one of four ways, see `cacheStatus`: `hit` (fresh, no\n * network), `stale` (inside the stale-while-revalidate window, refreshed in\n * the background), `revalidated` (a `304` rebuilt from the stored entry with\n * merged headers) or `miss`.\n *\n * Freshness beyond \"revalidate every time\" is granted only while the\n * resource's SSE stream is up (`setEventsActive`): events are the only thing\n * that can end a fresh window early. `stats` and `predict` are bounded by\n * `statsTtl` instead, as the server caches those bodies the same way. A\n * per-resource generation counter stops a response that was in flight when\n * an invalidation landed from being stored fresh.\n */\nimport type {\n CacheEntry,\n MaybePromise,\n CacheStore,\n Middleware,\n Next,\n RequestContext,\n} from \"../interfaces.ts\";\nimport {\n buildEntry,\n cacheControl,\n isJson,\n mergeHeaders,\n tagStatus,\n toResponse,\n varyAllowsCaching,\n} from \"./entry.ts\";\nimport {\n buildKey,\n collectionPrefixes,\n eventPrefixes,\n isCacheKind,\n isStatsKind,\n kindPrefix,\n manifestKey,\n recordPrefix,\n resourcePrefixes,\n revisionsPrefix,\n} from \"./keys.ts\";\nimport type { CacheKind } from \"./keys.ts\";\nimport { cachePolicyFromManifest } from \"./policy.ts\";\nimport { credentialScope } from \"./scope.ts\";\nimport type { CachePolicy } from \"./policy.ts\";\n\n/**\n * A store may clear several prefixes in one pass (one walk, one transaction);\n * the cache uses it when present and falls back to `clear` per prefix.\n */\nexport interface CacheStoreMany extends CacheStore {\n clearMany?(prefixes: readonly string[]): MaybePromise<void>;\n}\n\nexport interface CacheOptions {\n readonly store: CacheStoreMany;\n /**\n * Policy per manifest resource: `name@apiVersion` (built from that version's\n * manifest entry) or, for every version, plain `name`. A resource without\n * one is not cached. The event stream and the freshness gate are per name.\n */\n readonly policies: Readonly<Record<string, CachePolicy>>;\n /** Cache the manifest document per caller scope. Default `true`. */\n readonly cacheManifest?: boolean;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\nexport interface Cache {\n /** Put it in the client's middleware chain. */\n readonly middleware: Middleware;\n /** Record whether the SSE stream of `resource` is connected (gates fresh hits). */\n setEventsActive(resource: string, active: boolean): void;\n eventsActive(resource: string): boolean;\n /** Clear what an SSE event makes stale, in every API version. */\n invalidateEvent(\n resource: string,\n action: string,\n recordId: number | undefined,\n ): Promise<void>;\n /** Clear everything cached for `resource` except `filters` (after a reconnect). */\n clearResource(resource: string): Promise<void>;\n /** Clear keys under `prefix` (everything the store owns when omitted). */\n clear(prefix?: string): Promise<void>;\n /**\n * Adds `If-Match` from the cached record ETag to `PATCH`/`DELETE ?id=` that\n * have none. Put it innermost, next to `middleware`, so it sees the same\n * Authorization (and so scope) the request is sent with.\n */\n readonly autoIfMatch: Middleware;\n /** The stored ETag of a record of one API version, for `auto_if_match`. */\n cachedEtag(\n resource: string,\n recordId: number,\n scope: string,\n apiVersion: string | null,\n ): Promise<string | undefined>;\n /** Abort background revalidations and wait for them. */\n close(): Promise<void>;\n}\n\ntype Kind = CacheKind | \"manifest\";\ntype Decision = \"hit\" | \"stale\" | \"revalidate\" | \"miss\";\ntype StoreOp =\n | { readonly type: \"clear\"; readonly prefixes: readonly string[] }\n | {\n readonly type: \"set\";\n readonly key: string;\n readonly entry: CacheEntry;\n readonly ttlMs: number;\n }\n | { readonly type: \"delete\"; readonly key: string };\n\n/** What `begin` learned about one request. */\ninterface State {\n readonly ctx: RequestContext;\n readonly policy: CachePolicy;\n /** The cache kind of a read; `undefined` for a write. */\n readonly kind: Kind | undefined;\n readonly key: string | undefined;\n /** The resource's invalidation count when the request was made. */\n readonly generation: number;\n /** From the Authorization header of the request as the cache sees it. */\n readonly scope: string;\n readonly forceRevalidate: boolean;\n entry?: CacheEntry | undefined;\n /** Whether we put `If-None-Match` on the request. */\n validated?: boolean;\n}\n\nconst MIN_RETENTION_MS = 3_600_000;\nconst MANIFEST_POLICY: CachePolicy = cachePolicyFromManifest({});\nconst WRITE_METHODS: ReadonlySet<string> = new Set([\n \"POST\",\n \"PATCH\",\n \"PUT\",\n \"DELETE\",\n]);\nconst BULK_OPS: ReadonlySet<string> = new Set([\"bulk_update\", \"bulk_delete\"]);\nconst RECORD_WRITES: ReadonlySet<string> = new Set([\n \"delete\",\n \"publish\",\n \"restore\",\n]);\nconst CREATES: ReadonlySet<string> = new Set([\"create\", \"clone\", \"draft\"]);\n\nfunction isStorable(response: Response): boolean {\n return (\n isJson(response) &&\n !cacheControl(response.headers).has(\"no-store\") &&\n varyAllowsCaching(response)\n );\n}\n\nfunction retentionMs(freshMs: number, staleMs: number): number {\n return Math.max(freshMs + staleMs, MIN_RETENTION_MS);\n}\n\n/** The ids of a bulk response (`{matched, ids}`), or `undefined` if unreadable. */\nfunction idsFromBody(body: string): number[] | undefined {\n try {\n const ids = (JSON.parse(body) as { ids?: unknown }).ids;\n return Array.isArray(ids) && ids.every((id) => Number.isInteger(id))\n ? (ids as number[])\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The resource names in a manifest document, or none if unreadable. */\nfunction manifestResources(body: string): string[] {\n try {\n const resources = (JSON.parse(body) as { resources?: unknown }).resources;\n return Array.isArray(resources)\n ? resources\n .map((item: { name?: unknown }) => item.name)\n .filter((name): name is string => typeof name === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nasync function safe<T>(call: () => T | Promise<T>): Promise<T | undefined> {\n try {\n return await call();\n } catch {\n return undefined; // a failing store is a miss, never a failed request\n }\n}\n\nexport function createCache(options: CacheOptions): Cache {\n const { store } = options;\n const policies = new Map(Object.entries(options.policies));\n const policyFor = (\n resource: string | undefined,\n apiVersion: string | null,\n ): CachePolicy | undefined =>\n resource === undefined\n ? undefined\n : (policies.get(`${resource}@${apiVersion}`) ?? policies.get(resource));\n const cacheManifest = options.cacheManifest ?? true;\n const now = options.now ?? Date.now;\n const eventsActive = new Set<string>();\n const generations = new Map<string, number>();\n const inflight = new Set<string>();\n const tasks = new Set<Promise<void>>();\n const background = new AbortController();\n\n let globalGeneration = 0; // bumped by `clear`, which may span resources\n const generation = (resource: string | undefined): number =>\n (generations.get(resource ?? \"\") ?? 0) + globalGeneration;\n const bump = (resource: string): void => {\n generations.set(resource, (generations.get(resource) ?? 0) + 1);\n };\n\n // -- freshness ---------------------------------------------------------------\n\n /**\n * Whether an entry of `kind` may be served without the network right now:\n * only while the stream is up, except `stats`/`predict`, which `stats_ttl`\n * bounds on its own. Never `filters` (events never clear them) or the manifest.\n */\n const gateOpen = (\n policy: CachePolicy,\n resource: string | undefined,\n kind: Kind,\n ): boolean =>\n !policy.revalidateOnly &&\n kind !== \"manifest\" &&\n kind !== \"filters\" &&\n (isStatsKind(kind) || eventsActive.has(resource as string));\n\n /** Stale-while-revalidate needs the stream for every kind, stats and predict included. */\n const staleOpen = (\n policy: CachePolicy,\n resource: string | undefined,\n kind: Kind,\n ): boolean =>\n gateOpen(policy, resource, kind) && eventsActive.has(resource as string);\n\n /** Milliseconds fresh and of stale-while-revalidate for a new entry; none if the response may predate an invalidation. */\n const entryTimes = (\n state: State,\n kind: Kind,\n ): { fresh: number; stale: number } => {\n const resource = state.ctx.resource;\n if (\n generation(resource) !== state.generation ||\n !gateOpen(state.policy, resource, kind)\n ) {\n return { fresh: 0, stale: 0 };\n }\n const fresh = isStatsKind(kind)\n ? state.policy.statsTtl\n : state.policy.maxAge;\n return {\n fresh: fresh * 1000,\n stale: staleOpen(state.policy, resource, kind)\n ? state.policy.staleWhileRevalidate * 1000\n : 0,\n };\n };\n\n const decide = (state: State, entry: CacheEntry | undefined): Decision => {\n if (entry === undefined) {\n return \"miss\";\n }\n if (\n !state.forceRevalidate &&\n gateOpen(state.policy, state.ctx.resource, state.kind as Kind)\n ) {\n const at = now();\n if (at < entry.freshUntil) {\n return \"hit\";\n }\n if (\n at < entry.staleUntil &&\n staleOpen(state.policy, state.ctx.resource, state.kind as Kind)\n ) {\n return \"stale\";\n }\n }\n return entry.etag !== undefined && state.policy.etag\n ? \"revalidate\"\n : \"miss\";\n };\n\n // -- request side ------------------------------------------------------------\n\n const begin = async (ctx: RequestContext): Promise<State | undefined> => {\n const { request, resource } = ctx;\n const scope = await credentialScope(request.headers);\n const generationNow = generation(resource);\n if (request.method !== \"GET\") {\n if (!WRITE_METHODS.has(request.method)) {\n return undefined; // HEAD, OPTIONS and the like change nothing\n }\n const policy = policyFor(resource, ctx.apiVersion);\n return policy?.enabled === true\n ? {\n ctx,\n policy,\n kind: undefined,\n key: undefined,\n generation: generationNow,\n scope,\n forceRevalidate: false,\n }\n : undefined;\n }\n let policy: CachePolicy | undefined;\n let kind: Kind;\n let key: string;\n if (ctx.op === \"manifest\") {\n if (!cacheManifest) {\n return undefined;\n }\n policy = MANIFEST_POLICY;\n kind = \"manifest\";\n key = manifestKey(scope);\n } else {\n policy = policyFor(resource, ctx.apiVersion);\n if (\n resource === undefined ||\n policy?.enabled !== true ||\n !isCacheKind(ctx.op)\n ) {\n return undefined;\n }\n kind = ctx.op;\n key = buildKey({\n resource,\n kind,\n recordId: ctx.recordId,\n scope,\n query: new URL(request.url).search,\n apiVersion: ctx.apiVersion,\n });\n }\n const control = cacheControl(request.headers);\n if (control.has(\"no-store\") || request.headers.has(\"if-none-match\")) {\n return undefined; // the caller forbids storing or manages validators itself\n }\n return {\n ctx,\n policy,\n kind,\n key,\n generation: generationNow,\n scope,\n forceRevalidate: control.has(\"no-cache\"),\n };\n };\n\n /** A copy of `ctx` whose request carries our validator (a `GET` has no body to lose). */\n const withValidator = (ctx: RequestContext, etag: string): RequestContext => {\n const headers = new Headers(ctx.request.headers);\n headers.set(\"if-none-match\", etag);\n return { ...ctx, request: new Request(ctx.request, { headers }) };\n };\n\n // -- response side -----------------------------------------------------------\n\n const readBody = async (\n state: State,\n response: Response,\n ): Promise<string | undefined> => {\n const { status } = response;\n const { op, recordId } = state.ctx;\n let wanted: boolean;\n if (state.kind !== undefined) {\n wanted = status === 200 && isStorable(response);\n } else if (op === \"update\") {\n wanted = status === 200 && recordId !== undefined && isStorable(response);\n } else {\n wanted =\n status >= 200 &&\n status < 300 &&\n (BULK_OPS.has(op) || (op === \"restore\" && recordId === undefined));\n }\n return wanted ? response.clone().text() : undefined;\n };\n\n /** One clear op for all `prefixes`: the store clears them in a single pass. */\n const clears = (prefixes: readonly string[]): StoreOp[] => [\n { type: \"clear\", prefixes },\n ];\n\n const manifestOps = (\n old: CacheEntry | undefined,\n entry: CacheEntry,\n ): StoreOp[] => {\n if (\n old === undefined ||\n (old.etag !== undefined && old.etag === entry.etag)\n ) {\n return [];\n }\n const names = new Set([\n ...[...policies.keys()].map((key) => key.replace(/@[^@]*$/, \"\")),\n ...manifestResources(old.body),\n ...manifestResources(entry.body),\n ]);\n return clears([...names].sort().map((name) => kindPrefix(name, \"filters\")));\n };\n\n const readPlan = (\n state: State,\n response: Response,\n body: string | undefined,\n ): { result: Response; ops: StoreOp[] } => {\n const key = state.key as string;\n const kind = state.kind as Kind;\n const { status } = response;\n const at = now();\n const { fresh, stale } = entryTimes(state, kind);\n const old = state.entry;\n if (status === 304 && state.validated === true && old !== undefined) {\n // `validated` means the request carried `old.etag`.\n const updated: CacheEntry = {\n ...old,\n headers: mergeHeaders(old.headers, response.headers),\n storedAt: at,\n freshUntil: at + fresh,\n staleUntil: at + fresh + stale,\n etag: response.headers.get(\"etag\") ?? (old.etag as string),\n };\n return {\n result: toResponse(updated, \"revalidated\"),\n ops: [\n {\n type: \"set\",\n key,\n entry: updated,\n ttlMs: retentionMs(fresh, stale),\n },\n ],\n };\n }\n if (status === 200 && body !== undefined) {\n tagStatus(response, \"miss\");\n const entry = buildEntry(\n response,\n body,\n { now: at, fresh, stale },\n state.policy.etag,\n );\n if (entry.etag === undefined && fresh <= 0) {\n // No validator and never fresh: drop the old entry.\n return { result: response, ops: [{ type: \"delete\", key }] };\n }\n const ops: StoreOp[] = [\n { type: \"set\", key, entry, ttlMs: retentionMs(fresh, stale) },\n ];\n if (kind === \"manifest\") {\n ops.push(...manifestOps(old, entry));\n }\n return { result: response, ops };\n }\n if (status === 200 || status === 404 || status === 410) {\n return { result: response, ops: [{ type: \"delete\", key }] };\n }\n return { result: response, ops: [] };\n };\n\n /** Store the PATCH response as the record's entry (it is the read view plus ETag). */\n const writeThrough = (\n state: State,\n response: Response,\n body: string | undefined,\n resource: string,\n recordId: number,\n ): StoreOp[] => {\n if (body === undefined) {\n return [];\n }\n const { fresh, stale } = entryTimes(state, \"record\");\n const entry = buildEntry(\n response,\n body,\n { now: now(), fresh, stale },\n state.policy.etag,\n );\n if (entry.etag === undefined && fresh <= 0) {\n return [];\n }\n const key = buildKey({\n resource,\n kind: \"record\",\n recordId,\n scope: state.scope,\n apiVersion: state.ctx.apiVersion,\n });\n return [{ type: \"set\", key, entry, ttlMs: retentionMs(fresh, stale) }];\n };\n\n /**\n * The store changes a write calls for. `response` is `undefined` when the\n * request threw: like a `5xx`, the outcome is unknown (the server may have\n * applied the write before the failure), so it clears what a success would\n * have, minus the write-through, and for a bulk op the whole resource (the\n * ids are unknown). Other failures changed nothing.\n */\n const writeOps = (\n state: State,\n response: Response | undefined,\n body: string | undefined,\n ): StoreOp[] => {\n const { op, recordId } = state.ctx;\n const resource = state.ctx.resource as string;\n const status = response?.status;\n if (status === 412) {\n return recordId === undefined\n ? []\n : clears([recordPrefix(resource, recordId)]);\n }\n const ok = status !== undefined && status >= 200 && status < 300;\n if (!ok && status !== undefined && status < 500) {\n return []; // refused: nothing changed\n }\n const collections = collectionPrefixes(resource);\n if (CREATES.has(op)) {\n return clears(collections);\n }\n if (recordId !== undefined && (RECORD_WRITES.has(op) || op === \"update\")) {\n return [\n ...clears([\n ...collections,\n revisionsPrefix(resource, recordId),\n recordPrefix(resource, recordId),\n ]),\n ...(op === \"update\" && response !== undefined && ok\n ? writeThrough(state, response, body, resource, recordId)\n : []),\n ];\n }\n if (ok && (BULK_OPS.has(op) || op === \"restore\")) {\n // Bulk ops and a bulk restore always have their body read (see `readBody`).\n const ids = idsFromBody(body as string);\n if (ids !== undefined) {\n return clears([\n ...collections,\n ...ids.flatMap((id) => [\n recordPrefix(resource, id),\n revisionsPrefix(resource, id),\n ]),\n ]);\n }\n }\n return clears(resourcePrefixes(resource));\n };\n\n /** One pass when the store can, else one `clear` per prefix; a failure is a miss. */\n const clearPrefixes = async (prefixes: readonly string[]): Promise<void> => {\n if (store.clearMany !== undefined) {\n await safe(() => store.clearMany?.(prefixes));\n return;\n }\n for (const prefix of prefixes) {\n await safe(() => store.clear(prefix));\n }\n };\n\n const apply = async (op: StoreOp): Promise<void> => {\n await safe(() =>\n op.type === \"clear\"\n ? clearPrefixes(op.prefixes)\n : op.type === \"set\"\n ? store.set(op.key, op.entry, op.ttlMs)\n : store.delete(op.key),\n );\n };\n\n /** Plan, then apply, the store changes a response calls for. */\n const finish = async (\n state: State,\n response: Response,\n ): Promise<Response> => {\n const body = await readBody(state, response);\n const { result, ops } =\n state.kind === undefined\n ? { result: response, ops: writeOps(state, response, body) }\n : readPlan(state, response, body);\n await applyOps(state, ops);\n return result;\n };\n\n /** Apply `ops`, guarding against an invalidation that lands meanwhile. */\n const applyOps = async (\n state: State,\n ops: readonly StoreOp[],\n ): Promise<void> => {\n if (state.kind === undefined && ops.some((op) => op.type === \"clear\")) {\n bump(state.ctx.resource as string); // before applying: reads now in flight must not store fresh\n }\n const expected = generation(state.ctx.resource);\n for (const op of ops) {\n await apply(op);\n }\n if (generation(state.ctx.resource) !== expected) {\n // An invalidation landed while storing: drop what was just stored.\n for (const op of ops.filter((candidate) => candidate.type === \"set\")) {\n await apply({ type: \"delete\", key: op.key });\n }\n }\n };\n\n // -- stale-while-revalidate --------------------------------------------------\n\n const schedule = (state: State, next: Next): void => {\n const key = state.key as string;\n if (background.signal.aborted || inflight.has(key)) {\n return;\n }\n inflight.add(key);\n const entry = state.entry as CacheEntry;\n const task = (async (): Promise<void> => {\n try {\n const signal = background.signal;\n const headers = new Headers(state.ctx.request.headers);\n if (entry.etag !== undefined) {\n headers.set(\"if-none-match\", entry.etag);\n }\n // Keeps the caller's init (credentials, redirect, ...) but is detached\n // from their signal: this outlives their request. The cache mode is\n // forced to \"no-store\": a caller's \"force-cache\" or \"only-if-cached\"\n // would be answered from the HTTP cache and never revalidate (Fetch,\n // HTTP-network-or-cache fetch step 16 does the same for a \"default\"\n // request that carries If-None-Match).\n const request = new Request(state.ctx.request, {\n headers,\n signal,\n cache: \"no-store\",\n });\n const fresh: State = {\n ...state,\n validated: entry.etag !== undefined,\n };\n const response = await next({ ...state.ctx, request, signal });\n const result = await finish(fresh, response);\n await result.body?.cancel();\n } catch {\n // Nothing to report to: the caller already has the stale answer.\n } finally {\n inflight.delete(key);\n }\n })();\n tasks.add(task);\n void task.then(() => tasks.delete(task));\n };\n\n // -- the middleware ----------------------------------------------------------\n\n const middleware: Middleware = async (ctx, next) => {\n const state = await begin(ctx);\n if (state === undefined) {\n return next(ctx);\n }\n if (state.kind === undefined) {\n const unsent = ctx.request.signal.aborted; // aborted mid-flight is unknown, not unsent\n let response: Response;\n try {\n response = await next(ctx);\n } catch (error) {\n // The write may have been applied before it failed: forget what it\n // touches, unless it was aborted before it could be sent. The\n // caller still gets the original error.\n if (!unsent) {\n await safe(() =>\n applyOps(state, writeOps(state, undefined, undefined)),\n );\n }\n throw error;\n }\n return finish(state, response);\n }\n state.entry = await safe(() => store.get(state.key as string));\n const decision = decide(state, state.entry);\n const entry = state.entry as CacheEntry;\n if (decision === \"hit\") {\n return toResponse(entry, \"hit\");\n }\n if (decision === \"stale\") {\n schedule(state, next);\n return toResponse(entry, \"stale\");\n }\n let sent = ctx;\n if (decision === \"revalidate\") {\n sent = withValidator(ctx, entry.etag as string);\n state.validated = true;\n }\n return finish(state, await next(sent));\n };\n\n // -- control surface ---------------------------------------------------------\n\n const autoIfMatch: Middleware = async (ctx, next) => {\n const { request, resource, recordId } = ctx;\n if (\n (ctx.op === \"update\" || ctx.op === \"delete\") &&\n recordId !== undefined &&\n resource !== undefined &&\n policyFor(resource, ctx.apiVersion)?.enabled === true &&\n !request.headers.has(\"if-match\")\n ) {\n const etag = await lookupEtag(\n resource,\n recordId,\n await credentialScope(request.headers),\n ctx.apiVersion,\n );\n if (etag !== undefined) {\n const headers = new Headers(request.headers);\n headers.set(\"if-match\", etag);\n return next({ ...ctx, request: new Request(request, { headers }) });\n }\n }\n return next(ctx);\n };\n\n const lookupEtag = async (\n resource: string,\n recordId: number,\n scope: string,\n apiVersion: string | null,\n ): Promise<string | undefined> => {\n const entry = await safe(() =>\n store.get(\n buildKey({ resource, kind: \"record\", recordId, scope, apiVersion }),\n ),\n );\n return entry?.etag;\n };\n\n return {\n middleware,\n autoIfMatch,\n setEventsActive(resource, active) {\n if (active) {\n eventsActive.add(resource);\n } else {\n eventsActive.delete(resource);\n }\n },\n eventsActive: (resource) => eventsActive.has(resource),\n async invalidateEvent(resource, action, recordId) {\n bump(resource);\n await clearPrefixes(eventPrefixes(resource, action, recordId));\n },\n async clearResource(resource) {\n bump(resource);\n await clearPrefixes(resourcePrefixes(resource));\n },\n async clear(prefix) {\n globalGeneration += 1; // reads in flight must not store fresh over the clear\n await safe(() => store.clear(prefix));\n },\n cachedEtag: lookupEtag,\n async close() {\n background.abort();\n await Promise.allSettled(tasks);\n },\n };\n}\n","/** In-memory cache store: LRU with a per-entry TTL. */\nimport type { CacheEntry, CacheStore } from \"../interfaces.ts\";\n\n/** How long a store keeps an entry when the caller gives no ttl (the spec's retention floor). */\nexport const DEFAULT_TTL_MS = 3_600_000;\n\nexport interface MemoryStoreOptions {\n /** Default 1024. */\n readonly maxEntries?: number;\n readonly defaultTtlMs?: number;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\n/** LRU store (default 1024 entries) that also honours each entry's ttl. */\nexport class MemoryStore implements CacheStore {\n private readonly data = new Map<\n string,\n { readonly expiresAt: number; readonly entry: CacheEntry }\n >();\n private readonly maxEntries: number;\n private readonly defaultTtlMs: number;\n private readonly now: () => number;\n\n constructor(options: MemoryStoreOptions = {}) {\n const maxEntries = options.maxEntries ?? 1024;\n if (!Number.isInteger(maxEntries) || maxEntries < 1) {\n throw new RangeError(\"maxEntries must be an integer of at least 1\");\n }\n this.maxEntries = maxEntries;\n this.defaultTtlMs = options.defaultTtlMs ?? DEFAULT_TTL_MS;\n this.now = options.now ?? Date.now;\n }\n\n /** Number of stored entries (expired ones included until touched). */\n get size(): number {\n return this.data.size;\n }\n\n get(key: string): CacheEntry | undefined {\n const item = this.data.get(key);\n if (item === undefined) {\n return undefined;\n }\n if (item.expiresAt <= this.now()) {\n this.data.delete(key);\n return undefined;\n }\n this.data.delete(key); // re-insert: Map order is the recency order\n this.data.set(key, item);\n return item.entry;\n }\n\n set(key: string, entry: CacheEntry, ttlMs?: number): void {\n this.data.delete(key);\n this.data.set(key, {\n expiresAt: this.now() + (ttlMs ?? this.defaultTtlMs),\n entry,\n });\n while (this.data.size > this.maxEntries) {\n // The first key is the least recently used.\n this.data.delete(this.data.keys().next().value as string);\n }\n }\n\n delete(key: string): void {\n this.data.delete(key);\n }\n\n /** Remove every key under any of `prefixes` in one walk. */\n clearMany(prefixes: readonly string[]): void {\n for (const key of this.data.keys()) {\n if (prefixes.some((prefix) => key.startsWith(prefix))) {\n this.data.delete(key);\n }\n }\n }\n\n clear(prefix?: string): void {\n for (const key of this.data.keys()) {\n if (prefix === undefined || key.startsWith(prefix)) {\n this.data.delete(key);\n }\n }\n }\n}\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBIndex,\n IDBObjectStore,\n IDBCursor,\n IDBDatabase,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = ['advance', 'continue', 'continuePrimaryKey'].map((method) => IDBCursor.prototype[method])));\n}\nconst transactionDoneMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n // This mapping exists in reverseTransformCache but doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(this.request);\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (!Object.is(newValue, value)) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = {};\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods[prop])\n return cachedMethods[prop];\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n return (cachedMethods[prop] = method);\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nconst advanceMethodProps = ['advance', 'continue', 'continuePrimaryKey'];\nconst methodMap = {};\nconst advanceResults = new WeakMap();\nconst ittrProxiedCursorToOriginalProxy = new WeakMap();\nconst cursorIteratorTraps = {\n get(target, prop) {\n if (!advanceMethodProps.includes(prop))\n return target[prop];\n let cachedFunc = methodMap[prop];\n if (!cachedFunc) {\n cachedFunc = methodMap[prop] = function (...args) {\n advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));\n };\n }\n return cachedFunc;\n },\n};\nasync function* iterate(...args) {\n // tslint:disable-next-line:no-this-assignment\n let cursor = this;\n if (!(cursor instanceof IDBCursor)) {\n cursor = await cursor.openCursor(...args);\n }\n if (!cursor)\n return;\n cursor = cursor;\n const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);\n ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);\n // Map this double-proxy back to the original, so other cursor methods work.\n reverseTransformCache.set(proxiedCursor, unwrap(cursor));\n while (cursor) {\n yield proxiedCursor;\n // If one of the advancing methods was not called, call continue().\n cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());\n advanceResults.delete(proxiedCursor);\n }\n}\nfunction isIteratorProp(target, prop) {\n return ((prop === Symbol.asyncIterator &&\n instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||\n (prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get(target, prop, receiver) {\n if (isIteratorProp(target, prop))\n return iterate;\n return oldTraps.get(target, prop, receiver);\n },\n has(target, prop) {\n return isIteratorProp(target, prop) || oldTraps.has(target, prop);\n },\n}));\n\nexport { deleteDB, openDB, unwrap, wrap };\n","/**\n * IndexedDB cache store (one object store, out-of-line string keys, an\n * `expiresAt` index).\n *\n * Database version 2 adds the `expiresAt` index; upgrading from version 1\n * keeps every entry (the index is built over the stored values). Expired\n * entries are swept once when the database is opened, and `maxEntries` evicts\n * the entries closest to expiry on `set`.\n *\n * Every call is wrapped: a blocked database, private mode or a missing\n * `indexedDB` is a miss (or a no-op), never a failed request. `clear(prefix)`\n * is one `IDBKeyRange.bound(prefix, prefix + \"\\uffff\")` delete.\n */\nimport { openDB } from \"idb\";\nimport type { IDBPDatabase } from \"idb\";\nimport type { CacheEntry, CacheStore } from \"../interfaces.ts\";\nimport { DEFAULT_TTL_MS } from \"./memory.ts\";\n\n// The package compiles without the DOM lib; this is the one global it needs.\ndeclare const IDBKeyRange: {\n bound(lower: string, upper: string): unknown;\n upperBound(upper: number): unknown;\n};\n\nconst STORE = \"entries\";\nconst BY_EXPIRY = \"expiresAt\";\nconst VERSION = 2;\n\ninterface Stored {\n readonly expiresAt: number;\n readonly entry: CacheEntry;\n}\n\n/**\n * Observes `tx.done` up front: when an operation throws, the aborted\n * transaction's `done` rejects with nobody left to await it, which would be\n * an unhandled rejection.\n */\nfunction guarded<T extends { readonly done: Promise<void> }>(tx: T): T {\n tx.done.catch(() => undefined);\n return tx;\n}\n\nfunction isInvalidState(error: unknown): boolean {\n return (\n typeof error === \"object\" &&\n error !== null &&\n (error as { name?: unknown }).name === \"InvalidStateError\"\n );\n}\n\nexport interface IndexedDbStoreOptions {\n /** Database name; default `craftainer-crud-cache`. */\n readonly name?: string;\n readonly defaultTtlMs?: number;\n /**\n * Upper bound on stored entries, a positive integer; default unbounded.\n * When a `set` leaves more than this many, the entries with the earliest\n * `expiresAt` are deleted until the cap holds (a just-written entry with a\n * short ttl can be among them). The cap is per database and per store\n * instance: two instances on one `name` with different caps each enforce\n * their own on their writes.\n */\n readonly maxEntries?: number;\n /** Epoch milliseconds; injectable for tests. */\n readonly now?: () => number;\n}\n\nexport class IndexedDbStore implements CacheStore {\n private readonly name: string;\n private readonly defaultTtlMs: number;\n private readonly maxEntries: number | undefined;\n private readonly now: () => number;\n private opening: Promise<IDBPDatabase> | undefined;\n\n constructor(options: IndexedDbStoreOptions = {}) {\n this.name = options.name ?? \"craftainer-crud-cache\";\n if (this.name === \"\") {\n // The name is the per-origin namespace of the store.\n throw new RangeError(\"IndexedDbStore: name must not be empty\");\n }\n this.defaultTtlMs = options.defaultTtlMs ?? DEFAULT_TTL_MS;\n if (\n options.maxEntries !== undefined &&\n !(Number.isInteger(options.maxEntries) && options.maxEntries >= 1)\n ) {\n throw new RangeError(\n \"IndexedDbStore: maxEntries must be an integer >= 1\",\n );\n }\n this.maxEntries = options.maxEntries;\n this.now = options.now ?? Date.now;\n }\n\n private open(): Promise<IDBPDatabase> {\n if (this.opening === undefined) {\n // A newer version wants to upgrade: let it, and reopen on the next call.\n const opening: Promise<IDBPDatabase> = this.openAndSweep(() => {\n if (this.opening === opening) {\n this.opening = undefined;\n }\n });\n this.opening = opening;\n }\n return this.opening;\n }\n\n private async openAndSweep(onDropped: () => void): Promise<IDBPDatabase> {\n const db = await openDB(this.name, VERSION, {\n blocking() {\n db.close();\n onDropped();\n },\n // The browser closed the connection (storage cleared, evicted): drop it\n // so the next call opens a fresh one.\n terminated: onDropped,\n upgrade(upgrading, oldVersion, _newVersion, tx) {\n if (oldVersion < 1) {\n upgrading.createObjectStore(STORE);\n }\n // Version 1 had the store but no index; its values already carry expiresAt.\n tx.objectStore(STORE).createIndex(BY_EXPIRY, BY_EXPIRY);\n },\n });\n try {\n await this.sweep(db);\n } catch {\n // A failed sweep leaves the entries for `get` to expire one by one.\n }\n return db;\n }\n\n /** Deletes every entry that has expired (`expiresAt <= now`). */\n private async sweep(db: IDBPDatabase): Promise<void> {\n const tx = guarded(db.transaction(STORE, \"readwrite\"));\n let cursor = await tx.store\n .index(BY_EXPIRY)\n .openCursor(IDBKeyRange.upperBound(this.now()));\n while (cursor !== null) {\n await cursor.delete();\n cursor = await cursor.continue();\n }\n await tx.done;\n }\n\n /**\n * Run `work` on the database; a failure yields `fallback`. The handle is\n * shared by every in-flight call, so it is dropped (and closed) only when it\n * is itself unusable: it never opened, or it was closed under us\n * (`InvalidStateError`). Any other failure belongs to one operation (an\n * uncloneable value, a quota error) and leaves the handle to the others.\n */\n private async run<T>(\n fallback: T,\n work: (db: IDBPDatabase) => Promise<T>,\n ): Promise<T> {\n // The handle this call runs on: a concurrent call may already have replaced it.\n let used: Promise<IDBPDatabase> | undefined;\n let db: IDBPDatabase | undefined;\n try {\n used = this.open(); // may throw at once when indexedDB is unusable\n db = await used;\n return await work(db);\n } catch (error) {\n if (db === undefined || isInvalidState(error)) {\n // Drop only our own handle: a newer one belongs to other calls.\n if (this.opening === used) {\n this.opening = undefined;\n }\n // An orphaned connection can block an upgrade.\n db?.close();\n }\n return fallback;\n }\n }\n\n get(key: string): Promise<CacheEntry | undefined> {\n return this.run<CacheEntry | undefined>(undefined, async (db) => {\n const stored = (await db.get(STORE, key)) as Stored | undefined;\n if (stored === undefined) {\n return undefined;\n }\n if (stored.expiresAt <= this.now()) {\n await db.delete(STORE, key);\n return undefined;\n }\n return stored.entry;\n });\n }\n\n set(key: string, entry: CacheEntry, ttlMs?: number): Promise<void> {\n return this.run(undefined, async (db) => {\n const stored: Stored = {\n expiresAt: this.now() + (ttlMs ?? this.defaultTtlMs),\n entry,\n };\n const tx = guarded(db.transaction(STORE, \"readwrite\"));\n await tx.store.put(stored, key);\n if (this.maxEntries !== undefined) {\n let excess = (await tx.store.count()) - this.maxEntries;\n let cursor =\n excess > 0 ? await tx.store.index(BY_EXPIRY).openCursor() : null;\n while (cursor !== null && excess > 0) {\n await cursor.delete();\n excess -= 1;\n cursor = await cursor.continue();\n }\n }\n await tx.done;\n });\n }\n\n delete(key: string): Promise<void> {\n return this.run(undefined, (db) => db.delete(STORE, key));\n }\n\n clear(prefix?: string): Promise<void> {\n return this.run(undefined, (db) =>\n prefix === undefined\n ? db.clear(STORE)\n : db.delete(STORE, IDBKeyRange.bound(prefix, `${prefix}\\uffff`)),\n );\n }\n\n /** Clear several prefixes in one transaction (one bound range each). */\n clearMany(prefixes: readonly string[]): Promise<void> {\n return this.run(undefined, async (db) => {\n const tx = db.transaction(STORE, \"readwrite\");\n await Promise.all([\n ...prefixes.map((prefix) =>\n tx.store.delete(IDBKeyRange.bound(prefix, `${prefix}\\uffff`)),\n ),\n tx.done,\n ]);\n });\n }\n\n /** Close the database handle; a later call reopens it. */\n async close(): Promise<void> {\n const opening = this.opening;\n this.opening = undefined;\n try {\n (await opening)?.close();\n } catch {\n // Never opened.\n }\n }\n}\n","/** Wiring an event hub to a cache. */\nimport type { EventConnection, EventHub } from \"../events.ts\";\nimport { resetOnce } from \"../reset-once.ts\";\nimport type { Cache } from \"./middleware.ts\";\n\n/** The subscriptions that currently see a connected stream, per cache and resource. */\nconst connected = new WeakMap<Cache, Map<string, Set<object>>>();\n\n/**\n * Subscribe `cache` to `hub` for the manifest resource `resource`. Events\n * clear the spec's prefixes, a reconnect clears the resource, and fresh hits\n * are allowed only while the stream is connected. The cache is cleared once\n * per connection, whenever it subscribes: a subscription that joins an already\n * connected hub (or during the handshake) clears the resource first, unless\n * the cache was already cleared on that connection, and only then opens the\n * gate. Returns the unsubscribe (it also closes the freshness gate).\n */\nexport function invalidateOnEvents(\n cache: Cache,\n hub: EventHub,\n resource: string,\n): () => void {\n const byResource = connected.get(cache) ?? new Map<string, Set<object>>();\n connected.set(cache, byResource);\n const live = byResource.get(resource) ?? new Set<object>();\n byResource.set(resource, live);\n const self = {}; // this subscription's identity\n let latest = 0; // a status change supersedes a gate-opening that awaits its reset\n const reset = (connection: EventConnection): Promise<void> =>\n resetOnce(connection, cache, resource, () => cache.clearResource(resource));\n // The gate is open while any subscription sees a connected stream.\n const mark = (up: boolean): void => {\n if (up) {\n live.add(self);\n } else {\n live.delete(self);\n }\n cache.setEventsActive(resource, live.size > 0);\n };\n const unsubscribe = hub.subscribe({\n onEvent: (event) =>\n cache.invalidateEvent(resource, event.action, event.recordId),\n onReset: (connection) =>\n connection === undefined\n ? cache.clearResource(resource)\n : reset(connection),\n onStatus: async (up, connection) => {\n latest += 1;\n const mine = latest;\n if (up && connection !== undefined) {\n await reset(connection); // a late joiner clears before the gate opens\n if (mine !== latest) {\n return;\n }\n }\n mark(up);\n },\n });\n return () => {\n latest += 1;\n try {\n unsubscribe();\n } finally {\n mark(false); // idempotent; the gate stays open while another subscription is connected\n }\n };\n}\n"],"ignoreList":[5],"mappings":"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"}
@@ -242,7 +242,7 @@ declare class ResourceClient<Read extends object = JsonObject, Create extends ob
242
242
  /** The manifest entry this handle is bound to. */
243
243
  readonly resource: Resource;
244
244
  /** Use `CrudClient.resource`. */
245
- constructor(baseUrl: string, resource: Resource, transport: Transport);
245
+ constructor(baseUrl: string, resource: Resource, transport: Transport, allowedOrigins?: readonly string[]);
246
246
  /** One page of records. */
247
247
  list(options?: ListOptions): Promise<Read[]>;
248
248
  /** Every record, paging with `skip += limit` until a page shorter than `limit`. */
@@ -296,6 +296,23 @@ interface ClientOptions {
296
296
  * cached data, and keep it constant across reloads of a persisted cache.
297
297
  */
298
298
  readonly id?: string | undefined;
299
+ /**
300
+ * Exact origins (`https://files.example`) that manifest-supplied
301
+ * `base_path`, `paths.*` and `events.path` may resolve to besides
302
+ * `baseUrl`'s own. By default such a URL (absolute or protocol-relative) is
303
+ * refused with a `ManifestError`, because the token would follow it. For
304
+ * split-origin deployments only.
305
+ */
306
+ readonly allowedOrigins?: readonly string[] | undefined;
307
+ /**
308
+ * Allow sending an `Authorization` header (the bearer token) over plain
309
+ * `http:` to a host that is not loopback. Default `false`: such a request
310
+ * is refused with {@link InsecureTransportError} before it is sent.
311
+ * Loopback (`localhost`, `*.localhost`, `127.0.0.0/8`, `[::1]`) and
312
+ * anonymous requests are always allowed. Opt in only for a trusted
313
+ * network, as the token is readable on the wire.
314
+ */
315
+ readonly allowInsecureHttp?: boolean | undefined;
299
316
  }
300
317
  /** Discovers the manifest and hands out {@link ResourceClient}s. */
301
318
  declare class CrudClient {
@@ -326,4 +343,4 @@ declare class CrudClient {
326
343
  declare function createClient(options: ClientOptions): CrudClient;
327
344
  //#endregion
328
345
  export { Pagination as A, Capabilities as C, JsonObject as D, GetJson as E, manifestUrlFromCatalog as F, parseManifest as I, Schemas as M, findResource as N, Manifest as O, loadManifest as P, CacheHints as S, FieldSpec as T, encodeParams as _, PredictOptions as a, AuthSpec as b, RestoreOptions as c, createClient as d, Filters as f, buildQuery as g, Sort as h, ListOptions as i, Resource as j, PROFILE as k, StatsOptions as l, QueryParams as m, ClientOptions as n, RequestOptions as o, QueryField as p, CrudClient as r, ResourceClient as s, BulkResult as t, WriteOptions as u, formatScalar as v, EventsSpec as w, CATALOG_PATH as x, quote as y };
329
- //# sourceMappingURL=client-BU9rPr8h.d.mts.map
346
+ //# sourceMappingURL=client-Drvc7htU.d.mts.map
@@ -1,4 +1,4 @@
1
- import { n as cacheScope } from "./auth-DwtNEKrw.mjs";
1
+ import { n as cacheScope, r as isInsecureRemote } from "./auth-C5__H0Ln.mjs";
2
2
  //#region src/interfaces.ts
3
3
  /**
4
4
  * Folds `chain` (outermost first) around `terminal`, usually the network
@@ -33,6 +33,12 @@ var ClientError = class extends CrudError {};
33
33
  var CapabilityError = class extends ClientError {};
34
34
  /** A filter, sort or paging argument is not valid for the resource. */
35
35
  var InvalidQueryError = class extends ClientError {};
36
+ /**
37
+ * A request carrying an `Authorization` header was about to go over plain
38
+ * `http:` to a non-loopback host. Nothing was sent. Use `https:`, or opt in
39
+ * with `allowInsecureHttp`.
40
+ */
41
+ var InsecureTransportError = class extends ClientError {};
36
42
  /** The discovery document or the manifest is missing, unusable or of an unknown version. */
37
43
  var ManifestError = class extends ClientError {};
38
44
  /** The manifest lists no such resource for this caller. */
@@ -380,7 +386,7 @@ function parseManifest(document) {
380
386
  * highest `api_version` (`v2` beats `v1`); `undefined` when there is none.
381
387
  */
382
388
  function findResource(manifest, name, apiVersion) {
383
- const rank = (r) => Number.parseInt(r.api_version.slice(1), 10) || 0;
389
+ const rank = (r) => Number(/^v(\d+)$/.exec(r.api_version)?.[1] ?? 0);
384
390
  let best;
385
391
  for (const r of manifest.resources) if (r.name === name && (apiVersion === void 0 || r.api_version === apiVersion)) {
386
392
  if (best === void 0 || rank(r) > rank(best)) best = r;
@@ -548,19 +554,51 @@ function buildQuery(fields, params = {}) {
548
554
  //#endregion
549
555
  //#region src/client.ts
550
556
  /** Fetch client: manifest-driven resource handles over a middleware chain. */
551
- const joinUrl = (base, path) => new URL(path, base).href;
557
+ /**
558
+ * The origins of `allowedOrigins`, normalised (`https://a.example:443` and
559
+ * `https://a.example` are one). Anything that is not an http(s) origin is a
560
+ * configuration error.
561
+ */
562
+ function normalizeOrigins(origins) {
563
+ const out = /* @__PURE__ */ new Set();
564
+ for (const value of origins) {
565
+ let origin;
566
+ try {
567
+ const url = new URL(value);
568
+ origin = (url.protocol === "http:" || url.protocol === "https:") && url.origin !== "null" ? url.origin : void 0;
569
+ } catch {
570
+ origin = void 0;
571
+ }
572
+ if (origin === void 0) throw new ClientError(`allowedOrigins entry "${value}" is not an http(s) origin`);
573
+ out.add(origin);
574
+ }
575
+ return out;
576
+ }
552
577
  /** Builds the requests of one resource. */
553
578
  var Plan = class {
554
579
  #baseUrl;
580
+ #origins;
555
581
  resource;
556
- constructor(baseUrl, resource) {
582
+ constructor(baseUrl, resource, allowedOrigins) {
557
583
  this.#baseUrl = baseUrl;
584
+ this.#origins = /* @__PURE__ */ new Set([...allowedOrigins, new URL(baseUrl).origin]);
558
585
  this.resource = resource;
559
586
  }
587
+ /**
588
+ * Resolves a manifest-supplied path against `baseUrl`. The client sends its
589
+ * bearer token there, so a path that leaves baseUrl's origin (an absolute
590
+ * URL or a protocol-relative `//host`) is refused unless the client lists
591
+ * that origin in `allowedOrigins`.
592
+ */
593
+ #url(path) {
594
+ const url = new URL(path, this.#baseUrl);
595
+ if (!this.#origins.has(url.origin)) throw new ManifestError(`resource "${this.resource.name}": the manifest path "${path}" points at another origin (${url.origin}), where the token would follow; pass allowedOrigins to the client to allow it`);
596
+ return url.href;
597
+ }
560
598
  #call(method, op, path, query = "", extra = {}) {
561
599
  return {
562
600
  method,
563
- url: joinUrl(this.#baseUrl, path) + (query === "" ? "" : `?${query}`),
601
+ url: this.#url(path) + (query === "" ? "" : `?${query}`),
564
602
  op,
565
603
  resource: this.resource.name,
566
604
  recordId: extra.recordId,
@@ -597,7 +635,7 @@ var Plan = class {
597
635
  eventsUrl() {
598
636
  const events = this.resource.capabilities.events;
599
637
  if (events === null) throw new CapabilityError(`resource "${this.resource.name}" does not support events`);
600
- return joinUrl(this.#baseUrl, events.path);
638
+ return this.#url(events.path);
601
639
  }
602
640
  list(o) {
603
641
  const query = this.#query({
@@ -697,9 +735,9 @@ var ResourceClient = class {
697
735
  #plan;
698
736
  #transport;
699
737
  /** Use `CrudClient.resource`. */
700
- constructor(baseUrl, resource, transport) {
738
+ constructor(baseUrl, resource, transport, allowedOrigins = []) {
701
739
  this.resource = resource;
702
- this.#plan = new Plan(baseUrl, resource);
740
+ this.#plan = new Plan(baseUrl, resource, normalizeOrigins(allowedOrigins));
703
741
  this.#transport = transport;
704
742
  }
705
743
  async #one(call, options) {
@@ -794,7 +832,11 @@ var CrudClient = class {
794
832
  #tokenProvider;
795
833
  #middleware;
796
834
  #manifestUrl;
835
+ #allowedOrigins;
836
+ #allowInsecureHttp;
797
837
  #manifests = /* @__PURE__ */ new Map();
838
+ /** The newest `refresh: true` load per scope, until it settles. */
839
+ #refreshes = /* @__PURE__ */ new Map();
798
840
  #transport;
799
841
  constructor(options) {
800
842
  this.baseUrl = options.baseUrl;
@@ -803,6 +845,9 @@ var CrudClient = class {
803
845
  this.#tokenProvider = options.tokenProvider;
804
846
  this.#middleware = [...options.middleware ?? []];
805
847
  this.#manifestUrl = options.manifestUrl;
848
+ this.#allowedOrigins = [...options.allowedOrigins ?? []];
849
+ this.#allowInsecureHttp = options.allowInsecureHttp ?? false;
850
+ normalizeOrigins(this.#allowedOrigins);
806
851
  this.#transport = {
807
852
  json: async (call, signal) => {
808
853
  const response = await this.#send(call, signal);
@@ -858,6 +903,7 @@ var CrudClient = class {
858
903
  }
859
904
  const request = new Request(call.url, init);
860
905
  const terminal = async (ctx) => {
906
+ if (!this.#allowInsecureHttp && ctx.request.headers.has("Authorization") && isInsecureRemote(ctx.request.url)) throw new InsecureTransportError(`refusing to send credentials over plain http to ${new URL(ctx.request.url).origin}; use https, or pass allowInsecureHttp: true to createClient to allow it`);
861
907
  try {
862
908
  return await (this.#fetch ?? globalThis.fetch)(ctx.request);
863
909
  } catch (error) {
@@ -909,23 +955,34 @@ var CrudClient = class {
909
955
  /** The manifest for this caller, loaded once per caller scope (`refresh: true` reloads). */
910
956
  async manifest(options = {}) {
911
957
  const scope = await this.scope();
912
- let loading = this.#manifests.get(scope);
913
- if (loading === void 0 || options.refresh === true) {
914
- const started = loadManifest((url, catalog) => this.#getJson(url, catalog), this.baseUrl, this.#manifestUrl);
915
- loading = started;
916
- if (!this.#manifests.has(scope)) this.#manifests.clear();
958
+ const current = this.#manifests.get(scope);
959
+ if (current !== void 0 && options.refresh !== true) return current;
960
+ const started = loadManifest((url, catalog) => this.#getJson(url, catalog), this.baseUrl, this.#manifestUrl);
961
+ if (current === void 0) {
962
+ this.#manifests.clear();
917
963
  this.#manifests.set(scope, started);
918
964
  started.catch(() => {
919
965
  if (this.#manifests.get(scope) === started) this.#manifests.delete(scope);
920
966
  });
967
+ } else {
968
+ this.#refreshes.set(scope, started);
969
+ const settle = (loaded) => {
970
+ if (this.#refreshes.get(scope) === started) {
971
+ this.#refreshes.delete(scope);
972
+ if (loaded !== void 0 && (this.#manifests.has(scope) || this.#manifests.size === 0)) this.#manifests.set(scope, Promise.resolve(loaded));
973
+ }
974
+ };
975
+ started.then(settle, () => {
976
+ settle();
977
+ });
921
978
  }
922
- return loading;
979
+ return started;
923
980
  }
924
981
  /** A handle for `name` (the highest `api_version` unless given). */
925
982
  async resource(name, apiVersion) {
926
983
  const resource = findResource(await this.manifest(), name, apiVersion);
927
984
  if (resource === void 0) throw new UnknownResourceError(`the manifest has no resource "${name}"${apiVersion === void 0 ? "" : ` ${apiVersion}`} for this caller`);
928
- return new ResourceClient(this.baseUrl, resource, this.#transport);
985
+ return new ResourceClient(this.baseUrl, resource, this.#transport, this.#allowedOrigins);
929
986
  }
930
987
  };
931
988
  /** Creates a client; the manifest loads on first use. */
@@ -933,6 +990,6 @@ function createClient(options) {
933
990
  return new CrudClient(options);
934
991
  }
935
992
  //#endregion
936
- export { UnknownResourceError as A, NotFound as C, ResponseError as D, RateLimited as E, problemFromResponse as M, composeMiddleware as N, ServerError as O, ManifestError as S, ProblemError as T, ConnectionFailed as _, encodeParams as a, InvalidQueryError as b, CATALOG_PATH as c, loadManifest as d, manifestUrlFromCatalog as f, ClientError as g, CapabilityError as h, buildQuery as i, ValidationFailed as j, Unauthorized as k, PROFILE as l, BadRequest as m, ResourceClient as n, formatScalar as o, parseManifest as p, createClient as r, quote as s, CrudClient as t, findResource as u, CrudError as v, PreconditionFailed as w, Locked as x, Forbidden as y };
993
+ export { Unauthorized as A, ManifestError as C, RateLimited as D, ProblemError as E, ValidationFailed as M, problemFromResponse as N, ResponseError as O, composeMiddleware as P, Locked as S, PreconditionFailed as T, ConnectionFailed as _, encodeParams as a, InsecureTransportError as b, CATALOG_PATH as c, loadManifest as d, manifestUrlFromCatalog as f, ClientError as g, CapabilityError as h, buildQuery as i, UnknownResourceError as j, ServerError as k, PROFILE as l, BadRequest as m, ResourceClient as n, formatScalar as o, parseManifest as p, createClient as r, quote as s, CrudClient as t, findResource as u, CrudError as v, NotFound as w, InvalidQueryError as x, Forbidden as y };
937
994
 
938
- //# sourceMappingURL=client-DJkbw9_q.mjs.map
995
+ //# sourceMappingURL=client-nciUfgwW.mjs.map