s3db.js 12.0.1 → 12.2.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 (45) hide show
  1. package/README.md +212 -196
  2. package/dist/s3db.cjs.js +1431 -4001
  3. package/dist/s3db.cjs.js.map +1 -1
  4. package/dist/s3db.es.js +1426 -3997
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/entrypoint.js +91 -57
  7. package/package.json +7 -1
  8. package/src/cli/index.js +954 -43
  9. package/src/cli/migration-manager.js +270 -0
  10. package/src/concerns/calculator.js +0 -4
  11. package/src/concerns/metadata-encoding.js +1 -21
  12. package/src/concerns/plugin-storage.js +17 -4
  13. package/src/concerns/typescript-generator.d.ts +171 -0
  14. package/src/concerns/typescript-generator.js +275 -0
  15. package/src/database.class.js +171 -28
  16. package/src/index.js +15 -9
  17. package/src/plugins/api/index.js +0 -1
  18. package/src/plugins/api/routes/resource-routes.js +86 -1
  19. package/src/plugins/api/server.js +79 -3
  20. package/src/plugins/api/utils/openapi-generator.js +195 -5
  21. package/src/plugins/backup/multi-backup-driver.class.js +0 -1
  22. package/src/plugins/backup.plugin.js +7 -14
  23. package/src/plugins/concerns/plugin-dependencies.js +73 -19
  24. package/src/plugins/eventual-consistency/analytics.js +0 -2
  25. package/src/plugins/eventual-consistency/consolidation.js +2 -13
  26. package/src/plugins/eventual-consistency/index.js +0 -1
  27. package/src/plugins/eventual-consistency/install.js +1 -1
  28. package/src/plugins/geo.plugin.js +5 -6
  29. package/src/plugins/importer/index.js +1 -1
  30. package/src/plugins/plugin.class.js +5 -0
  31. package/src/plugins/relation.plugin.js +193 -57
  32. package/src/plugins/replicator.plugin.js +12 -21
  33. package/src/plugins/s3-queue.plugin.js +4 -4
  34. package/src/plugins/scheduler.plugin.js +10 -12
  35. package/src/plugins/state-machine.plugin.js +8 -12
  36. package/src/plugins/tfstate/README.md +1 -1
  37. package/src/plugins/tfstate/errors.js +3 -3
  38. package/src/plugins/tfstate/index.js +41 -67
  39. package/src/plugins/ttl.plugin.js +479 -304
  40. package/src/resource.class.js +263 -61
  41. package/src/schema.class.js +0 -2
  42. package/src/testing/factory.class.js +286 -0
  43. package/src/testing/index.js +15 -0
  44. package/src/testing/seeder.class.js +183 -0
  45. package/dist/s3db-cli.js +0 -55543
