lsh-framework 3.2.5 → 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.
Files changed (53) 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/sync.js +49 -38
  5. package/dist/constants/config.js +3 -0
  6. package/dist/lib/floating-point-arithmetic.js +2 -2
  7. package/dist/lib/ipfs-client-manager.js +51 -13
  8. package/dist/lib/ipfs-secrets-storage.js +21 -16
  9. package/dist/lib/ipfs-sync.js +88 -14
  10. package/dist/lib/secrets-manager.js +117 -47
  11. package/dist/lib/sync-key-store.js +87 -0
  12. package/dist/services/secrets/secrets.js +77 -39
  13. package/package.json +16 -16
  14. package/dist/__tests__/fixtures/job-fixtures.js +0 -204
  15. package/dist/__tests__/fixtures/supabase-mocks.js +0 -252
  16. package/dist/daemon/job-registry.js +0 -556
  17. package/dist/daemon/lshd.js +0 -968
  18. package/dist/daemon/saas-api-routes.js +0 -599
  19. package/dist/daemon/saas-api-server.js +0 -231
  20. package/dist/examples/supabase-integration.js +0 -106
  21. package/dist/lib/api-response.js +0 -226
  22. package/dist/lib/base-command-registrar.js +0 -287
  23. package/dist/lib/base-job-manager.js +0 -295
  24. package/dist/lib/cloud-config-manager.js +0 -348
  25. package/dist/lib/cron-job-manager.js +0 -368
  26. package/dist/lib/daemon-client-helper.js +0 -145
  27. package/dist/lib/daemon-client.js +0 -513
  28. package/dist/lib/database-persistence.js +0 -727
  29. package/dist/lib/database-schema.js +0 -259
  30. package/dist/lib/database-types.js +0 -90
  31. package/dist/lib/enhanced-history-system.js +0 -247
  32. package/dist/lib/history-system.js +0 -246
  33. package/dist/lib/job-manager.js +0 -436
  34. package/dist/lib/job-storage-database.js +0 -164
  35. package/dist/lib/job-storage-memory.js +0 -73
  36. package/dist/lib/local-storage-adapter.js +0 -507
  37. package/dist/lib/optimized-job-scheduler.js +0 -356
  38. package/dist/lib/saas-audit.js +0 -215
  39. package/dist/lib/saas-auth.js +0 -465
  40. package/dist/lib/saas-billing.js +0 -503
  41. package/dist/lib/saas-email.js +0 -403
  42. package/dist/lib/saas-encryption.js +0 -221
  43. package/dist/lib/saas-organizations.js +0 -662
  44. package/dist/lib/saas-secrets.js +0 -408
  45. package/dist/lib/saas-types.js +0 -165
  46. package/dist/lib/supabase-client.js +0 -125
  47. package/dist/lib/supabase-utils.js +0 -396
  48. package/dist/services/cron/cron-registrar.js +0 -240
  49. package/dist/services/cron/cron.js +0 -9
  50. package/dist/services/daemon/daemon-registrar.js +0 -585
  51. package/dist/services/daemon/daemon.js +0 -9
  52. package/dist/services/supabase/supabase-registrar.js +0 -375
  53. package/dist/services/supabase/supabase.js +0 -9
