@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,7 +0,0 @@
1
- ---
2
- inject: true
3
- to: "<%= frontendEnabled ? `${locations.frontendEntities.path}/index.ts` : '' %>"
4
- after: "// \\[CODEGEN:ENTITY_ENTRIES\\]"
5
- skip_if: "<%= plural %>,"
6
- ---
7
- <%= plural %>,
@@ -1,7 +0,0 @@
1
- ---
2
- inject: true
3
- to: "<%= frontendEnabled ? `${locations.frontendEntities.path}/index.ts` : '' %>"
4
- after: "// \\[CODEGEN:ENTITY_IMPORTS\\]"
5
- skip_if: "from './<%= name %>'"
6
- ---
7
- import { <%= plural %> } from './<%= name %>';
@@ -1,10 +0,0 @@
1
- ---
2
- to: "<%= frontendEnabled ? (generate.collections ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
3
- inject: true
4
- append: true
5
- skip_if: "// Codegen collections"
6
- ---
7
- <% if (generate.collections) { -%>
8
-
9
- // Codegen collections
10
- <% } -%>
@@ -1,9 +0,0 @@
1
- ---
2
- to: "<%= frontendEnabled ? (generate.collectionsIndex ? `${locations.frontendCollections.path}/index.ts` : '') : '' %>"
3
- inject: true
4
- after: "// Generated entity collections"
5
- skip_if: "from './collections'"
6
- ---
7
- <% if (generate.collectionsIndex) { -%>
8
- export * from './collections';
9
- <% } -%>
@@ -1,9 +0,0 @@
1
- ---
2
- to: "<%= frontendEnabled ? (generate.collections && !frontend.collections?.schemaPrefix ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
3
- inject: true
4
- after: "// Codegen schema imports"
5
- skip_if: <%= camelName %>Schema
6
- ---
7
- <% if (generate.collections && !frontend.collections?.schemaPrefix) { -%>
8
- <%= camelName %>Schema,
9
- <% } -%>
@@ -1,86 +0,0 @@
1
- ---
2
- to: "<%= frontendEnabled ? (generate.collections ? `${locations.frontendCollections.path}/collections.ts` : '') : '' %>"
3
- inject: true
4
- after: "// Codegen collections"
5
- skip_if: <%= camelName %>Collection
6
- ---
7
- <% if (generate.collections) { -%>
8
- <%
9
- // Determine the URL expression based on config
10
- const hasApiBaseUrl = !!frontend.sync.apiBaseUrlImport;
11
- // For the URL path, use API_BASE_URL if configured
12
- const shapeUrlExpr = hasApiBaseUrl
13
- ? '`${API_BASE_URL}/' + plural + '`'
14
- : '`' + frontend.sync.shapeUrl + '/' + plural + '`';
15
- // REST list endpoint for 'api' sync mode
16
- const apiUrlExpr = hasApiBaseUrl
17
- ? '`${API_BASE_URL}/' + plural + '`'
18
- : '`' + frontend.sync.apiUrl + '/' + plural + '`';
19
- const schemaPrefix = frontend.collections?.schemaPrefix ?? 'schema.';
20
- -%>
21
- <% if (frontend.sync.mode === 'api') { -%>
22
- export const <%= camelName %>Collection = createCollection(
23
- queryCollectionOptions({
24
- id: '<%= plural %>',
25
- queryKey: ['<%= plural %>'],
26
- queryClient,
27
- queryFn: async () => {
28
- const res = await fetch(<%- apiUrlExpr %><% if (frontend.auth.function) { %>, {
29
- headers: { Authorization: <%= frontend.auth.function %>() },
30
- }<% } %>);
31
- if (!res.ok) {
32
- throw new Error(`GET <%= plural %> → ${res.status} ${res.statusText}`);
33
- }
34
- return res.json();
35
- },
36
- getKey: (item) => item.id,
37
- schema: <%= schemaPrefix %><%= camelName %>Schema,
38
- }),
39
- );
40
- <% } else { -%>
41
- export const <%= camelName %>Collection = createCollection(
42
- electricCollectionOptions({
43
- id: '<%= plural %>',
44
- shapeOptions: {
45
- <% if (frontend.sync.useTableParam) { -%>
46
- url: new URL(
47
- '<%= frontend.sync.shapeUrl %>',
48
- window.location.origin,
49
- ).toString(),
50
- params: {
51
- table: '<%= plural %>',
52
- },
53
- <% } else { -%>
54
- <% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
55
- url: new URL(
56
- <%- shapeUrlExpr %>,
57
- window.location.origin,
58
- ).toString(),
59
- <% } else { -%>
60
- url: <%- shapeUrlExpr %>,
61
- <% } -%>
62
- <% } -%>
63
- <% if (frontend.auth.function) { -%>
64
- headers: {
65
- Authorization: <%= frontend.auth.function %>(),
66
- },
67
- <% } -%>
68
- parser: {
69
- <% Object.entries(frontend.parsers).forEach(([type, fn]) => { -%>
70
- <%- type %>: <%- fn %>,
71
- <% }); -%>
72
- },
73
- <% if (frontend.sync.columnMapper) { -%>
74
- <% if (frontend.sync.columnMapperNeedsCall !== false) { -%>
75
- columnMapper: <%= frontend.sync.columnMapper %>(),
76
- <% } else { -%>
77
- columnMapper: <%= frontend.sync.columnMapper %>,
78
- <% } -%>
79
- <% } -%>
80
- },
81
- schema: <%= schemaPrefix %><%= camelName %>Schema,
82
- getKey: (item) => item.id,
83
- }),
84
- );
85
- <% } -%>
86
- <% } -%>
@@ -1,35 +0,0 @@
1
- ---
2
- to: <%= locations.frontendCollections.path %>/collections.ts
3
- skip_if: <%= !frontendEnabled %>
4
- force: false
5
- ---
6
- <% if (frontend.sync.mode === 'api') { -%>
7
- /**
8
- * REST-backed Collections (TanStack Query)
9
- * Generated by entity codegen - do not edit directly
10
- */
11
-
12
- import { queryCollectionOptions } from '@tanstack/query-db-collection';
13
- import { createCollection } from '@tanstack/react-db';
14
- import { queryClient } from '<%= frontend.sync.queryClientImport ?? './query-client' %>';
15
- <% } else { -%>
16
- /**
17
- * Electric SQL Collections
18
- * Generated by entity codegen - do not edit directly
19
- */
20
-
21
- import { electricCollectionOptions } from '@tanstack/electric-db-collection';
22
- import { createCollection } from '@tanstack/react-db';
23
- <% if (frontend.sync.columnMapper) { -%>
24
- import { <%= frontend.sync.columnMapper %> } from '@electric-sql/client';
25
- <% } -%>
26
- <% } -%>
27
- <% if (frontend.auth.function) { -%>
28
- import { <%= frontend.auth.function %> } from '<%= locations.frontendCollectionsAuth.import %>';
29
- <% } -%>
30
- <% if (frontend.sync.apiBaseUrlImport) { -%>
31
- import { API_BASE_URL } from '<%= frontend.sync.apiBaseUrlImport %>';
32
- <% } -%>
33
- import * as schema from '<%= locations.dbSchemaClient.import %>';
34
-
35
- // Codegen collections
@@ -1,173 +0,0 @@
1
- ---
2
- to: "<%= generate.structure === 'entity-first' ? `${locations.frontendGenerated.path}/${generate.fileNaming === 'plural' ? plural : name}/collection.ts` : generate.structure === 'concern-first' ? `${locations.frontendGenerated.path}/collections/${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 %> Collection
9
- * Generated by entity codegen - do not edit directly
10
- *
11
- * Real-time synced collection using Electric SQL
12
- */
13
-
14
- <% if (frontend.sync.columnMapper) { -%>
15
- import { <%= frontend.sync.columnMapper %> } from '@electric-sql/client';
16
- <% } -%>
17
- import { <%= camelName %>Schema } from '<%= locations.dbEntities.import %><% if (!locations.dbEntities.barrelExport) { %>/<%= name %><% } %>';
18
- <% if (exposeTrpc) { -%>
19
- import { trpc } from '<%= locations.trpcClient.import %>';
20
- <% } -%>
21
- import { electricCollectionOptions } from '@tanstack/electric-db-collection';
22
- import { createCollection } from '@tanstack/react-db';
23
- <% if (frontend.auth.function) { -%>
24
- import { <%= frontend.auth.function %> } from '<%= locations.frontendCollectionsAuth.import %>';
25
- <% } -%>
26
- <% if (frontend.sync.apiBaseUrlImport) { -%>
27
- import { API_BASE_URL } from '<%= frontend.sync.apiBaseUrlImport %>';
28
- <% } -%>
29
- <%
30
- // Collection variable name depends on collectionNaming config
31
- const collectionVar = generate.collectionNaming === 'plural' ? collectionVarNamePlural : collectionVarName;
32
- // File name for imports depends on fileNaming config
33
- const fileName = generate.fileNaming === 'plural' ? plural : name;
34
- -%>
35
- <% if (hasSoftDelete) { -%>
36
-
37
- // TODO: Backend Electric shape endpoint should filter soft-deleted records
38
- // by including WHERE deleted_at IS NULL in the shape configuration.
39
- // Without this filter, soft-deleted records will reappear after Electric sync.
40
- <% } -%>
41
-
42
- export const <%= collectionVar %> = createCollection(
43
- electricCollectionOptions({
44
- id: '<%= plural %>',
45
- shapeOptions: {
46
- <% if (frontend.sync.useTableParam) { -%>
47
- <% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
48
- url: new URL(
49
- '<%= frontend.sync.shapeUrl %>',
50
- typeof window !== 'undefined'
51
- ? window.location.origin
52
- : 'http://localhost:5173',
53
- ).toString(),
54
- <% } else { -%>
55
- url: '<%= frontend.sync.shapeUrl %>',
56
- <% } -%>
57
- params: {
58
- table: '<%= plural %>',
59
- },
60
- <% } else { -%>
61
- <% if (frontend.sync.wrapInUrlConstructor !== false) { -%>
62
- url: new URL(
63
- `<%= frontend.sync.shapeUrl %>/<%= plural %>`,
64
- typeof window !== 'undefined'
65
- ? window.location.origin
66
- : 'http://localhost:5173',
67
- ).toString(),
68
- <% } else { -%>
69
- url: `<%= frontend.sync.shapeUrl %>/<%= plural %>`,
70
- <% } -%>
71
- <% } -%>
72
- <% if (frontend.auth.function) { -%>
73
- headers: {
74
- Authorization: <%= frontend.auth.function %>(),
75
- },
76
- <% } -%>
77
- parser: {
78
- <% Object.entries(frontend.parsers).forEach(([type, fn]) => { -%>
79
- <%- type %>: <%- fn %>,
80
- <% }); -%>
81
- },
82
- <% if (frontend.sync.columnMapper) { -%>
83
- <% if (frontend.sync.columnMapperNeedsCall !== false) { -%>
84
- columnMapper: <%= frontend.sync.columnMapper %>(),
85
- <% } else { -%>
86
- columnMapper: <%= frontend.sync.columnMapper %>,
87
- <% } -%>
88
- <% } -%>
89
- },
90
- schema: <%= camelName %>Schema,
91
- getKey: (item) => item.id,
92
- <% if (generate.mutations && (exposeTrpc || exposeRepository)) { -%>
93
- <% if (exposeTrpc) { -%>
94
- onInsert: async ({ transaction }) => {
95
- const { modified } = transaction.mutations[0];
96
- const result = await trpc.<%= plural %>.create.mutate(modified);
97
- return { txid: result.txid };
98
- },
99
- onUpdate: async ({ transaction }) => {
100
- const { modified } = transaction.mutations[0];
101
- const result = await trpc.<%= plural %>.update.mutate({ id: modified.id, data: modified });
102
- return { txid: result.txid };
103
- },
104
- onDelete: async ({ transaction }) => {
105
- const { original } = transaction.mutations[0];
106
- const result = await trpc.<%= plural %>.delete.mutate({ id: original.id });
107
- return { txid: result.txid };
108
- },
109
- <% } -%>
110
- <% } -%>
111
- }),
112
- );
113
- <%
114
- // Collect unique belongs_to targets for imports (FK resolution)
115
- // Only import if fkResolution is enabled (default: true)
116
- const importedEntities = new Set();
117
- if (generate.fkResolution !== false) {
118
- existingBelongsTo.forEach((rel) => {
119
- if (rel.target !== name) {
120
- importedEntities.add(rel.target);
121
- }
122
- });
123
- }
124
- -%>
125
- <% if (importedEntities.size > 0) { -%>
126
-
127
- // Import related collections for FK resolution
128
- <% importedEntities.forEach((target) => {
129
- const targetCamel = target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
130
- // Simple pluralization matching prompt.js pluralize function
131
- const targetPlural = target.endsWith('y') ? target.slice(0, -1) + 'ies' :
132
- (target.endsWith('s') || target.endsWith('x') || target.endsWith('ch') || target.endsWith('sh')) ? target + 'es' : target + 's';
133
- const targetFileName = generate.fileNaming === 'plural' ? targetPlural : target;
134
- const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
135
- -%>
136
- import { <%= generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel %>Collection } from '<% if (generate.structure === 'entity-first') { %>./<%= targetFileName %>/collection<% } else { %>./<%= targetFileName %><% } %>';
137
- <% }); -%>
138
- <% } -%>
139
- <% if (generate.structure === 'entity-first') { -%>
140
-
141
- // Import types from sibling module
142
- import type { <%= className %>, <%= className %>Resolved } from './types';
143
- <% } else { -%>
144
-
145
- // Import types
146
- import type { <%= className %>, <%= className %>Resolved } from '<% if (generate.structure === 'concern-first') { %>../types/<%= fileName %><% } %>';
147
- <% } -%>
148
-
149
- // ============================================================================
150
- // Resolution (FK lookup - internal use)
151
- // ============================================================================
152
- <% if (existingBelongsTo.length > 0 && generate.fkResolution !== false) { -%>
153
-
154
- export function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
155
- return {
156
- ...entity,
157
- <% existingBelongsTo.forEach((rel) => {
158
- const targetCamel = rel.target.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
159
- const targetCamelPlural = targetCamel.endsWith('y') ? targetCamel.slice(0, -1) + 'ies' : targetCamel + 's';
160
- const targetCollectionVar = generate.collectionNaming === 'plural' ? targetCamelPlural : targetCamel;
161
- -%>
162
- <%= rel.name %>: entity.<%= rel.foreignKeyCamel %>
163
- ? <%= targetCollectionVar %>Collection.state.get(entity.<%= rel.foreignKeyCamel %>)
164
- : undefined,
165
- <% }); -%>
166
- };
167
- }
168
- <% } else { -%>
169
-
170
- export function resolveRelations(entity: <%= className %>): <%= className %>Resolved {
171
- return entity;
172
- }
173
- <% } -%>