s3db.js 13.3.0 → 13.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "s3db.js",
3
- "version": "13.3.0",
3
+ "version": "13.4.0",
4
4
  "description": "Use AWS S3, the world's most reliable document storage, as a database with this ORM.",
5
5
  "main": "dist/s3db.cjs.js",
6
6
  "module": "dist/s3db.es.js",
@@ -88,6 +88,8 @@
88
88
  "@google-cloud/bigquery": "^7.0.0",
89
89
  "@hono/node-server": "^1.0.0",
90
90
  "@hono/swagger-ui": "^0.5.0",
91
+ "@libsql/client": "^0.14.0",
92
+ "@planetscale/database": "^1.0.0",
91
93
  "@tensorflow/tfjs-node": "^4.0.0",
92
94
  "amqplib": "^0.10.8",
93
95
  "hono": "^4.0.0",
@@ -107,6 +109,12 @@
107
109
  "@hono/swagger-ui": {
108
110
  "optional": true
109
111
  },
112
+ "@libsql/client": {
113
+ "optional": true
114
+ },
115
+ "@planetscale/database": {
116
+ "optional": true
117
+ },
110
118
  "@tensorflow/tfjs-node": {
111
119
  "optional": true
112
120
  },
@@ -128,6 +136,10 @@
128
136
  "@babel/core": "^7.28.4",
129
137
  "@babel/preset-env": "^7.28.3",
130
138
  "@google-cloud/bigquery": "^7.9.4",
139
+ "@hono/node-server": "^1.13.7",
140
+ "@hono/swagger-ui": "^0.5.3",
141
+ "@libsql/client": "^0.14.0",
142
+ "@planetscale/database": "^1.19.0",
131
143
  "@rollup/plugin-commonjs": "^28.0.8",
132
144
  "@rollup/plugin-json": "^6.1.0",
133
145
  "@rollup/plugin-node-resolve": "^16.0.3",
@@ -143,6 +155,7 @@
143
155
  "cli-table3": "^0.6.5",
144
156
  "commander": "^14.0.1",
145
157
  "esbuild": "^0.25.11",
158
+ "hono": "^4.7.11",
146
159
  "inquirer": "^12.10.0",
147
160
  "jest": "^30.2.0",
148
161
  "node-cron": "^4.2.1",
@@ -185,7 +198,7 @@
185
198
  "validate:types": "pnpm run test:ts && echo 'TypeScript definitions are valid!'",
186
199
  "test:ts:runtime": "tsx tests/typescript/types-runtime-simple.ts",
187
200
  "test:mcp": "node mcp/entrypoint.js --help",
188
- "install:peers": "pnpm add -D @aws-sdk/client-sqs @google-cloud/bigquery amqplib node-cron pg",
201
+ "install:peers": "pnpm add -D @aws-sdk/client-sqs @google-cloud/bigquery @hono/node-server @hono/swagger-ui @libsql/client @planetscale/database @tensorflow/tfjs-node amqplib hono node-cron pg",
189
202
  "install:peers:script": "./scripts/install-peer-deps.sh"
190
203
  }
191
204
  }
