@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
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { i as tanstackCachePolicy, n as subscribeEvents, t as getEventHub } from "../events-Cqj9Rbon.mjs";
|
|
2
|
+
//#region src/tanstack/adapter.ts
|
|
3
|
+
/**
|
|
4
|
+
* A query is the client's `ListOptions`, `StatsOptions` or `PredictOptions`
|
|
5
|
+
* without `signal` (`{ filters, sort, skip, limit, ... }`); TanStack's signal
|
|
6
|
+
* is added per call.
|
|
7
|
+
*/
|
|
8
|
+
function tanstackApi(client, resource) {
|
|
9
|
+
return {
|
|
10
|
+
list: (query, init) => resource.list({
|
|
11
|
+
...query,
|
|
12
|
+
...init
|
|
13
|
+
}),
|
|
14
|
+
get: (id, init) => resource.get(id, init),
|
|
15
|
+
stats: (query, init) => resource.stats({
|
|
16
|
+
...query,
|
|
17
|
+
...init
|
|
18
|
+
}),
|
|
19
|
+
predict: (query, init) => resource.predict({
|
|
20
|
+
...query,
|
|
21
|
+
...init
|
|
22
|
+
}),
|
|
23
|
+
revisions: (id, init) => resource.revisions(id, init),
|
|
24
|
+
manifest: () => client.manifest(),
|
|
25
|
+
create: (body) => resource.create(body),
|
|
26
|
+
update: (id, patch, init) => resource.update(id, patch, init),
|
|
27
|
+
delete: (id) => resource.delete(id)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/tanstack/index.ts
|
|
32
|
+
/** The key factory. Every key starts with `["crud", resource, ...]`, so prefixes invalidate by resource, kind or record. */
|
|
33
|
+
const crudKeys = {
|
|
34
|
+
all: ["crud"],
|
|
35
|
+
resource: (resource) => ["crud", resource],
|
|
36
|
+
lists: (resource) => [
|
|
37
|
+
"crud",
|
|
38
|
+
resource,
|
|
39
|
+
"list"
|
|
40
|
+
],
|
|
41
|
+
list: (resource, query) => query === void 0 ? crudKeys.lists(resource) : [...crudKeys.lists(resource), query],
|
|
42
|
+
infiniteList: (resource, query, limit) => [
|
|
43
|
+
...crudKeys.lists(resource),
|
|
44
|
+
"infinite",
|
|
45
|
+
query,
|
|
46
|
+
limit
|
|
47
|
+
],
|
|
48
|
+
records: (resource) => [
|
|
49
|
+
"crud",
|
|
50
|
+
resource,
|
|
51
|
+
"record"
|
|
52
|
+
],
|
|
53
|
+
record: (resource, id) => [
|
|
54
|
+
"crud",
|
|
55
|
+
resource,
|
|
56
|
+
"record",
|
|
57
|
+
id
|
|
58
|
+
],
|
|
59
|
+
stats: (resource, query) => query === void 0 ? [
|
|
60
|
+
"crud",
|
|
61
|
+
resource,
|
|
62
|
+
"stats"
|
|
63
|
+
] : [
|
|
64
|
+
"crud",
|
|
65
|
+
resource,
|
|
66
|
+
"stats",
|
|
67
|
+
query
|
|
68
|
+
],
|
|
69
|
+
predict: (resource, query) => query === void 0 ? [
|
|
70
|
+
"crud",
|
|
71
|
+
resource,
|
|
72
|
+
"predict"
|
|
73
|
+
] : [
|
|
74
|
+
"crud",
|
|
75
|
+
resource,
|
|
76
|
+
"predict",
|
|
77
|
+
query
|
|
78
|
+
],
|
|
79
|
+
revisions: (resource, id) => [
|
|
80
|
+
"crud",
|
|
81
|
+
resource,
|
|
82
|
+
"revisions",
|
|
83
|
+
id
|
|
84
|
+
],
|
|
85
|
+
manifest: (scope) => scope === void 0 ? ["crud", "_manifest"] : [
|
|
86
|
+
"crud",
|
|
87
|
+
"_manifest",
|
|
88
|
+
scope
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
function tag(key) {
|
|
92
|
+
return key;
|
|
93
|
+
}
|
|
94
|
+
function listOptions(resource, api, query, init = {}) {
|
|
95
|
+
return {
|
|
96
|
+
queryKey: tag(crudKeys.list(resource, query)),
|
|
97
|
+
queryFn: ({ signal }) => api.list(query, { signal }),
|
|
98
|
+
staleTime: init.staleTime ?? 0
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function recordOptions(resource, api, id, init = {}) {
|
|
102
|
+
return {
|
|
103
|
+
queryKey: tag(crudKeys.record(resource, id)),
|
|
104
|
+
queryFn: ({ signal }) => api.get(id, { signal }),
|
|
105
|
+
staleTime: init.staleTime ?? 0
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function statsOptions(resource, api, query, init = {}) {
|
|
109
|
+
return {
|
|
110
|
+
queryKey: tag(crudKeys.stats(resource, query)),
|
|
111
|
+
queryFn: ({ signal }) => api.stats(query, { signal }),
|
|
112
|
+
staleTime: init.staleTime ?? 0
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function predictOptions(resource, api, query, init = {}) {
|
|
116
|
+
return {
|
|
117
|
+
queryKey: tag(crudKeys.predict(resource, query)),
|
|
118
|
+
queryFn: ({ signal }) => api.predict(query, { signal }),
|
|
119
|
+
staleTime: init.staleTime ?? 0
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function revisionsOptions(resource, api, id, init = {}) {
|
|
123
|
+
return {
|
|
124
|
+
queryKey: tag(crudKeys.revisions(resource, id)),
|
|
125
|
+
queryFn: ({ signal }) => api.revisions(id, { signal }),
|
|
126
|
+
staleTime: init.staleTime ?? 0
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function manifestOptions(api, scope, init = {}) {
|
|
130
|
+
return {
|
|
131
|
+
queryKey: tag(crudKeys.manifest(scope)),
|
|
132
|
+
queryFn: ({ signal }) => api.manifest({ signal }),
|
|
133
|
+
staleTime: init.staleTime ?? 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Offset paging: the next page starts at `skip + limit`, unless the last page was short. */
|
|
137
|
+
function infiniteListOptions(resource, api, query = {}, init = {}) {
|
|
138
|
+
const limit = init.limit ?? 100;
|
|
139
|
+
const skipKey = init.skipKey ?? "skip";
|
|
140
|
+
const limitKey = init.limitKey ?? "limit";
|
|
141
|
+
return {
|
|
142
|
+
queryKey: tag(crudKeys.infiniteList(resource, query, limit)),
|
|
143
|
+
queryFn: ({ signal, pageParam }) => api.list({
|
|
144
|
+
...query,
|
|
145
|
+
[skipKey]: pageParam,
|
|
146
|
+
[limitKey]: limit
|
|
147
|
+
}, { signal }),
|
|
148
|
+
initialPageParam: 0,
|
|
149
|
+
getNextPageParam: (lastPage, _all, lastPageParam) => lastPage.length < limit ? void 0 : lastPageParam + limit,
|
|
150
|
+
staleTime: init.staleTime ?? 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
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
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
/** Create: invalidate lists, stats and predict. */
|
|
161
|
+
function createMutationOptions(client, resource, api) {
|
|
162
|
+
return {
|
|
163
|
+
mutationKey: [
|
|
164
|
+
"crud",
|
|
165
|
+
resource,
|
|
166
|
+
"create"
|
|
167
|
+
],
|
|
168
|
+
mutationFn: (body) => api.create(body),
|
|
169
|
+
onSuccess: () => invalidateCollections(client, resource)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Update: `setQueryData` from the PATCH response, invalidate lists, stats, predict and revisions. */
|
|
173
|
+
function updateMutationOptions(client, resource, api) {
|
|
174
|
+
return {
|
|
175
|
+
mutationKey: [
|
|
176
|
+
"crud",
|
|
177
|
+
resource,
|
|
178
|
+
"update"
|
|
179
|
+
],
|
|
180
|
+
mutationFn: ({ id, patch, ifMatch }) => api.update(id, patch, ifMatch === void 0 ? {} : { ifMatch }),
|
|
181
|
+
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) })]);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/** Delete: drop the record query, invalidate lists, stats, predict and revisions. */
|
|
188
|
+
function deleteMutationOptions(client, resource, api) {
|
|
189
|
+
return {
|
|
190
|
+
mutationKey: [
|
|
191
|
+
"crud",
|
|
192
|
+
resource,
|
|
193
|
+
"delete"
|
|
194
|
+
],
|
|
195
|
+
mutationFn: (id) => api.delete(id),
|
|
196
|
+
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) })]);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Invalidate TanStack queries from the resource's event stream. Events
|
|
204
|
+
* invalidate and never apply snapshots: `create` invalidates lists, stats and
|
|
205
|
+
* predict; `update`/`delete`/`restore` also the record and its revisions; a
|
|
206
|
+
* reconnect (or an event with no usable record id) invalidates the resource.
|
|
207
|
+
* Returns the unsubscribe.
|
|
208
|
+
*/
|
|
209
|
+
function bridgeEvents(client, hub, resource) {
|
|
210
|
+
return hub.subscribe({
|
|
211
|
+
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) }));
|
|
218
|
+
await Promise.all(pending);
|
|
219
|
+
},
|
|
220
|
+
onReset: () => client.invalidateQueries({ queryKey: crudKeys.resource(resource) })
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
export { bridgeEvents, createMutationOptions, crudKeys, deleteMutationOptions, getEventHub, infiniteListOptions, listOptions, manifestOptions, predictOptions, recordOptions, revisionsOptions, statsOptions, subscribeEvents, tanstackApi, tanstackCachePolicy, updateMutationOptions };
|
|
225
|
+
|
|
226
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craftainer/crud-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/craftainer/lib-crud-sdk-ts.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/craftainer/lib-crud-sdk-ts#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/craftainer/lib-crud-sdk-ts/issues"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"packageManager": "pnpm@12.14.0",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=24.22.0"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"main": "./dist/index.mjs",
|
|
24
|
+
"types": "./dist/index.d.mts",
|
|
25
|
+
"bin": {
|
|
26
|
+
"crud-sdk-codegen": "./dist/codegen/cli.mjs"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"default": "./dist/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./cache": {
|
|
34
|
+
"types": "./dist/cache/index.d.mts",
|
|
35
|
+
"default": "./dist/cache/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./tanstack": {
|
|
38
|
+
"types": "./dist/tanstack/index.d.mts",
|
|
39
|
+
"default": "./dist/tanstack/index.mjs"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"lint": "oxlint",
|
|
47
|
+
"format": "prettier --check .",
|
|
48
|
+
"format:write": "prettier --write .",
|
|
49
|
+
"test": "vitest run --coverage",
|
|
50
|
+
"publint": "publint --strict",
|
|
51
|
+
"attw": "attw --pack . --profile esm-only",
|
|
52
|
+
"lockfile": "pnpm install --frozen-lockfile --lockfile-only"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@tanstack/query-core": "^5.90.0",
|
|
56
|
+
"json-schema-to-typescript": "^16.0.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"@tanstack/query-core": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"json-schema-to-typescript": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
68
|
+
"@tanstack/query-core": "5.104.5",
|
|
69
|
+
"@types/node": "24.19.2",
|
|
70
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
71
|
+
"eventsource-parser": "4.1.2",
|
|
72
|
+
"fake-indexeddb": "6.2.6",
|
|
73
|
+
"idb": "8.0.4",
|
|
74
|
+
"json-schema-to-typescript": "16.0.0",
|
|
75
|
+
"oxlint": "1.89.0",
|
|
76
|
+
"prettier": "3.9.10",
|
|
77
|
+
"publint": "0.3.25",
|
|
78
|
+
"tsdown": "0.23.1",
|
|
79
|
+
"typescript": "6.0.3",
|
|
80
|
+
"vitest": "5.0.5"
|
|
81
|
+
}
|
|
82
|
+
}
|