lsh-framework 3.2.5 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -34
  3. package/dist/commands/ipfs.js +7 -12
  4. package/dist/commands/self.js +22 -16
  5. package/dist/commands/sync.js +49 -38
  6. package/dist/constants/config.js +3 -0
  7. package/dist/lib/floating-point-arithmetic.js +2 -2
  8. package/dist/lib/ipfs-client-manager.js +51 -13
  9. package/dist/lib/ipfs-secrets-storage.js +21 -16
  10. package/dist/lib/ipfs-sync.js +88 -14
  11. package/dist/lib/secrets-manager.js +117 -47
  12. package/dist/lib/sync-key-store.js +87 -0
  13. package/dist/services/secrets/secrets.js +77 -39
  14. package/package.json +16 -16
  15. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  16. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  17. package/dist/daemon/job-registry.js +0 -556
  18. package/dist/daemon/lshd.js +0 -968
  19. package/dist/daemon/saas-api-routes.js +0 -599
  20. package/dist/daemon/saas-api-server.js +0 -231
  21. package/dist/examples/supabase-integration.js +0 -106
  22. package/dist/lib/api-response.js +0 -226
  23. package/dist/lib/base-command-registrar.js +0 -287
  24. package/dist/lib/base-job-manager.js +0 -295
  25. package/dist/lib/cloud-config-manager.js +0 -348
  26. package/dist/lib/cron-job-manager.js +0 -368
  27. package/dist/lib/daemon-client-helper.js +0 -145
  28. package/dist/lib/daemon-client.js +0 -513
  29. package/dist/lib/database-persistence.js +0 -727
  30. package/dist/lib/database-schema.js +0 -259
  31. package/dist/lib/database-types.js +0 -90
  32. package/dist/lib/enhanced-history-system.js +0 -247
  33. package/dist/lib/history-system.js +0 -246
  34. package/dist/lib/job-manager.js +0 -436
  35. package/dist/lib/job-storage-database.js +0 -164
  36. package/dist/lib/job-storage-memory.js +0 -73
  37. package/dist/lib/local-storage-adapter.js +0 -507
  38. package/dist/lib/optimized-job-scheduler.js +0 -356
  39. package/dist/lib/saas-audit.js +0 -215
  40. package/dist/lib/saas-auth.js +0 -465
  41. package/dist/lib/saas-billing.js +0 -503
  42. package/dist/lib/saas-email.js +0 -403
  43. package/dist/lib/saas-encryption.js +0 -221
  44. package/dist/lib/saas-organizations.js +0 -662
  45. package/dist/lib/saas-secrets.js +0 -408
  46. package/dist/lib/saas-types.js +0 -165
  47. package/dist/lib/supabase-client.js +0 -125
  48. package/dist/lib/supabase-utils.js +0 -396
  49. package/dist/services/cron/cron-registrar.js +0 -240
  50. package/dist/services/cron/cron.js +0 -9
  51. package/dist/services/daemon/daemon-registrar.js +0 -585
  52. package/dist/services/daemon/daemon.js +0 -9
  53. package/dist/services/supabase/supabase-registrar.js +0 -375
  54. package/dist/services/supabase/supabase.js +0 -9
