@craftainer/crud-sdk 0.1.0 → 0.3.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.
@@ -29,117 +29,145 @@ function tanstackApi(client, resource) {
29
29
  }
30
30
  //#endregion
31
31
  //#region src/tanstack/index.ts
32
- /** The key factory. Every key starts with `["crud", resource, ...]`, so prefixes invalidate by resource, kind or record. */
32
+ /**
33
+ * The key factory. Keys are
34
+ * `["crud", client, resource, scope, kind, ...]` (the manifest is
35
+ * `["crud", client, "_manifest", scope]`), so a prefix invalidates by client
36
+ * or by resource on one client; `resource` is the manifest name, or
37
+ * `name@apiVersion` for a pinned version (a different string is a different
38
+ * resource: pass the same one to the invalidate helpers). `_manifest` is
39
+ * reserved; a scope-only filter also matches the scope's manifest.
40
+ */
33
41
  const crudKeys = {
34
42
  all: ["crud"],
35
- resource: (resource) => ["crud", resource],
36
- lists: (resource) => [
43
+ client: (client) => ["crud", client],
44
+ resource: (client, resource) => [
45
+ "crud",
46
+ client,
47
+ resource
48
+ ],
49
+ lists: (ns, resource) => [
37
50
  "crud",
51
+ ns.client,
38
52
  resource,
53
+ ns.scope,
39
54
  "list"
40
55
  ],
41
- list: (resource, query) => query === void 0 ? crudKeys.lists(resource) : [...crudKeys.lists(resource), query],
42
- infiniteList: (resource, query, limit) => [
43
- ...crudKeys.lists(resource),
56
+ list: (ns, resource, query) => query === void 0 ? crudKeys.lists(ns, resource) : [...crudKeys.lists(ns, resource), query],
57
+ infiniteList: (ns, resource, query, limit) => [
58
+ ...crudKeys.lists(ns, resource),
44
59
  "infinite",
45
60
  query,
46
61
  limit
47
62
  ],
48
- records: (resource) => [
63
+ records: (ns, resource) => [
49
64
  "crud",
65
+ ns.client,
50
66
  resource,
67
+ ns.scope,
51
68
  "record"
52
69
  ],
53
- record: (resource, id) => [
54
- "crud",
55
- resource,
56
- "record",
57
- id
58
- ],
59
- stats: (resource, query) => query === void 0 ? [
70
+ record: (ns, resource, id) => [...crudKeys.records(ns, resource), id],
71
+ stats: (ns, resource, query) => query === void 0 ? [
60
72
  "crud",
73
+ ns.client,
61
74
  resource,
75
+ ns.scope,
62
76
  "stats"
63
77
  ] : [
64
78
  "crud",
79
+ ns.client,
65
80
  resource,
81
+ ns.scope,
66
82
  "stats",
67
83
  query
68
84
  ],
69
- predict: (resource, query) => query === void 0 ? [
85
+ predict: (ns, resource, query) => query === void 0 ? [
70
86
  "crud",
87
+ ns.client,
71
88
  resource,
89
+ ns.scope,
72
90
  "predict"
73
91
  ] : [
74
92
  "crud",
93
+ ns.client,
75
94
  resource,
95
+ ns.scope,
76
96
  "predict",
77
97
  query
78
98
  ],
79
- revisions: (resource, id) => [
99
+ revisions: (ns, resource, id) => [
80
100
  "crud",
101
+ ns.client,
81
102
  resource,
103
+ ns.scope,
82
104
  "revisions",
83
105
  id
84
106
  ],
85
- manifest: (scope) => scope === void 0 ? ["crud", "_manifest"] : [
107
+ manifest: (ns) => [
86
108
  "crud",
109
+ ns.client,
87
110
  "_manifest",
88
- scope
89
- ]
111
+ ns.scope
112
+ ],
113
+ filter: (filter) => {
114
+ const { client, resource, scope, exceptScope, kinds, id } = filter;
115
+ if (scope === void 0 && exceptScope === void 0 && kinds === void 0 && id === void 0) return { queryKey: resource === void 0 ? crudKeys.client(client) : crudKeys.resource(client, resource) };
116
+ return { predicate: ({ queryKey: key }) => key[0] === "crud" && key[1] === client && (resource === void 0 || key[2] === resource) && (scope === void 0 || key[3] === scope) && (exceptScope === void 0 || key[3] !== exceptScope) && (kinds === void 0 || kinds.includes(key[4])) && (id === void 0 || (key[4] === "record" || key[4] === "revisions") && key[5] === id) };
117
+ }
90
118
  };
91
119
  function tag(key) {
92
120
  return key;
93
121
  }
94
- function listOptions(resource, api, query, init = {}) {
122
+ function listOptions(ns, resource, api, query, init = {}) {
95
123
  return {
96
- queryKey: tag(crudKeys.list(resource, query)),
124
+ queryKey: tag(crudKeys.list(ns, resource, query)),
97
125
  queryFn: ({ signal }) => api.list(query, { signal }),
98
126
  staleTime: init.staleTime ?? 0
99
127
  };
100
128
  }
101
- function recordOptions(resource, api, id, init = {}) {
129
+ function recordOptions(ns, resource, api, id, init = {}) {
102
130
  return {
103
- queryKey: tag(crudKeys.record(resource, id)),
131
+ queryKey: tag(crudKeys.record(ns, resource, id)),
104
132
  queryFn: ({ signal }) => api.get(id, { signal }),
105
133
  staleTime: init.staleTime ?? 0
106
134
  };
107
135
  }
108
- function statsOptions(resource, api, query, init = {}) {
136
+ function statsOptions(ns, resource, api, query, init = {}) {
109
137
  return {
110
- queryKey: tag(crudKeys.stats(resource, query)),
138
+ queryKey: tag(crudKeys.stats(ns, resource, query)),
111
139
  queryFn: ({ signal }) => api.stats(query, { signal }),
112
140
  staleTime: init.staleTime ?? 0
113
141
  };
114
142
  }
115
- function predictOptions(resource, api, query, init = {}) {
143
+ function predictOptions(ns, resource, api, query, init = {}) {
116
144
  return {
117
- queryKey: tag(crudKeys.predict(resource, query)),
145
+ queryKey: tag(crudKeys.predict(ns, resource, query)),
118
146
  queryFn: ({ signal }) => api.predict(query, { signal }),
119
147
  staleTime: init.staleTime ?? 0
120
148
  };
121
149
  }
122
- function revisionsOptions(resource, api, id, init = {}) {
150
+ function revisionsOptions(ns, resource, api, id, init = {}) {
123
151
  return {
124
- queryKey: tag(crudKeys.revisions(resource, id)),
152
+ queryKey: tag(crudKeys.revisions(ns, resource, id)),
125
153
  queryFn: ({ signal }) => api.revisions(id, { signal }),
126
154
  staleTime: init.staleTime ?? 0
127
155
  };
128
156
  }
129
- function manifestOptions(api, scope, init = {}) {
157
+ function manifestOptions(ns, api, init = {}) {
130
158
  return {
131
- queryKey: tag(crudKeys.manifest(scope)),
159
+ queryKey: tag(crudKeys.manifest(ns)),
132
160
  queryFn: ({ signal }) => api.manifest({ signal }),
133
161
  staleTime: init.staleTime ?? 0
134
162
  };
135
163
  }
136
164
  /** Offset paging: the next page starts at `skip + limit`, unless the last page was short. */
137
- function infiniteListOptions(resource, api, query = {}, init = {}) {
165
+ function infiniteListOptions(ns, resource, api, query = {}, init = {}) {
138
166
  const limit = init.limit ?? 100;
139
167
  const skipKey = init.skipKey ?? "skip";
140
168
  const limitKey = init.limitKey ?? "limit";
141
169
  return {
142
- queryKey: tag(crudKeys.infiniteList(resource, query, limit)),
170
+ queryKey: tag(crudKeys.infiniteList(ns, resource, query, limit)),
143
171
  queryFn: ({ signal, pageParam }) => api.list({
144
172
  ...query,
145
173
  [skipKey]: pageParam,
@@ -150,52 +178,117 @@ function infiniteListOptions(resource, api, query = {}, init = {}) {
150
178
  staleTime: init.staleTime ?? 0
151
179
  };
152
180
  }
153
- async function invalidateCollections(client, resource) {
154
- await Promise.all([
155
- client.invalidateQueries({ queryKey: crudKeys.lists(resource) }),
156
- client.invalidateQueries({ queryKey: crudKeys.stats(resource) }),
157
- client.invalidateQueries({ queryKey: crudKeys.predict(resource) })
158
- ]);
181
+ const COLLECTION_KINDS = [
182
+ "list",
183
+ "stats",
184
+ "predict"
185
+ ];
186
+ /**
187
+ * Invalidates the list, stats and predict queries of `resource` on `client`,
188
+ * for every caller scope (a write changes what every caller sees).
189
+ */
190
+ function invalidateCollections(queryClient, { client, resource }) {
191
+ return queryClient.invalidateQueries(crudKeys.filter({
192
+ client,
193
+ resource,
194
+ kinds: COLLECTION_KINDS
195
+ }));
196
+ }
197
+ /** Invalidates everything cached for `resource` on `client`: one key prefix. */
198
+ function invalidateResource(queryClient, { client, resource }) {
199
+ return queryClient.invalidateQueries(crudKeys.filter({
200
+ client,
201
+ resource
202
+ }));
203
+ }
204
+ /** Invalidates one record's `record` and `revisions` queries, for every scope. */
205
+ function invalidateRecord(queryClient, { client, resource }, id) {
206
+ return queryClient.invalidateQueries(crudKeys.filter({
207
+ client,
208
+ resource,
209
+ kinds: ["record", "revisions"],
210
+ id
211
+ }));
159
212
  }
160
213
  /** Create: invalidate lists, stats and predict. */
161
- function createMutationOptions(client, resource, api) {
214
+ function createMutationOptions(queryClient, ns, resource, api) {
162
215
  return {
163
216
  mutationKey: [
164
217
  "crud",
218
+ ns.client,
165
219
  resource,
220
+ ns.scope,
166
221
  "create"
167
222
  ],
168
223
  mutationFn: (body) => api.create(body),
169
- onSuccess: () => invalidateCollections(client, resource)
224
+ onSuccess: () => invalidateCollections(queryClient, {
225
+ client: ns.client,
226
+ resource
227
+ })
170
228
  };
171
229
  }
172
230
  /** Update: `setQueryData` from the PATCH response, invalidate lists, stats, predict and revisions. */
173
- function updateMutationOptions(client, resource, api) {
231
+ function updateMutationOptions(queryClient, ns, resource, api) {
174
232
  return {
175
233
  mutationKey: [
176
234
  "crud",
235
+ ns.client,
177
236
  resource,
237
+ ns.scope,
178
238
  "update"
179
239
  ],
180
240
  mutationFn: ({ id, patch, ifMatch }) => api.update(id, patch, ifMatch === void 0 ? {} : { ifMatch }),
181
241
  onSuccess: async (record, { id }) => {
182
- client.setQueryData(crudKeys.record(resource, id), record);
183
- await Promise.all([invalidateCollections(client, resource), client.invalidateQueries({ queryKey: crudKeys.revisions(resource, id) })]);
242
+ queryClient.setQueryData(crudKeys.record(ns, resource, id), record);
243
+ await Promise.all([
244
+ invalidateCollections(queryClient, {
245
+ client: ns.client,
246
+ resource
247
+ }),
248
+ queryClient.invalidateQueries(crudKeys.filter({
249
+ client: ns.client,
250
+ resource,
251
+ exceptScope: ns.scope,
252
+ kinds: ["record"],
253
+ id
254
+ })),
255
+ queryClient.invalidateQueries(crudKeys.filter({
256
+ client: ns.client,
257
+ resource,
258
+ kinds: ["revisions"],
259
+ id
260
+ }))
261
+ ]);
184
262
  }
185
263
  };
186
264
  }
187
265
  /** Delete: drop the record query, invalidate lists, stats, predict and revisions. */
188
- function deleteMutationOptions(client, resource, api) {
266
+ function deleteMutationOptions(queryClient, ns, resource, api) {
189
267
  return {
190
268
  mutationKey: [
191
269
  "crud",
270
+ ns.client,
192
271
  resource,
272
+ ns.scope,
193
273
  "delete"
194
274
  ],
195
275
  mutationFn: (id) => api.delete(id),
196
276
  onSuccess: async (_result, id) => {
197
- client.removeQueries({ queryKey: crudKeys.record(resource, id) });
198
- await Promise.all([invalidateCollections(client, resource), client.invalidateQueries({ queryKey: crudKeys.revisions(resource, id) })]);
277
+ queryClient.removeQueries(crudKeys.filter({
278
+ client: ns.client,
279
+ resource,
280
+ kinds: ["record"],
281
+ id
282
+ }));
283
+ await Promise.all([invalidateCollections(queryClient, {
284
+ client: ns.client,
285
+ resource
286
+ }), queryClient.invalidateQueries(crudKeys.filter({
287
+ client: ns.client,
288
+ resource,
289
+ kinds: ["revisions"],
290
+ id
291
+ }))]);
199
292
  }
200
293
  };
201
294
  }
@@ -206,21 +299,17 @@ function deleteMutationOptions(client, resource, api) {
206
299
  * reconnect (or an event with no usable record id) invalidates the resource.
207
300
  * Returns the unsubscribe.
208
301
  */
209
- function bridgeEvents(client, hub, resource) {
302
+ function bridgeEvents(queryClient, hub, target) {
210
303
  return hub.subscribe({
211
304
  onEvent: async (event) => {
212
- const pending = [
213
- client.invalidateQueries({ queryKey: crudKeys.lists(resource) }),
214
- client.invalidateQueries({ queryKey: crudKeys.stats(resource) }),
215
- client.invalidateQueries({ queryKey: crudKeys.predict(resource) })
216
- ];
217
- if (event.action !== "create") pending.push(client.invalidateQueries({ queryKey: crudKeys.record(resource, event.recordId) }), client.invalidateQueries({ queryKey: crudKeys.revisions(resource, event.recordId) }));
305
+ const pending = [invalidateCollections(queryClient, target)];
306
+ if (event.action !== "create") pending.push(invalidateRecord(queryClient, target, event.recordId));
218
307
  await Promise.all(pending);
219
308
  },
220
- onReset: () => client.invalidateQueries({ queryKey: crudKeys.resource(resource) })
309
+ onReset: () => invalidateResource(queryClient, target)
221
310
  });
222
311
  }
223
312
  //#endregion
224
- export { bridgeEvents, createMutationOptions, crudKeys, deleteMutationOptions, getEventHub, infiniteListOptions, listOptions, manifestOptions, predictOptions, recordOptions, revisionsOptions, statsOptions, subscribeEvents, tanstackApi, tanstackCachePolicy, updateMutationOptions };
313
+ export { bridgeEvents, createMutationOptions, crudKeys, deleteMutationOptions, getEventHub, infiniteListOptions, invalidateCollections, invalidateRecord, invalidateResource, listOptions, manifestOptions, predictOptions, recordOptions, revisionsOptions, statsOptions, subscribeEvents, tanstackApi, tanstackCachePolicy, updateMutationOptions };
225
314
 
226
315
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/tanstack/adapter.ts","../../src/tanstack/index.ts"],"sourcesContent":["// Adapts TS1's `ResourceClient` (options-object methods) to the structural\n// slices the `./tanstack` factories take (`list(query, { signal })`, ...).\nimport type { CrudClient, ResourceClient } from \"../client.ts\";\nimport type { JsonObject, Manifest } from \"../manifest.ts\";\nimport type { CrudQuery } from \"./index.ts\";\n\ntype Init = { signal?: AbortSignal };\n\n/** What `tanstackApi` returns: every slice the factories use, bound to one resource. */\nexport interface TanstackApi<Read, Create, Update> {\n list(query: CrudQuery | undefined, init?: Init): Promise<Read[]>;\n get(id: number, init?: Init): Promise<Read>;\n stats(query: CrudQuery | undefined, init?: Init): Promise<JsonObject>;\n predict(query: CrudQuery | undefined, init?: Init): Promise<JsonObject>;\n revisions(id: number, init?: Init): Promise<JsonObject[]>;\n /** The manifest the client already loaded (the client's loader takes no signal). */\n manifest(init?: Init): Promise<Manifest>;\n create(body: Create): Promise<Read>;\n update(id: number, patch: Update, init?: { ifMatch?: string }): Promise<Read>;\n delete(id: number): Promise<void>;\n}\n\n/**\n * A query is the client's `ListOptions`, `StatsOptions` or `PredictOptions`\n * without `signal` (`{ filters, sort, skip, limit, ... }`); TanStack's signal\n * is added per call.\n */\nexport function tanstackApi<\n Read extends object,\n Create extends object,\n Update extends object,\n>(\n client: CrudClient,\n resource: ResourceClient<Read, Create, Update>,\n): TanstackApi<Read, Create, Update> {\n return {\n list: (query, init) => resource.list({ ...query, ...init }),\n get: (id, init) => resource.get(id, init),\n stats: (query, init) => resource.stats({ ...query, ...init }),\n predict: (query, init) => resource.predict({ ...query, ...init }),\n revisions: (id, init) => resource.revisions(id, init),\n manifest: () => client.manifest(),\n create: (body) => resource.create(body),\n update: (id, patch, init) => resource.update(id, patch, init),\n delete: (id) => resource.delete(id),\n };\n}\n","// Entry point `./tanstack`: `queryOptions` factories, key factory and the\n// invalidation bridge (TS2). Needs the optional peer `@tanstack/query-core`\n// (types only: nothing here imports it at runtime). query-core has no\n// `queryOptions`, so the factories return plain `{ queryKey, queryFn,\n// staleTime }` objects with `queryKey` tagged through `DataTag`; the React\n// wrappers live in lib-crud-views-ts.\nimport type { DataTag, InfiniteData, QueryClient } from \"@tanstack/query-core\";\nimport type { EventHub } from \"../events.ts\";\n\nexport { tanstackApi } from \"./adapter.ts\";\nexport type { TanstackApi } from \"./adapter.ts\";\nexport { tanstackCachePolicy } from \"../cache/policy.ts\";\nexport type { CachePolicy, ManifestCacheHints } from \"../cache/policy.ts\";\nexport { getEventHub, subscribeEvents } from \"../events.ts\";\nexport type {\n EventHandlers,\n EventHub,\n EventHubOptions,\n EventsConfig,\n SubscribeEventsOptions,\n} from \"../events.ts\";\nexport type { CrudEvent } from \"../interfaces.ts\";\n\n/** A query object as the client's query builder takes it. */\nexport type CrudQuery = Readonly<Record<string, unknown>>;\n\ntype Key = readonly unknown[];\n\n/** The key factory type. */\nexport interface CrudKeys {\n readonly all: readonly [\"crud\"];\n /** Everything of one resource. */\n resource(resource: string): readonly [\"crud\", string];\n /** All lists of one resource, plain and infinite. */\n lists(resource: string): readonly [\"crud\", string, \"list\"];\n list(resource: string, query?: CrudQuery): Key;\n infiniteList(resource: string, query: CrudQuery, limit: number): Key;\n /** All record queries of one resource. */\n records(resource: string): readonly [\"crud\", string, \"record\"];\n record(\n resource: string,\n id: number,\n ): readonly [\"crud\", string, \"record\", number];\n stats(resource: string, query?: CrudQuery): Key;\n predict(resource: string, query?: CrudQuery): Key;\n revisions(\n resource: string,\n id: number,\n ): readonly [\"crud\", string, \"revisions\", number];\n /** The manifest, optionally of one caller scope. */\n manifest(scope?: string): Key;\n}\n\n/** The key factory. Every key starts with `[\"crud\", resource, ...]`, so prefixes invalidate by resource, kind or record. */\nexport const crudKeys: CrudKeys = {\n all: [\"crud\"],\n resource: (resource) => [\"crud\", resource],\n lists: (resource) => [\"crud\", resource, \"list\"],\n list: (resource, query) =>\n query === undefined\n ? crudKeys.lists(resource)\n : [...crudKeys.lists(resource), query],\n infiniteList: (resource, query, limit) => [\n ...crudKeys.lists(resource),\n \"infinite\",\n query,\n limit,\n ],\n records: (resource) => [\"crud\", resource, \"record\"],\n record: (resource, id) => [\"crud\", resource, \"record\", id],\n stats: (resource, query) =>\n query === undefined\n ? [\"crud\", resource, \"stats\"]\n : [\"crud\", resource, \"stats\", query],\n predict: (resource, query) =>\n query === undefined\n ? [\"crud\", resource, \"predict\"]\n : [\"crud\", resource, \"predict\", query],\n revisions: (resource, id) => [\"crud\", resource, \"revisions\", id],\n manifest: (scope) =>\n scope === undefined ? [\"crud\", \"_manifest\"] : [\"crud\", \"_manifest\", scope],\n};\n\n/** What a query function receives from TanStack Query (a subset). */\nexport interface QueryContext {\n readonly signal: AbortSignal;\n}\n\nexport interface QueryOptionsInit {\n /** TanStack's `staleTime` in milliseconds. Default 0 (the SDK cache revalidates for free). */\n readonly staleTime?: number;\n}\n\n/** Plain query options with a typed key; spread into `useQuery`/`fetchQuery`. */\nexport interface CrudQueryOptions<T> {\n readonly queryKey: DataTag<Key, T, Error>;\n readonly queryFn: (context: QueryContext) => Promise<T>;\n readonly staleTime: number;\n}\n\nexport interface CrudInfiniteQueryOptions<T> {\n readonly queryKey: DataTag<\n Key,\n { pages: T[][]; pageParams: number[] },\n Error\n >;\n readonly queryFn: (\n context: QueryContext & { pageParam: number },\n ) => Promise<T[]>;\n readonly initialPageParam: number;\n /** `skip + limit` unless the page was short. */\n readonly getNextPageParam: (\n lastPage: T[],\n allPages: T[][],\n lastPageParam: number,\n ) => number | undefined;\n readonly staleTime: number;\n}\n\n/** The slices of a resource API the factories call (structural: TS1's resource client fits). */\nexport interface ListApi<T> {\n list(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<T[]>;\n}\nexport interface RecordApi<T> {\n get(id: number, init?: { signal?: AbortSignal }): Promise<T>;\n}\nexport interface StatsApi<S> {\n stats(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<S>;\n}\nexport interface PredictApi<P> {\n predict(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<P>;\n}\nexport interface RevisionsApi<R> {\n revisions(id: number, init?: { signal?: AbortSignal }): Promise<R[]>;\n}\nexport interface ManifestApi<M> {\n manifest(init?: { signal?: AbortSignal }): Promise<M>;\n}\nexport interface CreateApi<T, C> {\n create(body: C): Promise<T>;\n}\nexport interface UpdateApi<T, P> {\n update(id: number, patch: P, init?: { ifMatch?: string }): Promise<T>;\n}\nexport interface DeleteApi {\n delete(id: number): Promise<unknown>;\n}\n\nfunction tag<T>(key: Key): DataTag<Key, T, Error> {\n return key as DataTag<Key, T, Error>;\n}\n\nexport function listOptions<T>(\n resource: string,\n api: ListApi<T>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<T[]> {\n return {\n queryKey: tag(crudKeys.list(resource, query)),\n queryFn: ({ signal }) => api.list(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function recordOptions<T>(\n resource: string,\n api: RecordApi<T>,\n id: number,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<T> {\n return {\n queryKey: tag(crudKeys.record(resource, id)),\n queryFn: ({ signal }) => api.get(id, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function statsOptions<S>(\n resource: string,\n api: StatsApi<S>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<S> {\n return {\n queryKey: tag(crudKeys.stats(resource, query)),\n queryFn: ({ signal }) => api.stats(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function predictOptions<P>(\n resource: string,\n api: PredictApi<P>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<P> {\n return {\n queryKey: tag(crudKeys.predict(resource, query)),\n queryFn: ({ signal }) => api.predict(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function revisionsOptions<R>(\n resource: string,\n api: RevisionsApi<R>,\n id: number,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<R[]> {\n return {\n queryKey: tag(crudKeys.revisions(resource, id)),\n queryFn: ({ signal }) => api.revisions(id, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function manifestOptions<M>(\n api: ManifestApi<M>,\n scope?: string,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<M> {\n return {\n queryKey: tag(crudKeys.manifest(scope)),\n queryFn: ({ signal }) => api.manifest({ signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport interface InfiniteListInit extends QueryOptionsInit {\n /** Page size; default 100 (the server's default). */\n readonly limit?: number;\n /**\n * Keys of the paging parameters in the query object handed to `api.list`.\n * Default `skip`/`limit`, which is what `tanstackApi` (the client's\n * `ListOptions`) reads; change them only for a hand-written `ListApi`. The\n * client maps them to the manifest's `skip_param`/`limit_param` itself.\n */\n readonly skipKey?: string;\n readonly limitKey?: string;\n}\n\n/** Offset paging: the next page starts at `skip + limit`, unless the last page was short. */\nexport function infiniteListOptions<T>(\n resource: string,\n api: ListApi<T>,\n query: CrudQuery = {},\n init: InfiniteListInit = {},\n): CrudInfiniteQueryOptions<T> {\n const limit = init.limit ?? 100;\n const skipKey = init.skipKey ?? \"skip\";\n const limitKey = init.limitKey ?? \"limit\";\n return {\n queryKey: tag<InfiniteData<T[], number>>(\n crudKeys.infiniteList(resource, query, limit),\n ),\n queryFn: ({ signal, pageParam }) =>\n api.list(\n { ...query, [skipKey]: pageParam, [limitKey]: limit },\n { signal },\n ),\n initialPageParam: 0,\n getNextPageParam: (lastPage, _all, lastPageParam) =>\n lastPage.length < limit ? undefined : lastPageParam + limit,\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport type Invalidator = Pick<\n QueryClient,\n \"invalidateQueries\" | \"removeQueries\" | \"setQueryData\"\n>;\n\nasync function invalidateCollections(\n client: Invalidator,\n resource: string,\n): Promise<void> {\n await Promise.all([\n client.invalidateQueries({ queryKey: crudKeys.lists(resource) }),\n client.invalidateQueries({ queryKey: crudKeys.stats(resource) }),\n client.invalidateQueries({ queryKey: crudKeys.predict(resource) }),\n ]);\n}\n\nexport interface CreateMutationOptions<T, C> {\n readonly mutationKey: readonly [\"crud\", string, \"create\"];\n readonly mutationFn: (body: C) => Promise<T>;\n readonly onSuccess: () => Promise<void>;\n}\n\nexport interface UpdateMutationOptions<T, P> {\n readonly mutationKey: readonly [\"crud\", string, \"update\"];\n readonly mutationFn: (variables: UpdateVariables<P>) => Promise<T>;\n readonly onSuccess: (\n record: T,\n variables: UpdateVariables<P>,\n ) => Promise<void>;\n}\n\nexport interface DeleteMutationOptions {\n readonly mutationKey: readonly [\"crud\", string, \"delete\"];\n readonly mutationFn: (id: number) => Promise<unknown>;\n readonly onSuccess: (result: unknown, id: number) => Promise<void>;\n}\n\n/** Create: invalidate lists, stats and predict. */\nexport function createMutationOptions<T, C>(\n client: Invalidator,\n resource: string,\n api: CreateApi<T, C>,\n): CreateMutationOptions<T, C> {\n return {\n mutationKey: [\"crud\", resource, \"create\"],\n mutationFn: (body: C) => api.create(body),\n onSuccess: () => invalidateCollections(client, resource),\n };\n}\n\nexport interface UpdateVariables<P> {\n readonly id: number;\n readonly patch: P;\n /** Sent as `If-Match`; a stale one fails with 412. */\n readonly ifMatch?: string;\n}\n\n/** Update: `setQueryData` from the PATCH response, invalidate lists, stats, predict and revisions. */\nexport function updateMutationOptions<T, P>(\n client: Invalidator,\n resource: string,\n api: UpdateApi<T, P>,\n): UpdateMutationOptions<T, P> {\n return {\n mutationKey: [\"crud\", resource, \"update\"],\n mutationFn: ({ id, patch, ifMatch }: UpdateVariables<P>) =>\n api.update(id, patch, ifMatch === undefined ? {} : { ifMatch }),\n onSuccess: async (record: T, { id }: UpdateVariables<P>) => {\n client.setQueryData(crudKeys.record(resource, id), record);\n await Promise.all([\n invalidateCollections(client, resource),\n client.invalidateQueries({\n queryKey: crudKeys.revisions(resource, id),\n }),\n ]);\n },\n };\n}\n\n/** Delete: drop the record query, invalidate lists, stats, predict and revisions. */\nexport function deleteMutationOptions(\n client: Invalidator,\n resource: string,\n api: DeleteApi,\n): DeleteMutationOptions {\n return {\n mutationKey: [\"crud\", resource, \"delete\"],\n mutationFn: (id: number) => api.delete(id),\n onSuccess: async (_result: unknown, id: number) => {\n client.removeQueries({ queryKey: crudKeys.record(resource, id) });\n await Promise.all([\n invalidateCollections(client, resource),\n client.invalidateQueries({\n queryKey: crudKeys.revisions(resource, id),\n }),\n ]);\n },\n };\n}\n\n/**\n * Invalidate TanStack queries from the resource's event stream. Events\n * invalidate and never apply snapshots: `create` invalidates lists, stats and\n * predict; `update`/`delete`/`restore` also the record and its revisions; a\n * reconnect (or an event with no usable record id) invalidates the resource.\n * Returns the unsubscribe.\n */\nexport function bridgeEvents(\n client: Pick<QueryClient, \"invalidateQueries\">,\n hub: EventHub,\n resource: string,\n): () => void {\n return hub.subscribe({\n onEvent: async (event) => {\n const pending = [\n client.invalidateQueries({ queryKey: crudKeys.lists(resource) }),\n client.invalidateQueries({ queryKey: crudKeys.stats(resource) }),\n client.invalidateQueries({ queryKey: crudKeys.predict(resource) }),\n ];\n if (event.action !== \"create\") {\n pending.push(\n client.invalidateQueries({\n queryKey: crudKeys.record(resource, event.recordId),\n }),\n client.invalidateQueries({\n queryKey: crudKeys.revisions(resource, event.recordId),\n }),\n );\n }\n await Promise.all(pending);\n },\n onReset: () =>\n client.invalidateQueries({ queryKey: crudKeys.resource(resource) }),\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/tanstack/adapter.ts","../../src/tanstack/index.ts"],"sourcesContent":["// Adapts TS1's `ResourceClient` (options-object methods) to the structural\n// slices the `./tanstack` factories take (`list(query, { signal })`, ...).\nimport type { CrudClient, ResourceClient } from \"../client.ts\";\nimport type { JsonObject, Manifest } from \"../manifest.ts\";\nimport type { CrudQuery } from \"./index.ts\";\n\ntype Init = { signal?: AbortSignal };\n\n/** What `tanstackApi` returns: every slice the factories use, bound to one resource. */\nexport interface TanstackApi<Read, Create, Update> {\n list(query: CrudQuery | undefined, init?: Init): Promise<Read[]>;\n get(id: number, init?: Init): Promise<Read>;\n stats(query: CrudQuery | undefined, init?: Init): Promise<JsonObject>;\n predict(query: CrudQuery | undefined, init?: Init): Promise<JsonObject>;\n revisions(id: number, init?: Init): Promise<JsonObject[]>;\n /** The manifest the client already loaded (the client's loader takes no signal). */\n manifest(init?: Init): Promise<Manifest>;\n create(body: Create): Promise<Read>;\n update(id: number, patch: Update, init?: { ifMatch?: string }): Promise<Read>;\n delete(id: number): Promise<void>;\n}\n\n/**\n * A query is the client's `ListOptions`, `StatsOptions` or `PredictOptions`\n * without `signal` (`{ filters, sort, skip, limit, ... }`); TanStack's signal\n * is added per call.\n */\nexport function tanstackApi<\n Read extends object,\n Create extends object,\n Update extends object,\n>(\n client: CrudClient,\n resource: ResourceClient<Read, Create, Update>,\n): TanstackApi<Read, Create, Update> {\n return {\n list: (query, init) => resource.list({ ...query, ...init }),\n get: (id, init) => resource.get(id, init),\n stats: (query, init) => resource.stats({ ...query, ...init }),\n predict: (query, init) => resource.predict({ ...query, ...init }),\n revisions: (id, init) => resource.revisions(id, init),\n manifest: () => client.manifest(),\n create: (body) => resource.create(body),\n update: (id, patch, init) => resource.update(id, patch, init),\n delete: (id) => resource.delete(id),\n };\n}\n","// Entry point `./tanstack`: `queryOptions` factories, key factory and the\n// invalidation bridge (TS2). Needs the optional peer `@tanstack/query-core`\n// (types only: nothing here imports it at runtime). query-core has no\n// `queryOptions`, so the factories return plain `{ queryKey, queryFn,\n// staleTime }` objects with `queryKey` tagged through `DataTag`; the React\n// wrappers live in lib-crud-views-ts.\nimport type { DataTag, InfiniteData, QueryClient } from \"@tanstack/query-core\";\nimport type { EventHub } from \"../events.ts\";\n\nexport { tanstackApi } from \"./adapter.ts\";\nexport type { TanstackApi } from \"./adapter.ts\";\nexport { tanstackCachePolicy } from \"../cache/policy.ts\";\nexport type { CachePolicy, ManifestCacheHints } from \"../cache/policy.ts\";\nexport { getEventHub, subscribeEvents } from \"../events.ts\";\nexport type {\n EventHandlers,\n EventHub,\n EventHubOptions,\n EventsConfig,\n SubscribeEventsOptions,\n} from \"../events.ts\";\nexport type { CrudEvent } from \"../interfaces.ts\";\n\n/** A query object as the client's query builder takes it. */\nexport type CrudQuery = Readonly<Record<string, unknown>>;\n\ntype Key = readonly unknown[];\n\n/**\n * Who a query belongs to: the client's stable identity and the caller scope\n * (`await client.namespace()`). Two clients, or two callers, that share one\n * `QueryClient` never see each other's cached data.\n */\nexport interface CrudNamespace {\n readonly client: string;\n readonly scope: string;\n}\n\n/** The kinds that sit at `key[4]`. */\nexport type CrudKind = \"list\" | \"record\" | \"stats\" | \"predict\" | \"revisions\";\n\n/** Which queries a {@link CrudKeys.filter} matches; every member narrows. */\nexport interface CrudKeyFilter {\n readonly client: string;\n /** Omit for every resource of the client (including manifests). */\n readonly resource?: string;\n /** Omit for every caller scope. */\n readonly scope?: string;\n /** Skips this caller scope (every other scope matches). */\n readonly exceptScope?: string;\n /** Omit for every kind. */\n readonly kinds?: readonly CrudKind[];\n /** A record id; matches only the `record` and `revisions` keys of that record. */\n readonly id?: number;\n}\n\n/** Spread into `invalidateQueries`, `removeQueries` or `cancelQueries`. */\nexport type CrudKeyMatcher =\n | { readonly queryKey: readonly unknown[] }\n | {\n readonly predicate: (query: {\n readonly queryKey: readonly unknown[];\n }) => boolean;\n };\n\n/** The key factory type. */\nexport interface CrudKeys {\n readonly all: readonly [\"crud\"];\n /** Everything of one client. */\n client(client: string): readonly [\"crud\", string];\n /** Everything of one resource on one client, every scope and kind. */\n resource(client: string, resource: string): readonly [\"crud\", string, string];\n /** All lists of one resource and scope, plain and infinite. */\n lists(\n ns: CrudNamespace,\n resource: string,\n ): readonly [\"crud\", string, string, string, \"list\"];\n list(ns: CrudNamespace, resource: string, query?: CrudQuery): Key;\n infiniteList(\n ns: CrudNamespace,\n resource: string,\n query: CrudQuery,\n limit: number,\n ): Key;\n /** All record queries of one resource and scope. */\n records(\n ns: CrudNamespace,\n resource: string,\n ): readonly [\"crud\", string, string, string, \"record\"];\n record(\n ns: CrudNamespace,\n resource: string,\n id: number,\n ): readonly [\"crud\", string, string, string, \"record\", number];\n stats(ns: CrudNamespace, resource: string, query?: CrudQuery): Key;\n predict(ns: CrudNamespace, resource: string, query?: CrudQuery): Key;\n revisions(\n ns: CrudNamespace,\n resource: string,\n id: number,\n ): readonly [\"crud\", string, string, string, \"revisions\", number];\n /** The manifest of one caller scope. */\n manifest(ns: CrudNamespace): readonly [\"crud\", string, \"_manifest\", string];\n /**\n * A matcher for `invalidateQueries({ ...crudKeys.filter(f) })`. A client\n * or resource filter is one key prefix; narrowing by scope, kind or id\n * (which sit after the resource) uses a predicate.\n */\n filter(filter: CrudKeyFilter): CrudKeyMatcher;\n}\n\n/**\n * The key factory. Keys are\n * `[\"crud\", client, resource, scope, kind, ...]` (the manifest is\n * `[\"crud\", client, \"_manifest\", scope]`), so a prefix invalidates by client\n * or by resource on one client; `resource` is the manifest name, or\n * `name@apiVersion` for a pinned version (a different string is a different\n * resource: pass the same one to the invalidate helpers). `_manifest` is\n * reserved; a scope-only filter also matches the scope's manifest.\n */\nexport const crudKeys: CrudKeys = {\n all: [\"crud\"],\n client: (client) => [\"crud\", client],\n resource: (client, resource) => [\"crud\", client, resource],\n lists: (ns, resource) => [\"crud\", ns.client, resource, ns.scope, \"list\"],\n list: (ns, resource, query) =>\n query === undefined\n ? crudKeys.lists(ns, resource)\n : [...crudKeys.lists(ns, resource), query],\n infiniteList: (ns, resource, query, limit) => [\n ...crudKeys.lists(ns, resource),\n \"infinite\",\n query,\n limit,\n ],\n records: (ns, resource) => [\"crud\", ns.client, resource, ns.scope, \"record\"],\n record: (ns, resource, id) => [...crudKeys.records(ns, resource), id],\n stats: (ns, resource, query) =>\n query === undefined\n ? [\"crud\", ns.client, resource, ns.scope, \"stats\"]\n : [\"crud\", ns.client, resource, ns.scope, \"stats\", query],\n predict: (ns, resource, query) =>\n query === undefined\n ? [\"crud\", ns.client, resource, ns.scope, \"predict\"]\n : [\"crud\", ns.client, resource, ns.scope, \"predict\", query],\n revisions: (ns, resource, id) => [\n \"crud\",\n ns.client,\n resource,\n ns.scope,\n \"revisions\",\n id,\n ],\n manifest: (ns) => [\"crud\", ns.client, \"_manifest\", ns.scope],\n filter: (filter) => {\n const { client, resource, scope, exceptScope, kinds, id } = filter;\n if (\n scope === undefined &&\n exceptScope === undefined &&\n kinds === undefined &&\n id === undefined\n ) {\n return {\n queryKey:\n resource === undefined\n ? crudKeys.client(client)\n : crudKeys.resource(client, resource),\n };\n }\n return {\n predicate: ({ queryKey: key }) =>\n key[0] === \"crud\" &&\n key[1] === client &&\n (resource === undefined || key[2] === resource) &&\n (scope === undefined || key[3] === scope) &&\n (exceptScope === undefined || key[3] !== exceptScope) &&\n (kinds === undefined || kinds.includes(key[4] as CrudKind)) &&\n (id === undefined ||\n ((key[4] === \"record\" || key[4] === \"revisions\") && key[5] === id)),\n };\n },\n};\n\n/** What a query function receives from TanStack Query (a subset). */\nexport interface QueryContext {\n readonly signal: AbortSignal;\n}\n\nexport interface QueryOptionsInit {\n /** TanStack's `staleTime` in milliseconds. Default 0 (the SDK cache revalidates for free). */\n readonly staleTime?: number;\n}\n\n/** Plain query options with a typed key; spread into `useQuery`/`fetchQuery`. */\nexport interface CrudQueryOptions<T> {\n readonly queryKey: DataTag<Key, T, Error>;\n readonly queryFn: (context: QueryContext) => Promise<T>;\n readonly staleTime: number;\n}\n\nexport interface CrudInfiniteQueryOptions<T> {\n readonly queryKey: DataTag<\n Key,\n { pages: T[][]; pageParams: number[] },\n Error\n >;\n readonly queryFn: (\n context: QueryContext & { pageParam: number },\n ) => Promise<T[]>;\n readonly initialPageParam: number;\n /** `skip + limit` unless the page was short. */\n readonly getNextPageParam: (\n lastPage: T[],\n allPages: T[][],\n lastPageParam: number,\n ) => number | undefined;\n readonly staleTime: number;\n}\n\n/** The slices of a resource API the factories call (structural: TS1's resource client fits). */\nexport interface ListApi<T> {\n list(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<T[]>;\n}\nexport interface RecordApi<T> {\n get(id: number, init?: { signal?: AbortSignal }): Promise<T>;\n}\nexport interface StatsApi<S> {\n stats(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<S>;\n}\nexport interface PredictApi<P> {\n predict(\n query: CrudQuery | undefined,\n init?: { signal?: AbortSignal },\n ): Promise<P>;\n}\nexport interface RevisionsApi<R> {\n revisions(id: number, init?: { signal?: AbortSignal }): Promise<R[]>;\n}\nexport interface ManifestApi<M> {\n manifest(init?: { signal?: AbortSignal }): Promise<M>;\n}\nexport interface CreateApi<T, C> {\n create(body: C): Promise<T>;\n}\nexport interface UpdateApi<T, P> {\n update(id: number, patch: P, init?: { ifMatch?: string }): Promise<T>;\n}\nexport interface DeleteApi {\n delete(id: number): Promise<unknown>;\n}\n\nfunction tag<T>(key: Key): DataTag<Key, T, Error> {\n return key as DataTag<Key, T, Error>;\n}\n\nexport function listOptions<T>(\n ns: CrudNamespace,\n resource: string,\n api: ListApi<T>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<T[]> {\n return {\n queryKey: tag(crudKeys.list(ns, resource, query)),\n queryFn: ({ signal }) => api.list(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function recordOptions<T>(\n ns: CrudNamespace,\n resource: string,\n api: RecordApi<T>,\n id: number,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<T> {\n return {\n queryKey: tag(crudKeys.record(ns, resource, id)),\n queryFn: ({ signal }) => api.get(id, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function statsOptions<S>(\n ns: CrudNamespace,\n resource: string,\n api: StatsApi<S>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<S> {\n return {\n queryKey: tag(crudKeys.stats(ns, resource, query)),\n queryFn: ({ signal }) => api.stats(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function predictOptions<P>(\n ns: CrudNamespace,\n resource: string,\n api: PredictApi<P>,\n query?: CrudQuery,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<P> {\n return {\n queryKey: tag(crudKeys.predict(ns, resource, query)),\n queryFn: ({ signal }) => api.predict(query, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function revisionsOptions<R>(\n ns: CrudNamespace,\n resource: string,\n api: RevisionsApi<R>,\n id: number,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<R[]> {\n return {\n queryKey: tag(crudKeys.revisions(ns, resource, id)),\n queryFn: ({ signal }) => api.revisions(id, { signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport function manifestOptions<M>(\n ns: CrudNamespace,\n api: ManifestApi<M>,\n init: QueryOptionsInit = {},\n): CrudQueryOptions<M> {\n return {\n queryKey: tag(crudKeys.manifest(ns)),\n queryFn: ({ signal }) => api.manifest({ signal }),\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport interface InfiniteListInit extends QueryOptionsInit {\n /** Page size; default 100 (the server's default). */\n readonly limit?: number;\n /**\n * Keys of the paging parameters in the query object handed to `api.list`.\n * Default `skip`/`limit`, which is what `tanstackApi` (the client's\n * `ListOptions`) reads; change them only for a hand-written `ListApi`. The\n * client maps them to the manifest's `skip_param`/`limit_param` itself.\n */\n readonly skipKey?: string;\n readonly limitKey?: string;\n}\n\n/** Offset paging: the next page starts at `skip + limit`, unless the last page was short. */\nexport function infiniteListOptions<T>(\n ns: CrudNamespace,\n resource: string,\n api: ListApi<T>,\n query: CrudQuery = {},\n init: InfiniteListInit = {},\n): CrudInfiniteQueryOptions<T> {\n const limit = init.limit ?? 100;\n const skipKey = init.skipKey ?? \"skip\";\n const limitKey = init.limitKey ?? \"limit\";\n return {\n queryKey: tag<InfiniteData<T[], number>>(\n crudKeys.infiniteList(ns, resource, query, limit),\n ),\n queryFn: ({ signal, pageParam }) =>\n api.list(\n { ...query, [skipKey]: pageParam, [limitKey]: limit },\n { signal },\n ),\n initialPageParam: 0,\n getNextPageParam: (lastPage, _all, lastPageParam) =>\n lastPage.length < limit ? undefined : lastPageParam + limit,\n staleTime: init.staleTime ?? 0,\n };\n}\n\nexport type Invalidator = Pick<\n QueryClient,\n \"invalidateQueries\" | \"removeQueries\" | \"setQueryData\"\n>;\n\nconst COLLECTION_KINDS = [\"list\", \"stats\", \"predict\"] as const;\n\n/** One resource of one client; `resource` is the string used in its keys. */\nexport interface CrudTarget {\n readonly client: string;\n readonly resource: string;\n}\n\n/**\n * Invalidates the list, stats and predict queries of `resource` on `client`,\n * for every caller scope (a write changes what every caller sees).\n */\nexport function invalidateCollections(\n queryClient: Pick<QueryClient, \"invalidateQueries\">,\n { client, resource }: CrudTarget,\n): Promise<void> {\n return queryClient.invalidateQueries(\n crudKeys.filter({ client, resource, kinds: COLLECTION_KINDS }),\n );\n}\n\n/** Invalidates everything cached for `resource` on `client`: one key prefix. */\nexport function invalidateResource(\n queryClient: Pick<QueryClient, \"invalidateQueries\">,\n { client, resource }: CrudTarget,\n): Promise<void> {\n return queryClient.invalidateQueries(crudKeys.filter({ client, resource }));\n}\n\n/** Invalidates one record's `record` and `revisions` queries, for every scope. */\nexport function invalidateRecord(\n queryClient: Pick<QueryClient, \"invalidateQueries\">,\n { client, resource }: CrudTarget,\n id: number,\n): Promise<void> {\n return queryClient.invalidateQueries(\n crudKeys.filter({\n client,\n resource,\n kinds: [\"record\", \"revisions\"],\n id,\n }),\n );\n}\n\nexport interface CreateMutationOptions<T, C> {\n readonly mutationKey: readonly [\"crud\", string, string, string, \"create\"];\n readonly mutationFn: (body: C) => Promise<T>;\n readonly onSuccess: () => Promise<void>;\n}\n\nexport interface UpdateMutationOptions<T, P> {\n readonly mutationKey: readonly [\"crud\", string, string, string, \"update\"];\n readonly mutationFn: (variables: UpdateVariables<P>) => Promise<T>;\n readonly onSuccess: (\n record: T,\n variables: UpdateVariables<P>,\n ) => Promise<void>;\n}\n\nexport interface DeleteMutationOptions {\n readonly mutationKey: readonly [\"crud\", string, string, string, \"delete\"];\n readonly mutationFn: (id: number) => Promise<unknown>;\n readonly onSuccess: (result: unknown, id: number) => Promise<void>;\n}\n\n/** Create: invalidate lists, stats and predict. */\nexport function createMutationOptions<T, C>(\n queryClient: Invalidator,\n ns: CrudNamespace,\n resource: string,\n api: CreateApi<T, C>,\n): CreateMutationOptions<T, C> {\n return {\n mutationKey: [\"crud\", ns.client, resource, ns.scope, \"create\"],\n mutationFn: (body: C) => api.create(body),\n onSuccess: () =>\n invalidateCollections(queryClient, { client: ns.client, resource }),\n };\n}\n\nexport interface UpdateVariables<P> {\n readonly id: number;\n readonly patch: P;\n /** Sent as `If-Match`; a stale one fails with 412. */\n readonly ifMatch?: string;\n}\n\n/** Update: `setQueryData` from the PATCH response, invalidate lists, stats, predict and revisions. */\nexport function updateMutationOptions<T, P>(\n queryClient: Invalidator,\n ns: CrudNamespace,\n resource: string,\n api: UpdateApi<T, P>,\n): UpdateMutationOptions<T, P> {\n return {\n mutationKey: [\"crud\", ns.client, resource, ns.scope, \"update\"],\n mutationFn: ({ id, patch, ifMatch }: UpdateVariables<P>) =>\n api.update(id, patch, ifMatch === undefined ? {} : { ifMatch }),\n onSuccess: async (record: T, { id }: UpdateVariables<P>) => {\n queryClient.setQueryData(crudKeys.record(ns, resource, id), record);\n await Promise.all([\n invalidateCollections(queryClient, { client: ns.client, resource }),\n // The caller's own record was just set; other scopes' are stale.\n queryClient.invalidateQueries(\n crudKeys.filter({\n client: ns.client,\n resource,\n exceptScope: ns.scope,\n kinds: [\"record\"],\n id,\n }),\n ),\n queryClient.invalidateQueries(\n crudKeys.filter({\n client: ns.client,\n resource,\n kinds: [\"revisions\"],\n id,\n }),\n ),\n ]);\n },\n };\n}\n\n/** Delete: drop the record query, invalidate lists, stats, predict and revisions. */\nexport function deleteMutationOptions(\n queryClient: Invalidator,\n ns: CrudNamespace,\n resource: string,\n api: DeleteApi,\n): DeleteMutationOptions {\n return {\n mutationKey: [\"crud\", ns.client, resource, ns.scope, \"delete\"],\n mutationFn: (id: number) => api.delete(id),\n onSuccess: async (_result: unknown, id: number) => {\n queryClient.removeQueries(\n crudKeys.filter({\n client: ns.client,\n resource,\n kinds: [\"record\"],\n id,\n }),\n );\n await Promise.all([\n invalidateCollections(queryClient, { client: ns.client, resource }),\n queryClient.invalidateQueries(\n crudKeys.filter({\n client: ns.client,\n resource,\n kinds: [\"revisions\"],\n id,\n }),\n ),\n ]);\n },\n };\n}\n\n/**\n * Invalidate TanStack queries from the resource's event stream. Events\n * invalidate and never apply snapshots: `create` invalidates lists, stats and\n * predict; `update`/`delete`/`restore` also the record and its revisions; a\n * reconnect (or an event with no usable record id) invalidates the resource.\n * Returns the unsubscribe.\n */\nexport function bridgeEvents(\n queryClient: Pick<QueryClient, \"invalidateQueries\">,\n hub: EventHub,\n target: CrudTarget,\n): () => void {\n return hub.subscribe({\n onEvent: async (event) => {\n const pending = [invalidateCollections(queryClient, target)];\n if (event.action !== \"create\") {\n pending.push(invalidateRecord(queryClient, target, event.recordId));\n }\n await Promise.all(pending);\n },\n onReset: () => invalidateResource(queryClient, target),\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craftainer/crud-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Typed fetch client for craftainer CRUD APIs: manifest-driven resources, query builder, problem-details errors, opt-in cache with SSE invalidation and TanStack Query helpers.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {