lsh-framework 1.3.2 → 1.4.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.
@@ -1,15 +1,36 @@
1
1
  /**
2
2
  * Database Persistence Layer for LSH
3
- * Handles data synchronization with Supabase PostgreSQL
3
+ * Handles data synchronization with Supabase PostgreSQL or local storage fallback
4
4
  */
5
- import { supabaseClient } from './supabase-client.js';
5
+ import { supabaseClient, isSupabaseConfigured } from './supabase-client.js';
6
6
  import * as os from 'os';
7
+ import { LocalStorageAdapter } from './local-storage-adapter.js';
7
8
  export class DatabasePersistence {
8
9
  client;
10
+ localStorage;
9
11
  userId;
10
12
  sessionId;
13
+ useLocalStorage;
11
14
  constructor(userId) {
12
- this.client = supabaseClient.getClient();
15
+ this.useLocalStorage = !isSupabaseConfigured();
16
+ if (this.useLocalStorage) {
17
+ console.log('⚠️ Supabase not configured - using local storage fallback');
18
+ this.localStorage = new LocalStorageAdapter(userId);
19
+ this.localStorage.initialize().catch(err => {
20
+ console.error('Failed to initialize local storage:', err);
21
+ });
22
+ }
23
+ else {
24
+ // Supabase is configured, use it exclusively
25
+ try {
26
+ this.client = supabaseClient.getClient();
27
+ }
28
+ catch (error) {
29
+ // If Supabase is configured but fails, throw error instead of falling back
30
+ console.error('⚠️ Supabase is configured but connection failed:', error);
31
+ throw new Error('Supabase connection failed. Check your SUPABASE_URL and SUPABASE_ANON_KEY configuration.');
32
+ }
33
+ }
13
34
  this.userId = userId ? this.generateUserUUID(userId) : undefined;
14
35
  this.sessionId = this.generateSessionId();
15
36
  }
@@ -51,6 +72,9 @@ export class DatabasePersistence {
51
72
  * Save shell history entry
52
73
  */
53
74
  async saveHistoryEntry(entry) {
75
+ if (this.useLocalStorage && this.localStorage) {
76
+ return this.localStorage.saveHistoryEntry(entry);
77
+ }
54
78
  try {
55
79
  const insertData = {
56
80
  ...entry,
@@ -80,6 +104,9 @@ export class DatabasePersistence {
80
104
  * Get shell history entries
81
105
  */
82
106
  async getHistoryEntries(limit = 100, offset = 0) {
107
+ if (this.useLocalStorage && this.localStorage) {
108
+ return this.localStorage.getHistoryEntries(limit, offset);
109
+ }
83
110
  try {
84
111
  let query = this.client
85
112
  .from('shell_history')
@@ -109,6 +136,9 @@ export class DatabasePersistence {
109
136
  * Save shell job
110
137
  */
111
138
  async saveJob(job) {
139
+ if (this.useLocalStorage && this.localStorage) {
140
+ return this.localStorage.saveJob(job);
141
+ }
112
142
  try {
113
143
  const insertData = {
114
144
  ...job,
@@ -138,6 +168,9 @@ export class DatabasePersistence {
138
168
  * Update shell job status
139
169
  */
140
170
  async updateJobStatus(jobId, status, exitCode) {
171
+ if (this.useLocalStorage && this.localStorage) {
172
+ return this.localStorage.updateJobStatus(jobId, status, exitCode);
173
+ }
141
174
  try {
142
175
  const updateData = {
143
176
  status,
@@ -176,6 +209,9 @@ export class DatabasePersistence {
176
209
  * Get active jobs
177
210
  */
178
211
  async getActiveJobs() {
212
+ if (this.useLocalStorage && this.localStorage) {
213
+ return this.localStorage.getActiveJobs();
214
+ }
179
215
  try {
180
216
  let query = this.client
181
217
  .from('shell_jobs')
@@ -205,6 +241,9 @@ export class DatabasePersistence {
205
241
  * Save shell configuration
206
242
  */
207
243
  async saveConfiguration(config) {
244
+ if (this.useLocalStorage && this.localStorage) {
245
+ return this.localStorage.saveConfiguration(config);
246
+ }
208
247
  try {
209
248
  const upsertData = {
210
249
  ...config,
@@ -235,6 +274,9 @@ export class DatabasePersistence {
235
274
  * Get shell configuration
236
275
  */
237
276
  async getConfiguration(key) {
277
+ if (this.useLocalStorage && this.localStorage) {
278
+ return this.localStorage.getConfiguration(key);
279
+ }
238
280
  try {
239
281
  let query = this.client
240
282
  .from('shell_configuration')
@@ -266,6 +308,9 @@ export class DatabasePersistence {
266
308
  * Save shell alias
267
309
  */
268
310
  async saveAlias(alias) {
311
+ if (this.useLocalStorage && this.localStorage) {
312
+ return this.localStorage.saveAlias(alias);
313
+ }
269
314
  try {
270
315
  const upsertData = {
271
316
  ...alias,
@@ -296,6 +341,9 @@ export class DatabasePersistence {
296
341
  * Get shell aliases
297
342
  */
298
343
  async getAliases() {
344
+ if (this.useLocalStorage && this.localStorage) {
345
+ return this.localStorage.getAliases();
346
+ }
299
347
  try {
300
348
  let query = this.client
301
349
  .from('shell_aliases')
@@ -324,6 +372,9 @@ export class DatabasePersistence {
324
372
  * Save shell function
325
373
  */
326
374
  async saveFunction(func) {
375
+ if (this.useLocalStorage && this.localStorage) {
376
+ return this.localStorage.saveFunction(func);
377
+ }
327
378
  try {
328
379
  const upsertData = {
329
380
  ...func,
@@ -354,6 +405,9 @@ export class DatabasePersistence {
354
405
  * Get shell functions
355
406
  */
356
407
  async getFunctions() {
408
+ if (this.useLocalStorage && this.localStorage) {
409
+ return this.localStorage.getFunctions();
410
+ }
357
411
  try {
358
412
  let query = this.client
359
413
  .from('shell_functions')
@@ -382,6 +436,9 @@ export class DatabasePersistence {
382
436
  * Start a new shell session
383
437
  */
384
438
  async startSession(workingDirectory, environmentVariables) {
439
+ if (this.useLocalStorage && this.localStorage) {
440
+ return this.localStorage.startSession(workingDirectory, environmentVariables);
441
+ }
385
442
  try {
386
443
  const insertData = {
387
444
  session_id: this.sessionId,
@@ -415,6 +472,9 @@ export class DatabasePersistence {
415
472
  * End the current shell session
416
473
  */
417
474
  async endSession() {
475
+ if (this.useLocalStorage && this.localStorage) {
476
+ return this.localStorage.endSession();
477
+ }
418
478
  try {
419
479
  let query = this.client
420
480
  .from('shell_sessions')
@@ -447,18 +507,27 @@ export class DatabasePersistence {
447
507
  * Test database connectivity
448
508
  */
449
509
  async testConnection() {
510
+ if (this.useLocalStorage && this.localStorage) {
511
+ return this.localStorage.testConnection();
512
+ }
450
513
  return await supabaseClient.testConnection();
451
514
  }
452
515
  /**
453
516
  * Get session ID
454
517
  */
455
518
  getSessionId() {
519
+ if (this.useLocalStorage && this.localStorage) {
520
+ return this.localStorage.getSessionId();
521
+ }
456
522
  return this.sessionId;
457
523
  }
458
524
  /**
459
525
  * Get latest rows from all database tables
460
526
  */
461
527
  async getLatestRows(limit = 5) {
528
+ if (this.useLocalStorage && this.localStorage) {
529
+ return this.localStorage.getLatestRows(limit);
530
+ }
462
531
  const result = {};
463
532
  try {
464
533
  // Get latest shell history entries
@@ -591,6 +660,9 @@ export class DatabasePersistence {
591
660
  * Get latest rows from a specific table
592
661
  */
593
662
  async getLatestRowsFromTable(tableName, limit = 5) {
663
+ if (this.useLocalStorage && this.localStorage) {
664
+ return this.localStorage.getLatestRowsFromTable(tableName, limit);
665
+ }
594
666
  try {
595
667
  const validTables = [
596
668
  'shell_history',
@@ -203,6 +203,20 @@ export function validateEnvironment(requirements = LSH_ENV_REQUIREMENTS, env = p
203
203
  result.recommendations.push(`${req.name} not set, using default: ${req.defaultValue}`);
204
204
  }
205
205
  }
206
+ // Storage mode detection and informational messaging
207
+ const hasSupabase = !!(env.SUPABASE_URL && env.SUPABASE_ANON_KEY);
208
+ const hasPostgres = !!env.DATABASE_URL;
209
+ if (!hasSupabase && !hasPostgres) {
210
+ result.recommendations.push('No database configured - using local file storage (~/.lsh/data/storage.json)');
211
+ result.recommendations.push('For team collaboration, configure SUPABASE_URL and SUPABASE_ANON_KEY');
212
+ result.recommendations.push('For local PostgreSQL, run: docker-compose up -d');
213
+ }
214
+ else if (hasPostgres && !hasSupabase) {
215
+ result.recommendations.push('Using local PostgreSQL database');
216
+ }
217
+ else if (hasSupabase) {
218
+ result.recommendations.push('Using Supabase cloud database - team sync enabled');
219
+ }
206
220
  // Additional security checks
207
221
  if (isProduction) {
208
222
  if (env.LSH_ALLOW_DANGEROUS_COMMANDS === 'true') {
@@ -216,6 +230,9 @@ export function validateEnvironment(requirements = LSH_ENV_REQUIREMENTS, env = p
216
230
  result.errors.push('LSH_JWT_SECRET must be at least 32 characters in production');
217
231
  result.isValid = false;
218
232
  }
233
+ if (!hasSupabase && !hasPostgres) {
234
+ result.warnings.push('Running in production without a database - local storage is not recommended for production');
235
+ }
219
236
  }
220
237
  return result;
221
238
  }