@@ -16,81 +16,97 @@
16
16
  export const PLUGIN_DEPENDENCIES = {
17
17
  'postgresql-replicator': {
18
18
  name: 'PostgreSQL Replicator',
19
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/replicator.md',
19
20
  dependencies: {
20
21
  'pg': {
21
22
  version: '^8.0.0',
22
23
  description: 'PostgreSQL client for Node.js',
23
- installCommand: 'pnpm add pg'
24
+ installCommand: 'pnpm add pg',
25
+ npmUrl: 'https://www.npmjs.com/package/pg'
24
26
  }
25
27
  }
26
28
  },
27
29
  'bigquery-replicator': {
28
30
  name: 'BigQuery Replicator',
31
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/replicator.md',
29
32
  dependencies: {
30
33
  '@google-cloud/bigquery': {
31
34
  version: '^7.0.0',
32
35
  description: 'Google Cloud BigQuery SDK',
33
- installCommand: 'pnpm add @google-cloud/bigquery'
36
+ installCommand: 'pnpm add @google-cloud/bigquery',
37
+ npmUrl: 'https://www.npmjs.com/package/@google-cloud/bigquery'
34
38
  }
35
39
  }
36
40
  },
37
41
  'sqs-replicator': {
38
42
  name: 'SQS Replicator',
43
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/replicator.md',
39
44
  dependencies: {
40
45
  '@aws-sdk/client-sqs': {
41
46
  version: '^3.0.0',
42
47
  description: 'AWS SDK for SQS',
43
- installCommand: 'pnpm add @aws-sdk/client-sqs'
48
+ installCommand: 'pnpm add @aws-sdk/client-sqs',
49
+ npmUrl: 'https://www.npmjs.com/package/@aws-sdk/client-sqs'
44
50
  }
45
51
  }
46
52
  },
47
53
  'sqs-consumer': {
48
54
  name: 'SQS Queue Consumer',
55
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/queue-consumer.md',
49
56
  dependencies: {
50
57
  '@aws-sdk/client-sqs': {
51
58
  version: '^3.0.0',
52
59
  description: 'AWS SDK for SQS',
53
- installCommand: 'pnpm add @aws-sdk/client-sqs'
60
+ installCommand: 'pnpm add @aws-sdk/client-sqs',
61
+ npmUrl: 'https://www.npmjs.com/package/@aws-sdk/client-sqs'
54
62
  }
55
63
  }
56
64
  },
57
65
  'rabbitmq-consumer': {
58
66
  name: 'RabbitMQ Queue Consumer',
67
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/queue-consumer.md',
59
68
  dependencies: {
60
69
  'amqplib': {
61
70
  version: '^0.10.0',
62
71
  description: 'AMQP 0-9-1 library for RabbitMQ',
63
- installCommand: 'pnpm add amqplib'
72
+ installCommand: 'pnpm add amqplib',
73
+ npmUrl: 'https://www.npmjs.com/package/amqplib'
64
74
  }
65
75
  }
66
76
  },
67
77
  'tfstate-plugin': {
68
- name: 'Terraform State Plugin',
78
+ name: 'Tfstate Plugin',
79
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/tfstate.md',
69
80
  dependencies: {
70
81
  'node-cron': {
71
82
  version: '^4.0.0',
72
83
  description: 'Cron job scheduler for auto-sync functionality',
73
- installCommand: 'pnpm add node-cron'
84
+ installCommand: 'pnpm add node-cron',
85
+ npmUrl: 'https://www.npmjs.com/package/node-cron'
74
86
  }
75
87
  }
76
88
  },
77
89
  'api-plugin': {
78
90
  name: 'API Plugin',
91
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/api.md',
79
92
  dependencies: {
80
93
  'hono': {
81
94
  version: '^4.0.0',
82
95
  description: 'Ultra-light HTTP server framework',
83
- installCommand: 'pnpm add hono'
96
+ installCommand: 'pnpm add hono',
97
+ npmUrl: 'https://www.npmjs.com/package/hono'
84
98
  },
85
99
  '@hono/node-server': {
86
100
  version: '^1.0.0',
87
101
  description: 'Node.js adapter for Hono',
88
- installCommand: 'pnpm add @hono/node-server'
102
+ installCommand: 'pnpm add @hono/node-server',
103
+ npmUrl: 'https://www.npmjs.com/package/@hono/node-server'
89
104
  },
90
105
  '@hono/swagger-ui': {
91
106
  version: '^0.4.0',
92
107
  description: 'Swagger UI integration for Hono',
93
- installCommand: 'pnpm add @hono/swagger-ui'
108
+ installCommand: 'pnpm add @hono/swagger-ui',
109
+ npmUrl: 'https://www.npmjs.com/package/@hono/swagger-ui'
94
110
  }
95
111
  }
96
112
  }
@@ -229,22 +245,60 @@ export async function requirePluginDependency(pluginId, options = {}) {
229
245
 
230
246
  // Throw comprehensive error if validation failed
231
247
  if (!valid && throwOnError) {
248
+ const depCount = Object.keys(pluginDef.dependencies).length;
249
+ const missingCount = missing.length;
250
+ const incompatCount = incompatible.length;
251
+
232
252
  const errorMsg = [
233
- `\n${pluginDef.name} - Missing dependencies detected!\n`,
234
- `Plugin ID: ${pluginId}`,
235
253
  '',
254
+ '╔══════════════════════════════════════════════════════════════════════╗',
255
+ `║ ❌ ${pluginDef.name} - Missing Dependencies ║`,
256
+ '╚══════════════════════════════════════════════════════════════════════╝',
257
+ '',
258
+ `📦 Plugin: ${pluginId}`,
259
+ `📊 Status: ${depCount - missingCount - incompatCount}/${depCount} dependencies satisfied`,
260
+ '',
261
+ '🔍 Dependency Status:',
262
+ '─────────────────────────────────────────────────────────────────────',
236
263
  ...messages,
237
264
  '',
238
- 'Quick fix - Run all install commands:',
239
- Object.values(pluginDef.dependencies)
240
- .map(dep => ` ${dep.installCommand}`)
241
- .join('\n'),
265
+ '🚀 Quick Fix - Install Missing Dependencies:',
266
+ '─────────────────────────────────────────────────────────────────────',
267
+ '',
268
+ ' Option 1: Install individually',
269
+ ...Object.entries(pluginDef.dependencies)
270
+ .filter(([pkg]) => missing.includes(pkg) || incompatible.includes(pkg))
271
+ .map(([pkg, info]) => ` ${info.installCommand}`),
272
+ '',
273
+ ' Option 2: Install all at once',
274
+ ` pnpm add ${Object.keys(pluginDef.dependencies).join(' ')}`,
242
275
  '',
243
- 'Or install all peer dependencies at once:',
244
- ` pnpm add ${Object.keys(pluginDef.dependencies).join(' ')}`
276
+ '📚 Documentation:',
277
+ ` ${pluginDef.docsUrl}`,
278
+ '',
279
+ '💡 Troubleshooting:',
280
+ ' • If packages are installed but not detected, try:',
281
+ ' 1. Delete node_modules and reinstall: rm -rf node_modules && pnpm install',
282
+ ' 2. Check Node.js version: node --version (requires Node 18+)',
283
+ ' 3. Verify pnpm version: pnpm --version (requires pnpm 8+)',
284
+ '',
285
+ ' • Still having issues? Check:',
286
+ ' - Package.json has correct dependencies listed',
287
+ ' - No conflicting versions in pnpm-lock.yaml',
288
+ ' - File permissions (especially in node_modules/)',
289
+ '',
290
+ '═══════════════════════════════════════════════════════════════════════',
291
+ ''
245
292
  ].join('\n');
246
293
 
247
- throw new Error(errorMsg);
294
+ const error = new Error(errorMsg);
295
+ error.pluginId = pluginId;
296
+ error.pluginName = pluginDef.name;
297
+ error.missing = missing;
298
+ error.incompatible = incompatible;
299
+ error.docsUrl = pluginDef.docsUrl;
300
+
301
+ throw error;
248
302
  }
249
303
 
250
304
  return { valid, missing, incompatible, messages };
@@ -559,8 +559,6 @@ async function getAnalyticsForRecord(resourceName, field, recordId, options, han
559
559
  return [];
560
560
  }
561
561
 
562
- // ✅ FIX BUG #2: Ensure all transactions have cohortHour calculated
563
- // This handles legacy data that may be missing cohortHour
564
562
  allTransactions = ensureCohortHours(allTransactions, handler.config?.cohort?.timezone || 'UTC', false);
565
563
 
566
564
  // Filter transactions by temporal range
@@ -286,9 +286,7 @@ export async function consolidateRecord(
286
286
 
287
287
  if (!hasSetInApplied) {
288
288
  // No 'set' operation in applied transactions means we're missing the base value
289
- // This can only happen if:
290
- // 1. Record had an initial value before first transaction
291
- // 2. First consolidation didn't create an anchor transaction (legacy behavior)
289
+ // This can happen if record had an initial value before first transaction
292
290
  // Solution: Get the current record value and create an anchor transaction now
293
291
  const recordValue = recordExists[config.field] || 0;
294
292
 
@@ -525,7 +523,6 @@ export async function consolidateRecord(
525
523
  updateResult = result[2];
526
524
  }
527
525
 
528
- // For backward compatibility, return the value of the main field
529
526
  const consolidatedValue = consolidatedValues[config.field] ||
530
527
  (record ? lodash.get(record, config.field, 0) : 0);
531
528
 
@@ -610,13 +607,10 @@ export async function consolidateRecord(
610
607
 
611
608
  const { results, errors } = await PromisePool
612
609
  .for(transactionsToUpdate)
613
- .withConcurrency(markAppliedConcurrency) // ✅ Configurável e maior!
610
+ .withConcurrency(markAppliedConcurrency)
614
611
  .process(async (txn) => {
615
- // ✅ FIX BUG #3: Ensure cohort fields exist before marking as applied
616
- // This handles legacy transactions missing cohortHour, cohortDate, etc.
617
612
  const txnWithCohorts = ensureCohortHour(txn, config.cohort.timezone, false);
618
613
 
619
- // Build update data with applied flag
620
614
  const updateData = { applied: true };
621
615
 
622
616
  // Add missing cohort fields if they were calculated
@@ -633,11 +627,6 @@ export async function consolidateRecord(
633
627
  updateData.cohortMonth = txnWithCohorts.cohortMonth;
634
628
  }
635
629
 
636
- // Handle null value field (legacy data might have null)
637
- if (txn.value === null || txn.value === undefined) {
638
- updateData.value = 1; // Default to 1 for backward compatibility
639
- }
640
-
641
630
  const [ok, err] = await tryFn(() =>
642
631
  transactionResource.update(txn.id, updateData)
643
632
  );
@@ -183,7 +183,6 @@ export class EventualConsistencyPlugin extends Plugin {
183
183
  * @private
184
184
  */
185
185
  async _syncModeConsolidate(handler, id, field) {
186
- // Temporarily set config for legacy methods
187
186
  const oldResource = this.config.resource;
188
187
  const oldField = this.config.field;
189
188
  const oldTransactionResource = this.transactionResource;
@@ -100,7 +100,7 @@ export async function completeFieldSetup(handler, database, config, plugin) {
100
100
  operation: 'string|required',
101
101
  timestamp: 'string|required',
102
102
  cohortDate: 'string|required',
103
- cohortHour: 'string|optional', // ✅ FIX BUG #2: Changed from required to optional for migration compatibility
103
+ cohortHour: 'string|required',
104
104
  cohortWeek: 'string|optional',
105
105
  cohortMonth: 'string|optional',
106
106
  source: 'string|optional',
@@ -127,7 +127,7 @@ class GeoPlugin extends Plugin {
127
127
  return;
128
128
  }
129
129
 
130
- const resource = this.database.resource(resourceName);
130
+ const resource = this.database.resources[resourceName];
131
131
  if (!resource || typeof resource.addHook !== 'function') {
132
132
  if (this.verbose) {
133
133
  console.warn(`[GeoPlugin] Resource "${resourceName}" not found or invalid`);
@@ -325,14 +325,13 @@ class GeoPlugin extends Plugin {
325
325
  * Find nearby locations within radius
326
326
  * Automatically selects optimal zoom level if multi-zoom enabled
327
327
  */
328
- resource.findNearby = async function({ lat, lon, lng, radius = 10, limit = 100 }) {
329
- // Support both 'lon' and 'lng' for backward compatibility
330
- const longitude = lon !== undefined ? lon : lng;
331
-
332
- if (lat === undefined || longitude === undefined) {
328
+ resource.findNearby = async function({ lat, lon, radius = 10, limit = 100 }) {
329
+ if (lat === undefined || lon === undefined) {
333
330
  throw new Error('lat and lon are required for findNearby');
334
331
  }
335
332
 
333
+ const longitude = lon; // Alias for internal use
334
+
336
335
  let allRecords = [];
337
336
 
338
337
  // Use partitions if enabled for efficient queries
@@ -739,7 +739,7 @@ export class ImporterPlugin extends Plugin {
739
739
  async onInstall() {
740
740
  // Get resource - database.resource() returns a rejected Promise if not found
741
741
  try {
742
- this.resource = this.database.resource(this.resourceName);
742
+ this.resource = this.database.resources[this.resourceName];
743
743
  // If resource() returns a Promise, await it
744
744
  if (this.resource && typeof this.resource.then === 'function') {
745
745
  this.resource = await this.resource;
@@ -176,6 +176,11 @@ export class Plugin extends EventEmitter {
176
176
  * - Pode modificar argumentos/resultados.
177
177
  */
178
178
  addMiddleware(resource, methodName, middleware) {
179
+ // Safety check: verify method exists
180
+ if (typeof resource[methodName] !== 'function') {
181
+ throw new Error(`Cannot add middleware to "${methodName}": method does not exist on resource "${resource.name || 'unknown'}"`);
182
+ }
183
+
179
184
  if (!resource._pluginMiddlewares) {
180
185
  resource._pluginMiddlewares = {};
181
186
  }