@pattern-stack/codegen 0.17.2 → 0.19.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/CHANGELOG.md +32 -0
- package/README.md +157 -2
- package/consumer-skills/codegen/SKILL.md +32 -0
- package/consumer-skills/entities/SKILL.md +2 -0
- package/dist/{chunk-I6UXRJ3Q.js → chunk-43SBT72G.js} +4 -4
- package/dist/{chunk-T6SCOJF4.js → chunk-7LKAMLV4.js} +4 -4
- package/dist/{chunk-IOQMMH6C.js → chunk-F7KN3U6U.js} +122 -8
- package/dist/chunk-F7KN3U6U.js.map +1 -0
- package/dist/{chunk-CZQUOIDY.js → chunk-J7JMVS2B.js} +4 -4
- package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
- package/dist/{chunk-ATVGYF3D.js → chunk-PKDS6QIJ.js} +7 -7
- package/dist/{chunk-KZDHMZ45.js → chunk-SNH35CNA.js} +8 -8
- package/dist/runtime/base-classes/index.js +24 -24
- package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
- package/dist/runtime/subsystems/analytics/index.js +4 -4
- package/dist/runtime/subsystems/auth/auth.module.js +3 -3
- package/dist/runtime/subsystems/auth/index.js +14 -14
- package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +1 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +5 -5
- package/dist/runtime/subsystems/bridge/index.js +13 -13
- package/dist/runtime/subsystems/cache/cache.module.js +1 -1
- package/dist/runtime/subsystems/cache/index.js +3 -3
- package/dist/runtime/subsystems/index.js +94 -94
- package/dist/runtime/subsystems/integration/build-change-source.js +2 -2
- package/dist/runtime/subsystems/integration/index.js +36 -36
- package/dist/runtime/subsystems/integration/integration.module.js +4 -4
- package/dist/src/cli/index.js +1552 -254
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +18 -0
- package/dist/src/index.js +12 -12
- package/package.json +1 -1
- package/src/config/locations.mjs +0 -6
- package/src/config/paths.mjs +0 -13
- package/templates/entity/new/prompt.js +12 -88
- package/dist/chunk-IOQMMH6C.js.map +0 -1
- package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +0 -7
- package/templates/entity/new/frontend/_inject-entities-import.ejs.t +0 -7
- package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +0 -10
- package/templates/entity/new/frontend/collections/_inject-index.ejs.t +0 -9
- package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +0 -9
- package/templates/entity/new/frontend/collections/collection.ejs.t +0 -86
- package/templates/entity/new/frontend/collections/collections-base.ejs.t +0 -35
- package/templates/entity/new/frontend/entity/collection.ejs.t +0 -173
- package/templates/entity/new/frontend/entity/combined.ejs.t +0 -505
- package/templates/entity/new/frontend/entity/fields.ejs.t +0 -105
- package/templates/entity/new/frontend/entity/hooks.ejs.t +0 -74
- package/templates/entity/new/frontend/entity/index.ejs.t +0 -22
- package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +0 -85
- package/templates/entity/new/frontend/entity/mutations.ejs.t +0 -39
- package/templates/entity/new/frontend/entity/types.ejs.t +0 -60
- package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +0 -7
- package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +0 -7
- package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +0 -7
- package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-collections.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-entity.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-import.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +0 -9
- package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +0 -10
- package/templates/entity/new/frontend/store/hooks.ejs.t +0 -73
- package/templates/entity/new/frontend/unified-entity.ejs.t +0 -29
- /package/dist/{chunk-I6UXRJ3Q.js.map → chunk-43SBT72G.js.map} +0 -0
- /package/dist/{chunk-T6SCOJF4.js.map → chunk-7LKAMLV4.js.map} +0 -0
- /package/dist/{chunk-CZQUOIDY.js.map → chunk-J7JMVS2B.js.map} +0 -0
- /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
- /package/dist/{chunk-ATVGYF3D.js.map → chunk-PKDS6QIJ.js.map} +0 -0
- /package/dist/{chunk-KZDHMZ45.js.map → chunk-SNH35CNA.js.map} +0 -0
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= generate.structure === 'monolithic' ? `${locations.frontendGenerated.path}/${generate.fileNaming === 'plural' ? plural : name}.ts` : '' %>"
|
|
3
|
-
skip_if: <%= !frontendEnabled || (generate.structure !== 'monolithic') %>
|
|
4
|
-
force: true
|
|
5
|
-
---
|
|
6
|
-
<%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
|
|
7
|
-
/**
|
|
8
|
-
* <%= className %> - Generated Entity Module
|
|
9
|
-
*
|
|
10
|
-
* AUTO-GENERATED by entity codegen - do not edit directly.
|
|
11
|
-
* Source: entities/<%= name %>.yaml
|
|
12
|
-
*
|
|
13
|
-
* This file contains everything needed for the <%= className %> entity:
|
|
14
|
-
* - Types (base + resolved with relations)
|
|
15
|
-
* - Collection (Electric sync with optimistic mutations)
|
|
16
|
-
* - Hooks (useMany, useOne with auto-resolved relations)
|
|
17
|
-
* - Mutations (insert, update, delete)
|
|
18
|
-
* - Field metadata (for DataGrid, forms, admin)
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
<%
|
|
22
|
-
// Type import: depends on typeNaming config
|
|
23
|
-
// 'entity' = source exports OpportunityEntity, 'plain' = source exports Opportunity
|
|
24
|
-
const importedTypeName = generate.typeNaming === 'plain' ? className : className + 'Entity';
|
|
25
|
-
// Collection variable name depends on collectionNaming config
|
|
26
|
-
const collectionVar = generate.collectionNaming === 'plural' ? collectionVarNamePlural : collectionVarName;
|
|
27
|
-
// Hook return key depends on hookReturnStyle config
|
|
28
|
-
const returnKey = generate.hookReturnStyle === 'named' ? pluralCamelName : 'data';
|
|
29
|
-
const returnKeySingular = generate.hookReturnStyle === 'named' ? camelName : 'data';
|
|
30
|
-
-%>
|
|
31
|
-
<% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
|
|
32
|
-
import { useState, useCallback } from 'react';
|
|
33
|
-
<% } -%>
|
|
34
|
-
<% if (frontend.sync.mode !== 'api' && frontend.sync.columnMapper) { -%>
|
|
35
|
-
import { <%= frontend.sync.columnMapper %> } from '@electric-sql/client';
|
|
36
|
-
<% } -%>
|
|
37
|
-
import { <%= camelName %>Schema, type <%= importedTypeName %> } from '<%= locations.dbEntities.import %><% if (!locations.dbEntities.barrelExport) { %>/<%= name %><% } %>';
|
|
38
|
-
<% if (exposeTrpc) { -%>
|
|
39
|
-
import { trpc } from '<%= locations.trpcClient.import %>';
|
|
40
|
-
<% } -%>
|
|
41
|
-
<% if (frontend.sync.mode === 'api') { -%>
|
|
42
|
-
import { queryCollectionOptions } from '@tanstack/query-db-collection';
|
|
43
|
-
import { queryClient } from '<%= frontend.sync.queryClientImport ?? './query-client' %>';
|
|
44
|
-
<% } else { -%>
|
|
45
|
-
import { electricCollectionOptions } from '@tanstack/electric-db-collection';
|
|
46
|
-
<% } -%>
|
|
47
|
-
import { createCollection<% if (generate.hookStyle === 'useLiveQuery') { %>, useLiveQuery, eq<% } %> } from '@tanstack/react-db';
|
|
48
|
-
<% if (generate.fieldMetadata) { -%>
|
|
49
|
-
import type { FieldMeta, FieldType, FieldImportance } from '<%= locations.frontendFieldMetaTypes.import %>/field-meta';
|
|
50
|
-
<% } -%>
|
|
51
|
-
<% if (frontend.auth.function) { -%>
|
|
52
|
-
import { <%= frontend.auth.function %> } from '<%= locations.frontendCollectionsAuth.import %>';
|
|
53
|
-
<% } -%>
|
|
54
|
-
<% if (frontend.sync.apiBaseUrlImport) { -%>
|
|
55
|
-
import { API_BASE_URL } from '<%= frontend.sync.apiBaseUrlImport %>';
|
|
56
|
-
<% } -%>
|
|
57
|
-
<%
|
|
58
|
-
// Collect unique belongs_to targets for imports (FK resolution)
|
|
59
|
-
// Only import if fkResolution is enabled (default: true)
|
|
60
|
-
const importedEntities = new Set();
|
|
61
|
-
if (generate.fkResolution !== false) {
|
|
62
|
-
existingBelongsTo.forEach((rel) => {
|
|
63
|
-
if (rel.target !== name) {
|
|
64
|
-
importedEntities.add(rel.target);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
-%>
|
|
69
|
-
<% if (importedEntities.size > 0) { -%>
|
|
70
|
-
|
|
71
|
-
// Import related collections for FK resolution
|
|
72
|
-
<% importedEntities.forEach((target) => {
|
|
73
|
-
const targetCamel = target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
74
|
-
// Simple pluralization matching prompt.js pluralize function
|
|
75
|
-
const targetPlural = target.endsWith('y') ? target.slice(0, -1) + 'ies' :
|
|
76
|
-
(target.endsWith('s') || target.endsWith('x') || target.endsWith('ch') || target.endsWith('sh')) ? target + 'es' : target + 's';
|
|
77
|
-
const targetFileName = generate.fileNaming === 'plural' ? targetPlural : target;
|
|
78
|
-
const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
|
|
79
|
-
const targetCollectionVar = generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel;
|
|
80
|
-
-%>
|
|
81
|
-
import { <%= targetCollectionVar %>Collection } from './<%= targetFileName %>';
|
|
82
|
-
<% }); -%>
|
|
83
|
-
<% importedEntities.forEach((target) => {
|
|
84
|
-
const targetClass = target.charAt(0).toUpperCase() + target.slice(1).replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
85
|
-
-%>
|
|
86
|
-
import type { <%= generate.typeNaming === 'plain' ? targetClass : targetClass + 'Entity' %> } from '<%= locations.dbEntities.import %><% if (!locations.dbEntities.barrelExport) { %>/<%= target %><% } %>';
|
|
87
|
-
<% }); -%>
|
|
88
|
-
<% } -%>
|
|
89
|
-
|
|
90
|
-
// ============================================================================
|
|
91
|
-
// Types
|
|
92
|
-
// ============================================================================
|
|
93
|
-
|
|
94
|
-
/** Base entity from database */
|
|
95
|
-
<% if (generate.typeNaming === 'plain') { -%>
|
|
96
|
-
export type { <%= className %> };
|
|
97
|
-
<% } else { -%>
|
|
98
|
-
export type <%= className %> = <%= importedTypeName %>;
|
|
99
|
-
<% } -%>
|
|
100
|
-
<% if (existingBelongsTo.length > 0 && generate.fkResolution !== false) { -%>
|
|
101
|
-
|
|
102
|
-
/** Entity with resolved FK relations (only includes relations with existing targets) */
|
|
103
|
-
export interface <%= className %>Resolved extends <%= className %> {
|
|
104
|
-
<% existingBelongsTo.forEach((rel) => { -%>
|
|
105
|
-
<% // Use local type for self-referential, imported type for others -%>
|
|
106
|
-
<% const relTypeName = rel.target === name ? className : (generate.typeNaming === 'plain' ? rel.targetClass : rel.targetClass + 'Entity'); -%>
|
|
107
|
-
<%= rel.name %>?: <%= relTypeName %>;
|
|
108
|
-
<% }); -%>
|
|
109
|
-
}
|
|
110
|
-
<% } else { -%>
|
|
111
|
-
|
|
112
|
-
/** Entity type (no FK relations to resolve) */
|
|
113
|
-
export type <%= className %>Resolved = <%= className %>;
|
|
114
|
-
<% } -%>
|
|
115
|
-
|
|
116
|
-
// ============================================================================
|
|
117
|
-
// Collection (Electric sync with optimistic mutations)
|
|
118
|
-
// ============================================================================
|
|
119
|
-
<% if (hasSoftDelete) { -%>
|
|
120
|
-
|
|
121
|
-
// TODO: Backend Electric shape endpoint should filter soft-deleted records
|
|
122
|
-
// by including WHERE deleted_at IS NULL in the shape configuration.
|
|
123
|
-
// Without this filter, soft-deleted records will reappear after Electric sync.
|
|
124
|
-
<% } -%>
|
|
125
|
-
|
|
126
|
-
<%
|
|
127
|
-
// REST list endpoint for 'api' sync mode
|
|
128
|
-
const hasApiBaseUrl = !!frontend.sync.apiBaseUrlImport;
|
|
129
|
-
const apiUrlExpr = hasApiBaseUrl
|
|
130
|
-
? '`${API_BASE_URL}/' + plural + '`'
|
|
131
|
-
: '`' + frontend.sync.apiUrl + '/' + plural + '`';
|
|
132
|
-
-%>
|
|
133
|
-
export const <%= collectionVar %> = createCollection(
|
|
134
|
-
<% if (frontend.sync.mode === 'api') { -%>
|
|
135
|
-
queryCollectionOptions({
|
|
136
|
-
id: '<%= plural %>',
|
|
137
|
-
queryKey: ['<%= plural %>'],
|
|
138
|
-
queryClient,
|
|
139
|
-
queryFn: async () => {
|
|
140
|
-
const res = await fetch(<%- apiUrlExpr %><% if (frontend.auth.function) { %>, {
|
|
141
|
-
headers: { Authorization: <%= frontend.auth.function %>() },
|
|
142
|
-
}<% } %>);
|
|
143
|
-
if (!res.ok) {
|
|
144
|
-
throw new Error(`GET <%= plural %> → ${res.status} ${res.statusText}`);
|
|
145
|
-
}
|
|
146
|
-
return res.json();
|
|
147
|
-
},
|
|
148
|
-
getKey: (item) => item.id,
|
|
149
|
-
schema: <%= camelName %>Schema,
|
|
150
|
-
<% } else { -%>
|
|
151
|
-
electricCollectionOptions({
|
|
152
|
-
id: '<%= plural %>',
|
|
153
|
-
shapeOptions: {
|
|
154
|
-
<% if (frontend.sync.useTableParam) { -%>
|
|
155
|
-
<% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
|
|
156
|
-
url: new URL(
|
|
157
|
-
'<%= frontend.sync.shapeUrl %>',
|
|
158
|
-
typeof window !== 'undefined'
|
|
159
|
-
? window.location.origin
|
|
160
|
-
: 'http://localhost:5173',
|
|
161
|
-
).toString(),
|
|
162
|
-
<% } else { -%>
|
|
163
|
-
url: '<%= frontend.sync.shapeUrl %>',
|
|
164
|
-
<% } -%>
|
|
165
|
-
params: {
|
|
166
|
-
table: '<%= plural %>',
|
|
167
|
-
},
|
|
168
|
-
<% } else { -%>
|
|
169
|
-
<% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
|
|
170
|
-
url: new URL(
|
|
171
|
-
`<%= frontend.sync.shapeUrl %>/<%= plural %>`,
|
|
172
|
-
typeof window !== 'undefined'
|
|
173
|
-
? window.location.origin
|
|
174
|
-
: 'http://localhost:5173',
|
|
175
|
-
).toString(),
|
|
176
|
-
<% } else { -%>
|
|
177
|
-
url: `<%= frontend.sync.shapeUrl %>/<%= plural %>`,
|
|
178
|
-
<% } -%>
|
|
179
|
-
<% } -%>
|
|
180
|
-
<% if (frontend.auth.function) { -%>
|
|
181
|
-
headers: {
|
|
182
|
-
Authorization: <%= frontend.auth.function %>(),
|
|
183
|
-
},
|
|
184
|
-
<% } -%>
|
|
185
|
-
parser: {
|
|
186
|
-
<% Object.entries(frontend.parsers).forEach(([type, fn]) => { -%>
|
|
187
|
-
<%- type %>: <%- fn %>,
|
|
188
|
-
<% }); -%>
|
|
189
|
-
},
|
|
190
|
-
<% if (frontend.sync.columnMapper) { -%>
|
|
191
|
-
<% if (frontend.sync.columnMapperNeedsCall !== false) { -%>
|
|
192
|
-
columnMapper: <%= frontend.sync.columnMapper %>(),
|
|
193
|
-
<% } else { -%>
|
|
194
|
-
columnMapper: <%= frontend.sync.columnMapper %>,
|
|
195
|
-
<% } -%>
|
|
196
|
-
<% } -%>
|
|
197
|
-
},
|
|
198
|
-
schema: <%= camelName %>Schema,
|
|
199
|
-
getKey: (item) => item.id,
|
|
200
|
-
<% } -%>
|
|
201
|
-
<% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
|
|
202
|
-
<% if (exposeTrpc) { -%>
|
|
203
|
-
onInsert: async ({ transaction }) => {
|
|
204
|
-
const { modified } = transaction.mutations[0];
|
|
205
|
-
const result = await trpc.<%= plural %>.create.mutate(modified);
|
|
206
|
-
return { txid: result.txid };
|
|
207
|
-
},
|
|
208
|
-
onUpdate: async ({ transaction }) => {
|
|
209
|
-
const { modified } = transaction.mutations[0];
|
|
210
|
-
const result = await trpc.<%= plural %>.update.mutate({ id: modified.id, data: modified });
|
|
211
|
-
return { txid: result.txid };
|
|
212
|
-
},
|
|
213
|
-
onDelete: async ({ transaction }) => {
|
|
214
|
-
const { original } = transaction.mutations[0];
|
|
215
|
-
const result = await trpc.<%= plural %>.delete.mutate({ id: original.id });
|
|
216
|
-
return { txid: result.txid };
|
|
217
|
-
},
|
|
218
|
-
<% } -%>
|
|
219
|
-
<% } -%>
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
// ============================================================================
|
|
224
|
-
// Resolution (FK lookup - internal use)
|
|
225
|
-
// ============================================================================
|
|
226
|
-
<% if (existingBelongsTo.length > 0 && generate.fkResolution !== false) { -%>
|
|
227
|
-
|
|
228
|
-
function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
|
|
229
|
-
return {
|
|
230
|
-
...entity,
|
|
231
|
-
<% existingBelongsTo.forEach((rel) => {
|
|
232
|
-
const targetCamel = rel.target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
233
|
-
const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
|
|
234
|
-
const targetCollectionVar = generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel;
|
|
235
|
-
-%>
|
|
236
|
-
<%= rel.name %>: entity.<%= rel.foreignKeyCamel %>
|
|
237
|
-
? <%= targetCollectionVar %>Collection.state.get(entity.<%= rel.foreignKeyCamel %>)
|
|
238
|
-
: undefined,
|
|
239
|
-
<% }); -%>
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
<% } else { -%>
|
|
243
|
-
|
|
244
|
-
function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
|
|
245
|
-
return entity;
|
|
246
|
-
}
|
|
247
|
-
<% } -%>
|
|
248
|
-
|
|
249
|
-
// ============================================================================
|
|
250
|
-
// Hooks (React hooks returning resolved entities)
|
|
251
|
-
// ============================================================================
|
|
252
|
-
<% if (generate.hookStyle === 'useLiveQuery') { -%>
|
|
253
|
-
|
|
254
|
-
/** Get all <%= plural %> with relations resolved */
|
|
255
|
-
export function use<%= classNamePlural %>() {
|
|
256
|
-
const result = useLiveQuery((q) => q.from({ <%= collectionVar %> }), []);
|
|
257
|
-
return {
|
|
258
|
-
<%= returnKey %>: result.data?.map(resolveRelations) ?? [],
|
|
259
|
-
isLoading: result.isLoading,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/** Get single <%= camelName %> by ID with relations resolved */
|
|
264
|
-
export function use<%= className %>(id: string | undefined) {
|
|
265
|
-
const result = useLiveQuery(
|
|
266
|
-
(q) => {
|
|
267
|
-
if (!id) return undefined;
|
|
268
|
-
return q
|
|
269
|
-
.from({ <%= collectionVar %> })
|
|
270
|
-
.where(({ <%= collectionVar %> }) => eq(<%= collectionVar %>.id, id));
|
|
271
|
-
},
|
|
272
|
-
[id],
|
|
273
|
-
);
|
|
274
|
-
const entity = result.data?.[0];
|
|
275
|
-
return {
|
|
276
|
-
<%= returnKeySingular %>: entity ? resolveRelations(entity) : undefined,
|
|
277
|
-
isLoading: result.isLoading,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
<% } else { -%>
|
|
281
|
-
|
|
282
|
-
/** Get all <%= plural %> with relations resolved */
|
|
283
|
-
export function use<%= classNamePlural %>(): <%= className %>Resolved[] {
|
|
284
|
-
const raw = <%= collectionVar %>.useMany();
|
|
285
|
-
return raw.map(resolveRelations);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Get single <%= camelName %> by ID with relations resolved */
|
|
289
|
-
export function use<%= className %>(id: string | undefined): <%= className %>Resolved | undefined {
|
|
290
|
-
const raw = <%= collectionVar %>.useOne(id);
|
|
291
|
-
return raw ? resolveRelations(raw) : undefined;
|
|
292
|
-
}
|
|
293
|
-
<% } -%>
|
|
294
|
-
|
|
295
|
-
<% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
|
|
296
|
-
// ============================================================================
|
|
297
|
-
// Mutations (Optimistic updates)
|
|
298
|
-
// ============================================================================
|
|
299
|
-
|
|
300
|
-
export function insert<%= className %>(data: Omit<<%= className %>, 'id'<% if (hasTimestamps) { %> | 'createdAt' | 'updatedAt'<% } %>>) {
|
|
301
|
-
return <%= collectionVar %>.insert({
|
|
302
|
-
id: crypto.randomUUID(),
|
|
303
|
-
<% if (hasTimestamps) { -%>
|
|
304
|
-
createdAt: new Date(),
|
|
305
|
-
updatedAt: new Date(),
|
|
306
|
-
<% } -%>
|
|
307
|
-
...data,
|
|
308
|
-
} as <%= className %>);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function update<%= className %>(id: string, fn: (draft: <%= className %>) => void) {
|
|
312
|
-
return <%= collectionVar %>.update(id, fn);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export function delete<%= className %>(id: string) {
|
|
316
|
-
return <%= collectionVar %>.delete(id);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ============================================================================
|
|
320
|
-
// Mutation Hooks (Error tracking wrappers)
|
|
321
|
-
// ============================================================================
|
|
322
|
-
|
|
323
|
-
type MutationHookResult<TFn extends (...args: any[]) => any> = {
|
|
324
|
-
mutate: TFn;
|
|
325
|
-
error: Error | null;
|
|
326
|
-
clearError: () => void;
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
export function useCreate<%= className %>(): MutationHookResult<typeof insert<%= className %>> {
|
|
330
|
-
const [error, setError] = useState<Error | null>(null);
|
|
331
|
-
|
|
332
|
-
const mutate = useCallback<typeof insert<%= className %>>((data) => {
|
|
333
|
-
try {
|
|
334
|
-
insert<%= className %>(data);
|
|
335
|
-
setError(null);
|
|
336
|
-
} catch (err) {
|
|
337
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
338
|
-
setError(error);
|
|
339
|
-
throw error;
|
|
340
|
-
}
|
|
341
|
-
}, []);
|
|
342
|
-
|
|
343
|
-
const clearError = useCallback(() => setError(null), []);
|
|
344
|
-
return { mutate, error, clearError };
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function useUpdate<%= className %>(): MutationHookResult<typeof update<%= className %>> {
|
|
348
|
-
const [error, setError] = useState<Error | null>(null);
|
|
349
|
-
|
|
350
|
-
const mutate = useCallback<typeof update<%= className %>>((id, fn) => {
|
|
351
|
-
try {
|
|
352
|
-
update<%= className %>(id, fn);
|
|
353
|
-
setError(null);
|
|
354
|
-
} catch (err) {
|
|
355
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
356
|
-
setError(error);
|
|
357
|
-
throw error;
|
|
358
|
-
}
|
|
359
|
-
}, []);
|
|
360
|
-
|
|
361
|
-
const clearError = useCallback(() => setError(null), []);
|
|
362
|
-
return { mutate, error, clearError };
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function useDelete<%= className %>(): MutationHookResult<typeof delete<%= className %>> {
|
|
366
|
-
const [error, setError] = useState<Error | null>(null);
|
|
367
|
-
|
|
368
|
-
const mutate = useCallback<typeof delete<%= className %>>((id) => {
|
|
369
|
-
try {
|
|
370
|
-
delete<%= className %>(id);
|
|
371
|
-
setError(null);
|
|
372
|
-
} catch (err) {
|
|
373
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
374
|
-
setError(error);
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
}, []);
|
|
378
|
-
|
|
379
|
-
const clearError = useCallback(() => setError(null), []);
|
|
380
|
-
return { mutate, error, clearError };
|
|
381
|
-
}
|
|
382
|
-
<% } -%>
|
|
383
|
-
<% if (generate.fieldMetadata) { -%>
|
|
384
|
-
|
|
385
|
-
// ============================================================================
|
|
386
|
-
// Field Metadata (for DataGrid, forms, admin)
|
|
387
|
-
// ============================================================================
|
|
388
|
-
|
|
389
|
-
export const <%= camelName %>Fields: Record<string, FieldMeta<<%= className %>Resolved>> = {
|
|
390
|
-
<% fields.forEach((field) => { -%>
|
|
391
|
-
<% // Skip entity ref internal fields from metadata display
|
|
392
|
-
if (field.isEntityRefType || field.isEntityRefId) return;
|
|
393
|
-
-%>
|
|
394
|
-
<%= field.camelName %>: {
|
|
395
|
-
field: '<%= field.camelName %>',
|
|
396
|
-
label: '<%= field.ui_label %>',
|
|
397
|
-
type: '<%= field.ui_type %>' as FieldType,
|
|
398
|
-
importance: '<%= field.ui_importance %>' as FieldImportance,
|
|
399
|
-
<% if (field.ui_sortable) { -%>
|
|
400
|
-
sortable: true,
|
|
401
|
-
<% } -%>
|
|
402
|
-
<% if (field.ui_filterable) { -%>
|
|
403
|
-
filterable: true,
|
|
404
|
-
<% } -%>
|
|
405
|
-
<% if (field.ui_format) { -%>
|
|
406
|
-
format: <%- JSON.stringify(field.ui_format) %>,
|
|
407
|
-
<% } -%>
|
|
408
|
-
<% if (field.hasChoices) { -%>
|
|
409
|
-
choices: <%- JSON.stringify(field.choices) %>,
|
|
410
|
-
<% } -%>
|
|
411
|
-
<% if (field.foreignKey) { -%>
|
|
412
|
-
reference: '<%= field.foreignKey.split('.')[0] %>',
|
|
413
|
-
<% } -%>
|
|
414
|
-
},
|
|
415
|
-
<% }); -%>
|
|
416
|
-
<% // Add resolved relation fields to metadata
|
|
417
|
-
belongsToRelations.forEach((rel) => { -%>
|
|
418
|
-
<%= rel.name %>: {
|
|
419
|
-
field: '<%= rel.name %>',
|
|
420
|
-
label: '<%= rel.targetClass.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
421
|
-
type: 'entity' as FieldType,
|
|
422
|
-
importance: 'secondary' as FieldImportance,
|
|
423
|
-
reference: '<%= rel.targetPlural %>',
|
|
424
|
-
},
|
|
425
|
-
<% }); -%>
|
|
426
|
-
<% if (hasTimestamps) { -%>
|
|
427
|
-
createdAt: {
|
|
428
|
-
field: 'createdAt',
|
|
429
|
-
label: 'Created',
|
|
430
|
-
type: 'datetime' as FieldType,
|
|
431
|
-
importance: 'tertiary' as FieldImportance,
|
|
432
|
-
format: { dateFormat: 'relative' },
|
|
433
|
-
},
|
|
434
|
-
updatedAt: {
|
|
435
|
-
field: 'updatedAt',
|
|
436
|
-
label: 'Updated',
|
|
437
|
-
type: 'datetime' as FieldType,
|
|
438
|
-
importance: 'tertiary' as FieldImportance,
|
|
439
|
-
format: { dateFormat: 'relative' },
|
|
440
|
-
},
|
|
441
|
-
<% } -%>
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
export const <%= camelName %>Metadata = {
|
|
445
|
-
name: '<%= name %>',
|
|
446
|
-
plural: '<%= plural %>',
|
|
447
|
-
displayName: '<%= className.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
448
|
-
displayNamePlural: '<%= classNamePlural.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
449
|
-
|
|
450
|
-
fields: <%= camelName %>Fields,
|
|
451
|
-
|
|
452
|
-
// Display configuration
|
|
453
|
-
primaryFields: [
|
|
454
|
-
<% fields.filter(f => f.ui_importance === 'primary').forEach(f => { -%>
|
|
455
|
-
'<%= f.camelName %>',
|
|
456
|
-
<% }); -%>
|
|
457
|
-
],
|
|
458
|
-
searchFields: [
|
|
459
|
-
<% fields.filter(f => f.ui_filterable).forEach(f => { -%>
|
|
460
|
-
'<%= f.camelName %>',
|
|
461
|
-
<% }); -%>
|
|
462
|
-
],
|
|
463
|
-
defaultSort: { field: '<% if (hasTimestamps) { %>createdAt<% } else { %>id<% } %>', direction: 'desc' as const },
|
|
464
|
-
|
|
465
|
-
// Capabilities
|
|
466
|
-
capabilities: {
|
|
467
|
-
create: <%= exposeRepository || exposeTrpc %>,
|
|
468
|
-
update: <%= exposeRepository || exposeTrpc %>,
|
|
469
|
-
delete: <%= exposeRepository || exposeTrpc %>,
|
|
470
|
-
list: true,
|
|
471
|
-
get: true,
|
|
472
|
-
},
|
|
473
|
-
} as const;
|
|
474
|
-
<% } -%>
|
|
475
|
-
|
|
476
|
-
// ============================================================================
|
|
477
|
-
// Export bundle for registry
|
|
478
|
-
// ============================================================================
|
|
479
|
-
|
|
480
|
-
export const <%= camelName %> = {
|
|
481
|
-
// Collection
|
|
482
|
-
collection: <%= collectionVar %>,
|
|
483
|
-
|
|
484
|
-
// Hooks
|
|
485
|
-
useMany: use<%= classNamePlural %>,
|
|
486
|
-
useOne: use<%= className %>,
|
|
487
|
-
<% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
|
|
488
|
-
|
|
489
|
-
// Mutations
|
|
490
|
-
insert: insert<%= className %>,
|
|
491
|
-
update: update<%= className %>,
|
|
492
|
-
delete: delete<%= className %>,
|
|
493
|
-
|
|
494
|
-
// Mutation Hooks
|
|
495
|
-
useCreate: useCreate<%= className %>,
|
|
496
|
-
useUpdate: useUpdate<%= className %>,
|
|
497
|
-
useDelete: useDelete<%= className %>,
|
|
498
|
-
<% } -%>
|
|
499
|
-
<% if (generate.fieldMetadata) { -%>
|
|
500
|
-
|
|
501
|
-
// Metadata
|
|
502
|
-
fields: <%= camelName %>Fields,
|
|
503
|
-
metadata: <%= camelName %>Metadata,
|
|
504
|
-
<% } -%>
|
|
505
|
-
} as const;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= generate.structure === 'entity-first' ? `${locations.frontendGenerated.path}/${name}/fields.ts` : generate.structure === 'concern-first' ? `${locations.frontendGenerated.path}/fields/${name}.ts` : '' %>"
|
|
3
|
-
skip_if: <%= !frontendEnabled || (generate.structure === 'monolithic' || !generate.fieldMetadata) %>
|
|
4
|
-
force: true
|
|
5
|
-
---
|
|
6
|
-
<%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
|
|
7
|
-
/**
|
|
8
|
-
* <%= className %> Field Metadata
|
|
9
|
-
* Generated by entity codegen - do not edit directly
|
|
10
|
-
*
|
|
11
|
-
* Field definitions for DataGrid, forms, and admin interfaces
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { FieldMeta, FieldType, FieldImportance } from '<%= locations.frontendFieldMetaTypes.import %>/field-meta';
|
|
15
|
-
<% if (generate.structure === 'entity-first') { -%>
|
|
16
|
-
import type { <%= className %>Resolved } from './types';
|
|
17
|
-
<% } else if (generate.structure === 'concern-first') { -%>
|
|
18
|
-
import type { <%= className %>Resolved } from '../types/<%= name %>';
|
|
19
|
-
<% } -%>
|
|
20
|
-
|
|
21
|
-
export const <%= camelName %>Fields: Record<string, FieldMeta<<%= className %>Resolved>> = {
|
|
22
|
-
<% fields.forEach((field) => { -%>
|
|
23
|
-
<% // Skip entity ref internal fields from metadata display
|
|
24
|
-
if (field.isEntityRefType || field.isEntityRefId) return;
|
|
25
|
-
-%>
|
|
26
|
-
<%= field.camelName %>: {
|
|
27
|
-
field: '<%= field.camelName %>',
|
|
28
|
-
label: '<%= field.ui_label %>',
|
|
29
|
-
type: '<%= field.ui_type %>' as FieldType,
|
|
30
|
-
importance: '<%= field.ui_importance %>' as FieldImportance,
|
|
31
|
-
<% if (field.ui_sortable) { -%>
|
|
32
|
-
sortable: true,
|
|
33
|
-
<% } -%>
|
|
34
|
-
<% if (field.ui_filterable) { -%>
|
|
35
|
-
filterable: true,
|
|
36
|
-
<% } -%>
|
|
37
|
-
<% if (field.ui_format) { -%>
|
|
38
|
-
format: <%- JSON.stringify(field.ui_format) %>,
|
|
39
|
-
<% } -%>
|
|
40
|
-
<% if (field.hasChoices) { -%>
|
|
41
|
-
choices: <%- JSON.stringify(field.choices) %>,
|
|
42
|
-
<% } -%>
|
|
43
|
-
<% if (field.foreignKey) { -%>
|
|
44
|
-
reference: '<%= field.foreignKey.split('.')[0] %>',
|
|
45
|
-
<% } -%>
|
|
46
|
-
},
|
|
47
|
-
<% }); -%>
|
|
48
|
-
<% // Add resolved relation fields to metadata
|
|
49
|
-
belongsToRelations.forEach((rel) => { -%>
|
|
50
|
-
<%= rel.name %>: {
|
|
51
|
-
field: '<%= rel.name %>',
|
|
52
|
-
label: '<%= rel.targetClass.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
53
|
-
type: 'entity' as FieldType,
|
|
54
|
-
importance: 'secondary' as FieldImportance,
|
|
55
|
-
reference: '<%= rel.targetPlural %>',
|
|
56
|
-
},
|
|
57
|
-
<% }); -%>
|
|
58
|
-
<% if (hasTimestamps) { -%>
|
|
59
|
-
createdAt: {
|
|
60
|
-
field: 'createdAt',
|
|
61
|
-
label: 'Created',
|
|
62
|
-
type: 'datetime' as FieldType,
|
|
63
|
-
importance: 'tertiary' as FieldImportance,
|
|
64
|
-
format: { dateFormat: 'relative' },
|
|
65
|
-
},
|
|
66
|
-
updatedAt: {
|
|
67
|
-
field: 'updatedAt',
|
|
68
|
-
label: 'Updated',
|
|
69
|
-
type: 'datetime' as FieldType,
|
|
70
|
-
importance: 'tertiary' as FieldImportance,
|
|
71
|
-
format: { dateFormat: 'relative' },
|
|
72
|
-
},
|
|
73
|
-
<% } -%>
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const <%= camelName %>Metadata = {
|
|
77
|
-
name: '<%= name %>',
|
|
78
|
-
plural: '<%= plural %>',
|
|
79
|
-
displayName: '<%= className.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
80
|
-
displayNamePlural: '<%= classNamePlural.replace(/([A-Z])/g, ' $1').trim() %>',
|
|
81
|
-
|
|
82
|
-
fields: <%= camelName %>Fields,
|
|
83
|
-
|
|
84
|
-
// Display configuration
|
|
85
|
-
primaryFields: [
|
|
86
|
-
<% fields.filter(f => f.ui_importance === 'primary').forEach(f => { -%>
|
|
87
|
-
'<%= f.camelName %>',
|
|
88
|
-
<% }); -%>
|
|
89
|
-
],
|
|
90
|
-
searchFields: [
|
|
91
|
-
<% fields.filter(f => f.ui_filterable).forEach(f => { -%>
|
|
92
|
-
'<%= f.camelName %>',
|
|
93
|
-
<% }); -%>
|
|
94
|
-
],
|
|
95
|
-
defaultSort: { field: '<% if (hasTimestamps) { %>createdAt<% } else { %>id<% } %>', direction: 'desc' as const },
|
|
96
|
-
|
|
97
|
-
// Capabilities
|
|
98
|
-
capabilities: {
|
|
99
|
-
create: <%= exposeRepository || exposeTrpc %>,
|
|
100
|
-
update: <%= exposeRepository || exposeTrpc %>,
|
|
101
|
-
delete: <%= exposeRepository || exposeTrpc %>,
|
|
102
|
-
list: true,
|
|
103
|
-
get: true,
|
|
104
|
-
},
|
|
105
|
-
} as const;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
to: "<%= generate.structure === 'entity-first' ? `${locations.frontendGenerated.path}/${generate.fileNaming === 'plural' ? plural : name}/hooks.ts` : generate.structure === 'concern-first' ? `${locations.frontendGenerated.path}/hooks/${generate.fileNaming === 'plural' ? plural : name}.ts` : '' %>"
|
|
3
|
-
skip_if: <%= !frontendEnabled || (generate.structure === 'monolithic') %>
|
|
4
|
-
force: true
|
|
5
|
-
---
|
|
6
|
-
<%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
|
|
7
|
-
/**
|
|
8
|
-
* <%= className %> Hooks
|
|
9
|
-
* Generated by entity codegen - do not edit directly
|
|
10
|
-
*
|
|
11
|
-
* React hooks returning resolved entities with relations
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
<%
|
|
15
|
-
// Collection variable name depends on collectionNaming config
|
|
16
|
-
const collectionVar = generate.collectionNaming === 'plural' ? collectionVarNamePlural : collectionVarName;
|
|
17
|
-
// File name for imports depends on fileNaming config
|
|
18
|
-
const fileName = generate.fileNaming === 'plural' ? plural : name;
|
|
19
|
-
// Hook return key depends on hookReturnStyle config
|
|
20
|
-
const returnKey = generate.hookReturnStyle === 'named' ? pluralCamelName : 'data';
|
|
21
|
-
const returnKeySingular = generate.hookReturnStyle === 'named' ? camelName : 'data';
|
|
22
|
-
-%>
|
|
23
|
-
<% if (generate.hookStyle === 'useLiveQuery') { -%>
|
|
24
|
-
import { useLiveQuery, eq } from '@tanstack/react-db';
|
|
25
|
-
<% } -%>
|
|
26
|
-
<% if (generate.structure === 'entity-first') { -%>
|
|
27
|
-
import { <%= collectionVar %>, resolveRelations } from './collection';
|
|
28
|
-
import type { <%= className %>Resolved } from './types';
|
|
29
|
-
<% } else if (generate.structure === 'concern-first') { -%>
|
|
30
|
-
import { <%= collectionVar %>, resolveRelations } from '../collections/<%= fileName %>';
|
|
31
|
-
import type { <%= className %>Resolved } from '../types/<%= fileName %>';
|
|
32
|
-
<% } -%>
|
|
33
|
-
<% if (generate.hookStyle === 'useLiveQuery') { -%>
|
|
34
|
-
|
|
35
|
-
/** Get all <%= plural %> with relations resolved */
|
|
36
|
-
export function use<%= classNamePlural %>() {
|
|
37
|
-
const result = useLiveQuery((q) => q.from({ <%= collectionVar %> }), []);
|
|
38
|
-
return {
|
|
39
|
-
<%= returnKey %>: result.data?.map(resolveRelations) ?? [],
|
|
40
|
-
isLoading: result.isLoading,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Get single <%= camelName %> by ID with relations resolved */
|
|
45
|
-
export function use<%= className %>(id: string | undefined) {
|
|
46
|
-
const result = useLiveQuery(
|
|
47
|
-
(q) => {
|
|
48
|
-
if (!id) return undefined;
|
|
49
|
-
return q
|
|
50
|
-
.from({ <%= collectionVar %> })
|
|
51
|
-
.where(({ <%= collectionVar %> }) => eq(<%= collectionVar %>.id, id));
|
|
52
|
-
},
|
|
53
|
-
[id],
|
|
54
|
-
);
|
|
55
|
-
const entity = result.data?.[0];
|
|
56
|
-
return {
|
|
57
|
-
<%= returnKeySingular %>: entity ? resolveRelations(entity) : undefined,
|
|
58
|
-
isLoading: result.isLoading,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
<% } else { -%>
|
|
62
|
-
|
|
63
|
-
/** Get all <%= plural %> with relations resolved */
|
|
64
|
-
export function use<%= classNamePlural %>(): <%= className %>Resolved[] {
|
|
65
|
-
const raw = <%= collectionVar %>.useMany();
|
|
66
|
-
return raw.map(resolveRelations);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Get single <%= camelName %> by ID with relations resolved */
|
|
70
|
-
export function use<%= className %>(id: string | undefined): <%= className %>Resolved | undefined {
|
|
71
|
-
const raw = <%= collectionVar %>.useOne(id);
|
|
72
|
-
return raw ? resolveRelations(raw) : undefined;
|
|
73
|
-
}
|
|
74
|
-
<% } -%>
|