archetype-engine 2.0.1 → 2.1.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.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Seed Data Generator
3
+ *
4
+ * Generates realistic seed data for development and testing.
5
+ * Creates seed functions for each entity with smart mock data based on field types.
6
+ *
7
+ * Generated files:
8
+ * - seeds/{entity}.ts - Individual entity seed functions
9
+ * - seeds/index.ts - Main orchestrator handling dependencies
10
+ * - seeds/run.ts - CLI script to run seeds
11
+ *
12
+ * Features:
13
+ * - Smart field-to-data mapping (email → valid emails, name → person names)
14
+ * - Respects validation constraints (min/max, enum values)
15
+ * - Handles entity dependencies (creates in correct order)
16
+ * - Configurable quantities
17
+ * - Optional faker.js integration for realistic data
18
+ * - Database reset utilities
19
+ *
20
+ * @module generators/seed
21
+ */
22
+ import type { Generator } from '../../../template/types';
23
+ /**
24
+ * Seed data generator
25
+ */
26
+ export declare const seedGenerator: Generator;
27
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../../../../src/templates/nextjs-drizzle-trpc/generators/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,yBAAyB,CAAA;AAuWvE;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,SAwC3B,CAAA"}
@@ -0,0 +1,407 @@
1
+ "use strict";
2
+ /**
3
+ * Seed Data Generator
4
+ *
5
+ * Generates realistic seed data for development and testing.
6
+ * Creates seed functions for each entity with smart mock data based on field types.
7
+ *
8
+ * Generated files:
9
+ * - seeds/{entity}.ts - Individual entity seed functions
10
+ * - seeds/index.ts - Main orchestrator handling dependencies
11
+ * - seeds/run.ts - CLI script to run seeds
12
+ *
13
+ * Features:
14
+ * - Smart field-to-data mapping (email → valid emails, name → person names)
15
+ * - Respects validation constraints (min/max, enum values)
16
+ * - Handles entity dependencies (creates in correct order)
17
+ * - Configurable quantities
18
+ * - Optional faker.js integration for realistic data
19
+ * - Database reset utilities
20
+ *
21
+ * @module generators/seed
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.seedGenerator = void 0;
25
+ const utils_1 = require("../../../core/utils");
26
+ /**
27
+ * Generate mock value for a field
28
+ */
29
+ function generateMockValue(fieldName, field, index) {
30
+ const lowerName = fieldName.toLowerCase();
31
+ switch (field.type) {
32
+ case 'text':
33
+ // Email fields
34
+ if (field.validations.some(v => v.type === 'email')) {
35
+ return `faker ? faker.internet.email() : \`user\${i}@example.com\``;
36
+ }
37
+ // URL fields
38
+ if (field.validations.some(v => v.type === 'url')) {
39
+ return `faker ? faker.internet.url() : \`https://example.com/\${i}\``;
40
+ }
41
+ // Name fields
42
+ if (lowerName.includes('name') || lowerName === 'title') {
43
+ if (lowerName.includes('first')) {
44
+ return `faker ? faker.person.firstName() : \`FirstName\${i}\``;
45
+ }
46
+ if (lowerName.includes('last')) {
47
+ return `faker ? faker.person.lastName() : \`LastName\${i}\``;
48
+ }
49
+ if (lowerName.includes('full') || lowerName === 'name') {
50
+ return `faker ? faker.person.fullName() : \`Sample Name \${i}\``;
51
+ }
52
+ if (lowerName === 'title') {
53
+ return `faker ? faker.lorem.sentence(5) : \`Sample Title \${i}\``;
54
+ }
55
+ }
56
+ // Content/description fields
57
+ if (lowerName.includes('content') || lowerName.includes('description') || lowerName.includes('bio')) {
58
+ return `faker ? faker.lorem.paragraphs(2) : \`Sample ${fieldName} content for record \${i}\``;
59
+ }
60
+ // Address fields
61
+ if (lowerName.includes('address')) {
62
+ return `faker ? faker.location.streetAddress() : \`\${i} Main Street\``;
63
+ }
64
+ if (lowerName.includes('city')) {
65
+ return `faker ? faker.location.city() : \`City\${i}\``;
66
+ }
67
+ if (lowerName.includes('country')) {
68
+ return `faker ? faker.location.country() : \`Country\${i}\``;
69
+ }
70
+ // Phone fields
71
+ if (lowerName.includes('phone')) {
72
+ return `faker ? faker.phone.number() : \`+1-555-\${1000 + i}\``;
73
+ }
74
+ // Slug fields
75
+ if (lowerName.includes('slug')) {
76
+ return `faker ? faker.helpers.slugify(faker.lorem.words(3)).toLowerCase() : \`slug-\${i}\``;
77
+ }
78
+ // Enum fields
79
+ if (field.enumValues) {
80
+ const values = field.enumValues;
81
+ return `faker ? faker.helpers.arrayElement(${JSON.stringify(values)}) : ${JSON.stringify(values)}[i % ${values.length}]`;
82
+ }
83
+ // Generic text with min length
84
+ const minLength = field.validations.find(v => v.type === 'minLength')?.value;
85
+ if (minLength) {
86
+ return `faker ? faker.lorem.words(${Math.ceil(minLength / 5)}) : \`${'x'.repeat(minLength)}\${i}\``;
87
+ }
88
+ // Default text
89
+ return `faker ? faker.lorem.words(3) : \`Sample ${fieldName} \${i}\``;
90
+ case 'number':
91
+ const min = field.validations.find(v => v.type === 'min')?.value ?? 0;
92
+ const max = field.validations.find(v => v.type === 'max')?.value ?? 1000;
93
+ const isInteger = field.validations.some(v => v.type === 'integer');
94
+ if (lowerName.includes('age')) {
95
+ return `faker ? faker.number.int({ min: ${min || 18}, max: ${max || 100} }) : ${min || 18} + (i % ${(max || 100) - (min || 18)})`;
96
+ }
97
+ if (lowerName.includes('price') || lowerName.includes('amount')) {
98
+ return `faker ? faker.number.float({ min: ${min}, max: ${max}, precision: 0.01 }) : ${min} + (i * 10)`;
99
+ }
100
+ if (lowerName.includes('count') || lowerName.includes('quantity')) {
101
+ return `faker ? faker.number.int({ min: ${min}, max: ${max} }) : i % ${max || 100}`;
102
+ }
103
+ if (isInteger) {
104
+ return `faker ? faker.number.int({ min: ${min}, max: ${max} }) : ${min} + (i % ${max - min})`;
105
+ }
106
+ else {
107
+ return `faker ? faker.number.float({ min: ${min}, max: ${max} }) : ${min} + (i * 0.5)`;
108
+ }
109
+ case 'boolean':
110
+ if (lowerName.includes('active') || lowerName.includes('enabled')) {
111
+ return `i % 3 !== 0`; // 66% true
112
+ }
113
+ if (lowerName.includes('published')) {
114
+ return `i % 2 === 0`; // 50% true
115
+ }
116
+ return `faker ? faker.datatype.boolean() : i % 2 === 0`;
117
+ case 'date':
118
+ if (lowerName.includes('birth')) {
119
+ return `faker ? faker.date.birthdate() : new Date(1990 + (i % 30), i % 12, 1 + (i % 28))`;
120
+ }
121
+ return `faker ? faker.date.recent() : new Date()`;
122
+ case 'enum':
123
+ const enumValues = field.enumValues || [];
124
+ return `faker ? faker.helpers.arrayElement(${JSON.stringify(enumValues)}) : ${JSON.stringify(enumValues)}[i % ${enumValues.length}]`;
125
+ default:
126
+ return `\`value-\${i}\``;
127
+ }
128
+ }
129
+ /**
130
+ * Generate seed function for an entity
131
+ */
132
+ function generateEntitySeedFunction(entity) {
133
+ const entityName = entity.name;
134
+ const routerName = (0, utils_1.toCamelCase)(entityName);
135
+ const tableName = (0, utils_1.toSnakeCase)((0, utils_1.pluralize)(entityName));
136
+ const lines = [];
137
+ // Get non-computed, non-relation fields
138
+ const seedableFields = Object.entries(entity.fields).filter(([_, field]) => field.type !== 'computed');
139
+ // Check for relations
140
+ const hasRelations = Object.keys(entity.relations).length > 0;
141
+ const foreignKeyFields = Object.entries(entity.relations)
142
+ .filter(([_, rel]) => rel.type === 'hasOne')
143
+ .map(([name, _]) => `${name}Id`);
144
+ // Function signature
145
+ const params = hasRelations
146
+ ? `count = 10, options: { ${foreignKeyFields.map(fk => `${fk}s?: string[]`).join(', ')} } = {}`
147
+ : `count = 10`;
148
+ lines.push(`/**`);
149
+ lines.push(` * Seed ${entityName} records`);
150
+ if (hasRelations) {
151
+ lines.push(` * @param count - Number of records to create`);
152
+ lines.push(` * @param options - Related entity IDs for foreign keys`);
153
+ }
154
+ lines.push(` */`);
155
+ lines.push(`export async function seed${(0, utils_1.pluralize)(entityName)}(${params}) {`);
156
+ lines.push(` let faker: any`);
157
+ lines.push(` try {`);
158
+ lines.push(` faker = (await import('@faker-js/faker')).faker`);
159
+ lines.push(` } catch {`);
160
+ lines.push(` faker = null`);
161
+ lines.push(` }`);
162
+ lines.push(``);
163
+ lines.push(` const data = Array.from({ length: count }, (_, i) => ({`);
164
+ // Generate field values
165
+ for (const [fieldName, field] of seedableFields) {
166
+ const value = generateMockValue(fieldName, field, 0);
167
+ lines.push(` ${fieldName}: ${value},`);
168
+ }
169
+ // Add foreign keys from relations
170
+ for (const fkField of foreignKeyFields) {
171
+ const baseName = fkField.replace(/Id$/, '');
172
+ lines.push(` ${fkField}: options.${fkField}s?.[i % (options.${fkField}s?.length || 1)],`);
173
+ }
174
+ // Add timestamps if enabled
175
+ if (entity.behaviors.timestamps) {
176
+ lines.push(` createdAt: faker ? faker.date.recent({ days: 30 }) : new Date(),`);
177
+ lines.push(` updatedAt: faker ? faker.date.recent({ days: 7 }) : new Date(),`);
178
+ }
179
+ lines.push(` }))`);
180
+ lines.push(``);
181
+ lines.push(` const created = await db.insert(${tableName}).values(data).returning()`);
182
+ lines.push(` console.log(\`✓ Created \${created.length} ${(0, utils_1.pluralize)(entityName).toLowerCase()}\`)`);
183
+ lines.push(` return created`);
184
+ lines.push(`}`);
185
+ return lines.join('\n');
186
+ }
187
+ /**
188
+ * Analyze entity dependencies to determine seed order
189
+ */
190
+ function analyzeEntityDependencies(entities) {
191
+ const graph = new Map();
192
+ const entityMap = new Map();
193
+ // Build dependency graph
194
+ for (const entity of entities) {
195
+ entityMap.set(entity.name, entity);
196
+ graph.set(entity.name, new Set());
197
+ // Check for hasOne relations (foreign keys)
198
+ for (const [_, relation] of Object.entries(entity.relations)) {
199
+ if (relation.type === 'hasOne') {
200
+ graph.get(entity.name).add(relation.entity);
201
+ }
202
+ }
203
+ }
204
+ // Topological sort
205
+ const sorted = [];
206
+ const visited = new Set();
207
+ const visiting = new Set();
208
+ function visit(entityName) {
209
+ if (visited.has(entityName))
210
+ return;
211
+ if (visiting.has(entityName)) {
212
+ // Circular dependency - just add it
213
+ return;
214
+ }
215
+ visiting.add(entityName);
216
+ const deps = graph.get(entityName) || new Set();
217
+ for (const dep of deps) {
218
+ visit(dep);
219
+ }
220
+ visiting.delete(entityName);
221
+ visited.add(entityName);
222
+ sorted.push(entityMap.get(entityName));
223
+ }
224
+ for (const entity of entities) {
225
+ visit(entity.name);
226
+ }
227
+ return sorted;
228
+ }
229
+ /**
230
+ * Generate main seed orchestrator
231
+ */
232
+ function generateSeedOrchestrator(manifest) {
233
+ const lines = [];
234
+ const orderedEntities = analyzeEntityDependencies(manifest.entities);
235
+ lines.push(`/**`);
236
+ lines.push(` * Main seed orchestrator`);
237
+ lines.push(` * `);
238
+ lines.push(` * Seeds all entities in correct dependency order.`);
239
+ lines.push(` */`);
240
+ lines.push(``);
241
+ lines.push(`import { db } from '@/server/db'`);
242
+ // Import schemas for reset
243
+ const tableNames = orderedEntities.map(e => (0, utils_1.toSnakeCase)((0, utils_1.pluralize)(e.name)));
244
+ lines.push(`import { ${tableNames.join(', ')} } from '@/generated/db/schema'`);
245
+ lines.push(``);
246
+ // Import seed functions
247
+ for (const entity of orderedEntities) {
248
+ lines.push(`import { seed${(0, utils_1.pluralize)(entity.name)} } from './${(0, utils_1.toCamelCase)(entity.name)}'`);
249
+ }
250
+ lines.push(``);
251
+ // Reset function
252
+ lines.push(`/**`);
253
+ lines.push(` * Clear all data from database (in reverse dependency order)`);
254
+ lines.push(` */`);
255
+ lines.push(`export async function resetDatabase() {`);
256
+ lines.push(` console.log('🗑️ Clearing database...\\n')`);
257
+ lines.push(``);
258
+ // Delete in reverse order
259
+ for (const entity of [...orderedEntities].reverse()) {
260
+ const tableName = (0, utils_1.toSnakeCase)((0, utils_1.pluralize)(entity.name));
261
+ lines.push(` await db.delete(${tableName})`);
262
+ lines.push(` console.log(\`✓ Cleared ${(0, utils_1.pluralize)(entity.name).toLowerCase()}\`)`);
263
+ }
264
+ lines.push(``);
265
+ lines.push(` console.log('')`);
266
+ lines.push(`}`);
267
+ lines.push(``);
268
+ // Main seed function
269
+ lines.push(`/**`);
270
+ lines.push(` * Seed all entities`);
271
+ lines.push(` */`);
272
+ lines.push(`export async function seedAll(options: { reset?: boolean } = {}) {`);
273
+ lines.push(` console.log('🌱 Seeding database...\\n')`);
274
+ lines.push(``);
275
+ lines.push(` if (options.reset) {`);
276
+ lines.push(` await resetDatabase()`);
277
+ lines.push(` }`);
278
+ lines.push(``);
279
+ // Seed in dependency order
280
+ for (const entity of orderedEntities) {
281
+ const varName = (0, utils_1.toCamelCase)((0, utils_1.pluralize)(entity.name));
282
+ const hasOneRelations = Object.entries(entity.relations)
283
+ .filter(([_, rel]) => rel.type === 'hasOne');
284
+ if (hasOneRelations.length > 0) {
285
+ // Pass related IDs
286
+ const relatedIds = hasOneRelations
287
+ .map(([name, _]) => `${name}Ids: ${(0, utils_1.toCamelCase)((0, utils_1.pluralize)(name))}.map(x => x.id)`)
288
+ .join(', ');
289
+ lines.push(` const ${varName} = await seed${(0, utils_1.pluralize)(entity.name)}(10, { ${relatedIds} })`);
290
+ }
291
+ else {
292
+ lines.push(` const ${varName} = await seed${(0, utils_1.pluralize)(entity.name)}(10)`);
293
+ }
294
+ }
295
+ lines.push(``);
296
+ lines.push(` console.log('\\n✅ Seeding complete!')`);
297
+ lines.push(`}`);
298
+ return lines.join('\n');
299
+ }
300
+ /**
301
+ * Generate CLI runner script
302
+ */
303
+ function generateRunnerScript() {
304
+ return `#!/usr/bin/env tsx
305
+ /**
306
+ * Seed Database Script
307
+ *
308
+ * Usage:
309
+ * npm run seed # Seed with sample data
310
+ * npm run seed --reset # Reset database and seed
311
+ */
312
+
313
+ import { seedAll } from './index'
314
+
315
+ const shouldReset = process.argv.includes('--reset')
316
+
317
+ seedAll({ reset: shouldReset })
318
+ .then(() => {
319
+ console.log('\\n👋 Done!')
320
+ process.exit(0)
321
+ })
322
+ .catch((error) => {
323
+ console.error('\\n❌ Seeding failed:', error)
324
+ process.exit(1)
325
+ })
326
+ `;
327
+ }
328
+ /**
329
+ * Seed data generator
330
+ */
331
+ exports.seedGenerator = {
332
+ name: 'seed-data',
333
+ description: 'Generates realistic seed data for development and testing',
334
+ generate(manifest, ctx) {
335
+ const files = [];
336
+ // Generate seed function for each entity
337
+ for (const entity of manifest.entities) {
338
+ const imports = [
339
+ `import { db } from '@/server/db'`,
340
+ `import { ${(0, utils_1.toSnakeCase)((0, utils_1.pluralize)(entity.name))} } from '@/generated/db/schema'`,
341
+ ];
342
+ files.push({
343
+ path: `seeds/${(0, utils_1.toCamelCase)(entity.name)}.ts`,
344
+ content: imports.join('\n') + '\n\n' + generateEntitySeedFunction(entity),
345
+ });
346
+ }
347
+ // Generate orchestrator
348
+ files.push({
349
+ path: 'seeds/index.ts',
350
+ content: generateSeedOrchestrator(manifest),
351
+ });
352
+ // Generate runner script
353
+ files.push({
354
+ path: 'seeds/run.ts',
355
+ content: generateRunnerScript(),
356
+ });
357
+ // Generate README
358
+ files.push({
359
+ path: 'seeds/README.md',
360
+ content: generateSeedReadme(manifest),
361
+ });
362
+ return files;
363
+ },
364
+ };
365
+ /**
366
+ * Generate README for seed data
367
+ */
368
+ function generateSeedReadme(manifest) {
369
+ const lines = [];
370
+ lines.push('# Seed Data');
371
+ lines.push('');
372
+ lines.push('Auto-generated seed data for development and testing.');
373
+ lines.push('');
374
+ lines.push('## Usage');
375
+ lines.push('');
376
+ lines.push('```bash');
377
+ lines.push('# Seed database with sample data');
378
+ lines.push('npm run seed');
379
+ lines.push('');
380
+ lines.push('# Reset database and seed');
381
+ lines.push('npm run seed --reset');
382
+ lines.push('```');
383
+ lines.push('');
384
+ lines.push('## What Gets Seeded');
385
+ lines.push('');
386
+ const orderedEntities = analyzeEntityDependencies(manifest.entities);
387
+ for (const entity of orderedEntities) {
388
+ lines.push(`- **${(0, utils_1.pluralize)(entity.name)}**: 10 records`);
389
+ }
390
+ lines.push('');
391
+ lines.push('## Optional: Faker.js');
392
+ lines.push('');
393
+ lines.push('For more realistic data, install faker.js:');
394
+ lines.push('');
395
+ lines.push('```bash');
396
+ lines.push('npm install --save-dev @faker-js/faker');
397
+ lines.push('```');
398
+ lines.push('');
399
+ lines.push('If installed, faker will generate realistic:');
400
+ lines.push('- Names, emails, addresses');
401
+ lines.push('- Dates, numbers, booleans');
402
+ lines.push('- Lorem ipsum content');
403
+ lines.push('- URLs, phone numbers, etc.');
404
+ lines.push('');
405
+ lines.push('Without faker, simple but valid data is generated.');
406
+ return lines.join('\n');
407
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Test Generator
3
+ *
4
+ * Generates comprehensive Vitest test suites for each entity's tRPC router.
5
+ * Tests are generated based on entity configuration (fields, validations, relations, protection).
6
+ *
7
+ * Generated files:
8
+ * - tests/{entity}.test.ts - Full CRUD test suite with validation, auth, relations, filters
9
+ *
10
+ * Features:
11
+ * - CRUD operation tests (create, list, get, update, remove)
12
+ * - Validation tests (required fields, field constraints, type checking)
13
+ * - Authentication tests (protected operations require auth)
14
+ * - Relation tests (hasMany, belongsToMany associations)
15
+ * - Filter/search/pagination tests
16
+ * - Batch operation tests (createMany, updateMany, removeMany)
17
+ * - Soft delete tests (if enabled)
18
+ * - Computed field tests (if present)
19
+ *
20
+ * @module generators/test
21
+ */
22
+ import type { Generator } from '../../../template/types';
23
+ /**
24
+ * Test generator - creates Vitest test files for tRPC routers
25
+ */
26
+ export declare const testGenerator: Generator;
27
+ //# sourceMappingURL=test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../../../src/templates/nextjs-drizzle-trpc/generators/test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAiB,MAAM,yBAAyB,CAAA;AAmgBvE;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,SAuB3B,CAAA"}