s3db.js 10.0.5 → 10.0.7
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/dist/s3db.cjs.js +143 -13
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +143 -13
- package/dist/s3db.es.js.map +1 -1
- package/package.json +18 -18
- package/src/database.class.js +4 -1
- package/src/plugins/cache.plugin.js +9 -1
- package/src/plugins/eventual-consistency.plugin.js +186 -11
- package/src/resource.class.js +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "s3db.js",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.7",
|
|
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",
|
|
@@ -79,16 +79,16 @@
|
|
|
79
79
|
"validate:types": "pnpm run test:ts && echo 'TypeScript definitions are valid!'"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@aws-sdk/client-s3": "^3.
|
|
83
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
84
|
-
"@smithy/node-http-handler": "^4.
|
|
82
|
+
"@aws-sdk/client-s3": "^3.906.0",
|
|
83
|
+
"@modelcontextprotocol/sdk": "^1.19.1",
|
|
84
|
+
"@smithy/node-http-handler": "^4.3.0",
|
|
85
85
|
"@supercharge/promise-pool": "^3.2.0",
|
|
86
|
-
"dotenv": "^17.2.
|
|
86
|
+
"dotenv": "^17.2.3",
|
|
87
87
|
"fastest-validator": "^1.19.1",
|
|
88
88
|
"flat": "^6.0.1",
|
|
89
89
|
"json-stable-stringify": "^1.3.0",
|
|
90
90
|
"lodash-es": "^4.17.21",
|
|
91
|
-
"nanoid": "5.1.
|
|
91
|
+
"nanoid": "5.1.6"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
94
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
@@ -115,32 +115,32 @@
|
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
"devDependencies": {
|
|
118
|
-
"@babel/core": "^7.28.
|
|
118
|
+
"@babel/core": "^7.28.4",
|
|
119
119
|
"@babel/preset-env": "^7.28.3",
|
|
120
120
|
"@rollup/plugin-commonjs": "^28.0.6",
|
|
121
121
|
"@rollup/plugin-json": "^6.1.0",
|
|
122
|
-
"@rollup/plugin-node-resolve": "^16.0.
|
|
122
|
+
"@rollup/plugin-node-resolve": "^16.0.2",
|
|
123
123
|
"@rollup/plugin-replace": "^6.0.2",
|
|
124
124
|
"@rollup/plugin-terser": "^0.4.4",
|
|
125
|
-
"@types/node": "24.
|
|
125
|
+
"@types/node": "24.7.0",
|
|
126
126
|
"babel-loader": "^10.0.0",
|
|
127
|
-
"chalk": "^5.6.
|
|
127
|
+
"chalk": "^5.6.2",
|
|
128
128
|
"cli-table3": "^0.6.5",
|
|
129
|
-
"commander": "^14.0.
|
|
130
|
-
"esbuild": "^0.25.
|
|
131
|
-
"inquirer": "^12.9.
|
|
132
|
-
"jest": "^30.0
|
|
129
|
+
"commander": "^14.0.1",
|
|
130
|
+
"esbuild": "^0.25.10",
|
|
131
|
+
"inquirer": "^12.9.6",
|
|
132
|
+
"jest": "^30.2.0",
|
|
133
133
|
"node-loader": "^2.1.0",
|
|
134
|
-
"ora": "^
|
|
134
|
+
"ora": "^9.0.0",
|
|
135
135
|
"pkg": "^5.8.1",
|
|
136
|
-
"rollup": "^4.
|
|
136
|
+
"rollup": "^4.52.4",
|
|
137
137
|
"rollup-plugin-copy": "^3.5.0",
|
|
138
138
|
"rollup-plugin-esbuild": "^6.2.1",
|
|
139
139
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
|
140
140
|
"rollup-plugin-shebang-bin": "^0.1.0",
|
|
141
141
|
"rollup-plugin-terser": "^7.0.2",
|
|
142
|
-
"typescript": "5.9.
|
|
143
|
-
"webpack": "^5.
|
|
142
|
+
"typescript": "5.9.3",
|
|
143
|
+
"webpack": "^5.102.1",
|
|
144
144
|
"webpack-cli": "^6.0.1"
|
|
145
145
|
},
|
|
146
146
|
"funding": [
|
package/src/database.class.js
CHANGED
|
@@ -475,6 +475,7 @@ export class Database extends EventEmitter {
|
|
|
475
475
|
metadata.resources[name] = {
|
|
476
476
|
currentVersion: version,
|
|
477
477
|
partitions: resource.config.partitions || {},
|
|
478
|
+
createdBy: existingResource?.createdBy || resource.config.createdBy || 'user',
|
|
478
479
|
versions: {
|
|
479
480
|
...existingResource?.versions, // Preserve previous versions
|
|
480
481
|
[version]: {
|
|
@@ -906,6 +907,7 @@ export class Database extends EventEmitter {
|
|
|
906
907
|
* @param {boolean} [config.autoDecrypt=true] - Auto-decrypt secret fields
|
|
907
908
|
* @param {Function|number} [config.idGenerator] - Custom ID generator or size
|
|
908
909
|
* @param {number} [config.idSize=22] - Size for auto-generated IDs
|
|
910
|
+
* @param {string} [config.createdBy='user'] - Who created this resource ('user', 'plugin', or plugin name)
|
|
909
911
|
* @returns {Promise<Resource>} The created or updated resource
|
|
910
912
|
*/
|
|
911
913
|
async createResource({ name, attributes, behavior = 'user-managed', hooks, ...config }) {
|
|
@@ -968,7 +970,8 @@ export class Database extends EventEmitter {
|
|
|
968
970
|
idGenerator: config.idGenerator,
|
|
969
971
|
idSize: config.idSize,
|
|
970
972
|
asyncEvents: config.asyncEvents,
|
|
971
|
-
events: config.events || {}
|
|
973
|
+
events: config.events || {},
|
|
974
|
+
createdBy: config.createdBy || 'user'
|
|
972
975
|
});
|
|
973
976
|
resource.database = this;
|
|
974
977
|
this.resources[name] = resource;
|
|
@@ -113,7 +113,15 @@ export class CachePlugin extends Plugin {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
shouldCacheResource(resourceName) {
|
|
116
|
-
//
|
|
116
|
+
// Get resource metadata to check createdBy
|
|
117
|
+
const resourceMetadata = this.database.savedMetadata?.resources?.[resourceName];
|
|
118
|
+
|
|
119
|
+
// Skip plugin-created resources by default (unless explicitly included)
|
|
120
|
+
if (resourceMetadata?.createdBy && resourceMetadata.createdBy !== 'user' && !this.config.include) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Legacy: Skip plugin resources by name pattern (unless explicitly included)
|
|
117
125
|
if (resourceName.startsWith('plg_') && !this.config.include) {
|
|
118
126
|
return false;
|
|
119
127
|
}
|
|
@@ -144,7 +144,8 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
144
144
|
behavior: 'body-overflow',
|
|
145
145
|
timestamps: true,
|
|
146
146
|
partitions: partitionConfig,
|
|
147
|
-
asyncPartitions: true // Use async partitions for better performance
|
|
147
|
+
asyncPartitions: true, // Use async partitions for better performance
|
|
148
|
+
createdBy: 'EventualConsistencyPlugin'
|
|
148
149
|
})
|
|
149
150
|
);
|
|
150
151
|
|
|
@@ -165,7 +166,8 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
165
166
|
workerId: 'string|optional'
|
|
166
167
|
},
|
|
167
168
|
behavior: 'body-only',
|
|
168
|
-
timestamps: false
|
|
169
|
+
timestamps: false,
|
|
170
|
+
createdBy: 'EventualConsistencyPlugin'
|
|
169
171
|
})
|
|
170
172
|
);
|
|
171
173
|
|
|
@@ -186,10 +188,33 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
186
188
|
// Setup consolidation if enabled
|
|
187
189
|
if (this.config.autoConsolidate) {
|
|
188
190
|
this.startConsolidationTimer();
|
|
191
|
+
if (this.config.verbose) {
|
|
192
|
+
console.log(
|
|
193
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
194
|
+
`Auto-consolidation ENABLED (interval: ${this.config.consolidationInterval}s, ` +
|
|
195
|
+
`window: ${this.config.consolidationWindow}h, mode: ${this.config.mode})`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
if (this.config.verbose) {
|
|
200
|
+
console.log(
|
|
201
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
202
|
+
`Auto-consolidation DISABLED (manual consolidation only)`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
// Setup garbage collection timer
|
|
192
208
|
this.startGarbageCollectionTimer();
|
|
209
|
+
|
|
210
|
+
if (this.config.verbose) {
|
|
211
|
+
console.log(
|
|
212
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
213
|
+
`Setup complete. Resources: ${this.config.resource}_transactions_${this.config.field}, ` +
|
|
214
|
+
`${this.config.resource}_consolidation_locks_${this.config.field}` +
|
|
215
|
+
`${this.config.enableAnalytics ? `, ${this.config.resource}_analytics_${this.config.field}` : ''}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
193
218
|
}
|
|
194
219
|
|
|
195
220
|
async onStart() {
|
|
@@ -286,7 +311,8 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
286
311
|
byCohort: {
|
|
287
312
|
fields: { cohort: 'string' }
|
|
288
313
|
}
|
|
289
|
-
}
|
|
314
|
+
},
|
|
315
|
+
createdBy: 'EventualConsistencyPlugin'
|
|
290
316
|
})
|
|
291
317
|
);
|
|
292
318
|
|
|
@@ -539,15 +565,31 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
539
565
|
// Batch transactions if configured
|
|
540
566
|
if (this.config.batchTransactions) {
|
|
541
567
|
this.pendingTransactions.set(transaction.id, transaction);
|
|
542
|
-
|
|
568
|
+
|
|
569
|
+
if (this.config.verbose) {
|
|
570
|
+
console.log(
|
|
571
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
572
|
+
`Transaction batched: ${data.operation} ${data.value} for ${data.originalId} ` +
|
|
573
|
+
`(batch: ${this.pendingTransactions.size}/${this.config.batchSize})`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
543
577
|
// Flush if batch size reached
|
|
544
578
|
if (this.pendingTransactions.size >= this.config.batchSize) {
|
|
545
579
|
await this.flushPendingTransactions();
|
|
546
580
|
}
|
|
547
581
|
} else {
|
|
548
582
|
await this.transactionResource.insert(transaction);
|
|
583
|
+
|
|
584
|
+
if (this.config.verbose) {
|
|
585
|
+
console.log(
|
|
586
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
587
|
+
`Transaction created: ${data.operation} ${data.value} for ${data.originalId} ` +
|
|
588
|
+
`(cohort: ${cohortInfo.hour}, applied: false)`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
549
591
|
}
|
|
550
|
-
|
|
592
|
+
|
|
551
593
|
return transaction;
|
|
552
594
|
}
|
|
553
595
|
|
|
@@ -636,12 +678,30 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
636
678
|
startConsolidationTimer() {
|
|
637
679
|
const intervalMs = this.config.consolidationInterval * 1000; // Convert seconds to ms
|
|
638
680
|
|
|
681
|
+
if (this.config.verbose) {
|
|
682
|
+
const nextRun = new Date(Date.now() + intervalMs);
|
|
683
|
+
console.log(
|
|
684
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
685
|
+
`Consolidation timer started. Next run at ${nextRun.toISOString()} ` +
|
|
686
|
+
`(every ${this.config.consolidationInterval}s)`
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
639
690
|
this.consolidationTimer = setInterval(async () => {
|
|
640
691
|
await this.runConsolidation();
|
|
641
692
|
}, intervalMs);
|
|
642
693
|
}
|
|
643
694
|
|
|
644
695
|
async runConsolidation() {
|
|
696
|
+
const startTime = Date.now();
|
|
697
|
+
|
|
698
|
+
if (this.config.verbose) {
|
|
699
|
+
console.log(
|
|
700
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
701
|
+
`Starting consolidation run at ${new Date().toISOString()}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
645
705
|
try {
|
|
646
706
|
// Query unapplied transactions from recent cohorts (last 24 hours by default)
|
|
647
707
|
// This uses hourly partition for O(1) performance instead of full scan
|
|
@@ -655,6 +715,13 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
655
715
|
cohortHours.push(cohortInfo.hour);
|
|
656
716
|
}
|
|
657
717
|
|
|
718
|
+
if (this.config.verbose) {
|
|
719
|
+
console.log(
|
|
720
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
721
|
+
`Querying ${hoursToCheck} hour partitions for pending transactions...`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
658
725
|
// Query transactions by partition for each hour (parallel for speed)
|
|
659
726
|
const transactionsByHour = await Promise.all(
|
|
660
727
|
cohortHours.map(async (cohortHour) => {
|
|
@@ -673,7 +740,10 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
673
740
|
|
|
674
741
|
if (transactions.length === 0) {
|
|
675
742
|
if (this.config.verbose) {
|
|
676
|
-
console.log(
|
|
743
|
+
console.log(
|
|
744
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
745
|
+
`No pending transactions found. Next run in ${this.config.consolidationInterval}s`
|
|
746
|
+
);
|
|
677
747
|
}
|
|
678
748
|
return;
|
|
679
749
|
}
|
|
@@ -681,6 +751,14 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
681
751
|
// Get unique originalIds
|
|
682
752
|
const uniqueIds = [...new Set(transactions.map(t => t.originalId))];
|
|
683
753
|
|
|
754
|
+
if (this.config.verbose) {
|
|
755
|
+
console.log(
|
|
756
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
757
|
+
`Found ${transactions.length} pending transactions for ${uniqueIds.length} records. ` +
|
|
758
|
+
`Consolidating with concurrency=${this.config.consolidationConcurrency}...`
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
|
|
684
762
|
// Consolidate each record in parallel with concurrency limit
|
|
685
763
|
const { results, errors } = await PromisePool
|
|
686
764
|
.for(uniqueIds)
|
|
@@ -689,8 +767,22 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
689
767
|
return await this.consolidateRecord(id);
|
|
690
768
|
});
|
|
691
769
|
|
|
770
|
+
const duration = Date.now() - startTime;
|
|
771
|
+
|
|
692
772
|
if (errors && errors.length > 0) {
|
|
693
|
-
console.error(
|
|
773
|
+
console.error(
|
|
774
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
775
|
+
`Consolidation completed with ${errors.length} errors in ${duration}ms:`,
|
|
776
|
+
errors
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (this.config.verbose) {
|
|
781
|
+
console.log(
|
|
782
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
783
|
+
`Consolidation complete: ${results.length} records consolidated in ${duration}ms ` +
|
|
784
|
+
`(${errors.length} errors). Next run in ${this.config.consolidationInterval}s`
|
|
785
|
+
);
|
|
694
786
|
}
|
|
695
787
|
|
|
696
788
|
this.emit('eventual-consistency.consolidated', {
|
|
@@ -698,10 +790,16 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
698
790
|
field: this.config.field,
|
|
699
791
|
recordCount: uniqueIds.length,
|
|
700
792
|
successCount: results.length,
|
|
701
|
-
errorCount: errors.length
|
|
793
|
+
errorCount: errors.length,
|
|
794
|
+
duration
|
|
702
795
|
});
|
|
703
796
|
} catch (error) {
|
|
704
|
-
|
|
797
|
+
const duration = Date.now() - startTime;
|
|
798
|
+
console.error(
|
|
799
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
800
|
+
`Consolidation error after ${duration}ms:`,
|
|
801
|
+
error
|
|
802
|
+
);
|
|
705
803
|
this.emit('eventual-consistency.consolidation-error', error);
|
|
706
804
|
}
|
|
707
805
|
}
|
|
@@ -749,9 +847,23 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
749
847
|
);
|
|
750
848
|
|
|
751
849
|
if (!ok || !transactions || transactions.length === 0) {
|
|
850
|
+
if (this.config.verbose) {
|
|
851
|
+
console.log(
|
|
852
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
853
|
+
`No pending transactions for ${originalId}, skipping`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
752
856
|
return currentValue;
|
|
753
857
|
}
|
|
754
858
|
|
|
859
|
+
if (this.config.verbose) {
|
|
860
|
+
console.log(
|
|
861
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
862
|
+
`Consolidating ${originalId}: ${transactions.length} pending transactions ` +
|
|
863
|
+
`(current: ${currentValue})`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
755
867
|
// Sort transactions by timestamp
|
|
756
868
|
transactions.sort((a, b) =>
|
|
757
869
|
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
@@ -766,6 +878,14 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
766
878
|
// Apply reducer to get consolidated value
|
|
767
879
|
const consolidatedValue = this.config.reducer(transactions);
|
|
768
880
|
|
|
881
|
+
if (this.config.verbose) {
|
|
882
|
+
console.log(
|
|
883
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
884
|
+
`${originalId}: ${currentValue} → ${consolidatedValue} ` +
|
|
885
|
+
`(${consolidatedValue > currentValue ? '+' : ''}${consolidatedValue - currentValue})`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
769
889
|
// Update the original record
|
|
770
890
|
const [updateOk, updateErr] = await tryFn(() =>
|
|
771
891
|
this.targetResource.update(originalId, {
|
|
@@ -800,6 +920,29 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
800
920
|
if (this.config.enableAnalytics && transactionsToUpdate.length > 0) {
|
|
801
921
|
await this.updateAnalytics(transactionsToUpdate);
|
|
802
922
|
}
|
|
923
|
+
|
|
924
|
+
// Invalidate cache for this record after consolidation
|
|
925
|
+
if (this.targetResource.cache && typeof this.targetResource.cache.delete === 'function') {
|
|
926
|
+
try {
|
|
927
|
+
const cacheKey = await this.targetResource.cacheKeyFor({ id: originalId });
|
|
928
|
+
await this.targetResource.cache.delete(cacheKey);
|
|
929
|
+
|
|
930
|
+
if (this.config.verbose) {
|
|
931
|
+
console.log(
|
|
932
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
933
|
+
`Cache invalidated for ${originalId}`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
} catch (cacheErr) {
|
|
937
|
+
// Log but don't fail consolidation if cache invalidation fails
|
|
938
|
+
if (this.config.verbose) {
|
|
939
|
+
console.warn(
|
|
940
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
941
|
+
`Failed to invalidate cache for ${originalId}: ${cacheErr?.message}`
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
803
946
|
}
|
|
804
947
|
|
|
805
948
|
return consolidatedValue;
|
|
@@ -1087,9 +1230,24 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
1087
1230
|
async updateAnalytics(transactions) {
|
|
1088
1231
|
if (!this.analyticsResource || transactions.length === 0) return;
|
|
1089
1232
|
|
|
1233
|
+
if (this.config.verbose) {
|
|
1234
|
+
console.log(
|
|
1235
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
1236
|
+
`Updating analytics for ${transactions.length} transactions...`
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1090
1240
|
try {
|
|
1091
1241
|
// Group transactions by cohort hour
|
|
1092
1242
|
const byHour = this._groupByCohort(transactions, 'cohortHour');
|
|
1243
|
+
const cohortCount = Object.keys(byHour).length;
|
|
1244
|
+
|
|
1245
|
+
if (this.config.verbose) {
|
|
1246
|
+
console.log(
|
|
1247
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
1248
|
+
`Updating ${cohortCount} hourly analytics cohorts...`
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1093
1251
|
|
|
1094
1252
|
// Update hourly analytics
|
|
1095
1253
|
for (const [cohort, txns] of Object.entries(byHour)) {
|
|
@@ -1099,14 +1257,31 @@ export class EventualConsistencyPlugin extends Plugin {
|
|
|
1099
1257
|
// Roll up to daily and monthly if configured
|
|
1100
1258
|
if (this.config.analyticsConfig.rollupStrategy === 'incremental') {
|
|
1101
1259
|
const uniqueHours = Object.keys(byHour);
|
|
1260
|
+
|
|
1261
|
+
if (this.config.verbose) {
|
|
1262
|
+
console.log(
|
|
1263
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
1264
|
+
`Rolling up ${uniqueHours.length} hours to daily/monthly analytics...`
|
|
1265
|
+
);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1102
1268
|
for (const cohortHour of uniqueHours) {
|
|
1103
1269
|
await this._rollupAnalytics(cohortHour);
|
|
1104
1270
|
}
|
|
1105
1271
|
}
|
|
1106
|
-
|
|
1272
|
+
|
|
1107
1273
|
if (this.config.verbose) {
|
|
1108
|
-
console.
|
|
1274
|
+
console.log(
|
|
1275
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
1276
|
+
`Analytics update complete for ${cohortCount} cohorts`
|
|
1277
|
+
);
|
|
1109
1278
|
}
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
console.warn(
|
|
1281
|
+
`[EventualConsistency] ${this.config.resource}.${this.config.field} - ` +
|
|
1282
|
+
`Analytics update error:`,
|
|
1283
|
+
error.message
|
|
1284
|
+
);
|
|
1110
1285
|
}
|
|
1111
1286
|
}
|
|
1112
1287
|
|
package/src/resource.class.js
CHANGED
|
@@ -136,7 +136,8 @@ export class Resource extends AsyncEventEmitter {
|
|
|
136
136
|
versioningEnabled = false,
|
|
137
137
|
events = {},
|
|
138
138
|
asyncEvents = true,
|
|
139
|
-
asyncPartitions = true
|
|
139
|
+
asyncPartitions = true,
|
|
140
|
+
createdBy = 'user'
|
|
140
141
|
} = config;
|
|
141
142
|
|
|
142
143
|
// Set instance properties
|
|
@@ -179,6 +180,7 @@ export class Resource extends AsyncEventEmitter {
|
|
|
179
180
|
allNestedObjectsOptional,
|
|
180
181
|
asyncEvents,
|
|
181
182
|
asyncPartitions,
|
|
183
|
+
createdBy,
|
|
182
184
|
};
|
|
183
185
|
|
|
184
186
|
// Initialize hooks system
|