@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.
package/README.md CHANGED
@@ -1,3 +1,208 @@
1
- # Temporary Holding Version
1
+ # @craftainer/crud-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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`, `invalidateResource`/
121
+ `invalidateCollections`/`invalidateRecord` (each takes `{ client, resource }`)
122
+ and `bridgeEvents(queryClient, hub, { client, resource })`, where `client` is
123
+ the id string (`client.id`) and `resource` the same string the keys use. Use
124
+ `tanstackCachePolicy` so the SDK cache only revalidates and TanStack's
125
+ `staleTime` owns freshness. The factories take structural slices (`list(query, { signal })`, ...), which
126
+ the client's options-object methods do not match for `list`, `stats`,
127
+ `predict` and the manifest. `tanstackApi(client, resource)` (from
128
+ `./tanstack`) binds one `ResourceClient` to all of them; a query is the
129
+ client's `ListOptions` (`{ filters, sort, skip, limit, ... }`) without
130
+ `signal`, which TanStack's `signal` fills in.
131
+
132
+ **Query keys** are namespaced by client and caller scope, so clients or
133
+ callers sharing one `QueryClient` never see each other's data:
134
+
135
+ ```
136
+ ["crud", client, resource, scope, kind, ...rest] kind: list | record | stats | predict | revisions
137
+ ["crud", client, "_manifest", scope]
138
+ ```
139
+
140
+ `client` is `CrudClient.id` (`createClient({ id })`, default `baseUrl`; keep it
141
+ stable across reloads of a persisted cache). `scope` is `client.scope()`.
142
+ `await client.namespace()` returns the `{ client, scope }` that every factory
143
+ takes as its first argument (`listOptions(ns, resource, api, query)`,
144
+ `createMutationOptions(queryClient, ns, resource, api)`). `resource` is the
145
+ manifest name, or `name@apiVersion` for a pinned version (a different
146
+ string is a different resource: pass the same one to the invalidate helpers
147
+ and `bridgeEvents`; `_manifest` is reserved). The namespace is a snapshot: a
148
+ token change (login, logout, refresh to another identity) changes the scope,
149
+ so resolve it again then, for example in a provider keyed on the token.
150
+ Clients that share a `baseUrl` but differ in anything else need distinct ids.
151
+
152
+ Because `resource` precedes `scope`, "everything of resource X on client C" is
153
+ one prefix (`crudKeys.resource(c, x)`, or `invalidateResource(queryClient, c,
154
+ x)`), and `crudKeys.client(c)` clears a whole client. Narrowing by scope, kind
155
+ or record id uses `crudKeys.filter({ client, resource?, scope?, kinds?, id? })`,
156
+ which returns `{ queryKey }` or `{ predicate }` to spread into
157
+ `invalidateQueries`/`removeQueries`. Mutations and `bridgeEvents` invalidate
158
+ every scope of the client's resource, since a write changes what every caller sees.
159
+
160
+ ```ts
161
+ import { createClient } from "@craftainer/crud-sdk";
162
+ import {
163
+ createCache,
164
+ MemoryStore,
165
+ cachePolicyFromManifest,
166
+ } from "@craftainer/crud-sdk/cache";
167
+ import { listOptions, tanstackApi } from "@craftainer/crud-sdk/tanstack";
168
+
169
+ const cache = createCache({
170
+ store: new MemoryStore(),
171
+ policies: { heroes: cachePolicyFromManifest(heroesManifestEntry) },
172
+ });
173
+ const client = createClient({
174
+ baseUrl,
175
+ middleware: [cache.middleware, cache.autoIfMatch],
176
+ });
177
+ const api = tanstackApi(client, await client.resource("heroes"));
178
+ useQuery(listOptions(await client.namespace(), "heroes", api, { limit: 50 }));
179
+ ```
180
+
181
+ Cache keys carry no server origin, so a store shared between servers needs a
182
+ per-origin namespace: give each server its own `MemoryStore` or its own
183
+ `IndexedDbStore({ name })` (the IndexedDB database name). The key format does
184
+ not change.
185
+
186
+ `policies` is keyed by `name@apiVersion` (built from that version's manifest
187
+ entry) or by plain `name` for every version; the event stream and the
188
+ freshness gate are per resource name, and the gate stays open while at least
189
+ one subscription for that resource is connected. `filters` entries are never
190
+ served fresh.
191
+
192
+ The cache scope is the same on both sides: `client.scope()` equals
193
+ `credentialScope` of the `Authorization` header the client sends (an empty
194
+ token sends no header and is `anon`). `cache.autoIfMatch` reads `recordId`,
195
+ the resource and `apiVersion` from the client's request context, so
196
+ `update`/`delete` without `ifMatch` send the ETag cached for that caller.
197
+
198
+ See
199
+ [`docs/adrs/0002-cache-freshness-and-event-invalidation.md`](docs/adrs/0002-cache-freshness-and-event-invalidation.md).
200
+
201
+ ## Development
202
+
203
+ Open the repository in the devcontainer, then run
204
+ `prek run --all-files --hook-stage manual`.
205
+
206
+ ## License
207
+
208
+ 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