@@ -1,396 +0,0 @@
1
- /**
2
- * LSH Supabase Utilities
3
- *
4
- * Provides utility functions for common Supabase database operations.
5
- * Reduces code duplication and ensures consistent error handling across services.
6
- *
7
- * Key Features:
8
- * - Standardized error handling for Supabase queries
9
- * - Type-safe query builders with soft-delete support
10
- * - Consistent pagination and filtering patterns
11
- * - Integration with LSHError for structured error reporting
12
- *
13
- * @example
14
- * ```typescript
15
- * import { executeQuery, executeSingleQuery, buildFilteredQuery } from './supabase-utils.js';
16
- *
17
- * // Single record fetch with error handling
18
- * const org = await executeSingleQuery(
19
- * supabase.from('organizations').select('*').eq('id', id),
20
- * 'Organization',
21
- * { notFoundError: true }
22
- * );
23
- *
24
- * // Multiple records with soft-delete filter
25
- * const members = await executeQuery(
26
- * buildFilteredQuery(supabase.from('members').select('*'), { softDelete: true })
27
- * );
28
- * ```
29
- *
30
- * @module supabase-utils
31
- */
32
- import { LSHError, ErrorCodes, extractErrorMessage } from './lsh-error.js';
33
- // ============================================================================
34
- // QUERY EXECUTION
35
- // ============================================================================
36
- /**
37
- * Execute a Supabase query and handle errors consistently.
38
- *
39
- * @example
40
- * ```typescript
41
- * const members = await executeQuery(
42
- * supabase.from('organization_members').select('*').eq('org_id', orgId),
43
- * { context: 'Organization members' }
44
- * );
45
- * ```
46
- */
47
- export async function executeQuery(query, options = {}) {
48
- const { context = 'Query', throwOnError = true } = options;
49
- try {
50
- const { data, error } = await query;
51
- if (error) {
52
- if (throwOnError) {
53
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, details: error.details, hint: error.hint });
54
- }
55
- return [];
56
- }
57
- return data || [];
58
- }
59
- catch (err) {
60
- if (err instanceof LSHError) {
61
- throw err;
62
- }
63
- if (throwOnError) {
64
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { originalError: extractErrorMessage(err) });
65
- }
66
- return [];
67
- }
68
- }
69
- /**
70
- * Execute a Supabase query expecting a single result.
71
- *
72
- * @example
73
- * ```typescript
74
- * const org = await executeSingleQuery(
75
- * supabase.from('organizations').select('*').eq('id', id).single(),
76
- * { context: 'Organization', notFoundError: true }
77
- * );
78
- * ```
79
- */
80
- export async function executeSingleQuery(query, options = {}) {
81
- const { context = 'Record', throwOnError = true, notFoundError = false } = options;
82
- try {
83
- const { data, error } = await query;
84
- if (error) {
85
- // Handle "no rows" error specifically
86
- if (error.code === 'PGRST116') {
87
- if (notFoundError) {
88
- throw new LSHError(ErrorCodes.RESOURCE_NOT_FOUND, `${context} not found`, { code: error.code });
89
- }
90
- return null;
91
- }
92
- if (throwOnError) {
93
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} query failed: ${error.message}`, { code: error.code, details: error.details, hint: error.hint });
94
- }
95
- return null;
96
- }
97
- return data;
98
- }
99
- catch (err) {
100
- if (err instanceof LSHError) {
101
- throw err;
102
- }
103
- if (throwOnError) {
104
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} query failed: ${extractErrorMessage(err)}`, { originalError: extractErrorMessage(err) });
105
- }
106
- return null;
107
- }
108
- }
109
- /**
110
- * Execute a Supabase insert operation.
111
- *
112
- * @example
113
- * ```typescript
114
- * const newOrg = await executeInsert(
115
- * supabase.from('organizations').insert({ name: 'Acme' }).select().single(),
116
- * { context: 'Organization creation' }
117
- * );
118
- * ```
119
- */
120
- export async function executeInsert(query, options = {}) {
121
- const { context = 'Insert', throwOnError = true } = options;
122
- try {
123
- const { data, error } = await query;
124
- if (error) {
125
- // Handle unique constraint violations
126
- if (error.code === '23505') {
127
- throw new LSHError(ErrorCodes.RESOURCE_ALREADY_EXISTS, `${context} failed: Record already exists`, { code: error.code, details: error.details });
128
- }
129
- if (throwOnError) {
130
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, details: error.details, hint: error.hint });
131
- }
132
- throw new Error(`${context} failed: ${error.message}`);
133
- }
134
- if (!data) {
135
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: No data returned`, { context });
136
- }
137
- return data;
138
- }
139
- catch (err) {
140
- if (err instanceof LSHError) {
141
- throw err;
142
- }
143
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { originalError: extractErrorMessage(err) });
144
- }
145
- }
146
- /**
147
- * Execute a Supabase update operation.
148
- *
149
- * @example
150
- * ```typescript
151
- * const updated = await executeUpdate(
152
- * supabase.from('organizations').update({ name: 'New Name' }).eq('id', id).select().single(),
153
- * { context: 'Organization update' }
154
- * );
155
- * ```
156
- */
157
- export async function executeUpdate(query, options = {}) {
158
- const { context = 'Update', throwOnError = true, notFoundError = false } = options;
159
- try {
160
- const { data, error } = await query;
161
- if (error) {
162
- if (error.code === 'PGRST116' && notFoundError) {
163
- throw new LSHError(ErrorCodes.RESOURCE_NOT_FOUND, `${context} failed: Record not found`, { code: error.code });
164
- }
165
- if (throwOnError) {
166
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, details: error.details, hint: error.hint });
167
- }
168
- return null;
169
- }
170
- return data;
171
- }
172
- catch (err) {
173
- if (err instanceof LSHError) {
174
- throw err;
175
- }
176
- if (throwOnError) {
177
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { originalError: extractErrorMessage(err) });
178
- }
179
- return null;
180
- }
181
- }
182
- /**
183
- * Execute a Supabase delete operation (or soft delete).
184
- */
185
- export async function executeDelete(query, options = {}) {
186
- const { context = 'Delete', throwOnError = true } = options;
187
- try {
188
- const { error } = await query;
189
- if (error) {
190
- if (throwOnError) {
191
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, details: error.details });
192
- }
193
- return false;
194
- }
195
- return true;
196
- }
197
- catch (err) {
198
- if (err instanceof LSHError) {
199
- throw err;
200
- }
201
- if (throwOnError) {
202
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { originalError: extractErrorMessage(err) });
203
- }
204
- return false;
205
- }
206
- }
207
- // ============================================================================
208
- // QUERY BUILDERS
209
- // ============================================================================
210
- /**
211
- * Apply common filters to a Supabase query.
212
- *
213
- * @example
214
- * ```typescript
215
- * const query = applyFilters(
216
- * supabase.from('secrets').select('*'),
217
- * { softDelete: true, teamId: 'team_123', limit: 50 }
218
- * );
219
- * ```
220
- */
221
- export function applyFilters(query, filters) {
222
- let result = query;
223
- // Soft delete filter
224
- if (filters.softDelete !== false) {
225
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
226
- result = result.is('deleted_at', null);
227
- }
228
- // Organization filter
229
- if (filters.organizationId) {
230
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
- result = result.eq('organization_id', filters.organizationId);
232
- }
233
- // Team filter
234
- if (filters.teamId) {
235
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
236
- result = result.eq('team_id', filters.teamId);
237
- }
238
- // User filter
239
- if (filters.userId) {
240
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
241
- result = result.eq('user_id', filters.userId);
242
- }
243
- // Ordering
244
- if (filters.orderBy) {
245
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
246
- result = result.order(filters.orderBy, {
247
- ascending: filters.orderDirection !== 'desc',
248
- });
249
- }
250
- // Pagination
251
- if (filters.limit !== undefined) {
252
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
253
- result = result.limit(filters.limit);
254
- }
255
- if (filters.offset !== undefined) {
256
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
257
- result = result.range(filters.offset, filters.offset + (filters.limit || 50) - 1);
258
- }
259
- return result;
260
- }
261
- // ============================================================================
262
- // EXISTENCE CHECKS
263
- // ============================================================================
264
- /**
265
- * Check if a record exists by ID.
266
- */
267
- export async function recordExists(fromQuery, id, idColumn = 'id') {
268
- try {
269
- const { data } = await fromQuery.select(idColumn).eq(idColumn, id).single();
270
- return data !== null;
271
- }
272
- catch {
273
- return false;
274
- }
275
- }
276
- /**
277
- * Check if a record exists by a unique field.
278
- */
279
- export async function recordExistsByField(fromQuery, field, value) {
280
- try {
281
- const { data } = await fromQuery.select(field).eq(field, value).single();
282
- return data !== null;
283
- }
284
- catch {
285
- return false;
286
- }
287
- }
288
- // ============================================================================
289
- // SOFT DELETE UTILITIES
290
- // ============================================================================
291
- /**
292
- * Perform a soft delete on a record.
293
- */
294
- export async function softDelete(fromQuery, id, options = {}) {
295
- const { context = 'Soft delete', throwOnError = true } = options;
296
- try {
297
- const { error } = await fromQuery
298
- .update({ deleted_at: new Date().toISOString() })
299
- .eq('id', id);
300
- if (error) {
301
- if (throwOnError) {
302
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, id });
303
- }
304
- return false;
305
- }
306
- return true;
307
- }
308
- catch (err) {
309
- if (err instanceof LSHError) {
310
- throw err;
311
- }
312
- if (throwOnError) {
313
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { id, originalError: extractErrorMessage(err) });
314
- }
315
- return false;
316
- }
317
- }
318
- /**
319
- * Restore a soft-deleted record.
320
- */
321
- export async function restoreSoftDeleted(fromQuery, id, options = {}) {
322
- const { context = 'Restore', throwOnError = true } = options;
323
- try {
324
- const { error } = await fromQuery.update({ deleted_at: null }).eq('id', id);
325
- if (error) {
326
- if (throwOnError) {
327
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${error.message}`, { code: error.code, id });
328
- }
329
- return false;
330
- }
331
- return true;
332
- }
333
- catch (err) {
334
- if (err instanceof LSHError) {
335
- throw err;
336
- }
337
- if (throwOnError) {
338
- throw new LSHError(ErrorCodes.DB_QUERY_FAILED, `${context} failed: ${extractErrorMessage(err)}`, { id, originalError: extractErrorMessage(err) });
339
- }
340
- return false;
341
- }
342
- }
343
- // ============================================================================
344
- // ERROR CODE MAPPING
345
- // ============================================================================
346
- /**
347
- * Map Postgres error codes to LSH error codes.
348
- */
349
- export function mapPostgresErrorCode(pgCode) {
350
- const codeMap = {
351
- '23505': 'RESOURCE_ALREADY_EXISTS', // unique_violation
352
- '23503': 'DB_CONSTRAINT_VIOLATION', // foreign_key_violation
353
- '23502': 'VALIDATION_REQUIRED_FIELD', // not_null_violation
354
- '23514': 'VALIDATION_OUT_OF_RANGE', // check_violation
355
- '42P01': 'DB_QUERY_FAILED', // undefined_table
356
- '42703': 'DB_QUERY_FAILED', // undefined_column
357
- 'PGRST116': 'RESOURCE_NOT_FOUND', // no rows found
358
- 'PGRST301': 'DB_QUERY_FAILED', // JWT expired
359
- 'PGRST302': 'AUTH_INVALID_TOKEN', // JWT invalid
360
- '08006': 'DB_CONNECTION_FAILED', // connection failure
361
- '08001': 'DB_CONNECTION_FAILED', // unable to connect
362
- '57014': 'DB_TIMEOUT', // query_canceled (timeout)
363
- };
364
- return codeMap[pgCode] || 'DB_QUERY_FAILED';
365
- }
366
- /**
367
- * Create an LSHError from a Supabase error.
368
- */
369
- export function createDbError(pgError, context) {
370
- const code = pgError.code ? mapPostgresErrorCode(pgError.code) : 'DB_QUERY_FAILED';
371
- const message = context
372
- ? `${context}: ${pgError.message}`
373
- : pgError.message;
374
- return new LSHError(ErrorCodes[code], message, {
375
- pgCode: pgError.code,
376
- details: pgError.details,
377
- hint: pgError.hint,
378
- });
379
- }
380
- // ============================================================================
381
- // EXPORTS
382
- // ============================================================================
383
- export default {
384
- executeQuery,
385
- executeSingleQuery,
386
- executeInsert,
387
- executeUpdate,
388
- executeDelete,
389
- applyFilters,
390
- recordExists,
391
- recordExistsByField,
392
- softDelete,
393
- restoreSoftDeleted,
394
- mapPostgresErrorCode,
395
- createDbError,
396
- };
@@ -1,240 +0,0 @@
1
- /**
2
- * Cron Command Registrar
3
- * Registers all cron-related CLI commands using BaseCommandRegistrar
4
- */
5
- import { BaseCommandRegistrar } from '../../lib/base-command-registrar.js';
6
- export class CronCommandRegistrar extends BaseCommandRegistrar {
7
- constructor() {
8
- super('CronService');
9
- }
10
- async register(program) {
11
- const cronCmd = this.createCommand(program, 'cron', 'Cron job management with database integration');
12
- this.registerTemplateCommands(cronCmd);
13
- this.registerJobManagementCommands(cronCmd);
14
- this.registerReportingCommands(cronCmd);
15
- }
16
- registerTemplateCommands(cronCmd) {
17
- // List templates
18
- this.addSubcommand(cronCmd, {
19
- name: 'templates',
20
- description: 'List available job templates',
21
- action: async () => {
22
- await this.withCronManager(async (manager) => {
23
- const templates = manager.listTemplates();
24
- this.logInfo('Available Job Templates:');
25
- templates.forEach(template => {
26
- this.logInfo(`\n ${template.id}: ${template.name}`);
27
- this.logInfo(` Description: ${template.description}`);
28
- this.logInfo(` Command: ${template.command}`);
29
- this.logInfo(` Schedule: ${template.schedule}`);
30
- this.logInfo(` Category: ${template.category}`);
31
- this.logInfo(` Tags: ${template.tags.join(', ')}`);
32
- });
33
- }, { requireRunning: false });
34
- }
35
- });
36
- // Create job from template
37
- this.addSubcommand(cronCmd, {
38
- name: 'create-from-template',
39
- description: 'Create a job from a template',
40
- arguments: [{ name: 'templateId', required: true }],
41
- options: [
42
- { flags: '-n, --name <name>', description: 'Custom job name' },
43
- { flags: '-c, --command <command>', description: 'Custom command' },
44
- { flags: '-s, --schedule <schedule>', description: 'Custom cron schedule' },
45
- { flags: '-w, --working-dir <dir>', description: 'Working directory' },
46
- { flags: '-e, --env <env>', description: 'Environment variables (JSON)' },
47
- { flags: '-t, --tags <tags>', description: 'Comma-separated tags' },
48
- { flags: '-p, --priority <priority>', description: 'Priority (0-10)', defaultValue: '5' }
49
- ],
50
- action: async (templateId, options) => {
51
- const opts = options;
52
- const result = await this.withCronManager(async (manager) => {
53
- const customizations = {};
54
- if (opts.name)
55
- customizations.name = opts.name;
56
- if (opts.command)
57
- customizations.command = opts.command;
58
- if (opts.schedule)
59
- customizations.schedule = { cron: opts.schedule };
60
- if (opts.workingDir)
61
- customizations.workingDirectory = opts.workingDir;
62
- if (opts.env)
63
- customizations.environment = this.parseJSON(opts.env, 'environment variables');
64
- if (opts.tags)
65
- customizations.tags = this.parseTags(opts.tags);
66
- if (opts.priority)
67
- customizations.priority = parseInt(opts.priority);
68
- return await manager.createJobFromTemplate(templateId, customizations);
69
- });
70
- this.logSuccess('Job created from template:');
71
- this.logInfo(` Template: ${templateId}`);
72
- this.logInfo(` Job ID: ${result.id}`);
73
- this.logInfo(` Name: ${result.name}`);
74
- this.logInfo(` Command: ${result.command}`);
75
- this.logInfo(` Schedule: ${result.schedule?.cron || 'N/A'}`);
76
- }
77
- });
78
- }
79
- registerJobManagementCommands(cronCmd) {
80
- // List jobs
81
- this.addSubcommand(cronCmd, {
82
- name: 'list',
83
- description: 'List all cron jobs',
84
- options: [
85
- { flags: '-f, --filter <filter>', description: 'Filter by status' }
86
- ],
87
- action: async (options) => {
88
- const opts = options;
89
- const jobs = await this.withCronManager(async (manager) => {
90
- return await manager.listJobs(opts.filter ? { status: opts.filter } : undefined);
91
- });
92
- this.logInfo(`Cron Jobs (${jobs.length} total):`);
93
- jobs.forEach(job => {
94
- const schedule = job.schedule?.cron || `${job.schedule?.interval}ms interval`;
95
- this.logInfo(`\n ${job.id}: ${job.name}`);
96
- this.logInfo(` Command: ${job.command}`);
97
- this.logInfo(` Schedule: ${schedule}`);
98
- this.logInfo(` Status: ${job.status}`);
99
- this.logInfo(` Priority: ${job.priority}`);
100
- this.logInfo(` Tags: ${job.tags?.join(', ') || 'None'}`);
101
- });
102
- }
103
- });
104
- // Get job info
105
- this.addSubcommand(cronCmd, {
106
- name: 'info',
107
- description: 'Get job information',
108
- arguments: [{ name: 'jobId', required: true }],
109
- action: async (jobId) => {
110
- const job = await this.withCronManager(async (manager) => {
111
- return await manager.getJob(jobId);
112
- });
113
- if (!job) {
114
- throw new Error(`Job ${jobId} not found`);
115
- }
116
- this.logInfo(`Job Information: ${jobId}`);
117
- this.logInfo(` Name: ${job.name}`);
118
- this.logInfo(` Command: ${job.command}`);
119
- this.logInfo(` Status: ${job.status}`);
120
- this.logInfo(` Priority: ${job.priority}`);
121
- this.logInfo(` Working Directory: ${job.cwd}`);
122
- this.logInfo(` User: ${job.user}`);
123
- this.logInfo(` Tags: ${job.tags?.join(', ') || 'None'}`);
124
- if (job.schedule) {
125
- const schedule = job.schedule.cron || `${job.schedule.interval}ms interval`;
126
- this.logInfo(` Schedule: ${schedule}`);
127
- }
128
- }
129
- });
130
- // Start job
131
- this.addSubcommand(cronCmd, {
132
- name: 'start',
133
- description: 'Start a job',
134
- arguments: [{ name: 'jobId', required: true }],
135
- action: async (jobId) => {
136
- await this.withCronManager(async (manager) => {
137
- await manager.startJob(jobId);
138
- });
139
- this.logSuccess(`Job ${jobId} started`);
140
- }
141
- });
142
- // Stop job
143
- this.addSubcommand(cronCmd, {
144
- name: 'stop',
145
- description: 'Stop a job',
146
- arguments: [{ name: 'jobId', required: true }],
147
- options: [
148
- { flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
149
- ],
150
- action: async (jobId, options) => {
151
- const opts = options;
152
- await this.withCronManager(async (manager) => {
153
- await manager.stopJob(jobId, opts.signal);
154
- });
155
- this.logSuccess(`Job ${jobId} stopped with signal ${opts.signal}`);
156
- }
157
- });
158
- // Remove job
159
- this.addSubcommand(cronCmd, {
160
- name: 'remove',
161
- description: 'Remove a job',
162
- arguments: [{ name: 'jobId', required: true }],
163
- options: [
164
- { flags: '-f, --force', description: 'Force removal', defaultValue: false }
165
- ],
166
- action: async (jobId, options) => {
167
- const opts = options;
168
- await this.withCronManager(async (manager) => {
169
- await manager.removeJob(jobId, opts.force);
170
- });
171
- this.logSuccess(`Job ${jobId} removed`);
172
- }
173
- });
174
- }
175
- registerReportingCommands(cronCmd) {
176
- // Get job report
177
- this.addSubcommand(cronCmd, {
178
- name: 'report',
179
- description: 'Get detailed job execution report',
180
- arguments: [{ name: 'jobId', required: true }],
181
- action: async (jobId) => {
182
- const report = await this.withCronManager(async (manager) => {
183
- return await manager.getJobReport(jobId);
184
- });
185
- this.displayJobReport(report);
186
- }
187
- });
188
- // Get all job reports
189
- this.addSubcommand(cronCmd, {
190
- name: 'reports',
191
- description: 'Get reports for all jobs',
192
- action: async () => {
193
- const reports = await this.withCronManager(async (manager) => {
194
- return await manager.getAllJobReports();
195
- });
196
- this.logInfo('All Job Reports:');
197
- reports.forEach(report => {
198
- this.logInfo(`\n ${report.jobId}:`);
199
- this.logInfo(` Executions: ${report.executions}`);
200
- this.logInfo(` Success Rate: ${report.successRate.toFixed(1)}%`);
201
- this.logInfo(` Last Execution: ${report.lastExecution?.toISOString() || 'Never'}`);
202
- });
203
- }
204
- });
205
- // Generate comprehensive report
206
- this.addSubcommand(cronCmd, {
207
- name: 'comprehensive-report',
208
- description: 'Generate comprehensive job report',
209
- action: async () => {
210
- const report = await this.withCronManager(async (manager) => {
211
- return await manager.generateComprehensiveReport();
212
- });
213
- this.logInfo(report);
214
- }
215
- });
216
- // Export job data
217
- this.addSubcommand(cronCmd, {
218
- name: 'export',
219
- description: 'Export job data',
220
- options: [
221
- { flags: '-f, --format <format>', description: 'Export format (json or csv)', defaultValue: 'json' },
222
- { flags: '-o, --output <file>', description: 'Output file path' }
223
- ],
224
- action: async (options) => {
225
- const opts = options;
226
- const data = await this.withCronManager(async (manager) => {
227
- return await manager.exportJobData(opts.format);
228
- });
229
- if (opts.output) {
230
- const fs = await import('fs');
231
- fs.writeFileSync(opts.output, data);
232
- this.logSuccess(`Data exported to ${opts.output}`);
233
- }
234
- else {
235
- this.logInfo(data);
236
- }
237
- }
238
- });
239
- }
240
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Cron Service - CLI command registration
3
- * Uses CronCommandRegistrar for clean, maintainable command setup
4
- */
5
- import { CronCommandRegistrar } from './cron-registrar.js';
6
- export async function init_cron(program) {
7
- const registrar = new CronCommandRegistrar();
8
- await registrar.register(program);
9
- }