@@ -1,3 +1,430 @@
1
+ /**
2
+ * # AuditPlugin - Comprehensive Audit Trail for s3db.js
3
+ *
4
+ * ## Overview
5
+ *
6
+ * The AuditPlugin automatically tracks all changes (insert, update, delete) to your resources,
7
+ * creating a complete audit trail for compliance, debugging, and historical analysis.
8
+ *
9
+ * ## Features
10
+ *
11
+ * 1. **Automatic Change Tracking** - Captures all insert/update/delete operations
12
+ * 2. **Partition-Aware** - Efficient queries using date and resource partitions
13
+ * 3. **Configurable Data Inclusion** - Control whether to store full data or just metadata
14
+ * 4. **Data Truncation** - Automatically truncates large records to prevent storage issues
15
+ * 5. **Flexible Querying** - Filter by resource, operation, record ID, partition, or date range
16
+ * 6. **Statistics & Analytics** - Built-in aggregation methods for audit analysis
17
+ * 7. **Retention Management** - Automatic cleanup of old audit logs
18
+ *
19
+ * ## Configuration
20
+ *
21
+ * ```javascript
22
+ * import { Database } from 's3db.js';
23
+ * import { AuditPlugin } from 's3db.js/plugins/audit';
24
+ *
25
+ * // Basic configuration
26
+ * const db = new Database({
27
+ * connectionString: 's3://bucket/db'
28
+ * });
29
+ *
30
+ * await db.use(new AuditPlugin({
31
+ * includeData: true, // Store before/after data (default: true)
32
+ * includePartitions: true, // Track partition information (default: true)
33
+ * maxDataSize: 10000 // Max bytes for data field (default: 10000)
34
+ * }));
35
+ *
36
+ * // Minimal configuration (metadata only, faster)
37
+ * await db.use(new AuditPlugin({
38
+ * includeData: false, // Don't store data, only operation metadata
39
+ * includePartitions: false // Don't track partitions
40
+ * }));
41
+ * ```
42
+ *
43
+ * ## Usage Examples
44
+ *
45
+ * ### Basic Audit Trail
46
+ *
47
+ * ```javascript
48
+ * const users = await db.createResource({
49
+ * name: 'users',
50
+ * attributes: {
51
+ * email: 'string|required',
52
+ * name: 'string'
53
+ * }
54
+ * });
55
+ *
56
+ * // These operations are automatically audited
57
+ * await users.insert({ id: 'u1', email: 'john@example.com', name: 'John' });
58
+ * await users.update('u1', { name: 'John Doe' });
59
+ * await users.delete('u1');
60
+ *
61
+ * // Query audit logs
62
+ * const auditPlugin = db.plugins.AuditPlugin;
63
+ * const logs = await auditPlugin.getAuditLogs({
64
+ * resourceName: 'users',
65
+ * recordId: 'u1'
66
+ * });
67
+ *
68
+ * console.log(logs);
69
+ * // [
70
+ * // { operation: 'insert', recordId: 'u1', newData: '{"id":"u1",...}', timestamp: '...' },
71
+ * // { operation: 'update', recordId: 'u1', oldData: '...', newData: '...', timestamp: '...' },
72
+ * // { operation: 'delete', recordId: 'u1', oldData: '...', timestamp: '...' }
73
+ * // ]
74
+ * ```
75
+ *
76
+ * ### Querying Audit Logs
77
+ *
78
+ * ```javascript
79
+ * const auditPlugin = db.plugins.AuditPlugin;
80
+ *
81
+ * // Get all changes to a specific resource
82
+ * const userChanges = await auditPlugin.getAuditLogs({
83
+ * resourceName: 'users',
84
+ * limit: 100
85
+ * });
86
+ *
87
+ * // Get changes by operation type
88
+ * const deletions = await auditPlugin.getAuditLogs({
89
+ * resourceName: 'users',
90
+ * operation: 'delete'
91
+ * });
92
+ *
93
+ * // Get changes in a date range
94
+ * const recentChanges = await auditPlugin.getAuditLogs({
95
+ * startDate: '2025-01-01',
96
+ * endDate: '2025-01-31'
97
+ * });
98
+ *
99
+ * // Get specific record history
100
+ * const recordHistory = await auditPlugin.getRecordHistory('users', 'u1');
101
+ * ```
102
+ *
103
+ * ### Audit Statistics
104
+ *
105
+ * ```javascript
106
+ * // Get comprehensive statistics
107
+ * const stats = await auditPlugin.getAuditStats({
108
+ * resourceName: 'users',
109
+ * startDate: '2025-01-01'
110
+ * });
111
+ *
112
+ * console.log(stats);
113
+ * // {
114
+ * // total: 1523,
115
+ * // byOperation: { insert: 500, update: 1000, delete: 23 },
116
+ * // byResource: { users: 1523 },
117
+ * // byUser: { system: 1200, 'user@example.com': 323 },
118
+ * // timeline: { '2025-01-01': 45, '2025-01-02': 67, ... }
119
+ * // }
120
+ * ```
121
+ *
122
+ * ### Partition History
123
+ *
124
+ * ```javascript
125
+ * const orders = await db.createResource({
126
+ * name: 'orders',
127
+ * attributes: { region: 'string', amount: 'number' },
128
+ * partitions: {
129
+ * byRegion: { fields: { region: 'string' } }
130
+ * }
131
+ * });
132
+ *
133
+ * // Get audit trail for a specific partition
134
+ * const partitionLogs = await auditPlugin.getPartitionHistory(
135
+ * 'orders',
136
+ * 'byRegion',
137
+ * { region: 'US' }
138
+ * );
139
+ * ```
140
+ *
141
+ * ### Cleanup Old Audit Logs
142
+ *
143
+ * ```javascript
144
+ * // Delete audit logs older than 90 days (default)
145
+ * const deletedCount = await auditPlugin.cleanupOldAudits(90);
146
+ * console.log(`Deleted ${deletedCount} old audit logs`);
147
+ *
148
+ * // Custom retention period (30 days)
149
+ * await auditPlugin.cleanupOldAudits(30);
150
+ * ```
151
+ *
152
+ * ## Best Practices
153
+ *
154
+ * ### 1. Configure Data Inclusion Based on Needs
155
+ *
156
+ * ```javascript
157
+ * // For compliance (full audit trail)
158
+ * new AuditPlugin({
159
+ * includeData: true,
160
+ * includePartitions: true,
161
+ * maxDataSize: 50000 // Large limit for complete data
162
+ * });
163
+ *
164
+ * // For performance monitoring (metadata only)
165
+ * new AuditPlugin({
166
+ * includeData: false,
167
+ * includePartitions: false
168
+ * });
169
+ * ```
170
+ *
171
+ * ### 2. Use Partition-Aware Queries for Performance
172
+ *
173
+ * ```javascript
174
+ * // FAST: Query by resource (uses partition)
175
+ * await auditPlugin.getAuditLogs({ resourceName: 'users' });
176
+ *
177
+ * // FAST: Query by date (uses partition)
178
+ * await auditPlugin.getAuditLogs({ startDate: '2025-01-15', endDate: '2025-01-16' });
179
+ *
180
+ * // SLOWER: Multiple filters (requires list scan)
181
+ * await auditPlugin.getAuditLogs({
182
+ * resourceName: 'users',
183
+ * operation: 'update',
184
+ * recordId: 'u1'
185
+ * });
186
+ * ```
187
+ *
188
+ * ### 3. Implement Regular Cleanup
189
+ *
190
+ * ```javascript
191
+ * // Schedule monthly cleanup (using cron or scheduler)
192
+ * setInterval(async () => {
193
+ * const deleted = await auditPlugin.cleanupOldAudits(90);
194
+ * console.log(`Audit cleanup: removed ${deleted} records`);
195
+ * }, 30 * 24 * 60 * 60 * 1000); // 30 days
196
+ * ```
197
+ *
198
+ * ### 4. Track User Context
199
+ *
200
+ * ```javascript
201
+ * // Set current user for audit trails
202
+ * auditPlugin.getCurrentUserId = () => {
203
+ * // Return current user ID from your auth system
204
+ * return getCurrentUser()?.email || 'system';
205
+ * };
206
+ *
207
+ * // Now all audit logs will include the user ID
208
+ * await users.insert({ email: 'jane@example.com' });
209
+ * // Audit log will show: userId: 'admin@example.com'
210
+ * ```
211
+ *
212
+ * ## Performance Considerations
213
+ *
214
+ * ### Storage Overhead
215
+ *
216
+ * - **With includeData: true** - Approximately 2-3x storage per operation
217
+ * - **With includeData: false** - Approximately 200-500 bytes per operation
218
+ * - Large records are automatically truncated based on `maxDataSize`
219
+ *
220
+ * ### Query Performance
221
+ *
222
+ * | Query Type | Performance | Notes |
223
+ * |------------|-------------|-------|
224
+ * | By resource name | **O(n)** where n = records in resource | Uses `byResource` partition |
225
+ * | By date range | **O(n)** where n = records in date range | Uses `byDate` partition |
226
+ * | By operation | **O(n)** of all records | Requires full scan |
227
+ * | By record ID | **O(n)** of all records | Requires full scan |
228
+ * | Combined filters | **O(n)** of all records | Fetches up to 10,000 records |
229
+ *
230
+ * ### Optimization Tips
231
+ *
232
+ * ```javascript
233
+ * // 1. Use partition-aware queries when possible
234
+ * const logs = await auditPlugin.getAuditLogs({ resourceName: 'users' });
235
+ *
236
+ * // 2. Limit result sets
237
+ * const recent = await auditPlugin.getAuditLogs({
238
+ * resourceName: 'users',
239
+ * limit: 50
240
+ * });
241
+ *
242
+ * // 3. Use narrow date ranges
243
+ * const dailyLogs = await auditPlugin.getAuditLogs({
244
+ * startDate: '2025-01-15',
245
+ * endDate: '2025-01-15' // Single day
246
+ * });
247
+ *
248
+ * // 4. Disable data inclusion for high-volume resources
249
+ * new AuditPlugin({ includeData: false });
250
+ * ```
251
+ *
252
+ * ## Troubleshooting
253
+ *
254
+ * ### Audit Logs Not Being Created
255
+ *
256
+ * ```javascript
257
+ * // Check if plugin is installed
258
+ * console.log(db.plugins.AuditPlugin); // Should exist
259
+ *
260
+ * // Check if audit resource exists
261
+ * console.log(db.resources.plg_audits); // Should exist
262
+ *
263
+ * // Verify plugin started
264
+ * await db.start(); // Must call start() to activate plugin
265
+ * ```
266
+ *
267
+ * ### Large Audit Logs Slow Queries
268
+ *
269
+ * ```javascript
270
+ * // Solution 1: Reduce data inclusion
271
+ * new AuditPlugin({ includeData: false });
272
+ *
273
+ * // Solution 2: Implement regular cleanup
274
+ * await auditPlugin.cleanupOldAudits(30); // Keep only 30 days
275
+ *
276
+ * // Solution 3: Use more specific queries
277
+ * await auditPlugin.getAuditLogs({
278
+ * resourceName: 'users', // Use partition
279
+ * limit: 100 // Limit results
280
+ * });
281
+ * ```
282
+ *
283
+ * ### Data Truncation Issues
284
+ *
285
+ * ```javascript
286
+ * // Check if records are being truncated
287
+ * const logs = await auditPlugin.getAuditLogs({ resourceName: 'users' });
288
+ * const truncated = logs.filter(log => {
289
+ * const data = JSON.parse(log.newData || '{}');
290
+ * return data._truncated === true;
291
+ * });
292
+ *
293
+ * // Increase max size if needed
294
+ * new AuditPlugin({ maxDataSize: 50000 }); // Increase from default 10000
295
+ * ```
296
+ *
297
+ * ### Memory Usage with Large History
298
+ *
299
+ * ```javascript
300
+ * // Instead of loading all at once
301
+ * const all = await auditPlugin.getAuditLogs({ resourceName: 'users' });
302
+ *
303
+ * // Use pagination
304
+ * for (let offset = 0; offset < totalRecords; offset += 100) {
305
+ * const batch = await auditPlugin.getAuditLogs({
306
+ * resourceName: 'users',
307
+ * limit: 100,
308
+ * offset
309
+ * });
310
+ * processBatch(batch);
311
+ * }
312
+ * ```
313
+ *
314
+ * ## Audit Log Schema
315
+ *
316
+ * ```javascript
317
+ * {
318
+ * id: 'audit-1234567890-abc', // Unique audit log ID
319
+ * resourceName: 'users', // Resource that was modified
320
+ * operation: 'update', // 'insert' | 'update' | 'delete' | 'deleteMany'
321
+ * recordId: 'u1', // ID of the modified record
322
+ * userId: 'admin@example.com', // User who made the change
323
+ * timestamp: '2025-01-15T10:30:00Z', // When the change occurred
324
+ * createdAt: '2025-01-15', // Date for partitioning (YYYY-MM-DD)
325
+ * oldData: '{"id":"u1","name":"John"}', // Data before change (JSON string)
326
+ * newData: '{"id":"u1","name":"Jane"}', // Data after change (JSON string)
327
+ * partition: 'byRegion', // Partition name (if applicable)
328
+ * partitionValues: '{"region":"US"}', // Partition values (JSON string)
329
+ * metadata: '{"source":"audit-plugin"}', // Additional metadata
330
+ * }
331
+ * ```
332
+ *
333
+ * ## Real-World Use Cases
334
+ *
335
+ * ### 1. Compliance & Regulatory Requirements
336
+ *
337
+ * ```javascript
338
+ * // HIPAA, SOC2, GDPR compliance
339
+ * const auditPlugin = new AuditPlugin({
340
+ * includeData: true, // Full audit trail required
341
+ * includePartitions: true,
342
+ * maxDataSize: 100000 // Large records
343
+ * });
344
+ *
345
+ * // Generate compliance report
346
+ * const report = await auditPlugin.getAuditStats({
347
+ * startDate: '2025-01-01',
348
+ * endDate: '2025-12-31'
349
+ * });
350
+ * ```
351
+ *
352
+ * ### 2. Debugging & Troubleshooting
353
+ *
354
+ * ```javascript
355
+ * // Find when and who changed a specific record
356
+ * const history = await auditPlugin.getRecordHistory('orders', 'order-123');
357
+ * console.log(history.map(log => ({
358
+ * timestamp: log.timestamp,
359
+ * user: log.userId,
360
+ * operation: log.operation,
361
+ * before: JSON.parse(log.oldData || '{}'),
362
+ * after: JSON.parse(log.newData || '{}')
363
+ * })));
364
+ * ```
365
+ *
366
+ * ### 3. Activity Monitoring
367
+ *
368
+ * ```javascript
369
+ * // Real-time activity dashboard
370
+ * setInterval(async () => {
371
+ * const recentActivity = await auditPlugin.getAuditLogs({
372
+ * startDate: new Date(Date.now() - 60000).toISOString(), // Last minute
373
+ * limit: 100
374
+ * });
375
+ *
376
+ * updateDashboard(recentActivity);
377
+ * }, 10000); // Update every 10 seconds
378
+ * ```
379
+ *
380
+ * ### 4. Data Recovery
381
+ *
382
+ * ```javascript
383
+ * // Recover accidentally deleted record
384
+ * const deletedLog = await auditPlugin.getAuditLogs({
385
+ * resourceName: 'users',
386
+ * operation: 'delete',
387
+ * recordId: 'u1'
388
+ * });
389
+ *
390
+ * if (deletedLog.length > 0) {
391
+ * const originalData = JSON.parse(deletedLog[0].oldData);
392
+ * await users.insert(originalData); // Restore
393
+ * }
394
+ * ```
395
+ *
396
+ * ## API Reference
397
+ *
398
+ * ### Constructor Options
399
+ *
400
+ * - `includeData` (boolean, default: true) - Store before/after data in audit logs
401
+ * - `includePartitions` (boolean, default: true) - Track partition information
402
+ * - `maxDataSize` (number, default: 10000) - Maximum bytes for data field
403
+ *
404
+ * ### Methods
405
+ *
406
+ * - `getAuditLogs(options)` - Query audit logs with filters
407
+ * - `getRecordHistory(resourceName, recordId)` - Get complete history of a record
408
+ * - `getPartitionHistory(resourceName, partition, values)` - Get partition-specific history
409
+ * - `getAuditStats(options)` - Get aggregated statistics
410
+ * - `cleanupOldAudits(retentionDays)` - Delete old audit logs
411
+ *
412
+ * ### Query Options
413
+ *
414
+ * ```typescript
415
+ * interface AuditQueryOptions {
416
+ * resourceName?: string; // Filter by resource
417
+ * operation?: string; // Filter by operation ('insert' | 'update' | 'delete')
418
+ * recordId?: string; // Filter by record ID
419
+ * partition?: string; // Filter by partition name
420
+ * startDate?: string; // Filter by start date (ISO format)
421
+ * endDate?: string; // Filter by end date (ISO format)
422
+ * limit?: number; // Max results (default: 100)
423
+ * offset?: number; // Pagination offset (default: 0)
424
+ * }
425
+ * ```
426
+ */
427
+
1
428
  import { Plugin } from "./plugin.class.js";
2
429
  import tryFn from "../concerns/try-fn.js";
3
430