@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.
Files changed (68) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +157 -2
  3. package/consumer-skills/codegen/SKILL.md +32 -0
  4. package/consumer-skills/entities/SKILL.md +2 -0
  5. package/dist/{chunk-I6UXRJ3Q.js → chunk-43SBT72G.js} +4 -4
  6. package/dist/{chunk-T6SCOJF4.js → chunk-7LKAMLV4.js} +4 -4
  7. package/dist/{chunk-IOQMMH6C.js → chunk-F7KN3U6U.js} +122 -8
  8. package/dist/chunk-F7KN3U6U.js.map +1 -0
  9. package/dist/{chunk-CZQUOIDY.js → chunk-J7JMVS2B.js} +4 -4
  10. package/dist/{chunk-KSTZIULO.js → chunk-K2I6XIK5.js} +4 -4
  11. package/dist/{chunk-ATVGYF3D.js → chunk-PKDS6QIJ.js} +7 -7
  12. package/dist/{chunk-KZDHMZ45.js → chunk-SNH35CNA.js} +8 -8
  13. package/dist/runtime/base-classes/index.js +24 -24
  14. package/dist/runtime/subsystems/analytics/analytics.module.js +2 -2
  15. package/dist/runtime/subsystems/analytics/index.js +4 -4
  16. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  17. package/dist/runtime/subsystems/auth/index.js +14 -14
  18. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +2 -2
  19. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +1 -1
  20. package/dist/runtime/subsystems/bridge/bridge.module.js +5 -5
  21. package/dist/runtime/subsystems/bridge/index.js +13 -13
  22. package/dist/runtime/subsystems/cache/cache.module.js +1 -1
  23. package/dist/runtime/subsystems/cache/index.js +3 -3
  24. package/dist/runtime/subsystems/index.js +94 -94
  25. package/dist/runtime/subsystems/integration/build-change-source.js +2 -2
  26. package/dist/runtime/subsystems/integration/index.js +36 -36
  27. package/dist/runtime/subsystems/integration/integration.module.js +4 -4
  28. package/dist/src/cli/index.js +1552 -254
  29. package/dist/src/cli/index.js.map +1 -1
  30. package/dist/src/index.d.ts +18 -0
  31. package/dist/src/index.js +12 -12
  32. package/package.json +1 -1
  33. package/src/config/locations.mjs +0 -6
  34. package/src/config/paths.mjs +0 -13
  35. package/templates/entity/new/prompt.js +12 -88
  36. package/dist/chunk-IOQMMH6C.js.map +0 -1
  37. package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +0 -7
  38. package/templates/entity/new/frontend/_inject-entities-import.ejs.t +0 -7
  39. package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +0 -10
  40. package/templates/entity/new/frontend/collections/_inject-index.ejs.t +0 -9
  41. package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +0 -9
  42. package/templates/entity/new/frontend/collections/collection.ejs.t +0 -86
  43. package/templates/entity/new/frontend/collections/collections-base.ejs.t +0 -35
  44. package/templates/entity/new/frontend/entity/collection.ejs.t +0 -173
  45. package/templates/entity/new/frontend/entity/combined.ejs.t +0 -505
  46. package/templates/entity/new/frontend/entity/fields.ejs.t +0 -105
  47. package/templates/entity/new/frontend/entity/hooks.ejs.t +0 -74
  48. package/templates/entity/new/frontend/entity/index.ejs.t +0 -22
  49. package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +0 -85
  50. package/templates/entity/new/frontend/entity/mutations.ejs.t +0 -39
  51. package/templates/entity/new/frontend/entity/types.ejs.t +0 -60
  52. package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +0 -7
  53. package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +0 -7
  54. package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +0 -7
  55. package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +0 -9
  56. package/templates/entity/new/frontend/store/_inject-collections.ejs.t +0 -9
  57. package/templates/entity/new/frontend/store/_inject-entity.ejs.t +0 -9
  58. package/templates/entity/new/frontend/store/_inject-import.ejs.t +0 -9
  59. package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +0 -9
  60. package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +0 -10
  61. package/templates/entity/new/frontend/store/hooks.ejs.t +0 -73
  62. package/templates/entity/new/frontend/unified-entity.ejs.t +0 -29
  63. /package/dist/{chunk-I6UXRJ3Q.js.map → chunk-43SBT72G.js.map} +0 -0
  64. /package/dist/{chunk-T6SCOJF4.js.map → chunk-7LKAMLV4.js.map} +0 -0
  65. /package/dist/{chunk-CZQUOIDY.js.map → chunk-J7JMVS2B.js.map} +0 -0
  66. /package/dist/{chunk-KSTZIULO.js.map → chunk-K2I6XIK5.js.map} +0 -0
  67. /package/dist/{chunk-ATVGYF3D.js.map → chunk-PKDS6QIJ.js.map} +0 -0
  68. /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
- <% } -%>