@mark1russell7/client-sqlite 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,748 @@
1
- # Temporary Holding Version
1
+ # @mark1russell7/client-sqlite
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/@mark1russell7/client-sqlite?color=2346c4&label=npm)](https://www.npmjs.com/package/@mark1russell7/client-sqlite)
4
+
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
7
+ [![Node.js](https://img.shields.io/badge/Node.js-20+-green.svg)](https://nodejs.org/)
8
+
9
+ SQLite database operations via client procedures. Execute queries and store structured logs via `client.call()`.
10
+
11
+ ## Overview
12
+
13
+ `client-sqlite` provides SQLite database access through the client procedure ecosystem, with:
14
+ - **Database procedures**: Execute SQL queries and mutations
15
+ - **Logging procedures**: Store and query structured application logs
16
+ - **Migration support**: Schema versioning and migration utilities
17
+ - **Default log database**: Pre-configured logging at `~/logs/cli/cli.db`
18
+
19
+ The package auto-registers 4 procedures:
20
+ - **Database**: `db.query`, `db.execute`
21
+ - **Logs**: `logs.store`, `logs.query`
22
+
23
+ ## Architecture
24
+
25
+ ```mermaid
26
+ graph TB
27
+ subgraph "Client Ecosystem"
28
+ Client["@mark1russell7/client"]
29
+ Registry["Procedure Registry"]
30
+ end
31
+
32
+ subgraph "client-sqlite Package"
33
+ Register["register.ts<br/>(Auto-registers procedures)"]
34
+
35
+ subgraph "Procedure Categories"
36
+ DB["Database Procedures<br/>db.query, db.execute"]
37
+ Logs["Log Procedures<br/>logs.store, logs.query"]
38
+ end
39
+
40
+ Migrations["Log Table Migrations<br/>(Auto-created on first use)"]
41
+ Types["types.ts<br/>(Type Definitions)"]
42
+ end
43
+
44
+ subgraph "docker-sqlite Layer"
45
+ DockerSqlite["@mark1russell7/docker-sqlite"]
46
+ Utilities["withConnection<br/>query, execute<br/>runMigrations"]
47
+ end
48
+
49
+ subgraph "SQLite Engine"
50
+ SqlJs["sql.js<br/>(SQLite WASM)"]
51
+ end
52
+
53
+ subgraph "Storage"
54
+ LogDB["~/logs/cli/cli.db<br/>(Default log database)"]
55
+ CustomDB["Custom databases<br/>(via dbPath parameter)"]
56
+ end
57
+
58
+ Register --> Client
59
+ Register --> Registry
60
+
61
+ DB --> DockerSqlite
62
+ Logs --> DockerSqlite
63
+ Logs --> Migrations
64
+
65
+ DockerSqlite --> Utilities
66
+ Utilities --> SqlJs
67
+
68
+ SqlJs --> LogDB
69
+ SqlJs --> CustomDB
70
+
71
+ Types -.-> DB
72
+ Types -.-> Logs
73
+
74
+ style Register fill:#e1f5ff
75
+ style Migrations fill:#fff4e1
76
+ style Types fill:#f0f0f0
77
+ ```
78
+
79
+ ### Package Dependency Flow
80
+
81
+ ```mermaid
82
+ graph LR
83
+ App["Your Application"]
84
+ ClientSqlite["@mark1russell7/client-sqlite"]
85
+ Client["@mark1russell7/client"]
86
+ DockerSqlite["@mark1russell7/docker-sqlite"]
87
+ SqlJs["sql.js"]
88
+
89
+ App --> ClientSqlite
90
+ ClientSqlite --> Client
91
+ ClientSqlite --> DockerSqlite
92
+ DockerSqlite --> SqlJs
93
+
94
+ App -.client.call.-> Client
95
+ Client -.executes.-> ClientSqlite
96
+ ClientSqlite -.queries.-> DockerSqlite
97
+ DockerSqlite -.uses.-> SqlJs
98
+ ```
99
+
100
+ ### Log Storage Flow
101
+
102
+ ```mermaid
103
+ sequenceDiagram
104
+ participant App
105
+ participant LogsStore
106
+ participant DockerSqlite
107
+ participant Migration
108
+ participant SQLite
109
+
110
+ App->>LogsStore: logs.store({ level, message, ... })
111
+ LogsStore->>Migration: ensureLogsTable()
112
+ Migration->>DockerSqlite: withConnection()
113
+ DockerSqlite->>SQLite: Open ~/logs/cli/cli.db
114
+ Migration->>DockerSqlite: runMigrations()
115
+ DockerSqlite->>SQLite: CREATE TABLE IF NOT EXISTS logs
116
+ LogsStore->>DockerSqlite: execute(INSERT INTO logs ...)
117
+ DockerSqlite->>SQLite: INSERT
118
+ SQLite-->>DockerSqlite: lastInsertRowId
119
+ DockerSqlite-->>LogsStore: { id }
120
+ LogsStore-->>App: { id: 123 }
121
+ ```
122
+
123
+ ## Installation
124
+
125
+ ```bash
126
+ npm install @mark1russell7/client-sqlite
127
+ ```
128
+
129
+ ### Peer Dependencies
130
+
131
+ ```json
132
+ {
133
+ "@mark1russell7/client": "github:mark1russell7/client#main",
134
+ "@mark1russell7/docker-sqlite": "github:mark1russell7/docker-sqlite#main"
135
+ }
136
+ ```
137
+
138
+ ## Configuration
139
+
140
+ ### Default Database Path
141
+
142
+ The package uses a default database path for CLI logs:
143
+
144
+ ```typescript
145
+ // Default: ~/logs/cli/cli.db
146
+ const DEFAULT_DB_PATH = join(homedir(), "logs", "cli", "cli.db");
147
+ ```
148
+
149
+ You can override this path in each procedure call:
150
+
151
+ ```typescript
152
+ await client.exec(
153
+ ["db", "query"],
154
+ {
155
+ sql: "SELECT * FROM logs",
156
+ dbPath: "./custom/path/database.db"
157
+ }
158
+ );
159
+ ```
160
+
161
+ ## API Reference
162
+
163
+ ### Database Procedures
164
+
165
+ #### `db.query`
166
+
167
+ Execute a SQL SELECT query and return rows.
168
+
169
+ ```typescript
170
+ const result = await client.exec(
171
+ ["db", "query"],
172
+ {
173
+ sql: "SELECT * FROM logs WHERE level = ? ORDER BY timestamp DESC",
174
+ params: ["error"]
175
+ }
176
+ );
177
+
178
+ console.log(result.rows);
179
+ console.log(result.columns);
180
+ ```
181
+
182
+ **Input:**
183
+ ```typescript
184
+ interface DbQueryInput {
185
+ sql: string; // SQL SELECT query
186
+ params?: unknown[]; // Query parameters (uses ? placeholders)
187
+ dbPath?: string; // Database path (default: ~/logs/cli/cli.db)
188
+ }
189
+ ```
190
+
191
+ **Output:**
192
+ ```typescript
193
+ interface DbQueryOutput<T = Record<string, unknown>> {
194
+ columns: string[]; // Column names
195
+ rows: T[]; // Query results
196
+ }
197
+ ```
198
+
199
+ **Example:**
200
+ ```typescript
201
+ const result = await client.exec(
202
+ ["db", "query"],
203
+ {
204
+ sql: `
205
+ SELECT level, COUNT(*) as count
206
+ FROM logs
207
+ WHERE session_id = ?
208
+ GROUP BY level
209
+ `,
210
+ params: ["session-123"]
211
+ }
212
+ );
213
+
214
+ // result.rows = [{ level: "info", count: 45 }, { level: "error", count: 2 }]
215
+ ```
216
+
217
+ #### `db.execute`
218
+
219
+ Execute a SQL statement (INSERT, UPDATE, DELETE) and return the number of affected rows.
220
+
221
+ ```typescript
222
+ const result = await client.exec(
223
+ ["db", "execute"],
224
+ {
225
+ sql: "DELETE FROM logs WHERE timestamp < ?",
226
+ params: ["2024-01-01"]
227
+ }
228
+ );
229
+
230
+ console.log(`Deleted ${result.changes} rows`);
231
+ ```
232
+
233
+ **Input:**
234
+ ```typescript
235
+ interface DbExecuteInput {
236
+ sql: string; // SQL statement (INSERT, UPDATE, DELETE, etc.)
237
+ params?: unknown[]; // Statement parameters
238
+ dbPath?: string; // Database path (default: ~/logs/cli/cli.db)
239
+ }
240
+ ```
241
+
242
+ **Output:**
243
+ ```typescript
244
+ interface DbExecuteOutput {
245
+ changes: number; // Number of rows affected
246
+ }
247
+ ```
248
+
249
+ **Examples:**
250
+ ```typescript
251
+ // INSERT
252
+ await client.exec(
253
+ ["db", "execute"],
254
+ {
255
+ sql: "INSERT INTO users (name, email) VALUES (?, ?)",
256
+ params: ["Alice", "alice@example.com"]
257
+ }
258
+ );
259
+
260
+ // UPDATE
261
+ await client.exec(
262
+ ["db", "execute"],
263
+ {
264
+ sql: "UPDATE users SET active = ? WHERE id = ?",
265
+ params: [true, 123]
266
+ }
267
+ );
268
+
269
+ // DELETE
270
+ await client.exec(
271
+ ["db", "execute"],
272
+ {
273
+ sql: "DELETE FROM users WHERE active = ?",
274
+ params: [false]
275
+ }
276
+ );
277
+ ```
278
+
279
+ ### Log Procedures
280
+
281
+ #### `logs.store`
282
+
283
+ Store a structured log entry in the database. Automatically creates the logs table on first use.
284
+
285
+ ```typescript
286
+ await client.exec(
287
+ ["logs", "store"],
288
+ {
289
+ level: "error",
290
+ message: "Failed to connect to database",
291
+ sessionId: "session-123",
292
+ command: "db.connect",
293
+ context: "user-service",
294
+ data: {
295
+ host: "localhost",
296
+ port: 5432
297
+ },
298
+ errorStack: new Error().stack
299
+ }
300
+ );
301
+ ```
302
+
303
+ **Input:**
304
+ ```typescript
305
+ interface LogsStoreInput {
306
+ level: LogLevel; // Log level
307
+ message: string; // Log message
308
+ sessionId?: string; // Session ID for grouping logs
309
+ command?: string; // Command that generated this log
310
+ context?: string; // Additional context (e.g., package name)
311
+ data?: Record<string, unknown>; // Structured data (stored as JSON)
312
+ errorStack?: string; // Error stack trace
313
+ dbPath?: string; // Database path (default: ~/logs/cli/cli.db)
314
+ }
315
+
316
+ type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
317
+ ```
318
+
319
+ **Output:**
320
+ ```typescript
321
+ interface LogsStoreOutput {
322
+ id: number; // ID of the inserted log row
323
+ }
324
+ ```
325
+
326
+ **Log Table Schema:**
327
+ ```sql
328
+ CREATE TABLE logs (
329
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
330
+ timestamp TEXT NOT NULL DEFAULT (datetime('now')),
331
+ level TEXT NOT NULL CHECK (level IN ('trace', 'debug', 'info', 'warn', 'error')),
332
+ message TEXT NOT NULL,
333
+ data TEXT, -- JSON string
334
+ session_id TEXT,
335
+ command TEXT,
336
+ context TEXT,
337
+ error_stack TEXT,
338
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
339
+ );
340
+
341
+ -- Indexes for fast queries
342
+ CREATE INDEX idx_logs_session ON logs(session_id);
343
+ CREATE INDEX idx_logs_level ON logs(level);
344
+ CREATE INDEX idx_logs_timestamp ON logs(timestamp);
345
+ CREATE INDEX idx_logs_command ON logs(command);
346
+ ```
347
+
348
+ #### `logs.query`
349
+
350
+ Query log entries with filtering, pagination, and sorting.
351
+
352
+ ```typescript
353
+ const result = await client.exec(
354
+ ["logs", "query"],
355
+ {
356
+ sessionId: "session-123",
357
+ level: ["error", "warn"],
358
+ limit: 100,
359
+ orderBy: "desc"
360
+ }
361
+ );
362
+
363
+ console.log(result.logs);
364
+ ```
365
+
366
+ **Input:**
367
+ ```typescript
368
+ interface LogsQueryInput {
369
+ sessionId?: string; // Filter by session ID
370
+ command?: string; // Filter by command
371
+ level?: LogLevel | LogLevel[]; // Filter by log level(s)
372
+ limit?: number; // Maximum number of logs to return
373
+ offset?: number; // Offset for pagination
374
+ orderBy?: "asc" | "desc"; // Order by timestamp (default: desc)
375
+ dbPath?: string; // Database path (default: ~/logs/cli/cli.db)
376
+ }
377
+ ```
378
+
379
+ **Output:**
380
+ ```typescript
381
+ interface LogsQueryOutput {
382
+ logs: LogEntry[];
383
+ total?: number; // Total count (if limit was applied)
384
+ }
385
+
386
+ interface LogEntry {
387
+ id: number;
388
+ timestamp: string; // ISO timestamp
389
+ level: LogLevel;
390
+ message: string;
391
+ data: Record<string, unknown> | null;
392
+ sessionId: string | null;
393
+ command: string | null;
394
+ context: string | null;
395
+ errorStack: string | null;
396
+ }
397
+ ```
398
+
399
+ **Examples:**
400
+ ```typescript
401
+ // Get all errors from a session
402
+ const { logs } = await client.exec(
403
+ ["logs", "query"],
404
+ {
405
+ sessionId: "session-123",
406
+ level: "error"
407
+ }
408
+ );
409
+
410
+ // Get recent warnings and errors
411
+ const { logs } = await client.exec(
412
+ ["logs", "query"],
413
+ {
414
+ level: ["warn", "error"],
415
+ limit: 50,
416
+ orderBy: "desc"
417
+ }
418
+ );
419
+
420
+ // Paginated query
421
+ const { logs } = await client.exec(
422
+ ["logs", "query"],
423
+ {
424
+ command: "lib.build",
425
+ limit: 20,
426
+ offset: 40 // Page 3 (0-indexed)
427
+ }
428
+ );
429
+ ```
430
+
431
+ ## Usage Examples
432
+
433
+ ### Basic Database Operations
434
+
435
+ ```typescript
436
+ import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@mark1russell7/client";
437
+ import "@mark1russell7/client-sqlite"; // registers its procedures
438
+
439
+ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
440
+
441
+ // Create a table
442
+ await client.exec(
443
+ ["db", "execute"],
444
+ {
445
+ sql: `
446
+ CREATE TABLE IF NOT EXISTS users (
447
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
448
+ name TEXT NOT NULL,
449
+ email TEXT UNIQUE NOT NULL,
450
+ active BOOLEAN DEFAULT 1,
451
+ created_at TEXT DEFAULT (datetime('now'))
452
+ )
453
+ `,
454
+ dbPath: "./app.db"
455
+ }
456
+ );
457
+
458
+ // Insert data
459
+ await client.exec(
460
+ ["db", "execute"],
461
+ {
462
+ sql: "INSERT INTO users (name, email) VALUES (?, ?)",
463
+ params: ["Alice Johnson", "alice@example.com"],
464
+ dbPath: "./app.db"
465
+ }
466
+ );
467
+
468
+ // Query data
469
+ const result = await client.exec(
470
+ ["db", "query"],
471
+ {
472
+ sql: "SELECT * FROM users WHERE active = ? ORDER BY created_at DESC",
473
+ params: [1],
474
+ dbPath: "./app.db"
475
+ }
476
+ );
477
+
478
+ console.log(result.rows);
479
+ ```
480
+
481
+ ### Structured Logging
482
+
483
+ ```typescript
484
+ // Log an info message
485
+ await client.exec(
486
+ ["logs", "store"],
487
+ {
488
+ level: "info",
489
+ message: "Starting build process",
490
+ sessionId: "build-2024-03-15",
491
+ command: "lib.build",
492
+ context: "@myorg/package-name"
493
+ }
494
+ );
495
+
496
+ // Log an error with stack trace
497
+ try {
498
+ // Some operation
499
+ throw new Error("Connection timeout");
500
+ } catch (error) {
501
+ await client.exec(
502
+ ["logs", "store"],
503
+ {
504
+ level: "error",
505
+ message: error.message,
506
+ sessionId: "build-2024-03-15",
507
+ command: "lib.build",
508
+ context: "@myorg/package-name",
509
+ data: {
510
+ host: "api.example.com",
511
+ timeout: 5000
512
+ },
513
+ errorStack: error.stack
514
+ }
515
+ );
516
+ }
517
+
518
+ // Query logs by session
519
+ const { logs } = await client.exec(
520
+ ["logs", "query"],
521
+ {
522
+ sessionId: "build-2024-03-15",
523
+ orderBy: "asc"
524
+ }
525
+ );
526
+
527
+ logs.forEach(log => {
528
+ console.log(`[${log.level}] ${log.message}`);
529
+ if (log.data) {
530
+ console.log(" Data:", log.data);
531
+ }
532
+ });
533
+ ```
534
+
535
+ ### Direct Database Access (Without RPC)
536
+
537
+ For lower-level access, use re-exported utilities from `docker-sqlite`:
538
+
539
+ ```typescript
540
+ import {
541
+ withConnection,
542
+ query,
543
+ execute,
544
+ lastInsertRowId,
545
+ runMigrations,
546
+ createMigration
547
+ } from "@mark1russell7/client-sqlite";
548
+
549
+ // Managed connection with automatic cleanup
550
+ await withConnection({ dbPath: "./data/app.db" }, (db) => {
551
+ // Execute mutations
552
+ execute(db, "INSERT INTO users (name, email) VALUES (?, ?)", [
553
+ "Bob Smith",
554
+ "bob@example.com"
555
+ ]);
556
+
557
+ const userId = lastInsertRowId(db);
558
+ console.log("Inserted user ID:", userId);
559
+
560
+ // Query data
561
+ const result = query<{ id: number; name: string; email: string }>(
562
+ db,
563
+ "SELECT * FROM users WHERE id = ?",
564
+ [userId]
565
+ );
566
+
567
+ console.log(result.rows);
568
+ });
569
+
570
+ // Connection is automatically closed after callback
571
+ ```
572
+
573
+ ### Database Migrations
574
+
575
+ ```typescript
576
+ import {
577
+ withConnection,
578
+ runMigrations,
579
+ getMigrationStatus,
580
+ rollbackMigration,
581
+ type Migration
582
+ } from "@mark1russell7/client-sqlite";
583
+
584
+ const migrations: Migration[] = [
585
+ {
586
+ version: 1,
587
+ description: "Create users table",
588
+ up: `
589
+ CREATE TABLE users (
590
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
591
+ name TEXT NOT NULL,
592
+ email TEXT UNIQUE NOT NULL,
593
+ created_at TEXT DEFAULT (datetime('now'))
594
+ );
595
+ CREATE INDEX idx_users_email ON users(email);
596
+ `,
597
+ down: "DROP TABLE users;"
598
+ },
599
+ {
600
+ version: 2,
601
+ description: "Add users.active column",
602
+ up: "ALTER TABLE users ADD COLUMN active BOOLEAN DEFAULT 1;",
603
+ down: "ALTER TABLE users DROP COLUMN active;"
604
+ }
605
+ ];
606
+
607
+ await withConnection({ dbPath: "./app.db" }, (db) => {
608
+ // Run migrations
609
+ runMigrations(db, migrations);
610
+
611
+ // Check migration status
612
+ const status = getMigrationStatus(db);
613
+ console.log("Current version:", status.currentVersion);
614
+ console.log("Applied migrations:", status.appliedMigrations);
615
+
616
+ // Rollback last migration if needed
617
+ // rollbackMigration(db, migrations);
618
+ });
619
+ ```
620
+
621
+ ### Type-Safe Queries
622
+
623
+ ```typescript
624
+ // Define result types for type safety
625
+ interface User {
626
+ id: number;
627
+ name: string;
628
+ email: string;
629
+ active: boolean;
630
+ created_at: string;
631
+ }
632
+
633
+ const result = await client.call<DbQueryOutput<User>>(
634
+ ["db", "query"],
635
+ {
636
+ sql: "SELECT * FROM users WHERE active = ?",
637
+ params: [true],
638
+ dbPath: "./app.db"
639
+ }
640
+ );
641
+
642
+ // result.rows is typed as User[]
643
+ result.rows.forEach(user => {
644
+ console.log(`${user.name} <${user.email}>`);
645
+ });
646
+ ```
647
+
648
+ ## Integration with Ecosystem
649
+
650
+ ### Bundle Packages
651
+
652
+ `client-sqlite` can be included in bundle packages:
653
+
654
+ ```typescript
655
+ // In bundle-data/src/register.ts
656
+ import "@mark1russell7/client-sqlite"; // registers its procedures
657
+ import "@mark1russell7/client-mongo"; // registers its procedures
658
+ import "@mark1russell7/client-s3/register.js";
659
+ ```
660
+
661
+ ## Dependencies
662
+
663
+ ### Runtime Dependencies
664
+
665
+ - **@mark1russell7/client** - Core client procedure system
666
+ - **@mark1russell7/docker-sqlite** - SQLite connection and query utilities
667
+
668
+ ### Development Dependencies
669
+
670
+ - **@mark1russell7/cue** - Configuration and type utilities
671
+ - **typescript** (^5.9.3) - TypeScript compiler
672
+ - **@types/node** (^22.0.0) - Node.js type definitions
673
+
674
+ ## Re-exported docker-sqlite Utilities
675
+
676
+ `client-sqlite` re-exports all utilities from `docker-sqlite` for direct use:
677
+
678
+ ```typescript
679
+ // Connection management
680
+ export { withConnection, createConnection } from "@mark1russell7/docker-sqlite";
681
+
682
+ // Query execution
683
+ export { query, execute, execMultiple, lastInsertRowId } from "@mark1russell7/docker-sqlite";
684
+
685
+ // Table utilities
686
+ export { tableExists } from "@mark1russell7/docker-sqlite";
687
+
688
+ // Migration utilities
689
+ export {
690
+ getMigrationStatus,
691
+ runMigrations,
692
+ rollbackMigration,
693
+ createMigration
694
+ } from "@mark1russell7/docker-sqlite";
695
+
696
+ // Types
697
+ export type {
698
+ ConnectionConfig,
699
+ ConnectionCallback,
700
+ QueryResult,
701
+ ExecuteResult,
702
+ Migration,
703
+ MigrationStatus,
704
+ Database
705
+ } from "@mark1russell7/docker-sqlite";
706
+ ```
707
+
708
+ ## Package Structure
709
+
710
+ ```
711
+ client-sqlite/
712
+ ├── src/
713
+ │ ├── types.ts # Type definitions
714
+ │ ├── register.ts # Auto-registration + procedure implementations
715
+ │ └── index.ts # Main entry point
716
+ ├── dist/ # Compiled output
717
+ ├── package.json
718
+ ├── tsconfig.json
719
+ └── README.md
720
+ ```
721
+
722
+ ## Development
723
+
724
+ ### Build
725
+
726
+ ```bash
727
+ npm run build
728
+ ```
729
+
730
+ ### Type Check
731
+
732
+ ```bash
733
+ npm run typecheck
734
+ ```
735
+
736
+ ### Clean
737
+
738
+ ```bash
739
+ npm run clean
740
+ ```
741
+
742
+ ## License
743
+
744
+ MIT
745
+
746
+ ## Author
747
+
748
+ Mark Russell <marktheprogrammer17@gmail.com>