@@ -1,287 +0,0 @@
1
- /**
2
- * Base Command Registrar
3
- *
4
- * Abstract base class for command registration to eliminate duplication in:
5
- * - Command setup patterns
6
- * - Error handling
7
- * - Daemon client management
8
- * - Output formatting
9
- *
10
- * Usage:
11
- * ```typescript
12
- * class MyCommandRegistrar extends BaseCommandRegistrar {
13
- * constructor() {
14
- * super('MyService');
15
- * }
16
- *
17
- * async register(program: Command): Promise<void> {
18
- * const cmd = this.createCommand(program, 'mycommand', 'My command description');
19
- *
20
- * this.addSubcommand(cmd, {
21
- * name: 'list',
22
- * description: 'List items',
23
- * action: async () => {
24
- * await this.withDaemonAction(async (client) => {
25
- * const items = await client.listItems();
26
- * this.logSuccess('Items:', items);
27
- * });
28
- * }
29
- * });
30
- * }
31
- * }
32
- * ```
33
- */
34
- import CronJobManager from './cron-job-manager.js';
35
- import { createLogger } from './logger.js';
36
- import { withDaemonClient, withDaemonClientForUser, isDaemonRunning } from './daemon-client-helper.js';
37
- /**
38
- * Base class for command registrars
39
- */
40
- export class BaseCommandRegistrar {
41
- logger;
42
- serviceName;
43
- constructor(serviceName) {
44
- this.serviceName = serviceName;
45
- this.logger = createLogger(serviceName);
46
- }
47
- /**
48
- * Create a top-level command
49
- */
50
- createCommand(program, name, description) {
51
- return program
52
- .command(name)
53
- .description(description);
54
- }
55
- /**
56
- * Add a subcommand with automatic error handling
57
- */
58
- addSubcommand(parent, config) {
59
- let cmd = parent.command(config.name).description(config.description);
60
- // Add arguments
61
- if (config.arguments) {
62
- config.arguments.forEach(arg => {
63
- const argStr = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
64
- if (arg.description) {
65
- cmd = cmd.argument(argStr, arg.description);
66
- }
67
- else {
68
- cmd = cmd.argument(argStr);
69
- }
70
- });
71
- }
72
- // Add options
73
- if (config.options) {
74
- config.options.forEach(opt => {
75
- if (opt.defaultValue !== undefined) {
76
- cmd = cmd.option(opt.flags, opt.description, opt.defaultValue);
77
- }
78
- else {
79
- cmd = cmd.option(opt.flags, opt.description);
80
- }
81
- });
82
- }
83
- // Wrap action with error handling
84
- cmd.action(async (...args) => {
85
- try {
86
- await config.action(...args);
87
- }
88
- catch (error) {
89
- const err = error;
90
- this.logError('Command failed', err);
91
- process.exit(1);
92
- }
93
- });
94
- return cmd;
95
- }
96
- /**
97
- * Execute an action with daemon client
98
- */
99
- async withDaemonAction(action, config = {}) {
100
- const { requireRunning = true, exitOnError = true, forUser = false } = config;
101
- const helper = forUser ? withDaemonClientForUser : withDaemonClient;
102
- return await helper(action, { requireRunning, exitOnError });
103
- }
104
- /**
105
- * Execute an action with CronJobManager
106
- */
107
- async withCronManager(action, config = {}) {
108
- const { requireRunning = true } = config;
109
- const manager = new CronJobManager();
110
- if (requireRunning && !manager.isDaemonRunning()) {
111
- this.logError('Daemon is not running. Start it with: lsh daemon start');
112
- process.exit(1);
113
- }
114
- try {
115
- await manager.connect();
116
- const result = await action(manager);
117
- manager.disconnect();
118
- return result;
119
- }
120
- catch (error) {
121
- manager.disconnect();
122
- throw error;
123
- }
124
- }
125
- /**
126
- * Check if daemon is running
127
- */
128
- isDaemonRunning() {
129
- return isDaemonRunning();
130
- }
131
- /**
132
- * Log success message
133
- */
134
- logSuccess(message, data) {
135
- this.logger.info(message);
136
- if (data !== undefined) {
137
- if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
138
- Object.entries(data).forEach(([key, value]) => {
139
- this.logger.info(` ${key}: ${value}`);
140
- });
141
- }
142
- else if (Array.isArray(data)) {
143
- data.forEach(item => {
144
- if (typeof item === 'object' && item !== null) {
145
- this.logger.info(` ${JSON.stringify(item, null, 2)}`);
146
- }
147
- else {
148
- this.logger.info(` ${item}`);
149
- }
150
- });
151
- }
152
- else {
153
- this.logger.info(` ${data}`);
154
- }
155
- }
156
- }
157
- /**
158
- * Log error message
159
- */
160
- logError(message, error) {
161
- if (error instanceof Error) {
162
- this.logger.error(message, error);
163
- }
164
- else if (error) {
165
- this.logger.error(`${message}: ${error}`);
166
- }
167
- else {
168
- this.logger.error(message);
169
- }
170
- }
171
- /**
172
- * Log info message
173
- */
174
- logInfo(message) {
175
- this.logger.info(message);
176
- }
177
- /**
178
- * Log warning message
179
- */
180
- logWarning(message) {
181
- this.logger.warn(message);
182
- }
183
- /**
184
- * Parse JSON from string with error handling
185
- */
186
- parseJSON(jsonString, context = 'JSON') {
187
- try {
188
- return JSON.parse(jsonString);
189
- }
190
- catch (error) {
191
- throw new Error(`Invalid ${context}: ${error instanceof Error ? error.message : String(error)}`);
192
- }
193
- }
194
- /**
195
- * Parse comma-separated tags
196
- */
197
- parseTags(tagsString) {
198
- return tagsString.split(',').map(t => t.trim()).filter(t => t.length > 0);
199
- }
200
- /**
201
- * Format job schedule for display
202
- */
203
- formatSchedule(schedule) {
204
- if (schedule?.cron) {
205
- return schedule.cron;
206
- }
207
- if (schedule?.interval) {
208
- return `${schedule.interval}ms interval`;
209
- }
210
- return 'No schedule';
211
- }
212
- /**
213
- * Validate required options
214
- */
215
- validateRequired(options, required, commandName = 'command') {
216
- const missing = required.filter(key => !options[key]);
217
- if (missing.length > 0) {
218
- throw new Error(`Missing required options for ${commandName}: ${missing.map(k => `--${k}`).join(', ')}`);
219
- }
220
- }
221
- /**
222
- * Create a standardized job specification from options
223
- */
224
- createJobSpec(options) {
225
- return {
226
- id: options.id || `job_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
227
- name: options.name,
228
- description: options.description,
229
- command: options.command,
230
- schedule: {
231
- cron: options.schedule,
232
- interval: options.interval ? parseInt(options.interval) : undefined,
233
- },
234
- cwd: options.workingDir,
235
- env: options.env ? this.parseJSON(options.env, 'environment variables') : {},
236
- tags: options.tags ? this.parseTags(options.tags) : [],
237
- priority: options.priority ? parseInt(options.priority) : 5,
238
- maxRetries: options.maxRetries ? parseInt(options.maxRetries) : 3,
239
- timeout: options.timeout ? parseInt(options.timeout) : 0,
240
- databaseSync: options.databaseSync !== false,
241
- };
242
- }
243
- /**
244
- * Display job information
245
- */
246
- displayJob(job) {
247
- this.logInfo(` ${job.id}: ${job.name}`);
248
- this.logInfo(` Command: ${job.command}`);
249
- this.logInfo(` Schedule: ${this.formatSchedule(job.schedule)}`);
250
- this.logInfo(` Status: ${job.status}`);
251
- this.logInfo(` Priority: ${job.priority}`);
252
- if (job.tags && job.tags.length > 0) {
253
- this.logInfo(` Tags: ${job.tags.join(', ')}`);
254
- }
255
- }
256
- /**
257
- * Display multiple jobs
258
- */
259
- displayJobs(jobs) {
260
- this.logInfo(`Jobs (${jobs.length} total):`);
261
- jobs.forEach(job => {
262
- this.displayJob(job);
263
- this.logInfo('');
264
- });
265
- }
266
- /**
267
- * Display job report
268
- */
269
- displayJobReport(report) {
270
- this.logInfo(`Job Report: ${report.jobId || 'N/A'}`);
271
- this.logInfo(` Executions: ${report.executions}`);
272
- this.logInfo(` Successes: ${report.successes}`);
273
- this.logInfo(` Failures: ${report.failures}`);
274
- this.logInfo(` Success Rate: ${report.successRate.toFixed(1)}%`);
275
- this.logInfo(` Average Duration: ${Math.round(report.averageDuration)}ms`);
276
- this.logInfo(` Last Execution: ${report.lastExecution?.toISOString() || 'Never'}`);
277
- this.logInfo(` Last Success: ${report.lastSuccess?.toISOString() || 'Never'}`);
278
- this.logInfo(` Last Failure: ${report.lastFailure?.toISOString() || 'Never'}`);
279
- if (report.commonErrors && report.commonErrors.length > 0) {
280
- this.logInfo('\n Common Errors:');
281
- report.commonErrors.forEach((error) => {
282
- this.logInfo(` - ${error.error} (${error.count} times)`);
283
- });
284
- }
285
- }
286
- }
287
- export default BaseCommandRegistrar;
@@ -1,295 +0,0 @@
1
- /**
2
- * Base Job Manager
3
- * Abstract base class for all job management systems to eliminate duplication in:
4
- * - Job lifecycle management (create, start, stop, pause, resume, remove)
5
- * - Job status tracking and updates
6
- * - Event emission and handling
7
- * - Statistics and reporting
8
- * - Storage abstraction
9
- *
10
- * Subclasses implement storage-specific operations (memory, database, filesystem)
11
- */
12
- import { EventEmitter } from 'events';
13
- import { createLogger } from './logger.js';
14
- import { ENV_VARS } from '../constants/index.js';
15
- /**
16
- * Abstract base class for job managers
17
- */
18
- export class BaseJobManager extends EventEmitter {
19
- logger;
20
- storage;
21
- jobs = new Map();
22
- constructor(storage, loggerName = 'JobManager') {
23
- super();
24
- this.storage = storage;
25
- this.logger = createLogger(loggerName);
26
- }
27
- /**
28
- * Generate unique job ID
29
- */
30
- generateJobId(prefix = 'job') {
31
- return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
32
- }
33
- /**
34
- * Validate job specification
35
- */
36
- validateJobSpec(spec) {
37
- if (!spec.name) {
38
- throw new Error('Job name is required');
39
- }
40
- if (!spec.command) {
41
- throw new Error('Job command is required');
42
- }
43
- }
44
- /**
45
- * Create a new job
46
- */
47
- async createJob(spec) {
48
- this.validateJobSpec(spec);
49
- const job = {
50
- id: spec.id || this.generateJobId(),
51
- name: spec.name,
52
- command: spec.command,
53
- args: spec.args,
54
- status: 'created',
55
- createdAt: new Date(),
56
- env: spec.env,
57
- cwd: spec.cwd || process.cwd(),
58
- user: spec.user || process.env[ENV_VARS.USER],
59
- schedule: spec.schedule,
60
- tags: spec.tags || [],
61
- description: spec.description,
62
- priority: spec.priority ?? 5,
63
- timeout: spec.timeout,
64
- maxRetries: spec.maxRetries ?? 3,
65
- retryCount: 0,
66
- databaseSync: spec.databaseSync !== false,
67
- };
68
- await this.storage.save(job);
69
- this.jobs.set(job.id, job);
70
- this.emit('job:created', job);
71
- this.logger.info(`Job created: ${job.id} (${job.name})`);
72
- return job;
73
- }
74
- /**
75
- * Get job by ID
76
- */
77
- async getJob(jobId) {
78
- // Check memory cache first
79
- const job = this.jobs.get(jobId);
80
- if (job) {
81
- return job;
82
- }
83
- // Check storage
84
- const storedJob = await this.storage.get(jobId);
85
- if (storedJob) {
86
- this.jobs.set(jobId, storedJob);
87
- return storedJob;
88
- }
89
- return null;
90
- }
91
- /**
92
- * List jobs with optional filtering
93
- */
94
- async listJobs(filter) {
95
- let jobs = await this.storage.list(filter);
96
- // Apply additional filters
97
- if (filter) {
98
- jobs = this.applyFilters(jobs, filter);
99
- }
100
- return jobs;
101
- }
102
- /**
103
- * Apply filters to job list
104
- */
105
- applyFilters(jobs, filter) {
106
- return jobs.filter(job => {
107
- // Status filter
108
- if (filter.status) {
109
- const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
110
- if (!statuses.includes(job.status)) {
111
- return false;
112
- }
113
- }
114
- // Tags filter
115
- if (filter.tags && filter.tags.length > 0) {
116
- const hasTag = filter.tags.some(tag => job.tags?.includes(tag));
117
- if (!hasTag) {
118
- return false;
119
- }
120
- }
121
- // User filter
122
- if (filter.user && job.user !== filter.user) {
123
- return false;
124
- }
125
- // Name pattern filter
126
- if (filter.namePattern) {
127
- const pattern = typeof filter.namePattern === 'string'
128
- ? new RegExp(filter.namePattern)
129
- : filter.namePattern;
130
- if (!pattern.test(job.name)) {
131
- return false;
132
- }
133
- }
134
- // Date filters
135
- if (filter.createdAfter && job.createdAt < filter.createdAfter) {
136
- return false;
137
- }
138
- if (filter.createdBefore && job.createdAt > filter.createdBefore) {
139
- return false;
140
- }
141
- return true;
142
- });
143
- }
144
- /**
145
- * Update job
146
- */
147
- async updateJob(jobId, updates) {
148
- const job = await this.getJob(jobId);
149
- if (!job) {
150
- throw new Error(`Job ${jobId} not found`);
151
- }
152
- // Apply updates
153
- if (updates.name)
154
- job.name = updates.name;
155
- if (updates.description)
156
- job.description = updates.description;
157
- if (updates.priority !== undefined)
158
- job.priority = updates.priority;
159
- if (updates.tags)
160
- job.tags = updates.tags;
161
- if (updates.schedule)
162
- job.schedule = updates.schedule;
163
- if (updates.env)
164
- job.env = { ...job.env, ...updates.env };
165
- if (updates.timeout !== undefined)
166
- job.timeout = updates.timeout;
167
- if (updates.maxRetries !== undefined)
168
- job.maxRetries = updates.maxRetries;
169
- await this.storage.update(jobId, job);
170
- this.jobs.set(jobId, job);
171
- this.emit('job:updated', job);
172
- this.logger.info(`Job updated: ${jobId}`);
173
- return job;
174
- }
175
- /**
176
- * Update job status
177
- */
178
- async updateJobStatus(jobId, status, additionalUpdates) {
179
- const job = await this.getJob(jobId);
180
- if (!job) {
181
- throw new Error(`Job ${jobId} not found`);
182
- }
183
- job.status = status;
184
- // Apply additional updates
185
- if (additionalUpdates) {
186
- Object.assign(job, additionalUpdates);
187
- }
188
- // Update timestamps
189
- if (status === 'running' && !job.startedAt) {
190
- job.startedAt = new Date();
191
- }
192
- if (status === 'completed' || status === 'failed' || status === 'killed') {
193
- job.completedAt = new Date();
194
- }
195
- await this.storage.update(jobId, job);
196
- this.jobs.set(jobId, job);
197
- this.emit(`job:${status}`, job);
198
- this.logger.info(`Job ${status}: ${jobId}`);
199
- return job;
200
- }
201
- /**
202
- * Remove job
203
- */
204
- async removeJob(jobId, force = false) {
205
- const job = await this.getJob(jobId);
206
- if (!job) {
207
- // Return false instead of throwing when job doesn't exist
208
- return false;
209
- }
210
- // Check if job is running
211
- if (job.status === 'running' && !force) {
212
- throw new Error(`Job ${jobId} is running. Use force=true to remove.`);
213
- }
214
- // Stop job if running
215
- if (job.status === 'running') {
216
- await this.stopJob(jobId);
217
- }
218
- await this.storage.delete(jobId);
219
- this.jobs.delete(jobId);
220
- this.emit('job:removed', job);
221
- this.logger.info(`Job removed: ${jobId}`);
222
- return true;
223
- }
224
- /**
225
- * Get job execution history
226
- */
227
- async getJobHistory(jobId, limit = 50) {
228
- return await this.storage.getExecutions(jobId, limit);
229
- }
230
- /**
231
- * Calculate job statistics
232
- */
233
- async getJobStatistics(jobId) {
234
- const executions = await this.getJobHistory(jobId);
235
- const job = await this.getJob(jobId);
236
- if (!job) {
237
- throw new Error(`Job ${jobId} not found`);
238
- }
239
- const totalExecutions = executions.length;
240
- const successfulExecutions = executions.filter(e => e.status === 'completed').length;
241
- const failedExecutions = executions.filter(e => e.status === 'failed').length;
242
- const completedExecutions = executions.filter(e => e.duration);
243
- const averageDuration = completedExecutions.length > 0
244
- ? completedExecutions.reduce((sum, e) => sum + (e.duration || 0), 0) / completedExecutions.length
245
- : 0;
246
- const lastExecution = executions[0]?.startTime;
247
- const lastSuccess = executions.find(e => e.status === 'completed')?.startTime;
248
- const lastFailure = executions.find(e => e.status === 'failed')?.startTime;
249
- return {
250
- jobId: job.id,
251
- jobName: job.name,
252
- totalExecutions,
253
- successfulExecutions,
254
- failedExecutions,
255
- successRate: totalExecutions > 0 ? (successfulExecutions / totalExecutions) * 100 : 0,
256
- averageDuration,
257
- lastExecution,
258
- lastSuccess,
259
- lastFailure,
260
- };
261
- }
262
- /**
263
- * Record job execution
264
- */
265
- async recordExecution(job, status, details = {}) {
266
- const execution = {
267
- executionId: this.generateJobId('exec'),
268
- jobId: job.id,
269
- jobName: job.name,
270
- command: job.command,
271
- startTime: details.startTime || new Date(),
272
- endTime: details.endTime,
273
- duration: details.duration,
274
- status,
275
- exitCode: details.exitCode,
276
- stdout: details.stdout,
277
- stderr: details.stderr,
278
- errorMessage: details.errorMessage,
279
- };
280
- await this.storage.saveExecution(execution);
281
- this.emit('job:execution', execution);
282
- return execution;
283
- }
284
- /**
285
- * Cleanup - optional override
286
- */
287
- async cleanup() {
288
- if (this.storage.cleanup) {
289
- await this.storage.cleanup();
290
- }
291
- this.jobs.clear();
292
- this.removeAllListeners();
293
- }
294
- }
295
- export default BaseJobManager;