@metaobjectsdev/codegen-ts-tanstack 0.9.0-rc.1 → 0.10.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 +5 -1
- package/dist/grid-filter-validate.d.ts +20 -0
- package/dist/grid-filter-validate.d.ts.map +1 -0
- package/dist/grid-filter-validate.js +37 -0
- package/dist/grid-filter-validate.js.map +1 -0
- package/dist/tanstack-grid-hook.d.ts.map +1 -1
- package/dist/tanstack-grid-hook.js +2 -2
- package/dist/tanstack-grid-hook.js.map +1 -1
- package/dist/tanstack-grid.d.ts.map +1 -1
- package/dist/tanstack-grid.js +8 -3
- package/dist/tanstack-grid.js.map +1 -1
- package/dist/tanstack-query.d.ts.map +1 -1
- package/dist/tanstack-query.js +4 -2
- package/dist/tanstack-query.js.map +1 -1
- package/dist/templates/columns-file.d.ts.map +1 -1
- package/dist/templates/columns-file.js +35 -7
- package/dist/templates/columns-file.js.map +1 -1
- package/dist/templates/hooks-file.d.ts.map +1 -1
- package/dist/templates/hooks-file.js +261 -10
- package/dist/templates/hooks-file.js.map +1 -1
- package/package.json +37 -26
- package/src/tanstack-grid-hook.ts +2 -2
- package/src/tanstack-grid.ts +8 -3
- package/src/tanstack-query.ts +4 -2
- package/src/templates/columns-file.ts +47 -6
- package/src/templates/hooks-file.ts +309 -11
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { code, imp, joinCode, type Code } from "ts-poet";
|
|
1
|
+
import { code, imp, joinCode, Import, type Code } from "ts-poet";
|
|
2
2
|
import type { MetaObject } from "@metaobjectsdev/metadata";
|
|
3
|
-
import type { RenderContext } from "@metaobjectsdev/codegen-ts";
|
|
4
|
-
import {
|
|
3
|
+
import type { RenderContext, RelationEntry } from "@metaobjectsdev/codegen-ts";
|
|
4
|
+
import {
|
|
5
|
+
GENERATED_HEADER,
|
|
6
|
+
isProjection,
|
|
7
|
+
pluralize,
|
|
8
|
+
entityModuleSpecifier,
|
|
9
|
+
isTphDiscriminatorBase,
|
|
10
|
+
tphPlan,
|
|
11
|
+
} from "@metaobjectsdev/codegen-ts";
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Render <Entity>.hooks.ts — query-key factory + 2 query hooks + (for non-projections) 3 mutation hooks.
|
|
@@ -29,21 +36,125 @@ export function renderHooksFile(entity: MetaObject, ctx: RenderContext): string
|
|
|
29
36
|
entity.name,
|
|
30
37
|
ctx.extStyle,
|
|
31
38
|
);
|
|
39
|
+
// FR-017 Tier 3: a TPH discriminator base gets a polymorphic + per-subtype
|
|
40
|
+
// hooks file (the subtype entities are filtered out of this generator).
|
|
41
|
+
if (isTphDiscriminatorBase(entity, ctx.loadedRoot)) {
|
|
42
|
+
return renderTphHooksFile(entity, ctx, entityModule);
|
|
43
|
+
}
|
|
32
44
|
if (isProjection(entity)) {
|
|
33
|
-
return renderReadOnlyHooksFile(entity, entityModule);
|
|
45
|
+
return renderReadOnlyHooksFile(entity, entityModule, ctx);
|
|
46
|
+
}
|
|
47
|
+
return renderFullHooksFile(entity, entityModule, ctx);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// FR-018 — M:N collection hook(s).
|
|
52
|
+
//
|
|
53
|
+
// For each many-to-many relationship the source declares (`@cardinality: "many"`
|
|
54
|
+
// + `@through`), emit `use<Source><Relation>(sourceId, opts?)` — a useQuery that
|
|
55
|
+
// fetches the REST sub-resource `GET /<source-plural>/{sourceId}/<relationName>`
|
|
56
|
+
// (the exact URL mountM2mRoute serves) and returns the typed target collection
|
|
57
|
+
// (`Target[]`). The query is enabled only when sourceId is present, so callers
|
|
58
|
+
// can pass `undefined` before the parent row loads. A symmetric self-join is
|
|
59
|
+
// still ONE collection hook (the server unions both junction columns on read).
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
/** The M:N relation entries for an entity (cardinality 'many' + a junction). */
|
|
63
|
+
function m2mEntriesFor(entity: MetaObject, ctx: RenderContext): RelationEntry[] {
|
|
64
|
+
return (ctx.relationMap.get(entity.name) ?? []).filter(
|
|
65
|
+
(e) => e.cardinality === "many" && e.junctionEntity !== undefined,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The `relation: (relation, sourceId) => ...` query-key factory line, included
|
|
70
|
+
* in the keys factory ONLY when the entity has M:N relationships. */
|
|
71
|
+
function m2mKeyLine(keysVar: string): string {
|
|
72
|
+
return (
|
|
73
|
+
` relation: (relation: string, sourceId: number | undefined) =>\n` +
|
|
74
|
+
` [...${keysVar}.all(), "relation", relation, sourceId ?? null] as const,`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render `use<Source><Relation>(sourceId, opts?)` per M:N relationship. Returns
|
|
80
|
+
* null when the entity has no M:N relationships (no extra hooks emitted).
|
|
81
|
+
*/
|
|
82
|
+
function renderM2mHooks(
|
|
83
|
+
entity: MetaObject,
|
|
84
|
+
ctx: RenderContext,
|
|
85
|
+
keysVar: string,
|
|
86
|
+
entries: RelationEntry[],
|
|
87
|
+
): Code | null {
|
|
88
|
+
if (entries.length === 0) return null;
|
|
89
|
+
|
|
90
|
+
const useQuerySym = imp("useQuery@@tanstack/react-query");
|
|
91
|
+
const useQueryOptionsSym = imp("t:UseQueryOptions@@tanstack/react-query");
|
|
92
|
+
const useQueryResultSym = imp("t:UseQueryResult@@tanstack/react-query");
|
|
93
|
+
const useEntityFetcherSym = imp("useEntityFetcher@@metaobjectsdev/tanstack");
|
|
94
|
+
|
|
95
|
+
const source = entity.name;
|
|
96
|
+
|
|
97
|
+
// Distinct target row types, imported (aliased) from each target's entity
|
|
98
|
+
// module. ts-poet's imp() tracks + hoists these into the import block. The
|
|
99
|
+
// <Target>RelRow alias avoids colliding with the source file's own
|
|
100
|
+
// `type <Source> as <Source>Row` import on a self-join (source === target).
|
|
101
|
+
const targetTypeSym = new Map<string, Import>();
|
|
102
|
+
for (const e of entries) {
|
|
103
|
+
if (targetTypeSym.has(e.targetEntity)) continue;
|
|
104
|
+
const mod = entityModuleSpecifier(
|
|
105
|
+
ctx.selfTarget,
|
|
106
|
+
ctx.entityModuleTarget,
|
|
107
|
+
ctx.packageOf.get(e.targetEntity),
|
|
108
|
+
e.targetEntity,
|
|
109
|
+
ctx.extStyle,
|
|
110
|
+
);
|
|
111
|
+
// `import { type <Target> as <Target>RelRow } from "<mod>"` — the RelRow
|
|
112
|
+
// alias avoids colliding with the source file's own `type <Source> as
|
|
113
|
+
// <Source>Row` import on a self-join (source === target).
|
|
114
|
+
targetTypeSym.set(
|
|
115
|
+
e.targetEntity,
|
|
116
|
+
Import.importsName(`${e.targetEntity}RelRow`, mod, true, e.targetEntity),
|
|
117
|
+
);
|
|
34
118
|
}
|
|
35
|
-
|
|
119
|
+
|
|
120
|
+
const hooks = entries.map((e) => {
|
|
121
|
+
const targetSym = targetTypeSym.get(e.targetEntity)!;
|
|
122
|
+
const hookName = `use${source}${capitalize(e.name)}`;
|
|
123
|
+
const relLit = JSON.stringify(e.name);
|
|
124
|
+
return code`
|
|
125
|
+
export function ${hookName}(
|
|
126
|
+
sourceId: number | undefined,
|
|
127
|
+
opts?: Omit<${useQueryOptionsSym}<${targetSym}[]>, "queryKey" | "queryFn">,
|
|
128
|
+
): ${useQueryResultSym}<${targetSym}[]> {
|
|
129
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
130
|
+
return ${useQuerySym}<${targetSym}[]>({
|
|
131
|
+
queryKey: ${keysVar}.relation(${relLit}, sourceId),
|
|
132
|
+
queryFn: () => fetcher<${targetSym}[]>(\`\${${source}.$apiPrefix}\${${source}.$path}/\${sourceId}/${e.name}\`),
|
|
133
|
+
enabled: sourceId != null && (opts?.enabled ?? true),
|
|
134
|
+
...opts,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return joinCode(hooks, { on: "\n" });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function capitalize(s: string): string {
|
|
144
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
36
145
|
}
|
|
37
146
|
|
|
38
147
|
// ---------------------------------------------------------------------------
|
|
39
148
|
// Read-only path (projections)
|
|
40
149
|
// ---------------------------------------------------------------------------
|
|
41
150
|
|
|
42
|
-
function renderReadOnlyHooksFile(entity: MetaObject, entityModule: string): string {
|
|
151
|
+
function renderReadOnlyHooksFile(entity: MetaObject, entityModule: string, ctx: RenderContext): string {
|
|
43
152
|
const entityName = entity.name;
|
|
44
153
|
const entityNamePlural = pluralize(entityName);
|
|
45
154
|
const lcEntity = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
46
155
|
const keysVar = `${lcEntity}Keys`;
|
|
156
|
+
const m2mEntries = m2mEntriesFor(entity, ctx);
|
|
157
|
+
const relationKeyLine = m2mEntries.length > 0 ? `\n${m2mKeyLine(keysVar)}` : "";
|
|
47
158
|
|
|
48
159
|
const useQuerySym = imp("useQuery@@tanstack/react-query");
|
|
49
160
|
const useQueryOptionsSym = imp("t:UseQueryOptions@@tanstack/react-query");
|
|
@@ -65,7 +176,7 @@ export const ${keysVar} = {
|
|
|
65
176
|
lists: () => [...${keysVar}.all(), "list"] as const,
|
|
66
177
|
list: (filter?: ${entityName}Filter) => [...${keysVar}.lists(), filter ?? {}] as const,
|
|
67
178
|
details: () => [...${keysVar}.all(), "detail"] as const,
|
|
68
|
-
detail: (id: number) => [...${keysVar}.details(), id] as const
|
|
179
|
+
detail: (id: number) => [...${keysVar}.details(), id] as const,${relationKeyLine}
|
|
69
180
|
};
|
|
70
181
|
`;
|
|
71
182
|
|
|
@@ -96,7 +207,8 @@ export function use${entityNamePlural}(
|
|
|
96
207
|
}
|
|
97
208
|
`;
|
|
98
209
|
|
|
99
|
-
const
|
|
210
|
+
const m2mHooks = renderM2mHooks(entity, ctx, keysVar, m2mEntries);
|
|
211
|
+
const body: Code = joinCode(m2mHooks ? [queryKeys, queries, m2mHooks] : [queryKeys, queries], { on: "\n" });
|
|
100
212
|
|
|
101
213
|
const header =
|
|
102
214
|
`// ${GENERATED_HEADER}-tanstack — DO NOT EDIT.\n` +
|
|
@@ -108,11 +220,13 @@ export function use${entityNamePlural}(
|
|
|
108
220
|
// Full path (writable entities — table-backed or write-through)
|
|
109
221
|
// ---------------------------------------------------------------------------
|
|
110
222
|
|
|
111
|
-
function renderFullHooksFile(entity: MetaObject, entityModule: string): string {
|
|
223
|
+
function renderFullHooksFile(entity: MetaObject, entityModule: string, ctx: RenderContext): string {
|
|
112
224
|
const entityName = entity.name;
|
|
113
225
|
const entityNamePlural = pluralize(entityName);
|
|
114
226
|
const lcEntity = entityName.charAt(0).toLowerCase() + entityName.slice(1);
|
|
115
227
|
const keysVar = `${lcEntity}Keys`;
|
|
228
|
+
const m2mEntries = m2mEntriesFor(entity, ctx);
|
|
229
|
+
const relationKeyLine = m2mEntries.length > 0 ? `\n${m2mKeyLine(keysVar)}` : "";
|
|
116
230
|
|
|
117
231
|
const useMutationSym = imp("useMutation@@tanstack/react-query");
|
|
118
232
|
const useQuerySym = imp("useQuery@@tanstack/react-query");
|
|
@@ -140,7 +254,7 @@ export const ${keysVar} = {
|
|
|
140
254
|
lists: () => [...${keysVar}.all(), "list"] as const,
|
|
141
255
|
list: (filter?: ${entityName}Filter) => [...${keysVar}.lists(), filter ?? {}] as const,
|
|
142
256
|
details: () => [...${keysVar}.all(), "detail"] as const,
|
|
143
|
-
detail: (id: number) => [...${keysVar}.details(), id] as const
|
|
257
|
+
detail: (id: number) => [...${keysVar}.details(), id] as const,${relationKeyLine}
|
|
144
258
|
};
|
|
145
259
|
`;
|
|
146
260
|
|
|
@@ -171,6 +285,8 @@ export function use${entityNamePlural}(
|
|
|
171
285
|
}
|
|
172
286
|
`;
|
|
173
287
|
|
|
288
|
+
const m2mHooks = renderM2mHooks(entity, ctx, keysVar, m2mEntries);
|
|
289
|
+
|
|
174
290
|
const mutations: Code = code`
|
|
175
291
|
export function useCreate${entityName}(
|
|
176
292
|
opts?: Omit<${useMutationOptionsSym}<${entityName}Row, Error, ${entityName}Insert>, "mutationFn">,
|
|
@@ -226,10 +342,192 @@ export function useDelete${entityName}(
|
|
|
226
342
|
}
|
|
227
343
|
`;
|
|
228
344
|
|
|
229
|
-
const body: Code = joinCode(
|
|
345
|
+
const body: Code = joinCode(
|
|
346
|
+
m2mHooks ? [queryKeys, queries, m2mHooks, mutations] : [queryKeys, queries, mutations],
|
|
347
|
+
{ on: "\n" },
|
|
348
|
+
);
|
|
230
349
|
|
|
231
350
|
const header =
|
|
232
351
|
`// ${GENERATED_HEADER}-tanstack — DO NOT EDIT.\n` +
|
|
233
352
|
`// Source metadata: ${entityName} (${entity.fqn()})\n`;
|
|
234
353
|
return header + entityImports.toString() + body.toString();
|
|
235
354
|
}
|
|
355
|
+
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// FR-017 Tier 3 — TPH discriminator base: polymorphic + per-subtype hooks.
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
|
|
360
|
+
function renderTphHooksFile(base: MetaObject, ctx: RenderContext, baseModule: string): string {
|
|
361
|
+
const baseName = base.name;
|
|
362
|
+
const lcBase = baseName.charAt(0).toLowerCase() + baseName.slice(1);
|
|
363
|
+
const keysVar = `${lcBase}Keys`;
|
|
364
|
+
// Single source of truth for discriminator field + subtypes + route segments.
|
|
365
|
+
const plan = tphPlan(base, ctx.loadedRoot)!;
|
|
366
|
+
const discField = plan.discriminatorField;
|
|
367
|
+
|
|
368
|
+
const useQuerySym = imp("useQuery@@tanstack/react-query");
|
|
369
|
+
const useMutationSym = imp("useMutation@@tanstack/react-query");
|
|
370
|
+
const useQueryClientSym = imp("useQueryClient@@tanstack/react-query");
|
|
371
|
+
const useQueryOptionsSym = imp("t:UseQueryOptions@@tanstack/react-query");
|
|
372
|
+
const useMutationOptionsSym = imp("t:UseMutationOptions@@tanstack/react-query");
|
|
373
|
+
const useQueryResultSym = imp("t:UseQueryResult@@tanstack/react-query");
|
|
374
|
+
const useMutationResultSym = imp("t:UseMutationResult@@tanstack/react-query");
|
|
375
|
+
const useEntityFetcherSym = imp("useEntityFetcher@@metaobjectsdev/tanstack");
|
|
376
|
+
const buildFilterQsSym = imp("buildFilterQs@@metaobjectsdev/runtime-web");
|
|
377
|
+
|
|
378
|
+
const subtypes = plan.subtypes;
|
|
379
|
+
|
|
380
|
+
// `${baseName}` imports BOTH the constants value (for $path/$apiPrefix) and the
|
|
381
|
+
// discriminated-union type (declaration merge). Each subtype contributes its
|
|
382
|
+
// interface type AND its own filter type (discriminator-excluded — the route
|
|
383
|
+
// pins it), so per-subtype hooks filter on the fields the per-subtype
|
|
384
|
+
// allowlist actually permits.
|
|
385
|
+
const subImportLines = subtypes
|
|
386
|
+
.map((s) => {
|
|
387
|
+
const m = entityModuleSpecifier(ctx.selfTarget, ctx.entityModuleTarget, s.entity.package, s.entity.name, ctx.extStyle);
|
|
388
|
+
return `import { type ${s.entity.name}, type ${s.entity.name}Filter } from ${JSON.stringify(m)};`;
|
|
389
|
+
})
|
|
390
|
+
.join("\n");
|
|
391
|
+
const entityImports: Code = code`
|
|
392
|
+
import { ${baseName}, type ${baseName}Filter } from ${JSON.stringify(baseModule)};
|
|
393
|
+
${subImportLines}
|
|
394
|
+
`;
|
|
395
|
+
|
|
396
|
+
const queryKeys: Code = code`
|
|
397
|
+
export const ${keysVar} = {
|
|
398
|
+
all: () => [${JSON.stringify(lcBase)}] as const,
|
|
399
|
+
lists: () => [...${keysVar}.all(), "list"] as const,
|
|
400
|
+
list: (filter?: ${baseName}Filter) => [...${keysVar}.lists(), filter ?? {}] as const,
|
|
401
|
+
details: () => [...${keysVar}.all(), "detail"] as const,
|
|
402
|
+
detail: (id: number) => [...${keysVar}.details(), id] as const,
|
|
403
|
+
subtypeLists: (sub: string) => [...${keysVar}.all(), sub, "list"] as const,
|
|
404
|
+
// filter is loosely typed here (cache-key identity only); the per-subtype
|
|
405
|
+
// hooks below type it precisely as <Sub>Filter.
|
|
406
|
+
subtypeList: (sub: string, filter?: unknown) => [...${keysVar}.subtypeLists(sub), filter ?? {}] as const,
|
|
407
|
+
subtypeDetails:(sub: string) => [...${keysVar}.all(), sub, "detail"] as const,
|
|
408
|
+
subtypeDetail: (sub: string, id: number) => [...${keysVar}.subtypeDetails(sub), id] as const,
|
|
409
|
+
};
|
|
410
|
+
`;
|
|
411
|
+
|
|
412
|
+
// Polymorphic reads — return the discriminated union.
|
|
413
|
+
const polymorphic: Code = code`
|
|
414
|
+
export function use${baseName}(
|
|
415
|
+
id: number,
|
|
416
|
+
opts?: Omit<${useQueryOptionsSym}<${baseName}>, "queryKey" | "queryFn">,
|
|
417
|
+
): ${useQueryResultSym}<${baseName}> {
|
|
418
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
419
|
+
return ${useQuerySym}<${baseName}>({
|
|
420
|
+
queryKey: ${keysVar}.detail(id),
|
|
421
|
+
queryFn: () => fetcher<${baseName}>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/\${id}\`),
|
|
422
|
+
...opts,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function use${pluralize(baseName)}(
|
|
427
|
+
filter?: ${baseName}Filter,
|
|
428
|
+
opts?: Omit<${useQueryOptionsSym}<${baseName}[]>, "queryKey" | "queryFn">,
|
|
429
|
+
): ${useQueryResultSym}<${baseName}[]> {
|
|
430
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
431
|
+
const qs = filter ? "?" + ${buildFilterQsSym}(filter as Record<string, unknown>) : "";
|
|
432
|
+
return ${useQuerySym}<${baseName}[]>({
|
|
433
|
+
queryKey: ${keysVar}.list(filter),
|
|
434
|
+
queryFn: () => fetcher<${baseName}[]>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}\${qs}\`),
|
|
435
|
+
...opts,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
`;
|
|
439
|
+
|
|
440
|
+
// Per-subtype hooks — scoped to each discriminator value's REST sub-path.
|
|
441
|
+
const subtypeSections: Code[] = subtypes.map(({ entity: subEntity, value, routeSegment: seg }) => {
|
|
442
|
+
const subName = subEntity.name;
|
|
443
|
+
const valueLit = JSON.stringify(value);
|
|
444
|
+
const createInput = `Omit<${subName}, ${JSON.stringify(discField)}>`;
|
|
445
|
+
const updateInput = `Partial<${createInput}>`;
|
|
446
|
+
const subPath = `\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/${seg}\``;
|
|
447
|
+
return code`
|
|
448
|
+
export function use${pluralize(subName)}(
|
|
449
|
+
filter?: ${subName}Filter,
|
|
450
|
+
opts?: Omit<${useQueryOptionsSym}<${subName}[]>, "queryKey" | "queryFn">,
|
|
451
|
+
): ${useQueryResultSym}<${subName}[]> {
|
|
452
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
453
|
+
const qs = filter ? "?" + ${buildFilterQsSym}(filter as Record<string, unknown>) : "";
|
|
454
|
+
return ${useQuerySym}<${subName}[]>({
|
|
455
|
+
queryKey: ${keysVar}.subtypeList(${valueLit}, filter),
|
|
456
|
+
queryFn: () => fetcher<${subName}[]>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/${seg}\${qs}\`),
|
|
457
|
+
...opts,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function use${subName}(
|
|
462
|
+
id: number,
|
|
463
|
+
opts?: Omit<${useQueryOptionsSym}<${subName}>, "queryKey" | "queryFn">,
|
|
464
|
+
): ${useQueryResultSym}<${subName}> {
|
|
465
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
466
|
+
return ${useQuerySym}<${subName}>({
|
|
467
|
+
queryKey: ${keysVar}.subtypeDetail(${valueLit}, id),
|
|
468
|
+
queryFn: () => fetcher<${subName}>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/${seg}/\${id}\`),
|
|
469
|
+
...opts,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
export function useCreate${subName}(
|
|
474
|
+
opts?: Omit<${useMutationOptionsSym}<${subName}, Error, ${createInput}>, "mutationFn">,
|
|
475
|
+
): ${useMutationResultSym}<${subName}, Error, ${createInput}> {
|
|
476
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
477
|
+
const qc = ${useQueryClientSym}();
|
|
478
|
+
return ${useMutationSym}<${subName}, Error, ${createInput}>({
|
|
479
|
+
mutationFn: (input) => fetcher<${subName}>(${subPath}, {
|
|
480
|
+
method: "POST",
|
|
481
|
+
headers: { "Content-Type": "application/json" },
|
|
482
|
+
body: JSON.stringify(input),
|
|
483
|
+
}),
|
|
484
|
+
...opts,
|
|
485
|
+
onSuccess: (...args) => {
|
|
486
|
+
qc.invalidateQueries({ queryKey: ${keysVar}.all() });
|
|
487
|
+
opts?.onSuccess?.(...args);
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function useUpdate${subName}(
|
|
493
|
+
opts?: Omit<${useMutationOptionsSym}<${subName}, Error, { id: number; input: ${updateInput} }>, "mutationFn">,
|
|
494
|
+
): ${useMutationResultSym}<${subName}, Error, { id: number; input: ${updateInput} }> {
|
|
495
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
496
|
+
const qc = ${useQueryClientSym}();
|
|
497
|
+
return ${useMutationSym}({
|
|
498
|
+
mutationFn: ({ id, input }) => fetcher<${subName}>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/${seg}/\${id}\`, {
|
|
499
|
+
method: "PATCH",
|
|
500
|
+
headers: { "Content-Type": "application/json" },
|
|
501
|
+
body: JSON.stringify(input),
|
|
502
|
+
}),
|
|
503
|
+
...opts,
|
|
504
|
+
onSuccess: (...args) => {
|
|
505
|
+
qc.invalidateQueries({ queryKey: ${keysVar}.all() });
|
|
506
|
+
opts?.onSuccess?.(...args);
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export function useDelete${subName}(
|
|
512
|
+
opts?: Omit<${useMutationOptionsSym}<void, Error, number>, "mutationFn">,
|
|
513
|
+
): ${useMutationResultSym}<void, Error, number> {
|
|
514
|
+
const fetcher = ${useEntityFetcherSym}();
|
|
515
|
+
const qc = ${useQueryClientSym}();
|
|
516
|
+
return ${useMutationSym}({
|
|
517
|
+
mutationFn: (id) => fetcher<void>(\`\${${baseName}.$apiPrefix}\${${baseName}.$path}/${seg}/\${id}\`, { method: "DELETE" }),
|
|
518
|
+
...opts,
|
|
519
|
+
onSuccess: (...args) => {
|
|
520
|
+
qc.invalidateQueries({ queryKey: ${keysVar}.all() });
|
|
521
|
+
opts?.onSuccess?.(...args);
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
`;
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const body: Code = joinCode([queryKeys, polymorphic, ...subtypeSections], { on: "\n" });
|
|
529
|
+
const header =
|
|
530
|
+
`// ${GENERATED_HEADER}-tanstack — DO NOT EDIT.\n` +
|
|
531
|
+
`// Source metadata: ${baseName} (${base.fqn()}) — TPH discriminator base\n`;
|
|
532
|
+
return header + entityImports.toString() + body.toString();
|
|
533
|
+
}
|