@veloxts/cli 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -5
- package/dist/cli.js +2 -0
- package/dist/commands/sync.d.ts +22 -0
- package/dist/commands/sync.js +96 -0
- package/dist/generators/fields/types.d.ts +7 -0
- package/dist/generators/fields/types.js +29 -0
- package/dist/generators/generators/namespace.js +7 -2
- package/dist/generators/templates/namespace.d.ts +3 -0
- package/dist/generators/templates/namespace.js +85 -1
- package/dist/generators/utils/prisma-schema.d.ts +18 -0
- package/dist/generators/utils/prisma-schema.js +53 -2
- package/dist/sync/analyzer.d.ts +20 -0
- package/dist/sync/analyzer.js +277 -0
- package/dist/sync/detector.d.ts +18 -0
- package/dist/sync/detector.js +94 -0
- package/dist/sync/index.d.ts +21 -0
- package/dist/sync/index.js +302 -0
- package/dist/sync/planner.d.ts +24 -0
- package/dist/sync/planner.js +75 -0
- package/dist/sync/procedure-generator.d.ts +20 -0
- package/dist/sync/procedure-generator.js +253 -0
- package/dist/sync/prompter.d.ts +18 -0
- package/dist/sync/prompter.js +312 -0
- package/dist/sync/schema-generator.d.ts +24 -0
- package/dist/sync/schema-generator.js +213 -0
- package/dist/sync/types.d.ts +219 -0
- package/dist/sync/types.js +9 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
# @veloxts/cli
|
|
2
2
|
|
|
3
|
+
## 0.7.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- feat(cli): add velox sync command for whole-schema Prisma-to-TypeScript generation
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @veloxts/auth@0.7.4
|
|
10
|
+
- @veloxts/core@0.7.4
|
|
11
|
+
- @veloxts/orm@0.7.4
|
|
12
|
+
- @veloxts/router@0.7.4
|
|
13
|
+
- @veloxts/validation@0.7.4
|
|
14
|
+
|
|
15
|
+
## 0.7.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- feat(cli): auto-populate Zod schemas from Prisma model fields
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @veloxts/auth@0.7.3
|
|
22
|
+
- @veloxts/core@0.7.3
|
|
23
|
+
- @veloxts/orm@0.7.3
|
|
24
|
+
- @veloxts/router@0.7.3
|
|
25
|
+
- @veloxts/validation@0.7.3
|
|
26
|
+
|
|
3
27
|
## 0.7.2
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
6
30
|
|
|
7
|
-
-
|
|
31
|
+
- fix(cli): replace require() with readFileSync in MCP tests
|
|
32
|
+
- simplify code for clarity and maintainability
|
|
8
33
|
- Updated dependencies
|
|
9
|
-
- @veloxts/auth@0.7.2
|
|
10
34
|
- @veloxts/core@0.7.2
|
|
11
|
-
- @veloxts/orm@0.7.2
|
|
12
|
-
- @veloxts/router@0.7.2
|
|
13
|
-
- @veloxts/validation@0.7.2
|
|
14
35
|
|
|
15
36
|
## 0.7.1
|
|
16
37
|
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createMigrateCommand } from './commands/migrate.js';
|
|
|
18
18
|
import { createOpenApiCommand } from './commands/openapi.js';
|
|
19
19
|
import { createProceduresCommand } from './commands/procedures.js';
|
|
20
20
|
import { createScheduleCommand } from './commands/schedule.js';
|
|
21
|
+
import { createSyncCommand } from './commands/sync.js';
|
|
21
22
|
import { createTenantCommand } from './commands/tenant.js';
|
|
22
23
|
import { CLI_VERSION } from './index.js';
|
|
23
24
|
/**
|
|
@@ -40,6 +41,7 @@ function createCLI() {
|
|
|
40
41
|
program.addCommand(createOpenApiCommand());
|
|
41
42
|
program.addCommand(createProceduresCommand());
|
|
42
43
|
program.addCommand(createScheduleCommand());
|
|
44
|
+
program.addCommand(createSyncCommand());
|
|
43
45
|
program.addCommand(createTenantCommand());
|
|
44
46
|
return program;
|
|
45
47
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync command - Generate Zod schemas + CRUD procedures from Prisma schema
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* velox sync [options]
|
|
6
|
+
*
|
|
7
|
+
* Options:
|
|
8
|
+
* --dry-run Preview changes without writing files
|
|
9
|
+
* --force Skip prompts, generate all models with defaults
|
|
10
|
+
* --skip-registration Skip auto-registering procedures in router
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* velox sync Interactive sync for all models
|
|
14
|
+
* velox sync --dry-run Preview what would be generated
|
|
15
|
+
* velox sync --force Generate all models with defaults
|
|
16
|
+
* velox sync --skip-registration Skip router registration
|
|
17
|
+
*/
|
|
18
|
+
import { Command } from 'commander';
|
|
19
|
+
/**
|
|
20
|
+
* Create the `velox sync` command.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createSyncCommand(): Command;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync command - Generate Zod schemas + CRUD procedures from Prisma schema
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* velox sync [options]
|
|
6
|
+
*
|
|
7
|
+
* Options:
|
|
8
|
+
* --dry-run Preview changes without writing files
|
|
9
|
+
* --force Skip prompts, generate all models with defaults
|
|
10
|
+
* --skip-registration Skip auto-registering procedures in router
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* velox sync Interactive sync for all models
|
|
14
|
+
* velox sync --dry-run Preview what would be generated
|
|
15
|
+
* velox sync --force Generate all models with defaults
|
|
16
|
+
* velox sync --skip-registration Skip router registration
|
|
17
|
+
*/
|
|
18
|
+
import * as p from '@clack/prompts';
|
|
19
|
+
import { Command } from 'commander';
|
|
20
|
+
import pc from 'picocolors';
|
|
21
|
+
import { ensureVeloxProject } from '../generators/index.js';
|
|
22
|
+
import { findPrismaSchema } from '../generators/utils/prisma-schema.js';
|
|
23
|
+
import { executeSync } from '../sync/index.js';
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// Command Creation
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Create the `velox sync` command.
|
|
29
|
+
*/
|
|
30
|
+
export function createSyncCommand() {
|
|
31
|
+
const cmd = new Command('sync')
|
|
32
|
+
.description('Generate Zod schemas and CRUD procedures from Prisma schema')
|
|
33
|
+
.option('-d, --dry-run', 'Preview changes without writing files', false)
|
|
34
|
+
.option('-f, --force', 'Skip prompts and generate all models with defaults', false)
|
|
35
|
+
.option('--skip-registration', 'Skip auto-registering procedures in router', false)
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
await runSync(options);
|
|
38
|
+
});
|
|
39
|
+
return cmd;
|
|
40
|
+
}
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Command Execution
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Run the sync pipeline.
|
|
46
|
+
*/
|
|
47
|
+
async function runSync(options) {
|
|
48
|
+
const projectRoot = process.cwd();
|
|
49
|
+
try {
|
|
50
|
+
// Verify we're in a VeloxTS project
|
|
51
|
+
await ensureVeloxProject(projectRoot);
|
|
52
|
+
// Verify Prisma schema exists
|
|
53
|
+
const schemaPath = findPrismaSchema(projectRoot);
|
|
54
|
+
if (!schemaPath) {
|
|
55
|
+
p.log.error('Prisma schema not found. Ensure prisma/schema.prisma exists in your project.');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
p.intro(pc.bgCyan(pc.black(' VeloxTS Sync ')));
|
|
59
|
+
const result = await executeSync(projectRoot, {
|
|
60
|
+
dryRun: options.dryRun,
|
|
61
|
+
force: options.force,
|
|
62
|
+
skipRegistration: options.skipRegistration,
|
|
63
|
+
});
|
|
64
|
+
// Show final summary
|
|
65
|
+
const totalCreated = result.created.length;
|
|
66
|
+
const totalOverwritten = result.overwritten.length;
|
|
67
|
+
const totalRegistered = result.registered.length;
|
|
68
|
+
const totalErrors = result.errors.length;
|
|
69
|
+
if (totalErrors > 0) {
|
|
70
|
+
p.outro(pc.yellow(`Sync completed with ${totalErrors} error${totalErrors === 1 ? '' : 's'}. ` +
|
|
71
|
+
`${totalCreated} created, ${totalOverwritten} overwritten.`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
if (totalCreated === 0 && totalOverwritten === 0) {
|
|
75
|
+
p.outro(pc.dim('Nothing to generate.'));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const parts = [];
|
|
79
|
+
if (totalCreated > 0) {
|
|
80
|
+
parts.push(`${totalCreated} created`);
|
|
81
|
+
}
|
|
82
|
+
if (totalOverwritten > 0) {
|
|
83
|
+
parts.push(`${totalOverwritten} overwritten`);
|
|
84
|
+
}
|
|
85
|
+
if (totalRegistered > 0) {
|
|
86
|
+
parts.push(`${totalRegistered} registered`);
|
|
87
|
+
}
|
|
88
|
+
p.outro(pc.green(`Sync complete: ${parts.join(', ')}.`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
p.log.error(message);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -88,6 +88,13 @@ export declare function validateEnumName(name: string): string | undefined;
|
|
|
88
88
|
* Validate enum values format (UPPER_CASE)
|
|
89
89
|
*/
|
|
90
90
|
export declare function validateEnumValues(values: string[]): string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Map a Prisma schema type string to a FieldType
|
|
93
|
+
*
|
|
94
|
+
* Used when reading fields from an existing Prisma model to convert them
|
|
95
|
+
* into FieldDefinition objects for code generation.
|
|
96
|
+
*/
|
|
97
|
+
export declare function prismaTypeToFieldType(prismaType: string): FieldType;
|
|
91
98
|
/**
|
|
92
99
|
* Parse comma-separated enum values
|
|
93
100
|
*/
|
|
@@ -138,6 +138,35 @@ export function validateEnumValues(values) {
|
|
|
138
138
|
}
|
|
139
139
|
return undefined;
|
|
140
140
|
}
|
|
141
|
+
// ============================================================================
|
|
142
|
+
// Prisma Type Mapping
|
|
143
|
+
// ============================================================================
|
|
144
|
+
/**
|
|
145
|
+
* Map a Prisma schema type string to a FieldType
|
|
146
|
+
*
|
|
147
|
+
* Used when reading fields from an existing Prisma model to convert them
|
|
148
|
+
* into FieldDefinition objects for code generation.
|
|
149
|
+
*/
|
|
150
|
+
export function prismaTypeToFieldType(prismaType) {
|
|
151
|
+
switch (prismaType) {
|
|
152
|
+
case 'String':
|
|
153
|
+
return 'string';
|
|
154
|
+
case 'Int':
|
|
155
|
+
return 'int';
|
|
156
|
+
case 'Float':
|
|
157
|
+
case 'Decimal':
|
|
158
|
+
case 'BigInt':
|
|
159
|
+
return 'float';
|
|
160
|
+
case 'Boolean':
|
|
161
|
+
return 'boolean';
|
|
162
|
+
case 'DateTime':
|
|
163
|
+
return 'datetime';
|
|
164
|
+
case 'Json':
|
|
165
|
+
return 'json';
|
|
166
|
+
default:
|
|
167
|
+
return 'string';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
141
170
|
/**
|
|
142
171
|
* Parse comma-separated enum values
|
|
143
172
|
*/
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import { BaseGenerator } from '../base.js';
|
|
15
15
|
import { getNamespaceInstructions, getNamespaceProcedurePath, getNamespaceSchemaPath, getNamespaceTestPath, namespaceSchemaTemplate, namespaceTemplate, namespaceTestTemplate, } from '../templates/namespace.js';
|
|
16
16
|
import { deriveEntityNames } from '../utils/naming.js';
|
|
17
|
-
import { analyzePrismaSchema, findPrismaSchema, getModelRelations, hasModel, } from '../utils/prisma-schema.js';
|
|
17
|
+
import { analyzePrismaSchema, findPrismaSchema, getModelFields, getModelRelations, hasModel, } from '../utils/prisma-schema.js';
|
|
18
18
|
import { detectRouterPattern, isProcedureRegistered, registerProcedures, } from '../utils/router-integration.js';
|
|
19
19
|
// ============================================================================
|
|
20
20
|
// Generator Implementation
|
|
@@ -113,10 +113,15 @@ Examples:
|
|
|
113
113
|
hasMany: [...modelRelations.hasMany],
|
|
114
114
|
};
|
|
115
115
|
}
|
|
116
|
+
// Extract scalar fields for Zod schema generation
|
|
117
|
+
const scalarFields = getModelFields(schemaAnalysis, entity.pascal);
|
|
118
|
+
if (scalarFields.length > 0) {
|
|
119
|
+
enrichedOptions.fields = scalarFields;
|
|
120
|
+
}
|
|
116
121
|
}
|
|
117
122
|
}
|
|
118
123
|
catch {
|
|
119
|
-
// Schema parsing failed — proceed without relations
|
|
124
|
+
// Schema parsing failed — proceed without relations or fields
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
const enrichedConfig = { ...config, options: enrichedOptions };
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Unlike the procedure generator, this creates a minimal scaffold ready for
|
|
6
6
|
* custom procedures rather than pre-defined CRUD operations.
|
|
7
7
|
*/
|
|
8
|
+
import type { FieldDefinition } from '../fields/types.js';
|
|
8
9
|
import type { TemplateContext, TemplateFunction } from '../types.js';
|
|
9
10
|
/**
|
|
10
11
|
* Relation info for code generation (shared with resource template)
|
|
@@ -24,6 +25,8 @@ export interface NamespaceOptions {
|
|
|
24
25
|
withTests: boolean;
|
|
25
26
|
/** Detected relations from Prisma schema */
|
|
26
27
|
relations?: RelationInfo;
|
|
28
|
+
/** Scalar fields extracted from Prisma model */
|
|
29
|
+
fields?: FieldDefinition[];
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* Generate namespace procedure file
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* Unlike the procedure generator, this creates a minimal scaffold ready for
|
|
6
6
|
* custom procedures rather than pre-defined CRUD operations.
|
|
7
7
|
*/
|
|
8
|
+
import { FIELD_TYPES } from '../fields/types.js';
|
|
9
|
+
import { fieldToZod } from '../templates/resource.js';
|
|
8
10
|
// ============================================================================
|
|
9
11
|
// Relation Helpers
|
|
10
12
|
// ============================================================================
|
|
@@ -170,7 +172,89 @@ export const ${entity.camel}Procedures = procedures('${entity.plural}', {
|
|
|
170
172
|
* Generate schema file for the namespace
|
|
171
173
|
*/
|
|
172
174
|
export function namespaceSchemaTemplate(ctx) {
|
|
173
|
-
const { entity } = ctx;
|
|
175
|
+
const { entity, options } = ctx;
|
|
176
|
+
const fields = options.fields;
|
|
177
|
+
if (fields && fields.length > 0) {
|
|
178
|
+
return generateSchemaWithFields(entity, fields);
|
|
179
|
+
}
|
|
180
|
+
return generateSchemaPlaceholder(entity);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate schema with actual fields from Prisma model
|
|
184
|
+
*/
|
|
185
|
+
function generateSchemaWithFields(entity, fields) {
|
|
186
|
+
// Base schema: all fields
|
|
187
|
+
const baseFields = fields.map(fieldToZod).join('\n');
|
|
188
|
+
// Create input: non-auto-generated fields (those without @default or @updatedAt)
|
|
189
|
+
const createFields = fields.filter((f) => !f.attributes.hasDefault);
|
|
190
|
+
const createFieldLines = createFields.map((f) => {
|
|
191
|
+
// In create input, optional fields use .optional() instead of .nullable()
|
|
192
|
+
return fieldToZodForInput(f);
|
|
193
|
+
});
|
|
194
|
+
const createContent = createFieldLines.join('\n');
|
|
195
|
+
return `/**
|
|
196
|
+
* ${entity.pascal} Schemas
|
|
197
|
+
*
|
|
198
|
+
* Zod validation schemas for ${entity.humanReadable} entities.
|
|
199
|
+
*/
|
|
200
|
+
|
|
201
|
+
import { z } from 'zod';
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// Base Schema
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* ${entity.pascal} entity schema
|
|
209
|
+
*/
|
|
210
|
+
export const ${entity.pascal}Schema = z.object({
|
|
211
|
+
id: z.string().uuid(),
|
|
212
|
+
${baseFields}
|
|
213
|
+
createdAt: z.coerce.date(),
|
|
214
|
+
updatedAt: z.coerce.date(),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export type ${entity.pascal} = z.infer<typeof ${entity.pascal}Schema>;
|
|
218
|
+
|
|
219
|
+
// ============================================================================
|
|
220
|
+
// Input Schemas
|
|
221
|
+
// ============================================================================
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create ${entity.pascal} input
|
|
225
|
+
*/
|
|
226
|
+
export const Create${entity.pascal}Input = z.object({
|
|
227
|
+
${createContent}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
export type Create${entity.pascal}InputType = z.infer<typeof Create${entity.pascal}Input>;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Update ${entity.pascal} input
|
|
234
|
+
*/
|
|
235
|
+
export const Update${entity.pascal}Input = z.object({
|
|
236
|
+
${createContent}
|
|
237
|
+
}).partial();
|
|
238
|
+
|
|
239
|
+
export type Update${entity.pascal}InputType = z.infer<typeof Update${entity.pascal}Input>;
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Convert a field to Zod for input schemas (optional → .optional() instead of .nullable())
|
|
244
|
+
*/
|
|
245
|
+
function fieldToZodForInput(field) {
|
|
246
|
+
// Create a copy with optional mapped to .optional() suffix
|
|
247
|
+
const typeInfo = FIELD_TYPES.find((t) => t.type === field.type);
|
|
248
|
+
let zodType = typeInfo?.zodSchema ?? 'z.string()';
|
|
249
|
+
if (field.attributes.optional) {
|
|
250
|
+
zodType += '.optional()';
|
|
251
|
+
}
|
|
252
|
+
return ` ${field.name}: ${zodType},`;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Generate schema with TODO placeholders (no Prisma model found)
|
|
256
|
+
*/
|
|
257
|
+
function generateSchemaPlaceholder(entity) {
|
|
174
258
|
return `/**
|
|
175
259
|
* ${entity.pascal} Schemas
|
|
176
260
|
*
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Parses Prisma schema files to find models/enums and enables safe injection
|
|
5
5
|
* of new definitions without breaking existing code.
|
|
6
6
|
*/
|
|
7
|
+
import type { FieldDefinition } from '../fields/types.js';
|
|
7
8
|
/**
|
|
8
9
|
* Parsed information about a single field in a Prisma model
|
|
9
10
|
*/
|
|
@@ -18,6 +19,10 @@ export interface PrismaFieldInfo {
|
|
|
18
19
|
readonly isArray: boolean;
|
|
19
20
|
/** The related model name, if this is a relation field */
|
|
20
21
|
readonly relatedModel: string | undefined;
|
|
22
|
+
/** Whether this field has a `?` modifier (nullable/optional) */
|
|
23
|
+
readonly isOptional: boolean;
|
|
24
|
+
/** Whether this field has `@default(...)` or `@updatedAt` */
|
|
25
|
+
readonly hasDefault: boolean;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
28
|
* Detailed information about a Prisma model including its fields
|
|
@@ -129,6 +134,19 @@ export declare function injectIntoSchema(analysis: PrismaSchemaAnalysis, models:
|
|
|
129
134
|
* Throws if any model or enum already exists (prevents accidental overwrites)
|
|
130
135
|
*/
|
|
131
136
|
export declare function validateNoConflicts(analysis: PrismaSchemaAnalysis, models: PrismaModelDefinition[], enums?: PrismaEnumDefinition[]): void;
|
|
137
|
+
/**
|
|
138
|
+
* Convert a PrismaFieldInfo to a FieldDefinition for code generation
|
|
139
|
+
*
|
|
140
|
+
* Returns null for fields that should be skipped (relations, reserved names).
|
|
141
|
+
*/
|
|
142
|
+
export declare function prismaFieldToFieldDefinition(field: PrismaFieldInfo): FieldDefinition | null;
|
|
143
|
+
/**
|
|
144
|
+
* Get scalar, non-auto-generated fields from a Prisma model as FieldDefinitions
|
|
145
|
+
*
|
|
146
|
+
* Filters out relation fields, reserved fields (id, createdAt, updatedAt, deletedAt),
|
|
147
|
+
* and fields with @id annotation.
|
|
148
|
+
*/
|
|
149
|
+
export declare function getModelFields(analysis: PrismaSchemaAnalysis, modelName: string): FieldDefinition[];
|
|
132
150
|
/**
|
|
133
151
|
* Generate a minimal Prisma model string
|
|
134
152
|
*/
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* of new definitions without breaking existing code.
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { prismaTypeToFieldType, RESERVED_FIELD_NAMES } from '../fields/types.js';
|
|
8
9
|
import { GeneratorError, GeneratorErrorCode } from '../types.js';
|
|
9
10
|
// ============================================================================
|
|
10
11
|
// Schema Parsing
|
|
@@ -156,13 +157,15 @@ function parseModelFields(body, modelNames) {
|
|
|
156
157
|
continue;
|
|
157
158
|
}
|
|
158
159
|
// Parse field: name Type[?][] [@annotations...]
|
|
159
|
-
// Match: fieldName TypeName optional modifiers
|
|
160
|
-
const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\[\])
|
|
160
|
+
// Match: fieldName TypeName optional array and optional modifiers
|
|
161
|
+
const fieldMatch = trimmed.match(/^(\w+)\s+(\w+)(\[\])?(\?)?/);
|
|
161
162
|
if (!fieldMatch)
|
|
162
163
|
continue;
|
|
163
164
|
const name = fieldMatch[1];
|
|
164
165
|
const baseType = fieldMatch[2];
|
|
165
166
|
const isArray = fieldMatch[3] === '[]';
|
|
167
|
+
const isOptional = fieldMatch[4] === '?';
|
|
168
|
+
const hasDefault = /@default\(/.test(trimmed) || /@updatedAt/.test(trimmed);
|
|
166
169
|
// Check if this is a relation field (type matches a known model)
|
|
167
170
|
const isRelation = modelNames.has(baseType);
|
|
168
171
|
if (!isRelation) {
|
|
@@ -172,6 +175,8 @@ function parseModelFields(body, modelNames) {
|
|
|
172
175
|
isRelation: false,
|
|
173
176
|
isArray: false,
|
|
174
177
|
relatedModel: undefined,
|
|
178
|
+
isOptional,
|
|
179
|
+
hasDefault,
|
|
175
180
|
});
|
|
176
181
|
continue;
|
|
177
182
|
}
|
|
@@ -188,6 +193,8 @@ function parseModelFields(body, modelNames) {
|
|
|
188
193
|
isRelation: true,
|
|
189
194
|
isArray,
|
|
190
195
|
relatedModel: baseType,
|
|
196
|
+
isOptional,
|
|
197
|
+
hasDefault,
|
|
191
198
|
});
|
|
192
199
|
}
|
|
193
200
|
return fields;
|
|
@@ -311,6 +318,50 @@ export function validateNoConflicts(analysis, models, enums = []) {
|
|
|
311
318
|
}
|
|
312
319
|
}
|
|
313
320
|
// ============================================================================
|
|
321
|
+
// Field Conversion
|
|
322
|
+
// ============================================================================
|
|
323
|
+
/**
|
|
324
|
+
* Convert a PrismaFieldInfo to a FieldDefinition for code generation
|
|
325
|
+
*
|
|
326
|
+
* Returns null for fields that should be skipped (relations, reserved names).
|
|
327
|
+
*/
|
|
328
|
+
export function prismaFieldToFieldDefinition(field) {
|
|
329
|
+
// Skip relation fields
|
|
330
|
+
if (field.isRelation)
|
|
331
|
+
return null;
|
|
332
|
+
// Skip reserved fields (id, createdAt, updatedAt, deletedAt)
|
|
333
|
+
if (RESERVED_FIELD_NAMES.includes(field.name))
|
|
334
|
+
return null;
|
|
335
|
+
return {
|
|
336
|
+
name: field.name,
|
|
337
|
+
type: prismaTypeToFieldType(field.type),
|
|
338
|
+
attributes: {
|
|
339
|
+
optional: field.isOptional,
|
|
340
|
+
unique: false, // Not tracked in PrismaFieldInfo
|
|
341
|
+
hasDefault: field.hasDefault,
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Get scalar, non-auto-generated fields from a Prisma model as FieldDefinitions
|
|
347
|
+
*
|
|
348
|
+
* Filters out relation fields, reserved fields (id, createdAt, updatedAt, deletedAt),
|
|
349
|
+
* and fields with @id annotation.
|
|
350
|
+
*/
|
|
351
|
+
export function getModelFields(analysis, modelName) {
|
|
352
|
+
const modelInfo = analysis.modelDetails.get(modelName);
|
|
353
|
+
if (!modelInfo)
|
|
354
|
+
return [];
|
|
355
|
+
const fields = [];
|
|
356
|
+
for (const field of modelInfo.fields) {
|
|
357
|
+
const converted = prismaFieldToFieldDefinition(field);
|
|
358
|
+
if (converted) {
|
|
359
|
+
fields.push(converted);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return fields;
|
|
363
|
+
}
|
|
364
|
+
// ============================================================================
|
|
314
365
|
// Helpers
|
|
315
366
|
// ============================================================================
|
|
316
367
|
/**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Reads a Prisma schema and produces rich `SyncModelInfo[]` metadata
|
|
5
|
+
* for every model. This is stage 1 of the `velox sync` pipeline.
|
|
6
|
+
*
|
|
7
|
+
* The analyzer re-parses the raw schema text (rather than relying solely
|
|
8
|
+
* on the existing `analyzePrismaSchema` helper) because the helper skips
|
|
9
|
+
* relation fields that carry `@relation(fields: [...])` -- exactly the
|
|
10
|
+
* ones we need to classify belongsTo relations and detect foreign keys.
|
|
11
|
+
*/
|
|
12
|
+
import type { SyncModelInfo } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Analyze a Prisma schema and produce sync metadata for every model.
|
|
15
|
+
*
|
|
16
|
+
* @param projectRoot - Absolute path to the project root directory
|
|
17
|
+
* @returns Array of `SyncModelInfo` objects, one per Prisma model
|
|
18
|
+
* @throws If no Prisma schema can be found
|
|
19
|
+
*/
|
|
20
|
+
export declare function analyzeSchema(projectRoot: string): SyncModelInfo[];
|