@craftainer/crud-sdk 0.0.0-stage → 0.2.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.
@@ -0,0 +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 try {\n return await work(await this.open());\n } catch {\n // Close the handle we drop: an orphaned connection can block an upgrade.\n const dropped = this.opening;\n this.opening = undefined;\n try {\n (await dropped)?.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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"}
@@ -0,0 +1,316 @@
1
+ import { a as Middleware, l as TokenProvider, s as Op } from "./interfaces-s73cVfIF.mjs";
2
+ //#region src/manifest.d.ts
3
+ declare const PROFILE = "https://craftainer.github.io/schemas/crud-manifest/v1";
4
+ declare const CATALOG_PATH = "/.well-known/api-catalog";
5
+ /** A JSON object. */
6
+ type JsonObject = Record<string, unknown>;
7
+ /** One filterable and sortable field. */
8
+ interface FieldSpec {
9
+ readonly name: string;
10
+ readonly value_type: string;
11
+ readonly filter_ops: readonly string[];
12
+ readonly sortable: boolean;
13
+ readonly choices: readonly string[] | null;
14
+ readonly numeric: boolean;
15
+ readonly categorical: boolean;
16
+ readonly read_only: boolean;
17
+ readonly required: boolean;
18
+ }
19
+ /** Offset paging parameters and bounds. */
20
+ interface Pagination {
21
+ readonly style: string;
22
+ readonly skip_param: string;
23
+ readonly limit_param: string;
24
+ readonly sort_param: string;
25
+ readonly default_limit: number;
26
+ readonly max_limit: number;
27
+ readonly total: boolean;
28
+ }
29
+ /** The SSE stream of a resource. */
30
+ interface EventsSpec {
31
+ readonly transport: string;
32
+ readonly path: string;
33
+ /** The server-side event label (not the manifest name). */
34
+ readonly resource: string;
35
+ readonly resume_param: string;
36
+ readonly replay: string;
37
+ readonly keepalive_seconds: number;
38
+ readonly actions: readonly string[];
39
+ }
40
+ /** What the resource supports. */
41
+ interface Capabilities {
42
+ readonly bulk_update: boolean;
43
+ readonly bulk_delete: boolean;
44
+ readonly clone: boolean;
45
+ readonly draft_publish: boolean;
46
+ readonly archive_restore: boolean;
47
+ readonly revisions: boolean;
48
+ readonly events: EventsSpec | null;
49
+ readonly stats: boolean;
50
+ readonly predict: boolean;
51
+ readonly conditional_requests: boolean;
52
+ readonly owner_scoped: boolean;
53
+ readonly schedulable: boolean;
54
+ }
55
+ /** Authentication scheme and roles. */
56
+ interface AuthSpec {
57
+ readonly scheme: string;
58
+ readonly oidc_discovery_url: string | null;
59
+ readonly public_read: boolean;
60
+ readonly read_roles: readonly string[];
61
+ readonly write_roles: readonly string[];
62
+ readonly delete_roles: readonly string[];
63
+ }
64
+ /** The manifest `cache` hints of a resource. */
65
+ interface CacheHints {
66
+ readonly etag: boolean;
67
+ readonly max_age: number;
68
+ readonly stats_ttl: number | null;
69
+ readonly stale_while_revalidate: number;
70
+ readonly list_etag: string | null;
71
+ readonly record_etag: string | null;
72
+ readonly revalidate_only: boolean;
73
+ }
74
+ /** JSON Schemas of the record views (plain objects). */
75
+ interface Schemas {
76
+ readonly read: JsonObject;
77
+ readonly create: JsonObject;
78
+ readonly update: JsonObject;
79
+ readonly draft: JsonObject | null;
80
+ }
81
+ /** One resource (`name` + `api_version`). */
82
+ interface Resource {
83
+ readonly name: string;
84
+ readonly title: string;
85
+ readonly api_version: string;
86
+ readonly base_path: string;
87
+ readonly formats: Readonly<Record<string, string>>;
88
+ readonly id_field: string;
89
+ readonly schemas: Schemas;
90
+ readonly fields: readonly FieldSpec[];
91
+ readonly pagination: Pagination;
92
+ readonly capabilities: Capabilities;
93
+ readonly paths: Readonly<Record<string, string>>;
94
+ readonly bulk: {
95
+ readonly max_matched: number;
96
+ };
97
+ readonly auth: AuthSpec;
98
+ readonly cache: CacheHints;
99
+ }
100
+ /** The manifest document, as filtered to the caller. */
101
+ interface Manifest {
102
+ readonly manifest_version: string;
103
+ readonly service: {
104
+ readonly name: string;
105
+ readonly version: string;
106
+ };
107
+ readonly resources: readonly Resource[];
108
+ }
109
+ /** Parses a manifest document; unknown members are ignored, unknown versions refused. */
110
+ declare function parseManifest(document: unknown): Manifest;
111
+ /**
112
+ * The resource called `name`: the one with `apiVersion` when given, else the
113
+ * highest `api_version` (`v2` beats `v1`); `undefined` when there is none.
114
+ */
115
+ declare function findResource(manifest: Manifest, name: string, apiVersion?: string): Resource | undefined;
116
+ /** The absolute manifest URL: the first `describedby` link with the manifest profile. */
117
+ declare function manifestUrlFromCatalog(linkset: unknown, catalogUrl: string): string;
118
+ /** Fetches a JSON document; `catalog` marks the api-catalog request (kept out of the manifest cache). */
119
+ type GetJson = (url: string, catalog: boolean) => Promise<unknown>;
120
+ /**
121
+ * Loads and parses the manifest, via api-catalog discovery from `baseUrl` or
122
+ * a direct `manifestUrl`.
123
+ */
124
+ declare function loadManifest(getJson: GetJson, baseUrl: string, manifestUrl?: string): Promise<Manifest>;
125
+ //#endregion
126
+ //#region src/query.d.ts
127
+ /** The part of a manifest field the builder needs. */
128
+ type QueryField = Pick<FieldSpec, "name" | "value_type" | "filter_ops" | "sortable" | "choices">;
129
+ /** Filter arguments: `field` (equality, emitted bare) or `field__op` -> value. */
130
+ type Filters = Readonly<Record<string, unknown>>;
131
+ /** `"name,-other"` or `["name", "-other"]`. */
132
+ type Sort = string | readonly string[];
133
+ /** Everything {@link buildQuery} takes beside the fields. */
134
+ interface QueryParams {
135
+ readonly recordId?: number | undefined;
136
+ readonly filters?: Filters | undefined;
137
+ readonly sort?: Sort | undefined;
138
+ readonly skip?: number | undefined;
139
+ readonly limit?: number | undefined;
140
+ readonly maxLimit?: number | undefined;
141
+ readonly skipParam?: string | undefined;
142
+ readonly limitParam?: string | undefined;
143
+ readonly sortParam?: string | undefined;
144
+ readonly includeArchived?: boolean | undefined;
145
+ readonly includeUnpublished?: boolean | undefined;
146
+ }
147
+ /** Percent-encodes everything but the RFC 3986 unreserved characters `A-Za-z0-9-._~`. */
148
+ declare function quote(text: string): string;
149
+ /** Joins already-stringified pairs, encoding keys and values. */
150
+ declare function encodeParams(pairs: readonly (readonly [string, string])[]): string;
151
+ /**
152
+ * Wire form of one value for `field`; throws `InvalidQueryError` on a type
153
+ * mismatch. Dates are `YYYY-MM-DD` strings, datetimes `Date`s (UTC, with
154
+ * millisecond precision, trailing zeros trimmed).
155
+ */
156
+ declare function formatScalar(field: QueryField, value: unknown): string;
157
+ /**
158
+ * The query string (no leading `?`) for a list, get or bulk call. Order of
159
+ * parameters: `id`, filters sorted by (field, operator), sort, skip, limit,
160
+ * `include_archived`, `include_unpublished`. The sort/skip/limit names are
161
+ * `sortParam`/`skipParam`/`limitParam` (the manifest's `pagination`); the
162
+ * `id` parameter is always `id`. Flags are emitted whenever they are not
163
+ * `undefined`.
164
+ */
165
+ declare function buildQuery(fields: readonly QueryField[], params?: QueryParams): string;
166
+ //#endregion
167
+ //#region src/client.d.ts
168
+ /** Per-call options. */
169
+ interface RequestOptions {
170
+ readonly signal?: AbortSignal | undefined;
171
+ }
172
+ /** Options of `list` and `iterAll`. */
173
+ interface ListOptions extends RequestOptions {
174
+ readonly filters?: Filters | undefined;
175
+ readonly sort?: Sort | undefined;
176
+ readonly skip?: number | undefined;
177
+ readonly limit?: number | undefined;
178
+ readonly includeArchived?: boolean | undefined;
179
+ readonly includeUnpublished?: boolean | undefined;
180
+ }
181
+ /** Options of `update` and `delete`: `ifMatch` is an ETag (`412` when stale). */
182
+ interface WriteOptions extends RequestOptions {
183
+ readonly ifMatch?: string | undefined;
184
+ }
185
+ /** Options of `stats`. */
186
+ interface StatsOptions extends RequestOptions {
187
+ readonly bucket?: string | undefined;
188
+ readonly includeArchived?: boolean | undefined;
189
+ readonly includeUnpublished?: boolean | undefined;
190
+ }
191
+ /** Options of `predict`. */
192
+ interface PredictOptions extends RequestOptions {
193
+ readonly field?: string | undefined;
194
+ readonly periods?: number | undefined;
195
+ readonly bucket?: string | undefined;
196
+ }
197
+ /** Options of `restore`: exactly one of `id` and `filters`. */
198
+ interface RestoreOptions extends RequestOptions {
199
+ readonly id?: number | undefined;
200
+ readonly filters?: Filters | undefined;
201
+ }
202
+ /** The result of a bulk call. */
203
+ interface BulkResult {
204
+ readonly matched: number;
205
+ readonly ids: readonly number[];
206
+ }
207
+ interface Call {
208
+ readonly method: "GET" | "POST" | "PATCH" | "DELETE";
209
+ readonly url: string;
210
+ readonly op: Op;
211
+ readonly resource: string | undefined;
212
+ readonly recordId: number | undefined;
213
+ readonly apiVersion: string | null;
214
+ readonly json?: unknown;
215
+ readonly headers?: Readonly<Record<string, string>>;
216
+ }
217
+ /** What a {@link ResourceClient} needs from its client. */
218
+ interface Transport {
219
+ json(call: Call, signal: AbortSignal | undefined): Promise<unknown>;
220
+ none(call: Call, signal: AbortSignal | undefined): Promise<void>;
221
+ }
222
+ /**
223
+ * Operations on one resource. The type parameters are the record views;
224
+ * `crud-sdk-codegen` writes interfaces to pass for them (they are not
225
+ * checked against the manifest at runtime).
226
+ */
227
+ declare class ResourceClient<Read extends object = JsonObject, Create extends object = JsonObject, Update extends object = JsonObject> {
228
+ #private;
229
+ /** The manifest entry this handle is bound to. */
230
+ readonly resource: Resource;
231
+ /** Use `CrudClient.resource`. */
232
+ constructor(baseUrl: string, resource: Resource, transport: Transport);
233
+ /** One page of records. */
234
+ list(options?: ListOptions): Promise<Read[]>;
235
+ /** Every record, paging with `skip += limit` until a page shorter than `limit`. */
236
+ iterAll(options?: ListOptions): AsyncGenerator<Read, void, undefined>;
237
+ /** One record. */
238
+ get(id: number, options?: RequestOptions): Promise<Read>;
239
+ /** Creates a record. */
240
+ create(data: Create, options?: RequestOptions): Promise<Read>;
241
+ /** Patches one record; `ifMatch` is an ETag (`412` when stale). */
242
+ update(id: number, data: Update, options?: WriteOptions): Promise<Read>;
243
+ /** Patches every record matching `filters`. */
244
+ updateMany(filters: Filters, data: Update, options?: RequestOptions): Promise<BulkResult>;
245
+ /** Deletes one record; `ifMatch` as for `update`. */
246
+ delete(id: number, options?: WriteOptions): Promise<void>;
247
+ /** Deletes every record matching `filters`. */
248
+ deleteMany(filters: Filters, options?: RequestOptions): Promise<BulkResult>;
249
+ /** Restores archived records, by `id` or by `filters`. */
250
+ restore(options: RestoreOptions): Promise<BulkResult>;
251
+ /** Aggregates over the resource. */
252
+ stats(options?: StatsOptions): Promise<JsonObject>;
253
+ /** A forecast. */
254
+ predict(options?: PredictOptions): Promise<JsonObject>;
255
+ /** Copies a record; returns the copy. */
256
+ clone(id: number, options?: RequestOptions): Promise<Read>;
257
+ /** Creates a draft (needs `draft_publish`). */
258
+ createDraft(data: Create, options?: RequestOptions): Promise<Read>;
259
+ /** Publishes a draft (needs `draft_publish`). */
260
+ publish(id: number, options?: RequestOptions): Promise<Read>;
261
+ /** The server's filter metadata (`kind` instead of `value_type`). */
262
+ filters(options?: RequestOptions): Promise<JsonObject[]>;
263
+ /** Revisions of one record, newest first. */
264
+ revisions(id: number, options?: RequestOptions): Promise<JsonObject[]>;
265
+ /** Absolute URL of the resource's SSE stream. */
266
+ eventsUrl(): string;
267
+ }
268
+ /** Options of {@link createClient}. */
269
+ interface ClientOptions {
270
+ /** The server origin (or any URL on it). */
271
+ readonly baseUrl: string;
272
+ /** The network `fetch`; defaults to `globalThis.fetch`. */
273
+ readonly fetch?: typeof fetch | undefined;
274
+ /** Returns the bearer token; called again with `refresh: true` once after a `401`. */
275
+ readonly tokenProvider?: TokenProvider | undefined;
276
+ /** Outermost first; see `composeMiddleware`. */
277
+ readonly middleware?: readonly Middleware[] | undefined;
278
+ /** Skips api-catalog discovery. */
279
+ readonly manifestUrl?: string | undefined;
280
+ /**
281
+ * Stable identity of this client in TanStack query keys. Defaults to
282
+ * `baseUrl`; set it when two clients share a `baseUrl` but must not share
283
+ * cached data, and keep it constant across reloads of a persisted cache.
284
+ */
285
+ readonly id?: string | undefined;
286
+ }
287
+ /** Discovers the manifest and hands out {@link ResourceClient}s. */
288
+ declare class CrudClient {
289
+ #private;
290
+ readonly baseUrl: string;
291
+ /** Stable identity in TanStack query keys (`ClientOptions.id`, else `baseUrl`). */
292
+ readonly id: string;
293
+ constructor(options: ClientOptions);
294
+ /** Adds middleware after the existing ones (innermost); it sees every later request. */
295
+ use(middleware: Middleware): this;
296
+ /** The current bearer token, or `null` when anonymous. */
297
+ token(refresh?: boolean): Promise<string | null>;
298
+ /** The cache scope of the current caller (`"anon"` or a hash of the token). */
299
+ scope(): Promise<string>;
300
+ /** The `{ client, scope }` namespace the `./tanstack` key factory takes. */
301
+ namespace(): Promise<{
302
+ readonly client: string;
303
+ readonly scope: string;
304
+ }>;
305
+ /** The manifest for this caller, loaded once per caller scope (`refresh: true` reloads). */
306
+ manifest(options?: {
307
+ readonly refresh?: boolean;
308
+ }): Promise<Manifest>;
309
+ /** A handle for `name` (the highest `api_version` unless given). */
310
+ resource<Read extends object = JsonObject, Create extends object = JsonObject, Update extends object = JsonObject>(name: string, apiVersion?: string): Promise<ResourceClient<Read, Create, Update>>;
311
+ }
312
+ /** Creates a client; the manifest loads on first use. */
313
+ declare function createClient(options: ClientOptions): CrudClient;
314
+ //#endregion
315
+ 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 };
316
+ //# sourceMappingURL=client-B8fSsx4L.d.mts.map