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.
- package/README.md +212 -196
- package/dist/s3db.cjs.js +1431 -4001
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1426 -3997
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +91 -57
- package/package.json +7 -1
- package/src/cli/index.js +954 -43
- package/src/cli/migration-manager.js +270 -0
- package/src/concerns/calculator.js +0 -4
- package/src/concerns/metadata-encoding.js +1 -21
- package/src/concerns/plugin-storage.js +17 -4
- package/src/concerns/typescript-generator.d.ts +171 -0
- package/src/concerns/typescript-generator.js +275 -0
- package/src/database.class.js +171 -28
- package/src/index.js +15 -9
- package/src/plugins/api/index.js +0 -1
- package/src/plugins/api/routes/resource-routes.js +86 -1
- package/src/plugins/api/server.js +79 -3
- package/src/plugins/api/utils/openapi-generator.js +195 -5
- package/src/plugins/backup/multi-backup-driver.class.js +0 -1
- package/src/plugins/backup.plugin.js +7 -14
- package/src/plugins/concerns/plugin-dependencies.js +73 -19
- package/src/plugins/eventual-consistency/analytics.js +0 -2
- package/src/plugins/eventual-consistency/consolidation.js +2 -13
- package/src/plugins/eventual-consistency/index.js +0 -1
- package/src/plugins/eventual-consistency/install.js +1 -1
- package/src/plugins/geo.plugin.js +5 -6
- package/src/plugins/importer/index.js +1 -1
- package/src/plugins/plugin.class.js +5 -0
- package/src/plugins/relation.plugin.js +193 -57
- package/src/plugins/replicator.plugin.js +12 -21
- package/src/plugins/s3-queue.plugin.js +4 -4
- package/src/plugins/scheduler.plugin.js +10 -12
- package/src/plugins/state-machine.plugin.js +8 -12
- package/src/plugins/tfstate/README.md +1 -1
- package/src/plugins/tfstate/errors.js +3 -3
- package/src/plugins/tfstate/index.js +41 -67
- package/src/plugins/ttl.plugin.js +479 -304
- package/src/resource.class.js +263 -61
- package/src/schema.class.js +0 -2
- package/src/testing/factory.class.js +286 -0
- package/src/testing/index.js +15 -0
- package/src/testing/seeder.class.js +183 -0
- 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: '
|
|
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
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
'
|
|
244
|
-
`
|
|
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
|
-
|
|
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
|
|
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)
|
|
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|
|
|
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.
|
|
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,
|
|
329
|
-
|
|
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.
|
|
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
|
}
|