lsh-framework 3.2.4 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -34
- package/dist/commands/ipfs.js +7 -12
- package/dist/commands/sync.js +51 -39
- package/dist/constants/config.js +3 -0
- package/dist/lib/floating-point-arithmetic.js +2 -2
- package/dist/lib/ipfs-client-manager.js +51 -13
- package/dist/lib/ipfs-secrets-storage.js +21 -16
- package/dist/lib/ipfs-sync.js +88 -14
- package/dist/lib/secrets-manager.js +117 -47
- package/dist/lib/sync-key-store.js +87 -0
- package/dist/services/secrets/secrets.js +77 -39
- package/package.json +16 -16
- package/dist/__tests__/fixtures/job-fixtures.js +0 -204
- package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
- package/dist/daemon/job-registry.js +0 -556
- package/dist/daemon/lshd.js +0 -968
- package/dist/daemon/saas-api-routes.js +0 -599
- package/dist/daemon/saas-api-server.js +0 -231
- package/dist/examples/supabase-integration.js +0 -106
- package/dist/lib/api-response.js +0 -226
- package/dist/lib/base-command-registrar.js +0 -287
- package/dist/lib/base-job-manager.js +0 -295
- package/dist/lib/cloud-config-manager.js +0 -348
- package/dist/lib/cron-job-manager.js +0 -368
- package/dist/lib/daemon-client-helper.js +0 -145
- package/dist/lib/daemon-client.js +0 -513
- package/dist/lib/database-persistence.js +0 -727
- package/dist/lib/database-schema.js +0 -259
- package/dist/lib/database-types.js +0 -90
- package/dist/lib/enhanced-history-system.js +0 -247
- package/dist/lib/history-system.js +0 -246
- package/dist/lib/job-manager.js +0 -436
- package/dist/lib/job-storage-database.js +0 -164
- package/dist/lib/job-storage-memory.js +0 -73
- package/dist/lib/local-storage-adapter.js +0 -507
- package/dist/lib/optimized-job-scheduler.js +0 -356
- package/dist/lib/saas-audit.js +0 -215
- package/dist/lib/saas-auth.js +0 -465
- package/dist/lib/saas-billing.js +0 -503
- package/dist/lib/saas-email.js +0 -403
- package/dist/lib/saas-encryption.js +0 -221
- package/dist/lib/saas-organizations.js +0 -662
- package/dist/lib/saas-secrets.js +0 -408
- package/dist/lib/saas-types.js +0 -165
- package/dist/lib/supabase-client.js +0 -125
- package/dist/lib/supabase-utils.js +0 -396
- package/dist/services/cron/cron-registrar.js +0 -240
- package/dist/services/cron/cron.js +0 -9
- package/dist/services/daemon/daemon-registrar.js +0 -585
- package/dist/services/daemon/daemon.js +0 -9
- package/dist/services/supabase/supabase-registrar.js +0 -375
- 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
|
-
}
|