@craftainer/crud-sdk 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +176 -2
- package/dist/auth-DwtNEKrw.mjs +22 -0
- package/dist/auth-DwtNEKrw.mjs.map +1 -0
- package/dist/cache/index.d.mts +166 -0
- package/dist/cache/index.mjs +972 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/client-BLd126Jt.d.mts +303 -0
- package/dist/client-D0drumeA.mjs +913 -0
- package/dist/client-D0drumeA.mjs.map +1 -0
- package/dist/codegen/cli.d.mts +1 -0
- package/dist/codegen/cli.mjs +131 -0
- package/dist/codegen/cli.mjs.map +1 -0
- package/dist/events-Cqj9Rbon.mjs +616 -0
- package/dist/events-Cqj9Rbon.mjs.map +1 -0
- package/dist/events-DpSWzSN9.d.mts +88 -0
- package/dist/index.d.mts +98 -0
- package/dist/index.mjs +3 -0
- package/dist/interfaces-s73cVfIF.d.mts +109 -0
- package/dist/tanstack/index.d.mts +182 -0
- package/dist/tanstack/index.mjs +226 -0
- package/dist/tanstack/index.mjs.map +1 -0
- package/package.json +80 -4
package/README.md
CHANGED
|
@@ -1,3 +1,177 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @craftainer/crud-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> [!NOTE]
|
|
4
|
+
> This repository is vibe coded with [Claude](https://claude.com/product/claude-code).
|
|
5
|
+
|
|
6
|
+
Typed `fetch` client for craftainer CRUD APIs (the TypeScript twin of the
|
|
7
|
+
Python `crud-sdk`): manifest-driven resources, a query builder, RFC 9457
|
|
8
|
+
problem-details errors, an opt-in cache with SSE invalidation, and TanStack
|
|
9
|
+
Query helpers. ESM only. Created from
|
|
10
|
+
[`template-npm-package`](https://github.com/craftainer/template-npm-package);
|
|
11
|
+
the shared conventions are in [`docs/TEMPLATE.md`](docs/TEMPLATE.md).
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Source | Contents |
|
|
16
|
+
| ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `@craftainer/crud-sdk` | `src/index.ts` | The client core and the shared contracts in `src/interfaces.ts` (`CacheStore`, `Middleware`, `RequestContext`, `TokenProvider`, `CrudEvent`). Zero dependencies. |
|
|
18
|
+
| `@craftainer/crud-sdk/cache` | `src/cache/index.ts` | Cache middleware, memory and IndexedDB stores, SSE invalidation. |
|
|
19
|
+
| `@craftainer/crud-sdk/tanstack` | `src/tanstack/index.ts` | `queryOptions` factories, key factory, invalidation bridge. |
|
|
20
|
+
| `crud-sdk-codegen` (bin) | `src/codegen/cli.ts` | Writes TypeScript types from a manifest. |
|
|
21
|
+
|
|
22
|
+
The package has no `dependencies`. `idb` and `eventsource-parser` are dev
|
|
23
|
+
dependencies that `tsdown` inlines (`noExternal`). `@tanstack/query-core`
|
|
24
|
+
(for `./tanstack`) and `json-schema-to-typescript` (for the codegen bin) are
|
|
25
|
+
optional peer dependencies.
|
|
26
|
+
|
|
27
|
+
Add or remove an entry point in `package.json`'s `exports`/`bin` and
|
|
28
|
+
`tsdown.config.ts`'s `entry` together.
|
|
29
|
+
|
|
30
|
+
## Client
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createClient } from "@craftainer/crud-sdk";
|
|
34
|
+
|
|
35
|
+
const client = createClient({
|
|
36
|
+
baseUrl: "https://api.example.com",
|
|
37
|
+
tokenProvider: ({ refresh }) => getToken({ refresh }), // optional
|
|
38
|
+
// fetch, middleware, manifestUrl are optional too
|
|
39
|
+
});
|
|
40
|
+
const heroes = await client.resource("heroes"); // highest api_version, or ("heroes", "v1")
|
|
41
|
+
const page = await heroes.list({
|
|
42
|
+
filters: { power_level__min: 3, name__icontains: "ada" },
|
|
43
|
+
sort: "-power_level",
|
|
44
|
+
limit: 50,
|
|
45
|
+
});
|
|
46
|
+
for await (const hero of heroes.iterAll({ filters: { is_locked: false } })) {
|
|
47
|
+
// pages with skip += limit until a short page
|
|
48
|
+
}
|
|
49
|
+
await heroes.update(1, { name: "Ada" }, { ifMatch: etag }); // 412 -> PreconditionFailed
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Discovery.** The manifest loads on first use through
|
|
53
|
+
`/.well-known/api-catalog` (the `describedby` link with the manifest
|
|
54
|
+
profile), or from `manifestUrl`. Manifest types keep the wire member names
|
|
55
|
+
(`skip_param`, `max_age`, ...) and ignore unknown members.
|
|
56
|
+
- **Operations** (each gated on the manifest's capabilities and `paths`,
|
|
57
|
+
else `CapabilityError`): `list`, `iterAll`, `get`, `create`, `update`,
|
|
58
|
+
`updateMany`, `delete`, `deleteMany`, `restore({ id } | { filters })`,
|
|
59
|
+
`clone`, `createDraft`, `publish`, `filters`, `stats`, `predict`,
|
|
60
|
+
`revisions`, `eventsUrl()`. Every call takes `{ signal }`. A call that is
|
|
61
|
+
invalid for the manifest (unknown field or operator, wrong value type, bad
|
|
62
|
+
sort or paging) rejects with `InvalidQueryError` before any request.
|
|
63
|
+
- **Query strings** are byte for byte those of the Python SDK (a shared
|
|
64
|
+
table, `tests/fixtures/url-parity.json`, checks both): parameters in the
|
|
65
|
+
order `id`, filters sorted by (field, operator), sort, skip, limit,
|
|
66
|
+
`include_archived`, `include_unpublished`; everything but `A-Za-z0-9-._~`
|
|
67
|
+
percent-encoded; datetimes are `Date`s sent as UTC with milliseconds
|
|
68
|
+
trimmed; `date` fields take `YYYY-MM-DD` strings.
|
|
69
|
+
- **Errors.** `ProblemError` (RFC 9457) with subclasses `BadRequest`,
|
|
70
|
+
`Unauthorized`, `Forbidden`, `NotFound`, `PreconditionFailed`,
|
|
71
|
+
`ValidationFailed` (`errors`), `Locked`, `RateLimited` (`retryAfter`),
|
|
72
|
+
`ServerError`; `ConnectionFailed` for transport failures (an abort passes
|
|
73
|
+
through unchanged). All extend `CrudError`.
|
|
74
|
+
- **Auth.** The token provider is called per request; after a `401` it is
|
|
75
|
+
called once with `{ refresh: true }` and the request is retried only if
|
|
76
|
+
the token changed. The cache scope is `"anon"` or the first 32 hex of the
|
|
77
|
+
SHA-256 of the whole token (never decoded).
|
|
78
|
+
- **Middleware.** `createClient({ middleware })` or `client.use(mw)` (added
|
|
79
|
+
innermost). Each request carries a `RequestContext` (`resource`, `op`,
|
|
80
|
+
`recordId`, `scope`, `apiVersion`, `signal`).
|
|
81
|
+
- **Typed records.** `client.resource<Read, Create, Update>("heroes")` takes
|
|
82
|
+
the interfaces written by the codegen below (not checked at runtime).
|
|
83
|
+
|
|
84
|
+
## Codegen
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
CRUD_SDK_TOKEN=... crud-sdk-codegen --manifest https://api.example.com --out src/crud-types.ts
|
|
88
|
+
crud-sdk-codegen --manifest ./manifest.json --out src/crud-types.ts
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Writes `<Name><Version>Read|Create|Update` (and `Draft`) interfaces per
|
|
92
|
+
resource from the manifest's JSON Schemas. `--manifest` is a manifest URL, a
|
|
93
|
+
server origin (discovers through the api-catalog) or a file; the optional
|
|
94
|
+
peer `json-schema-to-typescript` must be installed.
|
|
95
|
+
|
|
96
|
+
## Caching / TanStack
|
|
97
|
+
|
|
98
|
+
`createCache({ store, policies })` (from `./cache`) returns two `Middleware`:
|
|
99
|
+
`cache.middleware` and `cache.autoIfMatch`. Put both **innermost**, after
|
|
100
|
+
whatever sets `Authorization`: the cache scope is `anon` without an
|
|
101
|
+
Authorization header, else the first 32 hex characters of the SHA-256 of the
|
|
102
|
+
whole credential, taken from the request as it is sent. `policies` maps
|
|
103
|
+
manifest resource names to `cachePolicyFromManifest(resource)` (plain I1
|
|
104
|
+
objects, snake_case members).
|
|
105
|
+
|
|
106
|
+
- Every read revalidates (`If-None-Match`, `304` rebuilt from the store).
|
|
107
|
+
`max_age`/`stale_while_revalidate` freshness applies only while the
|
|
108
|
+
resource's SSE stream is connected, never with `cache.revalidate_only`.
|
|
109
|
+
- Wire the stream with `invalidateOnEvents(cache, getEventHub({ fetch, url,
|
|
110
|
+
events }), "heroes")`; `fetch` must be authenticated. Events invalidate and
|
|
111
|
+
never apply snapshots. `subscribeEvents` is the lower-level entry.
|
|
112
|
+
- Stores: `MemoryStore` (LRU) and `IndexedDbStore` (store failures are a miss).
|
|
113
|
+
`clearMany(prefixes)` is optional on a custom `CacheStore`.
|
|
114
|
+
- `cacheStatus(response)` says `hit`, `stale`, `revalidated` or `miss`;
|
|
115
|
+
`cache.close()` stops background refreshes.
|
|
116
|
+
|
|
117
|
+
`./tanstack` has no runtime dependency on `@tanstack/query-core`: `crudKeys`,
|
|
118
|
+
`listOptions`/`recordOptions`/`statsOptions`/`predictOptions`/
|
|
119
|
+
`revisionsOptions`/`manifestOptions`, `infiniteListOptions`, the
|
|
120
|
+
`create`/`update`/`deleteMutationOptions` and `bridgeEvents(queryClient, hub,
|
|
121
|
+
resource)`. Use `tanstackCachePolicy` so the SDK cache only revalidates and
|
|
122
|
+
TanStack's `staleTime` owns freshness. The factories take structural slices (`list(query, { signal })`, ...), which
|
|
123
|
+
the client's options-object methods do not match for `list`, `stats`,
|
|
124
|
+
`predict` and the manifest. `tanstackApi(client, resource)` (from
|
|
125
|
+
`./tanstack`) binds one `ResourceClient` to all of them; a query is the
|
|
126
|
+
client's `ListOptions` (`{ filters, sort, skip, limit, ... }`) without
|
|
127
|
+
`signal`, which TanStack's `signal` fills in.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { createClient } from "@craftainer/crud-sdk";
|
|
131
|
+
import {
|
|
132
|
+
createCache,
|
|
133
|
+
MemoryStore,
|
|
134
|
+
cachePolicyFromManifest,
|
|
135
|
+
} from "@craftainer/crud-sdk/cache";
|
|
136
|
+
import { listOptions, tanstackApi } from "@craftainer/crud-sdk/tanstack";
|
|
137
|
+
|
|
138
|
+
const cache = createCache({
|
|
139
|
+
store: new MemoryStore(),
|
|
140
|
+
policies: { heroes: cachePolicyFromManifest(heroesManifestEntry) },
|
|
141
|
+
});
|
|
142
|
+
const client = createClient({
|
|
143
|
+
baseUrl,
|
|
144
|
+
middleware: [cache.middleware, cache.autoIfMatch],
|
|
145
|
+
});
|
|
146
|
+
const api = tanstackApi(client, await client.resource("heroes"));
|
|
147
|
+
useQuery(listOptions("heroes", api, { limit: 50 }));
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Cache keys carry no server origin, so a store shared between servers needs a
|
|
151
|
+
per-origin namespace: give each server its own `MemoryStore` or its own
|
|
152
|
+
`IndexedDbStore({ name })` (the IndexedDB database name). The key format does
|
|
153
|
+
not change.
|
|
154
|
+
|
|
155
|
+
`policies` is keyed by `name@apiVersion` (built from that version's manifest
|
|
156
|
+
entry) or by plain `name` for every version; the event stream and the
|
|
157
|
+
freshness gate are per resource name, and the gate stays open while at least
|
|
158
|
+
one subscription for that resource is connected. `filters` entries are never
|
|
159
|
+
served fresh.
|
|
160
|
+
|
|
161
|
+
The cache scope is the same on both sides: `client.scope()` equals
|
|
162
|
+
`credentialScope` of the `Authorization` header the client sends (an empty
|
|
163
|
+
token sends no header and is `anon`). `cache.autoIfMatch` reads `recordId`,
|
|
164
|
+
the resource and `apiVersion` from the client's request context, so
|
|
165
|
+
`update`/`delete` without `ifMatch` send the ETag cached for that caller.
|
|
166
|
+
|
|
167
|
+
See
|
|
168
|
+
[`docs/adrs/0002-cache-freshness-and-event-invalidation.md`](docs/adrs/0002-cache-freshness-and-event-invalidation.md).
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
Open the repository in the devcontainer, then run
|
|
173
|
+
`prek run --all-files --hook-stage manual`.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
Licensed under the [GNU General Public License v3.0](LICENSE).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/auth.ts
|
|
2
|
+
/** The scope of an anonymous caller. */
|
|
3
|
+
const ANON = "anon";
|
|
4
|
+
/**
|
|
5
|
+
* Cache scope of a caller: `"anon"`, or the first 32 hex characters of the
|
|
6
|
+
* SHA-256 of the whole bearer token. Nothing in the token is trusted or
|
|
7
|
+
* decoded (a forged `sub` must not select another caller's entries), and the
|
|
8
|
+
* raw token never leaves this function.
|
|
9
|
+
*/
|
|
10
|
+
async function cacheScope(token) {
|
|
11
|
+
if (token === null || token === void 0) return ANON;
|
|
12
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
13
|
+
return Array.from(new Uint8Array(digest).subarray(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
14
|
+
}
|
|
15
|
+
/** A provider that always returns `token` (a refresh changes nothing). */
|
|
16
|
+
function staticToken(token) {
|
|
17
|
+
return () => token;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { cacheScope as n, staticToken as r, ANON as t };
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=auth-DwtNEKrw.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-DwtNEKrw.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["import type { TokenProvider } from \"./interfaces.ts\";\n\n/** The scope of an anonymous caller. */\nexport const ANON = \"anon\";\n\n/**\n * Cache scope of a caller: `\"anon\"`, or the first 32 hex characters of the\n * SHA-256 of the whole bearer token. Nothing in the token is trusted or\n * decoded (a forged `sub` must not select another caller's entries), and the\n * raw token never leaves this function.\n */\nexport async function cacheScope(\n token: string | null | undefined,\n): Promise<string> {\n if (token === null || token === undefined) {\n return ANON;\n }\n const digest = await crypto.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(token),\n );\n return Array.from(new Uint8Array(digest).subarray(0, 16), (byte) =>\n byte.toString(16).padStart(2, \"0\"),\n ).join(\"\");\n}\n\n/** A provider that always returns `token` (a refresh changes nothing). */\nexport function staticToken(token: string | null): TokenProvider {\n return () => token;\n}\n"],"mappings":";;AAGA,MAAa,OAAO;;;;;;;AAQpB,eAAsB,WACpB,OACiB;CACjB,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,OAAO;CAET,MAAM,SAAS,MAAM,OAAO,OAAO,OACjC,WACA,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAChC;CACA,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC,CAAC,SAAS,GAAG,EAAE,IAAI,SACzD,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CACnC,CAAC,CAAC,KAAK,EAAE;AACX;;AAGA,SAAgB,YAAY,OAAqC;CAC/D,aAAa;AACf"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { a as Middleware, i as MaybePromise, n as CacheStore, r as CrudEvent, t as CacheEntry } from "../interfaces-s73cVfIF.mjs";
|
|
2
|
+
import { a as SubscribeEventsOptions, c as CachePolicy, d as tanstackCachePolicy, i as EventsConfig, l as ManifestCacheHints, n as EventHub, o as getEventHub, r as EventHubOptions, s as subscribeEvents, t as EventHandlers, u as cachePolicyFromManifest } from "../events-DpSWzSN9.mjs";
|
|
3
|
+
//#region src/cache/middleware.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A store may clear several prefixes in one pass (one walk, one transaction);
|
|
6
|
+
* the cache uses it when present and falls back to `clear` per prefix.
|
|
7
|
+
*/
|
|
8
|
+
interface CacheStoreMany extends CacheStore {
|
|
9
|
+
clearMany?(prefixes: readonly string[]): MaybePromise<void>;
|
|
10
|
+
}
|
|
11
|
+
interface CacheOptions {
|
|
12
|
+
readonly store: CacheStoreMany;
|
|
13
|
+
/**
|
|
14
|
+
* Policy per manifest resource: `name@apiVersion` (built from that version's
|
|
15
|
+
* manifest entry) or, for every version, plain `name`. A resource without
|
|
16
|
+
* one is not cached. The event stream and the freshness gate are per name.
|
|
17
|
+
*/
|
|
18
|
+
readonly policies: Readonly<Record<string, CachePolicy>>;
|
|
19
|
+
/** Cache the manifest document per caller scope. Default `true`. */
|
|
20
|
+
readonly cacheManifest?: boolean;
|
|
21
|
+
/** Epoch milliseconds; injectable for tests. */
|
|
22
|
+
readonly now?: () => number;
|
|
23
|
+
}
|
|
24
|
+
interface Cache {
|
|
25
|
+
/** Put it in the client's middleware chain. */
|
|
26
|
+
readonly middleware: Middleware;
|
|
27
|
+
/** Record whether the SSE stream of `resource` is connected (gates fresh hits). */
|
|
28
|
+
setEventsActive(resource: string, active: boolean): void;
|
|
29
|
+
eventsActive(resource: string): boolean;
|
|
30
|
+
/** Clear what an SSE event makes stale, in every API version. */
|
|
31
|
+
invalidateEvent(resource: string, action: string, recordId: number | undefined): Promise<void>;
|
|
32
|
+
/** Clear everything cached for `resource` except `filters` (after a reconnect). */
|
|
33
|
+
clearResource(resource: string): Promise<void>;
|
|
34
|
+
/** Clear keys under `prefix` (everything the store owns when omitted). */
|
|
35
|
+
clear(prefix?: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Adds `If-Match` from the cached record ETag to `PATCH`/`DELETE ?id=` that
|
|
38
|
+
* have none. Put it innermost, next to `middleware`, so it sees the same
|
|
39
|
+
* Authorization (and so scope) the request is sent with.
|
|
40
|
+
*/
|
|
41
|
+
readonly autoIfMatch: Middleware;
|
|
42
|
+
/** The stored ETag of a record of one API version, for `auto_if_match`. */
|
|
43
|
+
cachedEtag(resource: string, recordId: number, scope: string, apiVersion: string | null): Promise<string | undefined>;
|
|
44
|
+
/** Abort background revalidations and wait for them. */
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export declare function createCache(options: CacheOptions): Cache;
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/cache/scope.d.ts
|
|
50
|
+
/**
|
|
51
|
+
* `"anon"` when the request has no Authorization header; otherwise the first
|
|
52
|
+
* 32 hex characters of the SHA-256 of the whole credential, the same digest as
|
|
53
|
+
* the client's `cacheScope(token)` (a `Bearer ` prefix is dropped, any other value is hashed as it is). It is read from the
|
|
54
|
+
* request the cache sees, so it must be the innermost middleware that matters:
|
|
55
|
+
* whatever sets Authorization has to run before the cache. The raw credential
|
|
56
|
+
* never enters a key or a log, and the unverified `sub` claim is never used.
|
|
57
|
+
*/
|
|
58
|
+
export declare function credentialScope(headers: Headers): Promise<string>;
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/cache/entry.d.ts
|
|
61
|
+
/** How the cache answered a read: see `cacheStatus`. */
|
|
62
|
+
type CacheStatus = "hit" | "stale" | "revalidated" | "miss";
|
|
63
|
+
/**
|
|
64
|
+
* How the cache answered `response`: `hit` (fresh, no network), `stale`
|
|
65
|
+
* (inside the stale-while-revalidate window, refreshed in the background),
|
|
66
|
+
* `revalidated` (a `304` rebuilt from the stored entry) or `miss`.
|
|
67
|
+
* `undefined` when the cache did not handle the request.
|
|
68
|
+
*/
|
|
69
|
+
export declare function cacheStatus(response: Response): CacheStatus | undefined;
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/cache/memory.d.ts
|
|
72
|
+
interface MemoryStoreOptions {
|
|
73
|
+
/** Default 1024. */
|
|
74
|
+
readonly maxEntries?: number;
|
|
75
|
+
readonly defaultTtlMs?: number;
|
|
76
|
+
/** Epoch milliseconds; injectable for tests. */
|
|
77
|
+
readonly now?: () => number;
|
|
78
|
+
}
|
|
79
|
+
/** LRU store (default 1024 entries) that also honours each entry's ttl. */
|
|
80
|
+
export declare class MemoryStore implements CacheStore {
|
|
81
|
+
private readonly data;
|
|
82
|
+
private readonly maxEntries;
|
|
83
|
+
private readonly defaultTtlMs;
|
|
84
|
+
private readonly now;
|
|
85
|
+
constructor(options?: MemoryStoreOptions);
|
|
86
|
+
/** Number of stored entries (expired ones included until touched). */
|
|
87
|
+
get size(): number;
|
|
88
|
+
get(key: string): CacheEntry | undefined;
|
|
89
|
+
set(key: string, entry: CacheEntry, ttlMs?: number): void;
|
|
90
|
+
delete(key: string): void;
|
|
91
|
+
/** Remove every key under any of `prefixes` in one walk. */
|
|
92
|
+
clearMany(prefixes: readonly string[]): void;
|
|
93
|
+
clear(prefix?: string): void;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/cache/idb.d.ts
|
|
97
|
+
interface IndexedDbStoreOptions {
|
|
98
|
+
/** Database name; default `craftainer-crud-cache`. */
|
|
99
|
+
readonly name?: string;
|
|
100
|
+
readonly defaultTtlMs?: number;
|
|
101
|
+
/** Epoch milliseconds; injectable for tests. */
|
|
102
|
+
readonly now?: () => number;
|
|
103
|
+
}
|
|
104
|
+
export declare class IndexedDbStore implements CacheStore {
|
|
105
|
+
private readonly name;
|
|
106
|
+
private readonly defaultTtlMs;
|
|
107
|
+
private readonly now;
|
|
108
|
+
private opening;
|
|
109
|
+
constructor(options?: IndexedDbStoreOptions);
|
|
110
|
+
private open;
|
|
111
|
+
/** Run `work` on the database; a failure drops the handle (so a later call retries) and yields `fallback`. */
|
|
112
|
+
private run;
|
|
113
|
+
get(key: string): Promise<CacheEntry | undefined>;
|
|
114
|
+
set(key: string, entry: CacheEntry, ttlMs?: number): Promise<void>;
|
|
115
|
+
delete(key: string): Promise<void>;
|
|
116
|
+
clear(prefix?: string): Promise<void>;
|
|
117
|
+
/** Clear several prefixes in one transaction (one bound range each). */
|
|
118
|
+
clearMany(prefixes: readonly string[]): Promise<void>;
|
|
119
|
+
/** Close the database handle; a later call reopens it. */
|
|
120
|
+
close(): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/cache/keys.d.ts
|
|
124
|
+
/** The ops that read and are cached (`manifest` has its own key). */
|
|
125
|
+
type CacheKind = "list" | "record" | "stats" | "predict" | "revisions" | "filters";
|
|
126
|
+
/**
|
|
127
|
+
* Sorts the query pairs the client sent and drops `id` (it is the subject).
|
|
128
|
+
* Values are kept verbatim: the client already emits canonical,
|
|
129
|
+
* percent-encoded text, so nothing is decoded or re-encoded here.
|
|
130
|
+
*/
|
|
131
|
+
export declare function canonicalQuery(query: string): string;
|
|
132
|
+
interface KeyParts {
|
|
133
|
+
readonly resource: string;
|
|
134
|
+
readonly kind: CacheKind;
|
|
135
|
+
readonly recordId?: number | undefined;
|
|
136
|
+
readonly scope: string;
|
|
137
|
+
readonly query?: string;
|
|
138
|
+
/** `null` (no version) renders as `_`. */
|
|
139
|
+
readonly apiVersion: string | null;
|
|
140
|
+
}
|
|
141
|
+
/** The key of one cached response. */
|
|
142
|
+
export declare function buildKey(parts: KeyParts): string;
|
|
143
|
+
/** Key of the manifest document for one caller scope. */
|
|
144
|
+
export declare function manifestKey(scope: string): string;
|
|
145
|
+
/** Prefix of the `record` entries of one record (every version, scope, query). */
|
|
146
|
+
export declare function recordPrefix(resource: string, recordId: number): string;
|
|
147
|
+
/** Everything cached for `resource` except `filters` (a reconnect or an unknown change). */
|
|
148
|
+
export declare function resourcePrefixes(resource: string): string[];
|
|
149
|
+
/**
|
|
150
|
+
* Prefixes an SSE event clears, per the spec's table: `create` clears the
|
|
151
|
+
* collection kinds; `update`/`delete`/`restore` also clear the record and its
|
|
152
|
+
* revisions; an unknown action or a missing record id clears the resource.
|
|
153
|
+
*/
|
|
154
|
+
export declare function eventPrefixes(resource: string, action: string, recordId: number | undefined): string[];
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/cache/wire.d.ts
|
|
157
|
+
/**
|
|
158
|
+
* Subscribe `cache` to `hub` for the manifest resource `resource`. Events
|
|
159
|
+
* clear the spec's prefixes, a reconnect clears the resource, and fresh hits
|
|
160
|
+
* are allowed only while the stream is connected. Returns the unsubscribe
|
|
161
|
+
* (it also closes the freshness gate).
|
|
162
|
+
*/
|
|
163
|
+
export declare function invalidateOnEvents(cache: Cache, hub: EventHub, resource: string): () => void;
|
|
164
|
+
//#endregion
|
|
165
|
+
export { type Cache, type CacheEntry, type CacheKind, type CacheOptions, type CachePolicy, type CacheStatus, type CacheStore, type CacheStoreMany, type CrudEvent, type EventHandlers, type EventHub, type EventHubOptions, type EventsConfig, type IndexedDbStoreOptions, type KeyParts, type ManifestCacheHints, type MemoryStoreOptions, type SubscribeEventsOptions, cachePolicyFromManifest, getEventHub, subscribeEvents, tanstackCachePolicy };
|
|
166
|
+
//# sourceMappingURL=index.d.mts.map
|