@veloxts/cli 0.6.64 → 0.6.66
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 +24 -0
- package/dist/generators/generators/index.d.ts +1 -0
- package/dist/generators/generators/index.js +4 -0
- package/dist/generators/generators/namespace.d.ts +39 -0
- package/dist/generators/generators/namespace.js +202 -0
- package/dist/generators/generators/procedure.js +25 -7
- package/dist/generators/generators/resource.js +31 -1
- package/dist/generators/templates/namespace.d.ts +44 -0
- package/dist/generators/templates/namespace.js +444 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @veloxts/cli
|
|
2
2
|
|
|
3
|
+
## 0.6.66
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- docs(skills): recommend namespace generator for AI agents
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @veloxts/auth@0.6.66
|
|
10
|
+
- @veloxts/core@0.6.66
|
|
11
|
+
- @veloxts/orm@0.6.66
|
|
12
|
+
- @veloxts/router@0.6.66
|
|
13
|
+
- @veloxts/validation@0.6.66
|
|
14
|
+
|
|
15
|
+
## 0.6.65
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- improve ai integration and simplify api router definition
|
|
20
|
+
- Updated dependencies
|
|
21
|
+
- @veloxts/auth@0.6.65
|
|
22
|
+
- @veloxts/core@0.6.65
|
|
23
|
+
- @veloxts/orm@0.6.65
|
|
24
|
+
- @veloxts/router@0.6.65
|
|
25
|
+
- @veloxts/validation@0.6.65
|
|
26
|
+
|
|
3
27
|
## 0.6.64
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -14,6 +14,7 @@ export { createMailGenerator, MailGenerator } from './mail.js';
|
|
|
14
14
|
export { createMiddlewareGenerator, MiddlewareGenerator } from './middleware.js';
|
|
15
15
|
export { createMigrationGenerator, MigrationGenerator } from './migration.js';
|
|
16
16
|
export { createModelGenerator, ModelGenerator } from './model.js';
|
|
17
|
+
export { createNamespaceGenerator, NamespaceGenerator } from './namespace.js';
|
|
17
18
|
export { createPageGenerator, PageGenerator } from './page.js';
|
|
18
19
|
export { createPolicyGenerator, PolicyGenerator } from './policy.js';
|
|
19
20
|
export { createProcedureGenerator, ProcedureGenerator } from './procedure.js';
|
|
@@ -15,6 +15,7 @@ import { createMailGenerator } from './mail.js';
|
|
|
15
15
|
import { createMiddlewareGenerator } from './middleware.js';
|
|
16
16
|
import { createMigrationGenerator } from './migration.js';
|
|
17
17
|
import { createModelGenerator } from './model.js';
|
|
18
|
+
import { createNamespaceGenerator } from './namespace.js';
|
|
18
19
|
import { createPageGenerator } from './page.js';
|
|
19
20
|
import { createPolicyGenerator } from './policy.js';
|
|
20
21
|
import { createProcedureGenerator } from './procedure.js';
|
|
@@ -39,6 +40,7 @@ export { createMailGenerator, MailGenerator } from './mail.js';
|
|
|
39
40
|
export { createMiddlewareGenerator, MiddlewareGenerator } from './middleware.js';
|
|
40
41
|
export { createMigrationGenerator, MigrationGenerator } from './migration.js';
|
|
41
42
|
export { createModelGenerator, ModelGenerator } from './model.js';
|
|
43
|
+
export { createNamespaceGenerator, NamespaceGenerator } from './namespace.js';
|
|
42
44
|
export { createPageGenerator, PageGenerator } from './page.js';
|
|
43
45
|
export { createPolicyGenerator, PolicyGenerator } from './policy.js';
|
|
44
46
|
export { createProcedureGenerator, ProcedureGenerator } from './procedure.js';
|
|
@@ -61,6 +63,8 @@ export function registerBuiltinGenerators() {
|
|
|
61
63
|
registerGenerator(createProcedureGenerator());
|
|
62
64
|
// Register model generator
|
|
63
65
|
registerGenerator(createModelGenerator());
|
|
66
|
+
// Register namespace generator
|
|
67
|
+
registerGenerator(createNamespaceGenerator());
|
|
64
68
|
// Register migration generator
|
|
65
69
|
registerGenerator(createMigrationGenerator());
|
|
66
70
|
// Register schema generator
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace Generator
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds a complete procedure namespace with schema file.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* velox make namespace <name> [options]
|
|
8
|
+
* velox m ns <name> [options]
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* velox make namespace products # Empty namespace with schema
|
|
12
|
+
* velox make namespace orders --example # With example CRUD procedures
|
|
13
|
+
*/
|
|
14
|
+
import { BaseGenerator } from '../base.js';
|
|
15
|
+
import { type NamespaceOptions } from '../templates/namespace.js';
|
|
16
|
+
import type { GeneratorConfig, GeneratorMetadata, GeneratorOption, GeneratorOutput } from '../types.js';
|
|
17
|
+
/**
|
|
18
|
+
* Namespace generator - creates procedure namespace with schema
|
|
19
|
+
*/
|
|
20
|
+
export declare class NamespaceGenerator extends BaseGenerator<NamespaceOptions> {
|
|
21
|
+
readonly metadata: GeneratorMetadata;
|
|
22
|
+
readonly options: ReadonlyArray<GeneratorOption>;
|
|
23
|
+
/**
|
|
24
|
+
* Validate and transform raw options
|
|
25
|
+
*/
|
|
26
|
+
validateOptions(raw: Record<string, unknown>): NamespaceOptions;
|
|
27
|
+
/**
|
|
28
|
+
* Generate namespace files
|
|
29
|
+
*/
|
|
30
|
+
generate(config: GeneratorConfig<NamespaceOptions>): Promise<GeneratorOutput>;
|
|
31
|
+
/**
|
|
32
|
+
* Build post-instructions based on registration result
|
|
33
|
+
*/
|
|
34
|
+
private buildPostInstructions;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a new namespace generator instance
|
|
38
|
+
*/
|
|
39
|
+
export declare function createNamespaceGenerator(): NamespaceGenerator;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace Generator
|
|
3
|
+
*
|
|
4
|
+
* Scaffolds a complete procedure namespace with schema file.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* velox make namespace <name> [options]
|
|
8
|
+
* velox m ns <name> [options]
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* velox make namespace products # Empty namespace with schema
|
|
12
|
+
* velox make namespace orders --example # With example CRUD procedures
|
|
13
|
+
*/
|
|
14
|
+
import { BaseGenerator } from '../base.js';
|
|
15
|
+
import { getNamespaceInstructions, getNamespaceProcedurePath, getNamespaceSchemaPath, getNamespaceTestPath, namespaceSchemaTemplate, namespaceTemplate, namespaceTestTemplate, } from '../templates/namespace.js';
|
|
16
|
+
import { detectRouterPattern, isProcedureRegistered, registerProcedures, } from '../utils/router-integration.js';
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Generator Implementation
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Namespace generator - creates procedure namespace with schema
|
|
22
|
+
*/
|
|
23
|
+
export class NamespaceGenerator extends BaseGenerator {
|
|
24
|
+
metadata = {
|
|
25
|
+
name: 'namespace',
|
|
26
|
+
description: 'Generate procedure namespace + schema (for existing models or external APIs)',
|
|
27
|
+
longDescription: `
|
|
28
|
+
Scaffold a procedure namespace with a separate Zod schema file.
|
|
29
|
+
|
|
30
|
+
Use this when you already have a Prisma model or are working with external data.
|
|
31
|
+
For new database entities, use "velox make resource" instead (recommended).
|
|
32
|
+
|
|
33
|
+
Creates:
|
|
34
|
+
• Schema file (src/schemas/{kebab}.ts)
|
|
35
|
+
• Procedure file (src/procedures/{plural}.ts)
|
|
36
|
+
• Test file (src/procedures/__tests__/{plural}.test.ts)
|
|
37
|
+
• Auto-registers in router.ts
|
|
38
|
+
|
|
39
|
+
When to use:
|
|
40
|
+
✓ You have an EXISTING Prisma model and need procedures for it
|
|
41
|
+
✓ Calling an external API (no database model needed)
|
|
42
|
+
✓ You want separate schema files (cleaner than inline)
|
|
43
|
+
✓ Need procedure collection without Prisma injection
|
|
44
|
+
|
|
45
|
+
When NOT to use:
|
|
46
|
+
• Creating a new database entity → use "resource" instead (recommended)
|
|
47
|
+
• Adding a single procedure → use "procedure" instead
|
|
48
|
+
|
|
49
|
+
Comparison:
|
|
50
|
+
• "resource" = Prisma model + schema + procedures + tests (RECOMMENDED)
|
|
51
|
+
• "namespace" = schema + procedures (no Prisma injection)
|
|
52
|
+
• "procedure" = procedures only (inline schemas)
|
|
53
|
+
|
|
54
|
+
Examples:
|
|
55
|
+
velox make namespace products # Empty namespace for existing model
|
|
56
|
+
velox make namespace orders --example # With example CRUD procedures
|
|
57
|
+
velox m ns external-api -e # For external API integration
|
|
58
|
+
`,
|
|
59
|
+
aliases: ['ns'],
|
|
60
|
+
category: 'resource',
|
|
61
|
+
};
|
|
62
|
+
options = [
|
|
63
|
+
{
|
|
64
|
+
name: 'example',
|
|
65
|
+
short: 'e',
|
|
66
|
+
description: 'Include example CRUD procedures',
|
|
67
|
+
type: 'boolean',
|
|
68
|
+
default: false,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: 'with-tests',
|
|
72
|
+
short: 't',
|
|
73
|
+
description: 'Generate test file (default: true)',
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
default: true,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'skip-registration',
|
|
79
|
+
short: 'S',
|
|
80
|
+
description: 'Skip auto-registering the procedure in router.ts',
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
default: false,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* Validate and transform raw options
|
|
87
|
+
*/
|
|
88
|
+
validateOptions(raw) {
|
|
89
|
+
return {
|
|
90
|
+
withExample: Boolean(raw.example ?? raw.withExample ?? false),
|
|
91
|
+
withTests: raw['with-tests'] !== false && raw.withTests !== false,
|
|
92
|
+
skipRegistration: Boolean(raw['skip-registration'] ?? raw.skipRegistration ?? false),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Generate namespace files
|
|
97
|
+
*/
|
|
98
|
+
async generate(config) {
|
|
99
|
+
const context = this.createContext(config);
|
|
100
|
+
const { entity } = context;
|
|
101
|
+
const { options } = config;
|
|
102
|
+
// Collect files to generate
|
|
103
|
+
const files = [];
|
|
104
|
+
// Generate schema file
|
|
105
|
+
const schemaContent = namespaceSchemaTemplate(context);
|
|
106
|
+
files.push({
|
|
107
|
+
path: getNamespaceSchemaPath(entity.kebab),
|
|
108
|
+
content: schemaContent,
|
|
109
|
+
});
|
|
110
|
+
// Generate procedure file
|
|
111
|
+
const procedureContent = namespaceTemplate(context);
|
|
112
|
+
files.push({
|
|
113
|
+
path: getNamespaceProcedurePath(entity.plural),
|
|
114
|
+
content: procedureContent,
|
|
115
|
+
});
|
|
116
|
+
// Generate test file (default: true)
|
|
117
|
+
if (options.withTests) {
|
|
118
|
+
const testContent = namespaceTestTemplate(context);
|
|
119
|
+
files.push({
|
|
120
|
+
path: getNamespaceTestPath(entity.plural),
|
|
121
|
+
content: testContent,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Try auto-registration if not skipped
|
|
125
|
+
let registrationResult = null;
|
|
126
|
+
if (!options.skipRegistration && !config.dryRun) {
|
|
127
|
+
const procedureVar = `${entity.camel}Procedures`;
|
|
128
|
+
// Check if already registered
|
|
129
|
+
if (!isProcedureRegistered(config.cwd, procedureVar)) {
|
|
130
|
+
// Detect router pattern
|
|
131
|
+
const pattern = detectRouterPattern(config.cwd);
|
|
132
|
+
if (pattern.type !== 'unknown') {
|
|
133
|
+
// Perform registration
|
|
134
|
+
registrationResult = registerProcedures(config.cwd, entity.kebab, procedureVar, false // not a dry run
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Generate post-creation instructions
|
|
140
|
+
const postInstructions = this.buildPostInstructions(entity, options, registrationResult);
|
|
141
|
+
return {
|
|
142
|
+
files,
|
|
143
|
+
postInstructions,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Build post-instructions based on registration result
|
|
148
|
+
*/
|
|
149
|
+
buildPostInstructions(entity, options, registrationResult) {
|
|
150
|
+
const procedureVar = `${entity.camel}Procedures`;
|
|
151
|
+
// If registration was successful
|
|
152
|
+
if (registrationResult?.success) {
|
|
153
|
+
const modifiedFiles = registrationResult.modifiedFiles.map((f) => ` - ${f}`).join('\n');
|
|
154
|
+
const testFileInfo = options.withTests
|
|
155
|
+
? `
|
|
156
|
+
- src/procedures/__tests__/${entity.plural}.test.ts`
|
|
157
|
+
: '';
|
|
158
|
+
let instructions = `
|
|
159
|
+
✓ Namespace ${procedureVar} auto-registered!
|
|
160
|
+
|
|
161
|
+
Created files:
|
|
162
|
+
- src/schemas/${entity.kebab}.ts
|
|
163
|
+
- src/procedures/${entity.plural}.ts${testFileInfo}
|
|
164
|
+
|
|
165
|
+
Modified files:
|
|
166
|
+
${modifiedFiles}`;
|
|
167
|
+
const nextStep = options.withExample
|
|
168
|
+
? 'Customize the example procedures'
|
|
169
|
+
: `Add procedures to src/procedures/${entity.plural}.ts`;
|
|
170
|
+
const testStep = options.withTests ? '\n 4. Run tests: pnpm test' : '';
|
|
171
|
+
instructions += `
|
|
172
|
+
|
|
173
|
+
Next steps:
|
|
174
|
+
1. Add fields to your schema in src/schemas/${entity.kebab}.ts
|
|
175
|
+
2. Add the ${entity.pascal} model to your Prisma schema
|
|
176
|
+
3. ${nextStep}${testStep}`;
|
|
177
|
+
return instructions;
|
|
178
|
+
}
|
|
179
|
+
// If registration was skipped
|
|
180
|
+
if (options.skipRegistration) {
|
|
181
|
+
return getNamespaceInstructions(entity.plural, entity.pascal, entity.kebab);
|
|
182
|
+
}
|
|
183
|
+
// If registration failed
|
|
184
|
+
if (registrationResult?.error) {
|
|
185
|
+
return `
|
|
186
|
+
⚠ Auto-registration failed: ${registrationResult.error}
|
|
187
|
+
|
|
188
|
+
${getNamespaceInstructions(entity.plural, entity.pascal, entity.kebab)}`;
|
|
189
|
+
}
|
|
190
|
+
// Default: show manual instructions
|
|
191
|
+
return getNamespaceInstructions(entity.plural, entity.pascal, entity.kebab);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ============================================================================
|
|
195
|
+
// Export
|
|
196
|
+
// ============================================================================
|
|
197
|
+
/**
|
|
198
|
+
* Create a new namespace generator instance
|
|
199
|
+
*/
|
|
200
|
+
export function createNamespaceGenerator() {
|
|
201
|
+
return new NamespaceGenerator();
|
|
202
|
+
}
|
|
@@ -24,17 +24,35 @@ import { detectRouterPattern, isProcedureRegistered, registerProcedures, } from
|
|
|
24
24
|
export class ProcedureGenerator extends BaseGenerator {
|
|
25
25
|
metadata = {
|
|
26
26
|
name: 'procedure',
|
|
27
|
-
description: 'Generate a procedure file for
|
|
27
|
+
description: 'Generate a procedure file (for single or additional procedures)',
|
|
28
28
|
longDescription: `
|
|
29
|
-
Scaffold a
|
|
29
|
+
Scaffold a standalone procedure file with inline schemas.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Use this for adding individual procedures or extending existing namespaces.
|
|
32
|
+
For new database entities, use "velox make resource" instead (recommended).
|
|
33
|
+
|
|
34
|
+
Creates:
|
|
35
|
+
• Procedure file (src/procedures/{plural}.ts) with inline Zod schemas
|
|
36
|
+
• Auto-registers in router.ts
|
|
37
|
+
|
|
38
|
+
When to use:
|
|
39
|
+
✓ Adding a single procedure to your API
|
|
40
|
+
✓ Quick prototyping (inline schemas, no separate files)
|
|
41
|
+
✓ Extending an existing namespace with additional procedures
|
|
42
|
+
|
|
43
|
+
When NOT to use:
|
|
44
|
+
• Creating a new database entity → use "resource" instead (recommended)
|
|
45
|
+
• Want separate schema files → use "namespace" instead
|
|
46
|
+
|
|
47
|
+
Comparison:
|
|
48
|
+
• "resource" = Prisma model + schema + procedures + tests (RECOMMENDED)
|
|
49
|
+
• "namespace" = schema + procedures (separate files)
|
|
50
|
+
• "procedure" = procedures only (inline schemas, self-contained)
|
|
33
51
|
|
|
34
52
|
Examples:
|
|
35
|
-
velox make procedure
|
|
36
|
-
velox make procedure
|
|
37
|
-
velox m p
|
|
53
|
+
velox make procedure health # Single health check procedure
|
|
54
|
+
velox make procedure users --crud # CRUD with inline schemas
|
|
55
|
+
velox m p analytics # Quick procedure for analytics
|
|
38
56
|
`,
|
|
39
57
|
aliases: ['p', 'proc'],
|
|
40
58
|
category: 'resource',
|
|
@@ -27,7 +27,37 @@ import { createSnapshot, rollback, saveOriginal, trackCreated, } from '../utils/
|
|
|
27
27
|
// ============================================================================
|
|
28
28
|
const metadata = {
|
|
29
29
|
name: 'resource',
|
|
30
|
-
description: 'Generate complete resource (model, schema, procedures, tests)',
|
|
30
|
+
description: 'Generate complete resource (model, schema, procedures, tests) [RECOMMENDED]',
|
|
31
|
+
longDescription: `
|
|
32
|
+
RECOMMENDED: The resource generator is the preferred way to create new entities.
|
|
33
|
+
|
|
34
|
+
This is VeloxTS's equivalent to Laravel's "php artisan make:model -a" - it scaffolds
|
|
35
|
+
everything you need for a new entity in one command.
|
|
36
|
+
|
|
37
|
+
Creates:
|
|
38
|
+
• Prisma model (auto-injected into schema.prisma)
|
|
39
|
+
• Zod validation schemas (src/schemas/{name}.schema.ts)
|
|
40
|
+
• CRUD procedures (src/procedures/{name}.ts)
|
|
41
|
+
• Test file (src/procedures/__tests__/{name}.test.ts)
|
|
42
|
+
• Auto-registers in router.ts
|
|
43
|
+
• Optional: runs Prisma migration
|
|
44
|
+
|
|
45
|
+
When to use:
|
|
46
|
+
✓ Creating a new database entity (users, posts, products, etc.)
|
|
47
|
+
✓ You want the full VeloxTS stack with tests
|
|
48
|
+
✓ Default choice for most scenarios
|
|
49
|
+
|
|
50
|
+
When NOT to use:
|
|
51
|
+
• You have an existing Prisma model → use "namespace" instead
|
|
52
|
+
• You're calling an external API (no database) → use "namespace" instead
|
|
53
|
+
• Adding a single procedure to existing namespace → use "procedure" instead
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
velox make resource Post # Full stack for Post entity
|
|
57
|
+
velox make resource Comment -i # Interactive field definition
|
|
58
|
+
velox make resource Order --soft-delete # With soft delete support
|
|
59
|
+
velox m r Product --auto-migrate # Auto-run migration
|
|
60
|
+
`,
|
|
31
61
|
category: 'resource',
|
|
32
62
|
aliases: ['r', 'res'],
|
|
33
63
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace Template
|
|
3
|
+
*
|
|
4
|
+
* Generates a procedure namespace (collection) with corresponding schema file.
|
|
5
|
+
* Unlike the procedure generator, this creates a minimal scaffold ready for
|
|
6
|
+
* custom procedures rather than pre-defined CRUD operations.
|
|
7
|
+
*/
|
|
8
|
+
import type { TemplateContext, TemplateFunction } from '../types.js';
|
|
9
|
+
export interface NamespaceOptions {
|
|
10
|
+
/** Skip auto-registering in router.ts */
|
|
11
|
+
skipRegistration: boolean;
|
|
12
|
+
/** Include example procedure */
|
|
13
|
+
withExample: boolean;
|
|
14
|
+
/** Generate test file */
|
|
15
|
+
withTests: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Generate namespace procedure file
|
|
19
|
+
*/
|
|
20
|
+
export declare const namespaceTemplate: TemplateFunction<NamespaceOptions>;
|
|
21
|
+
/**
|
|
22
|
+
* Generate schema file for the namespace
|
|
23
|
+
*/
|
|
24
|
+
export declare function namespaceSchemaTemplate(ctx: TemplateContext<NamespaceOptions>): string;
|
|
25
|
+
/**
|
|
26
|
+
* Generate test file for the namespace procedures
|
|
27
|
+
*/
|
|
28
|
+
export declare function namespaceTestTemplate(ctx: TemplateContext<NamespaceOptions>): string;
|
|
29
|
+
/**
|
|
30
|
+
* Get the file path for the procedure file
|
|
31
|
+
*/
|
|
32
|
+
export declare function getNamespaceProcedurePath(entityPlural: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Get the file path for the test file
|
|
35
|
+
*/
|
|
36
|
+
export declare function getNamespaceTestPath(entityPlural: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Get the file path for the schema file
|
|
39
|
+
*/
|
|
40
|
+
export declare function getNamespaceSchemaPath(entityKebab: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Generate post-generation instructions
|
|
43
|
+
*/
|
|
44
|
+
export declare function getNamespaceInstructions(entityPlural: string, entityPascal: string, entityKebab: string): string;
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespace Template
|
|
3
|
+
*
|
|
4
|
+
* Generates a procedure namespace (collection) with corresponding schema file.
|
|
5
|
+
* Unlike the procedure generator, this creates a minimal scaffold ready for
|
|
6
|
+
* custom procedures rather than pre-defined CRUD operations.
|
|
7
|
+
*/
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Template Functions
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Generate namespace procedure file
|
|
13
|
+
*/
|
|
14
|
+
export const namespaceTemplate = (ctx) => {
|
|
15
|
+
const { entity, options } = ctx;
|
|
16
|
+
if (options.withExample) {
|
|
17
|
+
return generateWithExample(ctx);
|
|
18
|
+
}
|
|
19
|
+
return `/**
|
|
20
|
+
* ${entity.pascal} Procedures
|
|
21
|
+
*
|
|
22
|
+
* Namespace for ${entity.humanReadable}-related API endpoints.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { procedure, procedures, z } from '@veloxts/velox';
|
|
26
|
+
import {
|
|
27
|
+
${entity.pascal}Schema,
|
|
28
|
+
Create${entity.pascal}Input,
|
|
29
|
+
Update${entity.pascal}Input,
|
|
30
|
+
} from '../schemas/${entity.kebab}.js';
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Procedures
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
export const ${entity.camel}Procedures = procedures('${entity.plural}', {
|
|
37
|
+
// Add your procedures here
|
|
38
|
+
//
|
|
39
|
+
// Examples:
|
|
40
|
+
//
|
|
41
|
+
// get${entity.pascal}: procedure()
|
|
42
|
+
// .input(z.object({ id: z.string().uuid() }))
|
|
43
|
+
// .output(${entity.pascal}Schema.nullable())
|
|
44
|
+
// .query(async ({ input, ctx }) => {
|
|
45
|
+
// return ctx.db.${entity.camel}.findUnique({ where: { id: input.id } });
|
|
46
|
+
// }),
|
|
47
|
+
//
|
|
48
|
+
// list${entity.pascalPlural}: procedure()
|
|
49
|
+
// .output(z.array(${entity.pascal}Schema))
|
|
50
|
+
// .query(async ({ ctx }) => {
|
|
51
|
+
// return ctx.db.${entity.camel}.findMany();
|
|
52
|
+
// }),
|
|
53
|
+
//
|
|
54
|
+
// create${entity.pascal}: procedure()
|
|
55
|
+
// .input(Create${entity.pascal}Input)
|
|
56
|
+
// .output(${entity.pascal}Schema)
|
|
57
|
+
// .mutation(async ({ input, ctx }) => {
|
|
58
|
+
// return ctx.db.${entity.camel}.create({ data: input });
|
|
59
|
+
// }),
|
|
60
|
+
});
|
|
61
|
+
`;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Generate namespace with example procedure
|
|
65
|
+
*/
|
|
66
|
+
function generateWithExample(ctx) {
|
|
67
|
+
const { entity } = ctx;
|
|
68
|
+
return `/**
|
|
69
|
+
* ${entity.pascal} Procedures
|
|
70
|
+
*
|
|
71
|
+
* Namespace for ${entity.humanReadable}-related API endpoints.
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
import { procedure, procedures, z } from '@veloxts/velox';
|
|
75
|
+
import {
|
|
76
|
+
${entity.pascal}Schema,
|
|
77
|
+
Create${entity.pascal}Input,
|
|
78
|
+
Update${entity.pascal}Input,
|
|
79
|
+
} from '../schemas/${entity.kebab}.js';
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// Procedures
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
export const ${entity.camel}Procedures = procedures('${entity.plural}', {
|
|
86
|
+
/**
|
|
87
|
+
* Get a single ${entity.humanReadable} by ID
|
|
88
|
+
* GET /${entity.plural}/:id
|
|
89
|
+
*/
|
|
90
|
+
get${entity.pascal}: procedure()
|
|
91
|
+
.input(z.object({ id: z.string().uuid() }))
|
|
92
|
+
.output(${entity.pascal}Schema.nullable())
|
|
93
|
+
.query(async ({ input, ctx }) => {
|
|
94
|
+
return ctx.db.${entity.camel}.findUnique({
|
|
95
|
+
where: { id: input.id },
|
|
96
|
+
});
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* List all ${entity.humanReadablePlural}
|
|
101
|
+
* GET /${entity.plural}
|
|
102
|
+
*/
|
|
103
|
+
list${entity.pascalPlural}: procedure()
|
|
104
|
+
.output(z.array(${entity.pascal}Schema))
|
|
105
|
+
.query(async ({ ctx }) => {
|
|
106
|
+
return ctx.db.${entity.camel}.findMany();
|
|
107
|
+
}),
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create a new ${entity.humanReadable}
|
|
111
|
+
* POST /${entity.plural}
|
|
112
|
+
*/
|
|
113
|
+
create${entity.pascal}: procedure()
|
|
114
|
+
.input(Create${entity.pascal}Input)
|
|
115
|
+
.output(${entity.pascal}Schema)
|
|
116
|
+
.mutation(async ({ input, ctx }) => {
|
|
117
|
+
return ctx.db.${entity.camel}.create({
|
|
118
|
+
data: input,
|
|
119
|
+
});
|
|
120
|
+
}),
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Update an existing ${entity.humanReadable}
|
|
124
|
+
* PUT /${entity.plural}/:id
|
|
125
|
+
*/
|
|
126
|
+
update${entity.pascal}: procedure()
|
|
127
|
+
.input(z.object({ id: z.string().uuid() }).merge(Update${entity.pascal}Input))
|
|
128
|
+
.output(${entity.pascal}Schema)
|
|
129
|
+
.mutation(async ({ input, ctx }) => {
|
|
130
|
+
const { id, ...data } = input;
|
|
131
|
+
return ctx.db.${entity.camel}.update({
|
|
132
|
+
where: { id },
|
|
133
|
+
data,
|
|
134
|
+
});
|
|
135
|
+
}),
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Delete a ${entity.humanReadable}
|
|
139
|
+
* DELETE /${entity.plural}/:id
|
|
140
|
+
*/
|
|
141
|
+
delete${entity.pascal}: procedure()
|
|
142
|
+
.input(z.object({ id: z.string().uuid() }))
|
|
143
|
+
.output(z.object({ success: z.boolean() }))
|
|
144
|
+
.mutation(async ({ input, ctx }) => {
|
|
145
|
+
await ctx.db.${entity.camel}.delete({
|
|
146
|
+
where: { id: input.id },
|
|
147
|
+
});
|
|
148
|
+
return { success: true };
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Generate schema file for the namespace
|
|
155
|
+
*/
|
|
156
|
+
export function namespaceSchemaTemplate(ctx) {
|
|
157
|
+
const { entity } = ctx;
|
|
158
|
+
return `/**
|
|
159
|
+
* ${entity.pascal} Schemas
|
|
160
|
+
*
|
|
161
|
+
* Zod validation schemas for ${entity.humanReadable} entities.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
import { z } from 'zod';
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Base Schema
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* ${entity.pascal} entity schema
|
|
172
|
+
*/
|
|
173
|
+
export const ${entity.pascal}Schema = z.object({
|
|
174
|
+
id: z.string().uuid(),
|
|
175
|
+
// TODO: Add ${entity.humanReadable} fields
|
|
176
|
+
createdAt: z.coerce.date(),
|
|
177
|
+
updatedAt: z.coerce.date(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
export type ${entity.pascal} = z.infer<typeof ${entity.pascal}Schema>;
|
|
181
|
+
|
|
182
|
+
// ============================================================================
|
|
183
|
+
// Input Schemas
|
|
184
|
+
// ============================================================================
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create ${entity.pascal} input
|
|
188
|
+
*/
|
|
189
|
+
export const Create${entity.pascal}Input = z.object({
|
|
190
|
+
// TODO: Add required fields for creating a ${entity.humanReadable}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
export type Create${entity.pascal}InputType = z.infer<typeof Create${entity.pascal}Input>;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Update ${entity.pascal} input
|
|
197
|
+
*/
|
|
198
|
+
export const Update${entity.pascal}Input = z.object({
|
|
199
|
+
// TODO: Add fields for updating a ${entity.humanReadable}
|
|
200
|
+
}).partial();
|
|
201
|
+
|
|
202
|
+
export type Update${entity.pascal}InputType = z.infer<typeof Update${entity.pascal}Input>;
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Test Template
|
|
207
|
+
// ============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Generate test file for the namespace procedures
|
|
210
|
+
*/
|
|
211
|
+
export function namespaceTestTemplate(ctx) {
|
|
212
|
+
const { entity, options } = ctx;
|
|
213
|
+
if (options.withExample) {
|
|
214
|
+
return generateTestWithCrud(entity);
|
|
215
|
+
}
|
|
216
|
+
return `/**
|
|
217
|
+
* ${entity.pascal} Procedures - Tests
|
|
218
|
+
*
|
|
219
|
+
* Unit tests for ${entity.humanReadable} API procedures.
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
223
|
+
// import { ${entity.camel}Procedures } from '../${entity.plural}.js';
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Test Setup
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
describe('${entity.pascal} Procedures', () => {
|
|
230
|
+
const mockCtx = {
|
|
231
|
+
db: {
|
|
232
|
+
${entity.camel}: {
|
|
233
|
+
findUnique: vi.fn(),
|
|
234
|
+
findMany: vi.fn(),
|
|
235
|
+
create: vi.fn(),
|
|
236
|
+
update: vi.fn(),
|
|
237
|
+
delete: vi.fn(),
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
beforeEach(() => {
|
|
243
|
+
vi.clearAllMocks();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Add your tests here
|
|
247
|
+
// Example:
|
|
248
|
+
//
|
|
249
|
+
// describe('get${entity.pascal}', () => {
|
|
250
|
+
// it('should return a ${entity.humanReadable} by id', async () => {
|
|
251
|
+
// const mock${entity.pascal} = { id: 'test-uuid' };
|
|
252
|
+
// mockCtx.db.${entity.camel}.findUnique.mockResolvedValue(mock${entity.pascal});
|
|
253
|
+
//
|
|
254
|
+
// // TODO: Implement test
|
|
255
|
+
// });
|
|
256
|
+
// });
|
|
257
|
+
|
|
258
|
+
it('should have mock context ready', () => {
|
|
259
|
+
expect(mockCtx.db.${entity.camel}).toBeDefined();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Generate test file with CRUD tests for --example flag
|
|
266
|
+
*/
|
|
267
|
+
function generateTestWithCrud(entity) {
|
|
268
|
+
return `/**
|
|
269
|
+
* ${entity.pascal} Procedures - Tests
|
|
270
|
+
*
|
|
271
|
+
* Unit tests for ${entity.humanReadable} API procedures.
|
|
272
|
+
*/
|
|
273
|
+
|
|
274
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
275
|
+
// import { ${entity.camel}Procedures } from '../${entity.plural}.js';
|
|
276
|
+
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Test Setup
|
|
279
|
+
// ============================================================================
|
|
280
|
+
|
|
281
|
+
describe('${entity.pascal} Procedures', () => {
|
|
282
|
+
const mockCtx = {
|
|
283
|
+
db: {
|
|
284
|
+
${entity.camel}: {
|
|
285
|
+
findUnique: vi.fn(),
|
|
286
|
+
findMany: vi.fn(),
|
|
287
|
+
count: vi.fn(),
|
|
288
|
+
create: vi.fn(),
|
|
289
|
+
update: vi.fn(),
|
|
290
|
+
delete: vi.fn(),
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
beforeEach(() => {
|
|
296
|
+
vi.clearAllMocks();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ============================================================================
|
|
300
|
+
// Query Tests
|
|
301
|
+
// ============================================================================
|
|
302
|
+
|
|
303
|
+
describe('get${entity.pascal}', () => {
|
|
304
|
+
it('should return a ${entity.humanReadable} by id', async () => {
|
|
305
|
+
const mock${entity.pascal} = {
|
|
306
|
+
id: 'test-uuid',
|
|
307
|
+
createdAt: new Date(),
|
|
308
|
+
updatedAt: new Date(),
|
|
309
|
+
};
|
|
310
|
+
mockCtx.db.${entity.camel}.findUnique.mockResolvedValue(mock${entity.pascal});
|
|
311
|
+
|
|
312
|
+
// TODO: Call procedure and assert
|
|
313
|
+
// const result = await ${entity.camel}Procedures.procedures.get${entity.pascal}.handler({
|
|
314
|
+
// input: { id: 'test-uuid' },
|
|
315
|
+
// ctx: mockCtx,
|
|
316
|
+
// });
|
|
317
|
+
// expect(result).toEqual(mock${entity.pascal});
|
|
318
|
+
expect(mockCtx.db.${entity.camel}.findUnique).toBeDefined();
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should return null for non-existent ${entity.humanReadable}', async () => {
|
|
322
|
+
mockCtx.db.${entity.camel}.findUnique.mockResolvedValue(null);
|
|
323
|
+
|
|
324
|
+
// TODO: Call procedure and assert
|
|
325
|
+
expect(mockCtx.db.${entity.camel}.findUnique).toBeDefined();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('list${entity.pascalPlural}', () => {
|
|
330
|
+
it('should return all ${entity.humanReadablePlural}', async () => {
|
|
331
|
+
const mock${entity.pascalPlural} = [
|
|
332
|
+
{ id: 'uuid-1', createdAt: new Date(), updatedAt: new Date() },
|
|
333
|
+
{ id: 'uuid-2', createdAt: new Date(), updatedAt: new Date() },
|
|
334
|
+
];
|
|
335
|
+
mockCtx.db.${entity.camel}.findMany.mockResolvedValue(mock${entity.pascalPlural});
|
|
336
|
+
|
|
337
|
+
// TODO: Call procedure and assert
|
|
338
|
+
expect(mockCtx.db.${entity.camel}.findMany).toBeDefined();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Mutation Tests
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
describe('create${entity.pascal}', () => {
|
|
347
|
+
it('should create a new ${entity.humanReadable}', async () => {
|
|
348
|
+
const input = { /* TODO: Add fields */ };
|
|
349
|
+
const created = {
|
|
350
|
+
id: 'new-uuid',
|
|
351
|
+
...input,
|
|
352
|
+
createdAt: new Date(),
|
|
353
|
+
updatedAt: new Date(),
|
|
354
|
+
};
|
|
355
|
+
mockCtx.db.${entity.camel}.create.mockResolvedValue(created);
|
|
356
|
+
|
|
357
|
+
// TODO: Call procedure and assert
|
|
358
|
+
expect(mockCtx.db.${entity.camel}.create).toBeDefined();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('update${entity.pascal}', () => {
|
|
363
|
+
it('should update an existing ${entity.humanReadable}', async () => {
|
|
364
|
+
const input = { id: 'test-uuid', /* TODO: Add fields */ };
|
|
365
|
+
const updated = {
|
|
366
|
+
id: 'test-uuid',
|
|
367
|
+
createdAt: new Date(),
|
|
368
|
+
updatedAt: new Date(),
|
|
369
|
+
};
|
|
370
|
+
mockCtx.db.${entity.camel}.update.mockResolvedValue(updated);
|
|
371
|
+
|
|
372
|
+
// TODO: Call procedure and assert
|
|
373
|
+
expect(mockCtx.db.${entity.camel}.update).toBeDefined();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
describe('delete${entity.pascal}', () => {
|
|
378
|
+
it('should delete a ${entity.humanReadable}', async () => {
|
|
379
|
+
mockCtx.db.${entity.camel}.delete.mockResolvedValue(undefined);
|
|
380
|
+
|
|
381
|
+
// TODO: Call procedure and assert
|
|
382
|
+
expect(mockCtx.db.${entity.camel}.delete).toBeDefined();
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
`;
|
|
387
|
+
}
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// Path Helpers
|
|
390
|
+
// ============================================================================
|
|
391
|
+
/**
|
|
392
|
+
* Get the file path for the procedure file
|
|
393
|
+
*/
|
|
394
|
+
export function getNamespaceProcedurePath(entityPlural) {
|
|
395
|
+
return `src/procedures/${entityPlural}.ts`;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Get the file path for the test file
|
|
399
|
+
*/
|
|
400
|
+
export function getNamespaceTestPath(entityPlural) {
|
|
401
|
+
return `src/procedures/__tests__/${entityPlural}.test.ts`;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get the file path for the schema file
|
|
405
|
+
*/
|
|
406
|
+
export function getNamespaceSchemaPath(entityKebab) {
|
|
407
|
+
return `src/schemas/${entityKebab}.ts`;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Generate post-generation instructions
|
|
411
|
+
*/
|
|
412
|
+
export function getNamespaceInstructions(entityPlural, entityPascal, entityKebab) {
|
|
413
|
+
return `
|
|
414
|
+
1. Add the schema export:
|
|
415
|
+
|
|
416
|
+
// src/schemas/index.ts
|
|
417
|
+
export * from './${entityKebab}.js';
|
|
418
|
+
|
|
419
|
+
2. Add the procedure export:
|
|
420
|
+
|
|
421
|
+
// src/procedures/index.ts
|
|
422
|
+
export * from './${entityPlural}.js';
|
|
423
|
+
|
|
424
|
+
3. Register the procedure collection:
|
|
425
|
+
|
|
426
|
+
// src/index.ts (or router.ts)
|
|
427
|
+
import { ${entityPlural.replace(/-/g, '')}Procedures } from './procedures/index.js';
|
|
428
|
+
|
|
429
|
+
const collections = [..., ${entityPlural.replace(/-/g, '')}Procedures];
|
|
430
|
+
|
|
431
|
+
4. Add the Prisma model:
|
|
432
|
+
|
|
433
|
+
// prisma/schema.prisma
|
|
434
|
+
model ${entityPascal} {
|
|
435
|
+
id String @id @default(uuid())
|
|
436
|
+
createdAt DateTime @default(now())
|
|
437
|
+
updatedAt DateTime @updatedAt
|
|
438
|
+
|
|
439
|
+
@@map("${entityPlural.replace(/-/g, '_')}")
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
Then run: pnpm db:push
|
|
443
|
+
`;
|
|
444
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.66",
|
|
4
4
|
"description": "Developer tooling and CLI commands for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"picocolors": "1.1.1",
|
|
41
41
|
"pluralize": "8.0.0",
|
|
42
42
|
"tsx": "4.21.0",
|
|
43
|
-
"@veloxts/
|
|
44
|
-
"@veloxts/
|
|
45
|
-
"@veloxts/
|
|
46
|
-
"@veloxts/
|
|
47
|
-
"@veloxts/
|
|
43
|
+
"@veloxts/router": "0.6.66",
|
|
44
|
+
"@veloxts/orm": "0.6.66",
|
|
45
|
+
"@veloxts/auth": "0.6.66",
|
|
46
|
+
"@veloxts/validation": "0.6.66",
|
|
47
|
+
"@veloxts/core": "0.6.66"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@prisma/client": ">=7.0.0"
|