archetype-engine 2.0.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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +241 -0
  3. package/dist/src/ai/adapters/anthropic.d.ts +31 -0
  4. package/dist/src/ai/adapters/anthropic.d.ts.map +1 -0
  5. package/dist/src/ai/adapters/anthropic.js +75 -0
  6. package/dist/src/ai/adapters/openai.d.ts +33 -0
  7. package/dist/src/ai/adapters/openai.d.ts.map +1 -0
  8. package/dist/src/ai/adapters/openai.js +120 -0
  9. package/dist/src/ai/adapters/vercel.d.ts +434 -0
  10. package/dist/src/ai/adapters/vercel.d.ts.map +1 -0
  11. package/dist/src/ai/adapters/vercel.js +162 -0
  12. package/dist/src/ai/index.d.ts +492 -0
  13. package/dist/src/ai/index.d.ts.map +1 -0
  14. package/dist/src/ai/index.js +71 -0
  15. package/dist/src/ai/state.d.ts +13 -0
  16. package/dist/src/ai/state.d.ts.map +1 -0
  17. package/dist/src/ai/state.js +215 -0
  18. package/dist/src/ai/tools.d.ts +13 -0
  19. package/dist/src/ai/tools.d.ts.map +1 -0
  20. package/dist/src/ai/tools.js +257 -0
  21. package/dist/src/ai/types.d.ts +196 -0
  22. package/dist/src/ai/types.d.ts.map +1 -0
  23. package/dist/src/ai/types.js +9 -0
  24. package/dist/src/cli.d.ts +3 -0
  25. package/dist/src/cli.d.ts.map +1 -0
  26. package/dist/src/cli.js +540 -0
  27. package/dist/src/core/utils.d.ts +27 -0
  28. package/dist/src/core/utils.d.ts.map +1 -0
  29. package/dist/src/core/utils.js +56 -0
  30. package/dist/src/entity.d.ts +165 -0
  31. package/dist/src/entity.d.ts.map +1 -0
  32. package/dist/src/entity.js +108 -0
  33. package/dist/src/fields.d.ts +207 -0
  34. package/dist/src/fields.d.ts.map +1 -0
  35. package/dist/src/fields.js +291 -0
  36. package/dist/src/generators/erd-ir.d.ts +10 -0
  37. package/dist/src/generators/erd-ir.d.ts.map +1 -0
  38. package/dist/src/generators/erd-ir.js +119 -0
  39. package/dist/src/index.d.ts +51 -0
  40. package/dist/src/index.d.ts.map +1 -0
  41. package/dist/src/index.js +101 -0
  42. package/dist/src/init/dependencies.d.ts +31 -0
  43. package/dist/src/init/dependencies.d.ts.map +1 -0
  44. package/dist/src/init/dependencies.js +101 -0
  45. package/dist/src/init/entity-templates.d.ts +42 -0
  46. package/dist/src/init/entity-templates.d.ts.map +1 -0
  47. package/dist/src/init/entity-templates.js +367 -0
  48. package/dist/src/init/index.d.ts +10 -0
  49. package/dist/src/init/index.d.ts.map +1 -0
  50. package/dist/src/init/index.js +250 -0
  51. package/dist/src/init/prompts.d.ts +11 -0
  52. package/dist/src/init/prompts.d.ts.map +1 -0
  53. package/dist/src/init/prompts.js +275 -0
  54. package/dist/src/init/templates.d.ts +24 -0
  55. package/dist/src/init/templates.d.ts.map +1 -0
  56. package/dist/src/init/templates.js +587 -0
  57. package/dist/src/json/index.d.ts +11 -0
  58. package/dist/src/json/index.d.ts.map +1 -0
  59. package/dist/src/json/index.js +26 -0
  60. package/dist/src/json/parser.d.ts +61 -0
  61. package/dist/src/json/parser.d.ts.map +1 -0
  62. package/dist/src/json/parser.js +309 -0
  63. package/dist/src/json/types.d.ts +275 -0
  64. package/dist/src/json/types.d.ts.map +1 -0
  65. package/dist/src/json/types.js +10 -0
  66. package/dist/src/manifest.d.ts +147 -0
  67. package/dist/src/manifest.d.ts.map +1 -0
  68. package/dist/src/manifest.js +104 -0
  69. package/dist/src/relations.d.ts +96 -0
  70. package/dist/src/relations.d.ts.map +1 -0
  71. package/dist/src/relations.js +108 -0
  72. package/dist/src/source.d.ts +93 -0
  73. package/dist/src/source.d.ts.map +1 -0
  74. package/dist/src/source.js +89 -0
  75. package/dist/src/template/context.d.ts +34 -0
  76. package/dist/src/template/context.d.ts.map +1 -0
  77. package/dist/src/template/context.js +31 -0
  78. package/dist/src/template/index.d.ts +6 -0
  79. package/dist/src/template/index.d.ts.map +1 -0
  80. package/dist/src/template/index.js +12 -0
  81. package/dist/src/template/registry.d.ts +18 -0
  82. package/dist/src/template/registry.d.ts.map +1 -0
  83. package/dist/src/template/registry.js +89 -0
  84. package/dist/src/template/runner.d.ts +9 -0
  85. package/dist/src/template/runner.d.ts.map +1 -0
  86. package/dist/src/template/runner.js +125 -0
  87. package/dist/src/template/types.d.ts +73 -0
  88. package/dist/src/template/types.d.ts.map +1 -0
  89. package/dist/src/template/types.js +3 -0
  90. package/dist/src/templates/nextjs-drizzle-trpc/generators/api.d.ts +22 -0
  91. package/dist/src/templates/nextjs-drizzle-trpc/generators/api.d.ts.map +1 -0
  92. package/dist/src/templates/nextjs-drizzle-trpc/generators/api.js +866 -0
  93. package/dist/src/templates/nextjs-drizzle-trpc/generators/auth.d.ts +20 -0
  94. package/dist/src/templates/nextjs-drizzle-trpc/generators/auth.d.ts.map +1 -0
  95. package/dist/src/templates/nextjs-drizzle-trpc/generators/auth.js +273 -0
  96. package/dist/src/templates/nextjs-drizzle-trpc/generators/crud-hooks.d.ts +22 -0
  97. package/dist/src/templates/nextjs-drizzle-trpc/generators/crud-hooks.d.ts.map +1 -0
  98. package/dist/src/templates/nextjs-drizzle-trpc/generators/crud-hooks.js +237 -0
  99. package/dist/src/templates/nextjs-drizzle-trpc/generators/hooks.d.ts +30 -0
  100. package/dist/src/templates/nextjs-drizzle-trpc/generators/hooks.d.ts.map +1 -0
  101. package/dist/src/templates/nextjs-drizzle-trpc/generators/hooks.js +345 -0
  102. package/dist/src/templates/nextjs-drizzle-trpc/generators/i18n.d.ts +25 -0
  103. package/dist/src/templates/nextjs-drizzle-trpc/generators/i18n.d.ts.map +1 -0
  104. package/dist/src/templates/nextjs-drizzle-trpc/generators/i18n.js +199 -0
  105. package/dist/src/templates/nextjs-drizzle-trpc/generators/index.d.ts +8 -0
  106. package/dist/src/templates/nextjs-drizzle-trpc/generators/index.d.ts.map +1 -0
  107. package/dist/src/templates/nextjs-drizzle-trpc/generators/index.js +18 -0
  108. package/dist/src/templates/nextjs-drizzle-trpc/generators/schema.d.ts +22 -0
  109. package/dist/src/templates/nextjs-drizzle-trpc/generators/schema.d.ts.map +1 -0
  110. package/dist/src/templates/nextjs-drizzle-trpc/generators/schema.js +270 -0
  111. package/dist/src/templates/nextjs-drizzle-trpc/generators/service.d.ts +23 -0
  112. package/dist/src/templates/nextjs-drizzle-trpc/generators/service.d.ts.map +1 -0
  113. package/dist/src/templates/nextjs-drizzle-trpc/generators/service.js +304 -0
  114. package/dist/src/templates/nextjs-drizzle-trpc/generators/validation.d.ts +21 -0
  115. package/dist/src/templates/nextjs-drizzle-trpc/generators/validation.d.ts.map +1 -0
  116. package/dist/src/templates/nextjs-drizzle-trpc/generators/validation.js +248 -0
  117. package/dist/src/templates/nextjs-drizzle-trpc/index.d.ts +30 -0
  118. package/dist/src/templates/nextjs-drizzle-trpc/index.d.ts.map +1 -0
  119. package/dist/src/templates/nextjs-drizzle-trpc/index.js +71 -0
  120. package/dist/src/validation/index.d.ts +71 -0
  121. package/dist/src/validation/index.d.ts.map +1 -0
  122. package/dist/src/validation/index.js +314 -0
  123. package/package.json +86 -0
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ /**
3
+ * Drizzle ORM Schema Generator
4
+ *
5
+ * Generates database table definitions for Drizzle ORM from entity definitions.
6
+ * Supports SQLite, PostgreSQL, and MySQL databases.
7
+ *
8
+ * Generated files:
9
+ * - db/schema.ts - All entity table definitions and junction tables
10
+ *
11
+ * Features:
12
+ * - Maps field types to appropriate database column types
13
+ * - Handles required/optional fields with notNull()
14
+ * - Creates foreign key references for hasOne relations
15
+ * - Generates junction tables for belongsToMany relations
16
+ * - Adds timestamp fields (createdAt, updatedAt) when behaviors.timestamps is true
17
+ * - Adds deletedAt field when behaviors.softDelete is true
18
+ *
19
+ * @module generators/schema
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.schemaGenerator = void 0;
23
+ /**
24
+ * Map entity field type to Drizzle column type
25
+ *
26
+ * @param config - Field configuration from entity definition
27
+ * @param isSqlite - Whether target database is SQLite
28
+ * @returns Drizzle column type name
29
+ */
30
+ function mapFieldType(config, isSqlite) {
31
+ switch (config.type) {
32
+ case 'text': return 'text';
33
+ case 'number': return 'integer';
34
+ case 'boolean': return isSqlite ? 'integer' : 'boolean';
35
+ case 'date': return 'text';
36
+ case 'enum': return 'text'; // Enum uses text for SQLite, pgEnum for PG (handled separately)
37
+ default: return 'text';
38
+ }
39
+ }
40
+ /**
41
+ * Generate enum type name from field name
42
+ */
43
+ function getEnumTypeName(entityName, fieldName) {
44
+ return `${entityName.toLowerCase()}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Enum`;
45
+ }
46
+ /**
47
+ * Collect all enum fields from entities and generate pgEnum definitions
48
+ */
49
+ function generateEnumDefinitions(manifest) {
50
+ const enums = [];
51
+ const generated = new Set();
52
+ for (const entity of manifest.entities) {
53
+ for (const [fieldName, config] of Object.entries(entity.fields)) {
54
+ if (config.type === 'enum' && config.enumValues) {
55
+ const enumName = config.enumName || getEnumTypeName(entity.name, fieldName);
56
+ if (!generated.has(enumName)) {
57
+ generated.add(enumName);
58
+ const values = config.enumValues.map(v => `'${v}'`).join(', ');
59
+ enums.push(`export const ${enumName} = pgEnum('${enumName}', [${values}])`);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return enums.join('\n');
65
+ }
66
+ /**
67
+ * Generate a single field definition line for the schema
68
+ *
69
+ * @param fieldName - Name of the field in the entity
70
+ * @param config - Field configuration with type, validations, etc.
71
+ * @param isSqlite - Whether target database is SQLite
72
+ * @param ctx - Generator context with naming utilities
73
+ * @param entityName - Entity name for enum type naming
74
+ * @returns Drizzle field definition code (e.g., "email: text('email').notNull().unique(),")
75
+ */
76
+ function generateFieldDefinition(fieldName, config, isSqlite, ctx, entityName) {
77
+ const columnName = ctx.naming.getColumnName(fieldName);
78
+ const parts = [];
79
+ // Handle enum fields specially
80
+ if (config.type === 'enum' && config.enumValues) {
81
+ if (isSqlite) {
82
+ // SQLite: use text column (validation enforced by Zod)
83
+ parts.push(`text('${columnName}')`);
84
+ }
85
+ else {
86
+ // PostgreSQL: use pgEnum reference
87
+ const enumName = config.enumName || (entityName ? getEnumTypeName(entityName, fieldName) : `${fieldName}Enum`);
88
+ parts.push(`${enumName}('${columnName}')`);
89
+ }
90
+ }
91
+ else if (config.type === 'boolean' && isSqlite) {
92
+ parts.push(`integer('${columnName}', { mode: 'boolean' })`);
93
+ }
94
+ else {
95
+ const type = mapFieldType(config, isSqlite);
96
+ parts.push(`${type}('${columnName}')`);
97
+ }
98
+ if (config.required)
99
+ parts.push('.notNull()');
100
+ if (config.unique)
101
+ parts.push('.unique()');
102
+ if (config.default !== undefined) {
103
+ if (typeof config.default === 'string') {
104
+ parts.push(`.default('${config.default}')`);
105
+ }
106
+ else {
107
+ parts.push(`.default(${config.default})`);
108
+ }
109
+ }
110
+ return ` ${fieldName}: ${parts.join('')},`;
111
+ }
112
+ /**
113
+ * Generate complete table definition for an entity
114
+ *
115
+ * @param entity - Compiled entity IR with fields, relations, and behaviors
116
+ * @param isSqlite - Whether target database is SQLite
117
+ * @param ctx - Generator context with naming utilities
118
+ * @returns Complete Drizzle table definition code
119
+ */
120
+ function generateEntity(entity, isSqlite, ctx) {
121
+ const tableName = ctx.naming.getTableName(entity.name);
122
+ const tableFunc = isSqlite ? 'sqliteTable' : 'pgTable';
123
+ const lines = [];
124
+ lines.push(`export const ${tableName} = ${tableFunc}('${tableName}', {`);
125
+ lines.push(` id: text('id').primaryKey(),`);
126
+ // User-defined fields (skip computed fields - they're not stored in DB)
127
+ for (const [fieldName, config] of Object.entries(entity.fields)) {
128
+ if (config.type === 'computed')
129
+ continue;
130
+ lines.push(generateFieldDefinition(fieldName, config, isSqlite, ctx, entity.name));
131
+ }
132
+ // Foreign keys for hasOne relations (skip if field already exists)
133
+ for (const [relName, rel] of Object.entries(entity.relations)) {
134
+ if (rel.type === 'hasOne') {
135
+ const fkField = rel.field || `${relName}Id`;
136
+ // Skip if this field was already defined in entity.fields
137
+ if (entity.fields[fkField])
138
+ continue;
139
+ // Skip self-referential relations - they cause TypeScript issues with inline references
140
+ if (rel.entity === entity.name) {
141
+ const fkColumn = ctx.naming.getColumnName(fkField);
142
+ lines.push(` ${fkField}: text('${fkColumn}'), // Self-reference handled via relations`);
143
+ continue;
144
+ }
145
+ const fkColumn = ctx.naming.getColumnName(fkField);
146
+ const targetTable = ctx.naming.getTableName(rel.entity);
147
+ const notNull = rel.optional ? '' : '.notNull()';
148
+ lines.push(` ${fkField}: text('${fkColumn}').references(() => ${targetTable}.id)${notNull},`);
149
+ }
150
+ }
151
+ // Timestamps
152
+ if (entity.behaviors.timestamps) {
153
+ lines.push(` createdAt: text('created_at').notNull(),`);
154
+ lines.push(` updatedAt: text('updated_at').notNull(),`);
155
+ }
156
+ // Soft delete
157
+ if (entity.behaviors.softDelete) {
158
+ lines.push(` deletedAt: text('deleted_at'),`);
159
+ }
160
+ lines.push('})');
161
+ return lines.join('\n');
162
+ }
163
+ /**
164
+ * Generate pivot fields for junction tables
165
+ *
166
+ * @param fields - Pivot field configurations
167
+ * @param isSqlite - Whether target database is SQLite
168
+ * @param ctx - Generator context
169
+ * @returns Array of field definition lines
170
+ */
171
+ function generatePivotFields(fields, isSqlite, ctx) {
172
+ if (!fields)
173
+ return [];
174
+ return Object.entries(fields).map(([fieldName, config]) => generateFieldDefinition(fieldName, config, isSqlite, ctx));
175
+ }
176
+ /**
177
+ * Generate junction tables for many-to-many relations
178
+ *
179
+ * Creates tables for belongsToMany relations. Handles both regular many-to-many
180
+ * and self-referential relations (e.g., User following User).
181
+ * Supports pivot fields for additional data on the relation.
182
+ *
183
+ * @param manifest - Compiled manifest with all entities
184
+ * @param isSqlite - Whether target database is SQLite
185
+ * @param ctx - Generator context with naming utilities
186
+ * @returns Junction table definitions code
187
+ */
188
+ function generateJunctionTables(manifest, isSqlite, ctx) {
189
+ const tableFunc = isSqlite ? 'sqliteTable' : 'pgTable';
190
+ const tables = [];
191
+ const generated = new Set();
192
+ for (const entity of manifest.entities) {
193
+ for (const [relName, rel] of Object.entries(entity.relations)) {
194
+ if (rel.type === 'belongsToMany') {
195
+ const table1 = ctx.naming.getTableName(entity.name);
196
+ const table2 = ctx.naming.getTableName(rel.entity);
197
+ // Generate pivot fields if defined
198
+ const pivotFields = generatePivotFields(rel.pivot?.fields, isSqlite, ctx);
199
+ const pivotFieldsStr = pivotFields.length > 0 ? '\n' + pivotFields.join('\n') : '';
200
+ if (entity.name === rel.entity) {
201
+ // Self-referential
202
+ const junctionName = rel.pivot?.table || `${entity.name.toLowerCase()}_${ctx.naming.toSnakeCase(relName)}`;
203
+ if (!generated.has(junctionName)) {
204
+ generated.add(junctionName);
205
+ tables.push(`
206
+ export const ${ctx.naming.toCamelCase(junctionName)} = ${tableFunc}('${junctionName}', {
207
+ sourceId: text('source_id').notNull().references(() => ${table1}.id),
208
+ targetId: text('target_id').notNull().references(() => ${table2}.id),${pivotFieldsStr}
209
+ })`);
210
+ }
211
+ }
212
+ else if (entity.name < rel.entity) {
213
+ // Custom table name from pivot config or default
214
+ const junctionName = rel.pivot?.table || `${entity.name.toLowerCase()}_${rel.entity.toLowerCase()}`;
215
+ if (!generated.has(junctionName)) {
216
+ generated.add(junctionName);
217
+ tables.push(`
218
+ export const ${ctx.naming.toCamelCase(junctionName)} = ${tableFunc}('${junctionName}', {
219
+ ${entity.name.toLowerCase()}Id: text('${ctx.naming.toSnakeCase(entity.name)}_id').notNull().references(() => ${table1}.id),
220
+ ${rel.entity.toLowerCase()}Id: text('${ctx.naming.toSnakeCase(rel.entity)}_id').notNull().references(() => ${table2}.id),${pivotFieldsStr}
221
+ })`);
222
+ }
223
+ }
224
+ }
225
+ }
226
+ }
227
+ return tables.join('\n');
228
+ }
229
+ /**
230
+ * Check if manifest has any enum fields
231
+ */
232
+ function hasEnumFields(manifest) {
233
+ return manifest.entities.some(entity => Object.values(entity.fields).some(f => f.type === 'enum'));
234
+ }
235
+ exports.schemaGenerator = {
236
+ name: 'drizzle-schema',
237
+ description: 'Generate Drizzle ORM schema',
238
+ generate(manifest, ctx) {
239
+ const isSqlite = ctx.database.isSqlite;
240
+ const hasEnums = hasEnumFields(manifest);
241
+ // Build imports based on database type and features used
242
+ let imports;
243
+ if (isSqlite) {
244
+ imports = `import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'`;
245
+ }
246
+ else {
247
+ const pgImports = ['pgTable', 'text', 'integer', 'boolean', 'timestamp'];
248
+ if (hasEnums)
249
+ pgImports.push('pgEnum');
250
+ imports = `import { ${pgImports.join(', ')} } from 'drizzle-orm/pg-core'`;
251
+ }
252
+ // Generate enum definitions for PostgreSQL
253
+ const enumDefs = !isSqlite && hasEnums ? generateEnumDefinitions(manifest) + '\n\n' : '';
254
+ const entities = manifest.entities
255
+ .map(entity => generateEntity(entity, isSqlite, ctx))
256
+ .join('\n\n');
257
+ const junctionTables = generateJunctionTables(manifest, isSqlite, ctx);
258
+ return {
259
+ path: 'db/schema.ts',
260
+ content: `// Auto-generated Drizzle schema
261
+ // Do not edit manually - regenerate with: npx archetype generate
262
+
263
+ ${imports}
264
+
265
+ ${enumDefs}${entities}
266
+ ${junctionTables}
267
+ `,
268
+ };
269
+ },
270
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Service Layer Generator
3
+ *
4
+ * Generates service modules for entities backed by external APIs.
5
+ * Only runs when entities have external source configuration.
6
+ *
7
+ * Generated files:
8
+ * - services/apiClient.ts - Reusable HTTP client with typed methods
9
+ * - services/{entity}Service.ts - CRUD operations for each external entity
10
+ * - services/index.ts - Barrel export of all services
11
+ *
12
+ * Features:
13
+ * - Environment variable support for base URLs (env:VARIABLE_NAME syntax)
14
+ * - RESTful endpoint pattern support (GET /products/:id)
15
+ * - Typed request/response handling
16
+ * - Error handling with HTTP status codes
17
+ * - AbortSignal support for request cancellation
18
+ *
19
+ * @module generators/service
20
+ */
21
+ import type { Generator } from '../../../template/types';
22
+ export declare const serviceGenerator: Generator;
23
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../src/templates/nextjs-drizzle-trpc/generators/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,yBAAyB,CAAA;AA+QvE,eAAO,MAAM,gBAAgB,EAAE,SAoC9B,CAAA"}
@@ -0,0 +1,304 @@
1
+ "use strict";
2
+ /**
3
+ * Service Layer Generator
4
+ *
5
+ * Generates service modules for entities backed by external APIs.
6
+ * Only runs when entities have external source configuration.
7
+ *
8
+ * Generated files:
9
+ * - services/apiClient.ts - Reusable HTTP client with typed methods
10
+ * - services/{entity}Service.ts - CRUD operations for each external entity
11
+ * - services/index.ts - Barrel export of all services
12
+ *
13
+ * Features:
14
+ * - Environment variable support for base URLs (env:VARIABLE_NAME syntax)
15
+ * - RESTful endpoint pattern support (GET /products/:id)
16
+ * - Typed request/response handling
17
+ * - Error handling with HTTP status codes
18
+ * - AbortSignal support for request cancellation
19
+ *
20
+ * @module generators/service
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.serviceGenerator = void 0;
24
+ const source_1 = require("../../../source");
25
+ /**
26
+ * Parse environment variable syntax in base URL
27
+ *
28
+ * Converts 'env:VARIABLE_NAME' to 'process.env.VARIABLE_NAME' for runtime resolution.
29
+ * Regular URLs are returned as string literals.
30
+ *
31
+ * @param baseUrl - Base URL string, may contain env: prefix
32
+ * @returns JavaScript expression for the URL
33
+ */
34
+ function parseEnvUrl(baseUrl) {
35
+ if (baseUrl.startsWith('env:')) {
36
+ const varName = baseUrl.slice(4);
37
+ return `process.env.${varName}`;
38
+ }
39
+ return `'${baseUrl}'`;
40
+ }
41
+ /**
42
+ * Parse endpoint string into HTTP method and path
43
+ *
44
+ * @example
45
+ * parseEndpoint('GET /products/:id') // { method: 'GET', path: '/products/:id' }
46
+ * parseEndpoint('/products') // { method: 'GET', path: '/products' }
47
+ *
48
+ * @param endpoint - Endpoint string with optional method prefix
49
+ * @returns Object with method and path
50
+ */
51
+ function parseEndpoint(endpoint) {
52
+ const parts = endpoint.split(' ');
53
+ if (parts.length === 2) {
54
+ return { method: parts[0], path: parts[1] };
55
+ }
56
+ // Default to GET if no method specified
57
+ return { method: 'GET', path: endpoint };
58
+ }
59
+ /**
60
+ * Generate the shared API client module
61
+ *
62
+ * Creates a reusable HTTP client with typed methods for GET, POST, PUT, DELETE.
63
+ * Includes URL building with query parameters and error handling.
64
+ *
65
+ * @returns Generated file for apiClient.ts
66
+ */
67
+ function generateApiClient() {
68
+ return {
69
+ path: 'services/apiClient.ts',
70
+ content: `// Auto-generated API client
71
+ // Do not edit manually - regenerate with: npx archetype generate
72
+
73
+ export interface RequestConfig {
74
+ headers?: Record<string, string>
75
+ params?: Record<string, unknown>
76
+ signal?: AbortSignal
77
+ }
78
+
79
+ export interface ApiResponse<T> {
80
+ data: T
81
+ status: number
82
+ }
83
+
84
+ class ApiClient {
85
+ private defaultHeaders: Record<string, string>
86
+
87
+ constructor(headers: Record<string, string> = {}) {
88
+ this.defaultHeaders = headers
89
+ }
90
+
91
+ private buildUrl(baseUrl: string, path: string, params?: Record<string, unknown>): string {
92
+ const url = new URL(path, baseUrl)
93
+ if (params) {
94
+ Object.entries(params).forEach(([key, value]) => {
95
+ if (value !== undefined && value !== null) {
96
+ url.searchParams.set(key, String(value))
97
+ }
98
+ })
99
+ }
100
+ return url.toString()
101
+ }
102
+
103
+ async get<T>(baseUrl: string, path: string, config?: RequestConfig): Promise<ApiResponse<T>> {
104
+ const url = this.buildUrl(baseUrl, path, config?.params)
105
+ const response = await fetch(url, {
106
+ method: 'GET',
107
+ headers: { ...this.defaultHeaders, ...config?.headers },
108
+ signal: config?.signal,
109
+ })
110
+ if (!response.ok) {
111
+ throw new Error(\`HTTP error \${response.status}: \${response.statusText}\`)
112
+ }
113
+ return { data: await response.json(), status: response.status }
114
+ }
115
+
116
+ async post<T>(baseUrl: string, path: string, body: unknown, config?: RequestConfig): Promise<ApiResponse<T>> {
117
+ const response = await fetch(\`\${baseUrl}\${path}\`, {
118
+ method: 'POST',
119
+ headers: {
120
+ 'Content-Type': 'application/json',
121
+ ...this.defaultHeaders,
122
+ ...config?.headers,
123
+ },
124
+ body: JSON.stringify(body),
125
+ signal: config?.signal,
126
+ })
127
+ if (!response.ok) {
128
+ throw new Error(\`HTTP error \${response.status}: \${response.statusText}\`)
129
+ }
130
+ return { data: await response.json(), status: response.status }
131
+ }
132
+
133
+ async put<T>(baseUrl: string, path: string, body: unknown, config?: RequestConfig): Promise<ApiResponse<T>> {
134
+ const response = await fetch(\`\${baseUrl}\${path}\`, {
135
+ method: 'PUT',
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ ...this.defaultHeaders,
139
+ ...config?.headers,
140
+ },
141
+ body: JSON.stringify(body),
142
+ signal: config?.signal,
143
+ })
144
+ if (!response.ok) {
145
+ throw new Error(\`HTTP error \${response.status}: \${response.statusText}\`)
146
+ }
147
+ return { data: await response.json(), status: response.status }
148
+ }
149
+
150
+ async delete<T>(baseUrl: string, path: string, config?: RequestConfig): Promise<ApiResponse<T>> {
151
+ const response = await fetch(\`\${baseUrl}\${path}\`, {
152
+ method: 'DELETE',
153
+ headers: { ...this.defaultHeaders, ...config?.headers },
154
+ signal: config?.signal,
155
+ })
156
+ if (!response.ok) {
157
+ throw new Error(\`HTTP error \${response.status}: \${response.statusText}\`)
158
+ }
159
+ return { data: await response.json(), status: response.status }
160
+ }
161
+ }
162
+
163
+ export const apiClient = new ApiClient()
164
+ `,
165
+ };
166
+ }
167
+ /**
168
+ * Generate service module for a single entity with external API source
169
+ *
170
+ * @param entity - Entity IR with source configuration
171
+ * @param source - External source config with base URL and endpoints
172
+ * @param ctx - Generator context
173
+ * @returns Generated file for {entity}Service.ts
174
+ */
175
+ function generateEntityService(entity, source, ctx) {
176
+ const name = entity.name;
177
+ const lowerName = name.toLowerCase();
178
+ const endpoints = (0, source_1.resolveEndpoints)(name, source);
179
+ const listEndpoint = parseEndpoint(endpoints.list);
180
+ const getEndpoint = parseEndpoint(endpoints.get);
181
+ const createEndpoint = parseEndpoint(endpoints.create);
182
+ const updateEndpoint = parseEndpoint(endpoints.update);
183
+ const deleteEndpoint = parseEndpoint(endpoints.delete);
184
+ const baseUrlCode = parseEnvUrl(source.baseUrl);
185
+ // Generate path with parameter substitution
186
+ const pathWithId = (path) => {
187
+ // Replace :id with ${id}
188
+ return path.replace(/:([a-zA-Z_]+)/g, '${$1}');
189
+ };
190
+ return {
191
+ path: `services/${lowerName}Service.ts`,
192
+ content: `// Auto-generated service for ${name}
193
+ // Do not edit manually - regenerate with: npx archetype generate
194
+
195
+ import { apiClient } from './apiClient'
196
+ import type { ${name}Create, ${name}Update } from '../schemas/${lowerName}'
197
+
198
+ const BASE_URL = ${baseUrlCode}!
199
+
200
+ export interface ${name}ListParams {
201
+ page?: number
202
+ limit?: number
203
+ [key: string]: unknown
204
+ }
205
+
206
+ export const ${lowerName}Service = {
207
+ /**
208
+ * List all ${name}s
209
+ */
210
+ async list(params?: ${name}ListParams) {
211
+ const response = await apiClient.get<${name}[]>(BASE_URL, '${listEndpoint.path}', { params })
212
+ return response.data
213
+ },
214
+
215
+ /**
216
+ * Get a single ${name} by ID
217
+ */
218
+ async get(id: string) {
219
+ const response = await apiClient.get<${name}>(BASE_URL, \`${pathWithId(getEndpoint.path)}\`)
220
+ return response.data
221
+ },
222
+
223
+ /**
224
+ * Create a new ${name}
225
+ */
226
+ async create(data: ${name}Create) {
227
+ const response = await apiClient.post<${name}>(BASE_URL, '${createEndpoint.path}', data)
228
+ return response.data
229
+ },
230
+
231
+ /**
232
+ * Update an existing ${name}
233
+ */
234
+ async update(id: string, data: ${name}Update) {
235
+ const response = await apiClient.put<${name}>(BASE_URL, \`${pathWithId(updateEndpoint.path)}\`, data)
236
+ return response.data
237
+ },
238
+
239
+ /**
240
+ * Delete a ${name}
241
+ */
242
+ async delete(id: string) {
243
+ const response = await apiClient.delete<void>(BASE_URL, \`${pathWithId(deleteEndpoint.path)}\`)
244
+ return response.data
245
+ },
246
+ }
247
+
248
+ // Type for the entity (inferred from schema)
249
+ export interface ${name} extends ${name}Create {
250
+ id: string
251
+ }
252
+ `,
253
+ };
254
+ }
255
+ /**
256
+ * Generate barrel export file for all services
257
+ *
258
+ * @param entities - Entities with external source to include
259
+ * @returns Generated file for services/index.ts
260
+ */
261
+ function generateServiceIndex(entities) {
262
+ const externalEntities = entities.filter(e => e.source?.type === 'external');
263
+ const exports = externalEntities
264
+ .map(e => `export { ${e.name.toLowerCase()}Service } from './${e.name.toLowerCase()}Service'`)
265
+ .join('\n');
266
+ return {
267
+ path: 'services/index.ts',
268
+ content: `// Auto-generated service exports
269
+ // Do not edit manually - regenerate with: npx archetype generate
270
+
271
+ export { apiClient } from './apiClient'
272
+ ${exports}
273
+ `,
274
+ };
275
+ }
276
+ exports.serviceGenerator = {
277
+ name: 'service-layer',
278
+ description: 'Generate service layer for external APIs',
279
+ generate(manifest, ctx) {
280
+ const files = [];
281
+ // Collect entities with external source (from entity or manifest level)
282
+ const entitiesWithSource = [];
283
+ for (const entity of manifest.entities) {
284
+ // Entity-level source takes precedence
285
+ const source = entity.source || manifest.source;
286
+ if (source?.type === 'external') {
287
+ entitiesWithSource.push({ entity, source });
288
+ }
289
+ }
290
+ // Only generate if we have external entities
291
+ if (entitiesWithSource.length === 0) {
292
+ return files;
293
+ }
294
+ // Generate base API client
295
+ files.push(generateApiClient());
296
+ // Generate service for each external entity
297
+ for (const { entity, source } of entitiesWithSource) {
298
+ files.push(generateEntityService(entity, source, ctx));
299
+ }
300
+ // Generate service index
301
+ files.push(generateServiceIndex(entitiesWithSource.map(e => e.entity)));
302
+ return files;
303
+ },
304
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Zod Validation Schema Generator
3
+ *
4
+ * Generates Zod validation schemas for entity CRUD operations.
5
+ * Supports internationalized error messages via next-intl.
6
+ *
7
+ * Generated files:
8
+ * - schemas/{entity}.ts - Create and Update schemas with types
9
+ *
10
+ * Features:
11
+ * - Maps field types to Zod validators (z.string(), z.number(), etc.)
12
+ * - Generates validation rules from field config (min, max, email, url, regex)
13
+ * - Supports i18n error messages when multiple languages configured
14
+ * - Creates both static and i18n-aware schema factory functions
15
+ * - Exports TypeScript types derived from schemas
16
+ *
17
+ * @module generators/validation
18
+ */
19
+ import type { Generator } from '../../../template/types';
20
+ export declare const validationGenerator: Generator;
21
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../../../src/templates/nextjs-drizzle-trpc/generators/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,yBAAyB,CAAA;AAqOvE,eAAO,MAAM,mBAAmB,EAAE,SAYjC,CAAA"}