@veloxts/cli 0.6.31 → 0.6.52

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,99 @@
1
+ /**
2
+ * Task Generator
3
+ *
4
+ * Scaffolds scheduled task files for VeloxTS applications.
5
+ *
6
+ * Usage:
7
+ * velox make task <name> [options]
8
+ *
9
+ * Examples:
10
+ * velox make task cleanup-tokens # Simple daily task
11
+ * velox make task send-digest --callbacks # Task with success/failure callbacks
12
+ * velox make task backup-db --no-overlap # Task that prevents overlapping runs
13
+ * velox make task report --constraints # Task with time/day constraints
14
+ */
15
+ import { BaseGenerator } from '../base.js';
16
+ import { getTaskInstructions, getTaskPath, taskTemplate, } from '../templates/task.js';
17
+ // ============================================================================
18
+ // Generator Implementation
19
+ // ============================================================================
20
+ /**
21
+ * Task generator - creates scheduled task files
22
+ */
23
+ export class TaskGenerator extends BaseGenerator {
24
+ metadata = {
25
+ name: 'task',
26
+ description: 'Generate scheduled task definitions',
27
+ longDescription: `
28
+ Scaffold scheduled task definitions for VeloxTS applications.
29
+
30
+ Tasks are background operations that run on a cron-like schedule using the
31
+ @veloxts/scheduler package. They support fluent scheduling, constraints,
32
+ overlap prevention, and success/failure callbacks.
33
+
34
+ Examples:
35
+ velox make task cleanup-tokens # Simple daily task
36
+ velox make task send-digest --callbacks # Task with success/failure callbacks
37
+ velox make task backup-db --no-overlap # Task that prevents overlapping runs
38
+ velox make task report --constraints # Task with time/day constraints
39
+ `,
40
+ aliases: ['scheduled-task', 'cron'],
41
+ category: 'infrastructure',
42
+ };
43
+ options = [
44
+ {
45
+ name: 'callbacks',
46
+ short: 'c',
47
+ description: 'Generate task with onSuccess/onFailure callbacks',
48
+ type: 'boolean',
49
+ default: false,
50
+ },
51
+ {
52
+ name: 'constraints',
53
+ short: 't',
54
+ description: 'Generate task with time/day constraints',
55
+ type: 'boolean',
56
+ default: false,
57
+ },
58
+ {
59
+ name: 'no-overlap',
60
+ short: 'n',
61
+ description: 'Generate task with overlap prevention',
62
+ type: 'boolean',
63
+ default: false,
64
+ },
65
+ ];
66
+ /**
67
+ * Validate and transform raw options
68
+ */
69
+ validateOptions(raw) {
70
+ return {
71
+ callbacks: Boolean(raw.callbacks ?? false),
72
+ constraints: Boolean(raw.constraints ?? false),
73
+ noOverlap: Boolean(raw['no-overlap'] ?? raw.noOverlap ?? false),
74
+ };
75
+ }
76
+ /**
77
+ * Generate task files
78
+ */
79
+ async generate(config) {
80
+ const context = this.createContext(config);
81
+ const files = [];
82
+ // Generate task file
83
+ const taskContent = taskTemplate(context);
84
+ files.push({
85
+ path: getTaskPath(config.entityName, config.project),
86
+ content: taskContent,
87
+ });
88
+ return {
89
+ files,
90
+ postInstructions: getTaskInstructions(config.entityName, config.options),
91
+ };
92
+ }
93
+ }
94
+ /**
95
+ * Factory function for creating a TaskGenerator instance
96
+ */
97
+ export function createTaskGenerator() {
98
+ return new TaskGenerator();
99
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Event Template
3
+ *
4
+ * Generates event-related files for VeloxTS applications.
5
+ */
6
+ import type { ProjectContext, TemplateFunction } from '../types.js';
7
+ export interface EventOptions {
8
+ /** Generate event listener/handler */
9
+ listener: boolean;
10
+ /** Generate channel configuration with authorization */
11
+ channel: boolean;
12
+ }
13
+ /**
14
+ * Get the path for an event file based on variant
15
+ */
16
+ export declare function getEventPath(entityName: string, _project: ProjectContext, options: EventOptions): string;
17
+ /**
18
+ * Event template function
19
+ */
20
+ export declare const eventTemplate: TemplateFunction<EventOptions>;
21
+ export declare function getEventInstructions(entityName: string, options: EventOptions): string;
@@ -0,0 +1,410 @@
1
+ /**
2
+ * Event Template
3
+ *
4
+ * Generates event-related files for VeloxTS applications.
5
+ */
6
+ // ============================================================================
7
+ // Path Helpers
8
+ // ============================================================================
9
+ /**
10
+ * Get the path for an event file based on variant
11
+ */
12
+ export function getEventPath(entityName, _project, options) {
13
+ if (options.listener) {
14
+ return `src/events/listeners/${entityName.toLowerCase()}.ts`;
15
+ }
16
+ if (options.channel) {
17
+ return `src/events/channels/${entityName.toLowerCase()}.ts`;
18
+ }
19
+ // Default: broadcastable event
20
+ return `src/events/${entityName.toLowerCase()}.ts`;
21
+ }
22
+ // ============================================================================
23
+ // Templates
24
+ // ============================================================================
25
+ /**
26
+ * Generate broadcastable event definition
27
+ */
28
+ function generateBroadcastEvent(ctx) {
29
+ const { entity } = ctx;
30
+ return `/**
31
+ * ${entity.pascal} Event
32
+ *
33
+ * Broadcastable event for ${entity.humanReadable} notifications.
34
+ */
35
+
36
+ import { defineEvent } from '@veloxts/events';
37
+ import { z } from 'zod';
38
+
39
+ // ============================================================================
40
+ // Schema
41
+ // ============================================================================
42
+
43
+ const ${entity.pascal}EventSchema = z.object({
44
+ id: z.string().uuid(),
45
+ timestamp: z.number(),
46
+ // TODO: Add your event payload fields
47
+ });
48
+
49
+ export type ${entity.pascal}EventData = z.infer<typeof ${entity.pascal}EventSchema>;
50
+
51
+ // ============================================================================
52
+ // Event Definition
53
+ // ============================================================================
54
+
55
+ /**
56
+ * ${entity.pascal} event
57
+ *
58
+ * Fired when ${entity.humanReadable} occurs. Can be broadcast to connected clients
59
+ * via WebSocket channels.
60
+ *
61
+ * @example
62
+ * \`\`\`typescript
63
+ * import { ${entity.camel}Event } from '@/events/${entity.kebab}';
64
+ * import { events } from '@/events';
65
+ *
66
+ * // Dispatch event locally
67
+ * await events.dispatch(${entity.camel}Event, {
68
+ * id: '...',
69
+ * timestamp: Date.now(),
70
+ * });
71
+ *
72
+ * // Broadcast to specific channel
73
+ * await events.broadcast('user.123', ${entity.camel}Event, {
74
+ * id: '...',
75
+ * timestamp: Date.now(),
76
+ * });
77
+ * \`\`\`
78
+ */
79
+ export const ${entity.camel}Event = defineEvent({
80
+ name: '${entity.kebab}',
81
+ schema: ${entity.pascal}EventSchema,
82
+
83
+ /**
84
+ * Determine which channels should receive this event broadcast.
85
+ * Return an array of channel names to broadcast to.
86
+ */
87
+ broadcastOn: ({ id }) => [
88
+ \`${entity.kebab}.\${id}\`, // Specific ${entity.humanReadable}
89
+ '${entity.kebab}', // All ${entity.plural}
90
+ ],
91
+
92
+ /**
93
+ * Optional: Transform data before broadcasting to clients.
94
+ * Useful for removing sensitive fields or adding computed properties.
95
+ */
96
+ // broadcastWith: (data) => ({
97
+ // id: data.id,
98
+ // timestamp: data.timestamp,
99
+ // }),
100
+
101
+ /**
102
+ * Optional: Determine if event should be broadcast.
103
+ * Return false to prevent broadcasting (local event only).
104
+ */
105
+ // shouldBroadcast: ({ id }) => {
106
+ // // Only broadcast for specific conditions
107
+ // return true;
108
+ // },
109
+ });
110
+ `;
111
+ }
112
+ /**
113
+ * Generate event listener/handler
114
+ */
115
+ function generateEventListener(ctx) {
116
+ const { entity } = ctx;
117
+ return `/**
118
+ * ${entity.pascal} Listener
119
+ *
120
+ * Event listener for handling ${entity.humanReadable} events.
121
+ */
122
+
123
+ import { defineListener } from '@veloxts/events';
124
+ import { z } from 'zod';
125
+
126
+ // ============================================================================
127
+ // Event Schema
128
+ // ============================================================================
129
+
130
+ const ${entity.pascal}EventSchema = z.object({
131
+ id: z.string().uuid(),
132
+ // TODO: Add event payload fields matching your event definition
133
+ });
134
+
135
+ export type ${entity.pascal}EventData = z.infer<typeof ${entity.pascal}EventSchema>;
136
+
137
+ // ============================================================================
138
+ // Listener Definition
139
+ // ============================================================================
140
+
141
+ /**
142
+ * ${entity.pascal} event listener
143
+ *
144
+ * Handles ${entity.humanReadable} events when they are dispatched.
145
+ *
146
+ * @example
147
+ * \`\`\`typescript
148
+ * import { ${entity.camel}Listener } from '@/events/listeners/${entity.kebab}';
149
+ * import { events } from '@/events';
150
+ *
151
+ * // Register listener
152
+ * events.listen('${entity.kebab}', ${entity.camel}Listener);
153
+ *
154
+ * // Or in your event service configuration:
155
+ * const eventService = createEventService({
156
+ * listeners: {
157
+ * '${entity.kebab}': [${entity.camel}Listener],
158
+ * },
159
+ * });
160
+ * \`\`\`
161
+ */
162
+ export const ${entity.camel}Listener = defineListener({
163
+ name: '${entity.kebab}-handler',
164
+ schema: ${entity.pascal}EventSchema,
165
+
166
+ /**
167
+ * Handle the event
168
+ */
169
+ handler: async ({ data, ctx }) => {
170
+ // TODO: Implement your event handling logic
171
+ // Access database via ctx.db (if ORM plugin registered)
172
+ // Access user context via ctx.user (if auth plugin registered)
173
+
174
+ console.log(\`Handling ${entity.humanReadable} event:\`, data.id);
175
+
176
+ // Example: Update database
177
+ // await ctx.db.${entity.camel}.update({
178
+ // where: { id: data.id },
179
+ // data: { processedAt: new Date() },
180
+ // });
181
+
182
+ // Example: Send notification
183
+ // await sendNotification(data.id);
184
+
185
+ // Example: Dispatch another event
186
+ // await ctx.events.dispatch(anotherEvent, { ... });
187
+ },
188
+
189
+ /**
190
+ * Optional: Determine if this listener should handle the event.
191
+ * Return false to skip handling.
192
+ */
193
+ // shouldHandle: ({ data }) => {
194
+ // // Only handle specific conditions
195
+ // return true;
196
+ // },
197
+
198
+ /**
199
+ * Optional: Queue configuration for async processing.
200
+ * If provided, listener will be queued instead of executed immediately.
201
+ */
202
+ // queue: {
203
+ // name: '${entity.kebab}-events',
204
+ // attempts: 3,
205
+ // backoff: {
206
+ // type: 'exponential',
207
+ // delay: 1000,
208
+ // },
209
+ // },
210
+ });
211
+ `;
212
+ }
213
+ /**
214
+ * Generate channel configuration with authorization
215
+ */
216
+ function generateChannel(ctx) {
217
+ const { entity } = ctx;
218
+ return `/**
219
+ * ${entity.pascal} Channel
220
+ *
221
+ * Channel configuration and authorization for ${entity.humanReadable} events.
222
+ */
223
+
224
+ import { defineChannel } from '@veloxts/events';
225
+ import { z } from 'zod';
226
+
227
+ // ============================================================================
228
+ // Channel Parameters Schema
229
+ // ============================================================================
230
+
231
+ const ${entity.pascal}ChannelParamsSchema = z.object({
232
+ ${entity.camel}Id: z.string().uuid(),
233
+ });
234
+
235
+ export type ${entity.pascal}ChannelParams = z.infer<typeof ${entity.pascal}ChannelParamsSchema>;
236
+
237
+ // ============================================================================
238
+ // Channel Definition
239
+ // ============================================================================
240
+
241
+ /**
242
+ * ${entity.pascal} channel configuration
243
+ *
244
+ * Defines authorization rules and configuration for subscribing to
245
+ * ${entity.humanReadable} events via WebSocket.
246
+ *
247
+ * Channel name pattern: \`${entity.kebab}.{${entity.camel}Id}\`
248
+ *
249
+ * @example
250
+ * \`\`\`typescript
251
+ * // Frontend client subscribing to channel
252
+ * import { socket } from '@/lib/socket';
253
+ *
254
+ * socket.subscribe('${entity.kebab}.123', (event) => {
255
+ * console.log('Received event:', event);
256
+ * });
257
+ * \`\`\`
258
+ */
259
+ export const ${entity.camel}Channel = defineChannel({
260
+ /**
261
+ * Channel name pattern (use {param} for dynamic segments)
262
+ */
263
+ pattern: '${entity.kebab}.{${entity.camel}Id}',
264
+
265
+ /**
266
+ * Channel parameters schema for validation
267
+ */
268
+ schema: ${entity.pascal}ChannelParamsSchema,
269
+
270
+ /**
271
+ * Authorization logic - determine if user can subscribe to this channel
272
+ *
273
+ * @returns true to allow, false to deny, or throw error for specific messages
274
+ */
275
+ authorize: async ({ params, ctx }) => {
276
+ // TODO: Implement authorization logic
277
+
278
+ // Example: Public channel (anyone can subscribe)
279
+ // return true;
280
+
281
+ // Example: Authenticated users only
282
+ if (!ctx.user) {
283
+ return false;
284
+ }
285
+
286
+ // Example: Check ownership or permissions
287
+ const ${entity.camel} = await ctx.db.${entity.camel}.findUnique({
288
+ where: { id: params.${entity.camel}Id },
289
+ });
290
+
291
+ if (!${entity.camel}) {
292
+ return false;
293
+ }
294
+
295
+ // Check if user owns the ${entity.humanReadable} or has permission
296
+ return ${entity.camel}.userId === ctx.user.id;
297
+
298
+ // Example: Role-based access
299
+ // return ctx.user.role === 'admin' || ${entity.camel}.userId === ctx.user.id;
300
+ },
301
+
302
+ /**
303
+ * Optional: Transform channel params before matching
304
+ * Useful for normalizing IDs or adding computed values
305
+ */
306
+ // transform: (params) => ({
307
+ // ...params,
308
+ // ${entity.camel}Id: params.${entity.camel}Id.toLowerCase(),
309
+ // }),
310
+
311
+ /**
312
+ * Optional: Rate limiting configuration
313
+ * Prevents abuse by limiting subscription attempts
314
+ */
315
+ // rateLimit: {
316
+ // maxAttempts: 10,
317
+ // windowMs: 60000, // 1 minute
318
+ // },
319
+ });
320
+
321
+ // ============================================================================
322
+ // Public Channel Variant
323
+ // ============================================================================
324
+
325
+ /**
326
+ * Public ${entity.pascal} channel (no authentication required)
327
+ *
328
+ * Use this for public ${entity.humanReadable} updates that don't require authorization.
329
+ *
330
+ * Channel name: \`public.${entity.kebab}\`
331
+ */
332
+ export const public${entity.pascal}Channel = defineChannel({
333
+ pattern: 'public.${entity.kebab}',
334
+ schema: z.object({}),
335
+
336
+ /**
337
+ * Public channel - anyone can subscribe
338
+ */
339
+ authorize: async () => true,
340
+ });
341
+ `;
342
+ }
343
+ // ============================================================================
344
+ // Main Template
345
+ // ============================================================================
346
+ /**
347
+ * Event template function
348
+ */
349
+ export const eventTemplate = (ctx) => {
350
+ if (ctx.options.listener) {
351
+ return generateEventListener(ctx);
352
+ }
353
+ if (ctx.options.channel) {
354
+ return generateChannel(ctx);
355
+ }
356
+ // Default: broadcastable event
357
+ return generateBroadcastEvent(ctx);
358
+ };
359
+ // ============================================================================
360
+ // Post-generation Instructions
361
+ // ============================================================================
362
+ export function getEventInstructions(entityName, options) {
363
+ const lines = [];
364
+ if (options.listener) {
365
+ lines.push(`Your ${entityName} listener has been created.`, '', 'Next steps:');
366
+ lines.push(' 1. Update the Zod schema to match your event payload');
367
+ lines.push(' 2. Implement the handler logic');
368
+ lines.push(' 3. Register the listener in your event service:');
369
+ lines.push('');
370
+ lines.push(" import { events } from '@/events';");
371
+ lines.push(` import { ${entityName}Listener } from '@/events/listeners/${entityName.toLowerCase()}';`);
372
+ lines.push('');
373
+ lines.push(` events.listen('${entityName.toLowerCase()}', ${entityName}Listener);`);
374
+ lines.push('');
375
+ lines.push(' 4. Optional: Configure queue settings for async processing');
376
+ }
377
+ else if (options.channel) {
378
+ lines.push(`Your ${entityName} channel has been created.`, '', 'Next steps:');
379
+ lines.push(' 1. Update the channel pattern and schema for your use case');
380
+ lines.push(' 2. Implement authorization logic');
381
+ lines.push(' 3. Register the channel in your WebSocket server:');
382
+ lines.push('');
383
+ lines.push(" import { channels } from '@/events';");
384
+ lines.push(` import { ${entityName}Channel } from '@/events/channels/${entityName.toLowerCase()}';`);
385
+ lines.push('');
386
+ lines.push(` channels.register(${entityName}Channel);`);
387
+ lines.push('');
388
+ lines.push(' 4. Subscribe from your frontend:');
389
+ lines.push('');
390
+ lines.push(" socket.subscribe('pattern', (event) => { ... });");
391
+ }
392
+ else {
393
+ lines.push(`Your ${entityName} event has been created.`, '', 'Next steps:');
394
+ lines.push(' 1. Update the Zod schema with your event payload fields');
395
+ lines.push(' 2. Customize the broadcast channels in broadcastOn()');
396
+ lines.push(' 3. Dispatch the event from your procedures:');
397
+ lines.push('');
398
+ lines.push(" import { events } from '@/events';");
399
+ lines.push(` import { ${entityName}Event } from '@/events/${entityName.toLowerCase()}';`);
400
+ lines.push('');
401
+ lines.push(` await events.dispatch(${entityName}Event, {`);
402
+ lines.push(" id: '...',");
403
+ lines.push(' timestamp: Date.now(),');
404
+ lines.push(' });');
405
+ lines.push('');
406
+ lines.push(' 4. Optional: Add event listeners to handle the event');
407
+ lines.push(` velox make event ${entityName} --listener`);
408
+ }
409
+ return lines.join('\n');
410
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Job Template
3
+ *
4
+ * Generates background job files for VeloxTS applications.
5
+ */
6
+ import type { ProjectContext, TemplateFunction } from '../types.js';
7
+ export interface JobOptions {
8
+ /** Custom queue assignment */
9
+ queue: boolean;
10
+ /** Retry and backoff configuration */
11
+ retry: boolean;
12
+ /** Progress tracking */
13
+ progress: boolean;
14
+ }
15
+ /**
16
+ * Get the path for a job file
17
+ */
18
+ export declare function getJobPath(entityName: string, _project: ProjectContext): string;
19
+ /**
20
+ * Job template function
21
+ */
22
+ export declare const jobTemplate: TemplateFunction<JobOptions>;
23
+ export declare function getJobInstructions(entityName: string, options: JobOptions): string;