s3db.js 10.0.12 → 10.0.13
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 +241 -35
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +241 -35
- package/dist/s3db.es.js.map +1 -1
- package/package.json +5 -5
- package/src/plugins/eventual-consistency.plugin.js +308 -45
package/dist/s3db.cjs.js
CHANGED
|
@@ -4549,6 +4549,12 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
4549
4549
|
for (const [resourceName, fieldHandlers] of this.fieldHandlers) {
|
|
4550
4550
|
for (const [fieldName, handler] of fieldHandlers) {
|
|
4551
4551
|
if (!handler.deferredSetup) {
|
|
4552
|
+
if (this.config.autoConsolidate && this.config.mode === "async") {
|
|
4553
|
+
this.startConsolidationTimerForHandler(handler, resourceName, fieldName);
|
|
4554
|
+
}
|
|
4555
|
+
if (this.config.transactionRetention && this.config.transactionRetention > 0) {
|
|
4556
|
+
this.startGarbageCollectionTimerForHandler(handler, resourceName, fieldName);
|
|
4557
|
+
}
|
|
4552
4558
|
this.emit("eventual-consistency.started", {
|
|
4553
4559
|
resource: resourceName,
|
|
4554
4560
|
field: fieldName,
|
|
@@ -4662,68 +4668,189 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
4662
4668
|
const resource = firstHandler.targetResource;
|
|
4663
4669
|
const plugin = this;
|
|
4664
4670
|
resource.set = async (id, field, value) => {
|
|
4665
|
-
const { plugin:
|
|
4666
|
-
|
|
4671
|
+
const { plugin: handler } = plugin._resolveFieldAndPlugin(resource, field, value);
|
|
4672
|
+
const now = /* @__PURE__ */ new Date();
|
|
4673
|
+
const cohortInfo = plugin.getCohortInfo(now);
|
|
4674
|
+
const transaction = {
|
|
4675
|
+
id: idGenerator(),
|
|
4667
4676
|
originalId: id,
|
|
4668
|
-
|
|
4677
|
+
field: handler.field,
|
|
4669
4678
|
value,
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4679
|
+
operation: "set",
|
|
4680
|
+
timestamp: now.toISOString(),
|
|
4681
|
+
cohortDate: cohortInfo.date,
|
|
4682
|
+
cohortHour: cohortInfo.hour,
|
|
4683
|
+
cohortMonth: cohortInfo.month,
|
|
4684
|
+
source: "set",
|
|
4685
|
+
applied: false
|
|
4686
|
+
};
|
|
4687
|
+
await handler.transactionResource.insert(transaction);
|
|
4688
|
+
if (plugin.config.mode === "sync") {
|
|
4689
|
+
const oldResource = plugin.config.resource;
|
|
4690
|
+
const oldField = plugin.config.field;
|
|
4691
|
+
const oldTransactionResource = plugin.transactionResource;
|
|
4692
|
+
const oldTargetResource = plugin.targetResource;
|
|
4693
|
+
const oldLockResource = plugin.lockResource;
|
|
4694
|
+
const oldAnalyticsResource = plugin.analyticsResource;
|
|
4695
|
+
plugin.config.resource = handler.resource;
|
|
4696
|
+
plugin.config.field = handler.field;
|
|
4697
|
+
plugin.transactionResource = handler.transactionResource;
|
|
4698
|
+
plugin.targetResource = handler.targetResource;
|
|
4699
|
+
plugin.lockResource = handler.lockResource;
|
|
4700
|
+
plugin.analyticsResource = handler.analyticsResource;
|
|
4701
|
+
const result = await plugin._syncModeConsolidate(id, field);
|
|
4702
|
+
plugin.config.resource = oldResource;
|
|
4703
|
+
plugin.config.field = oldField;
|
|
4704
|
+
plugin.transactionResource = oldTransactionResource;
|
|
4705
|
+
plugin.targetResource = oldTargetResource;
|
|
4706
|
+
plugin.lockResource = oldLockResource;
|
|
4707
|
+
plugin.analyticsResource = oldAnalyticsResource;
|
|
4708
|
+
return result;
|
|
4674
4709
|
}
|
|
4675
4710
|
return value;
|
|
4676
4711
|
};
|
|
4677
4712
|
resource.add = async (id, field, amount) => {
|
|
4678
|
-
const { plugin:
|
|
4679
|
-
|
|
4713
|
+
const { plugin: handler } = plugin._resolveFieldAndPlugin(resource, field, amount);
|
|
4714
|
+
const now = /* @__PURE__ */ new Date();
|
|
4715
|
+
const cohortInfo = plugin.getCohortInfo(now);
|
|
4716
|
+
const transaction = {
|
|
4717
|
+
id: idGenerator(),
|
|
4680
4718
|
originalId: id,
|
|
4681
|
-
|
|
4719
|
+
field: handler.field,
|
|
4682
4720
|
value: amount,
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4721
|
+
operation: "add",
|
|
4722
|
+
timestamp: now.toISOString(),
|
|
4723
|
+
cohortDate: cohortInfo.date,
|
|
4724
|
+
cohortHour: cohortInfo.hour,
|
|
4725
|
+
cohortMonth: cohortInfo.month,
|
|
4726
|
+
source: "add",
|
|
4727
|
+
applied: false
|
|
4728
|
+
};
|
|
4729
|
+
await handler.transactionResource.insert(transaction);
|
|
4730
|
+
if (plugin.config.mode === "sync") {
|
|
4731
|
+
const oldResource = plugin.config.resource;
|
|
4732
|
+
const oldField = plugin.config.field;
|
|
4733
|
+
const oldTransactionResource = plugin.transactionResource;
|
|
4734
|
+
const oldTargetResource = plugin.targetResource;
|
|
4735
|
+
const oldLockResource = plugin.lockResource;
|
|
4736
|
+
const oldAnalyticsResource = plugin.analyticsResource;
|
|
4737
|
+
plugin.config.resource = handler.resource;
|
|
4738
|
+
plugin.config.field = handler.field;
|
|
4739
|
+
plugin.transactionResource = handler.transactionResource;
|
|
4740
|
+
plugin.targetResource = handler.targetResource;
|
|
4741
|
+
plugin.lockResource = handler.lockResource;
|
|
4742
|
+
plugin.analyticsResource = handler.analyticsResource;
|
|
4743
|
+
const result = await plugin._syncModeConsolidate(id, field);
|
|
4744
|
+
plugin.config.resource = oldResource;
|
|
4745
|
+
plugin.config.field = oldField;
|
|
4746
|
+
plugin.transactionResource = oldTransactionResource;
|
|
4747
|
+
plugin.targetResource = oldTargetResource;
|
|
4748
|
+
plugin.lockResource = oldLockResource;
|
|
4749
|
+
plugin.analyticsResource = oldAnalyticsResource;
|
|
4750
|
+
return result;
|
|
4687
4751
|
}
|
|
4688
|
-
const
|
|
4752
|
+
const [ok, err, record] = await tryFn(() => handler.targetResource.get(id));
|
|
4753
|
+
const currentValue = ok && record ? record[field] || 0 : 0;
|
|
4689
4754
|
return currentValue + amount;
|
|
4690
4755
|
};
|
|
4691
4756
|
resource.sub = async (id, field, amount) => {
|
|
4692
|
-
const { plugin:
|
|
4693
|
-
|
|
4757
|
+
const { plugin: handler } = plugin._resolveFieldAndPlugin(resource, field, amount);
|
|
4758
|
+
const now = /* @__PURE__ */ new Date();
|
|
4759
|
+
const cohortInfo = plugin.getCohortInfo(now);
|
|
4760
|
+
const transaction = {
|
|
4761
|
+
id: idGenerator(),
|
|
4694
4762
|
originalId: id,
|
|
4695
|
-
|
|
4763
|
+
field: handler.field,
|
|
4696
4764
|
value: amount,
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4765
|
+
operation: "sub",
|
|
4766
|
+
timestamp: now.toISOString(),
|
|
4767
|
+
cohortDate: cohortInfo.date,
|
|
4768
|
+
cohortHour: cohortInfo.hour,
|
|
4769
|
+
cohortMonth: cohortInfo.month,
|
|
4770
|
+
source: "sub",
|
|
4771
|
+
applied: false
|
|
4772
|
+
};
|
|
4773
|
+
await handler.transactionResource.insert(transaction);
|
|
4774
|
+
if (plugin.config.mode === "sync") {
|
|
4775
|
+
const oldResource = plugin.config.resource;
|
|
4776
|
+
const oldField = plugin.config.field;
|
|
4777
|
+
const oldTransactionResource = plugin.transactionResource;
|
|
4778
|
+
const oldTargetResource = plugin.targetResource;
|
|
4779
|
+
const oldLockResource = plugin.lockResource;
|
|
4780
|
+
const oldAnalyticsResource = plugin.analyticsResource;
|
|
4781
|
+
plugin.config.resource = handler.resource;
|
|
4782
|
+
plugin.config.field = handler.field;
|
|
4783
|
+
plugin.transactionResource = handler.transactionResource;
|
|
4784
|
+
plugin.targetResource = handler.targetResource;
|
|
4785
|
+
plugin.lockResource = handler.lockResource;
|
|
4786
|
+
plugin.analyticsResource = handler.analyticsResource;
|
|
4787
|
+
const result = await plugin._syncModeConsolidate(id, field);
|
|
4788
|
+
plugin.config.resource = oldResource;
|
|
4789
|
+
plugin.config.field = oldField;
|
|
4790
|
+
plugin.transactionResource = oldTransactionResource;
|
|
4791
|
+
plugin.targetResource = oldTargetResource;
|
|
4792
|
+
plugin.lockResource = oldLockResource;
|
|
4793
|
+
plugin.analyticsResource = oldAnalyticsResource;
|
|
4794
|
+
return result;
|
|
4701
4795
|
}
|
|
4702
|
-
const
|
|
4796
|
+
const [ok, err, record] = await tryFn(() => handler.targetResource.get(id));
|
|
4797
|
+
const currentValue = ok && record ? record[field] || 0 : 0;
|
|
4703
4798
|
return currentValue - amount;
|
|
4704
4799
|
};
|
|
4705
4800
|
resource.consolidate = async (id, field) => {
|
|
4706
4801
|
if (!field) {
|
|
4707
4802
|
throw new Error(`Field parameter is required: consolidate(id, field)`);
|
|
4708
4803
|
}
|
|
4709
|
-
const
|
|
4710
|
-
if (!
|
|
4804
|
+
const handler = resource._eventualConsistencyPlugins[field];
|
|
4805
|
+
if (!handler) {
|
|
4711
4806
|
const availableFields = Object.keys(resource._eventualConsistencyPlugins).join(", ");
|
|
4712
4807
|
throw new Error(
|
|
4713
4808
|
`No eventual consistency plugin found for field "${field}". Available fields: ${availableFields}`
|
|
4714
4809
|
);
|
|
4715
4810
|
}
|
|
4716
|
-
|
|
4811
|
+
const oldResource = plugin.config.resource;
|
|
4812
|
+
const oldField = plugin.config.field;
|
|
4813
|
+
const oldTransactionResource = plugin.transactionResource;
|
|
4814
|
+
const oldTargetResource = plugin.targetResource;
|
|
4815
|
+
const oldLockResource = plugin.lockResource;
|
|
4816
|
+
const oldAnalyticsResource = plugin.analyticsResource;
|
|
4817
|
+
plugin.config.resource = handler.resource;
|
|
4818
|
+
plugin.config.field = handler.field;
|
|
4819
|
+
plugin.transactionResource = handler.transactionResource;
|
|
4820
|
+
plugin.targetResource = handler.targetResource;
|
|
4821
|
+
plugin.lockResource = handler.lockResource;
|
|
4822
|
+
plugin.analyticsResource = handler.analyticsResource;
|
|
4823
|
+
const result = await plugin.consolidateRecord(id);
|
|
4824
|
+
plugin.config.resource = oldResource;
|
|
4825
|
+
plugin.config.field = oldField;
|
|
4826
|
+
plugin.transactionResource = oldTransactionResource;
|
|
4827
|
+
plugin.targetResource = oldTargetResource;
|
|
4828
|
+
plugin.lockResource = oldLockResource;
|
|
4829
|
+
plugin.analyticsResource = oldAnalyticsResource;
|
|
4830
|
+
return result;
|
|
4717
4831
|
};
|
|
4718
4832
|
resource.getConsolidatedValue = async (id, field, options = {}) => {
|
|
4719
|
-
const
|
|
4720
|
-
if (!
|
|
4833
|
+
const handler = resource._eventualConsistencyPlugins[field];
|
|
4834
|
+
if (!handler) {
|
|
4721
4835
|
const availableFields = Object.keys(resource._eventualConsistencyPlugins).join(", ");
|
|
4722
4836
|
throw new Error(
|
|
4723
4837
|
`No eventual consistency plugin found for field "${field}". Available fields: ${availableFields}`
|
|
4724
4838
|
);
|
|
4725
4839
|
}
|
|
4726
|
-
|
|
4840
|
+
const oldResource = plugin.config.resource;
|
|
4841
|
+
const oldField = plugin.config.field;
|
|
4842
|
+
const oldTransactionResource = plugin.transactionResource;
|
|
4843
|
+
const oldTargetResource = plugin.targetResource;
|
|
4844
|
+
plugin.config.resource = handler.resource;
|
|
4845
|
+
plugin.config.field = handler.field;
|
|
4846
|
+
plugin.transactionResource = handler.transactionResource;
|
|
4847
|
+
plugin.targetResource = handler.targetResource;
|
|
4848
|
+
const result = await plugin.getConsolidatedValue(id, options);
|
|
4849
|
+
plugin.config.resource = oldResource;
|
|
4850
|
+
plugin.config.field = oldField;
|
|
4851
|
+
plugin.transactionResource = oldTransactionResource;
|
|
4852
|
+
plugin.targetResource = oldTargetResource;
|
|
4853
|
+
return result;
|
|
4727
4854
|
};
|
|
4728
4855
|
}
|
|
4729
4856
|
async createTransaction(handler, data) {
|
|
@@ -4851,6 +4978,42 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
4851
4978
|
await this.runConsolidation();
|
|
4852
4979
|
}, intervalMs);
|
|
4853
4980
|
}
|
|
4981
|
+
startConsolidationTimerForHandler(handler, resourceName, fieldName) {
|
|
4982
|
+
const intervalMs = this.config.consolidationInterval * 1e3;
|
|
4983
|
+
if (this.config.verbose) {
|
|
4984
|
+
const nextRun = new Date(Date.now() + intervalMs);
|
|
4985
|
+
console.log(
|
|
4986
|
+
`[EventualConsistency] ${resourceName}.${fieldName} - Consolidation timer started. Next run at ${nextRun.toISOString()} (every ${this.config.consolidationInterval}s)`
|
|
4987
|
+
);
|
|
4988
|
+
}
|
|
4989
|
+
handler.consolidationTimer = setInterval(async () => {
|
|
4990
|
+
await this.runConsolidationForHandler(handler, resourceName, fieldName);
|
|
4991
|
+
}, intervalMs);
|
|
4992
|
+
}
|
|
4993
|
+
async runConsolidationForHandler(handler, resourceName, fieldName) {
|
|
4994
|
+
const oldResource = this.config.resource;
|
|
4995
|
+
const oldField = this.config.field;
|
|
4996
|
+
const oldTransactionResource = this.transactionResource;
|
|
4997
|
+
const oldTargetResource = this.targetResource;
|
|
4998
|
+
const oldLockResource = this.lockResource;
|
|
4999
|
+
const oldAnalyticsResource = this.analyticsResource;
|
|
5000
|
+
this.config.resource = resourceName;
|
|
5001
|
+
this.config.field = fieldName;
|
|
5002
|
+
this.transactionResource = handler.transactionResource;
|
|
5003
|
+
this.targetResource = handler.targetResource;
|
|
5004
|
+
this.lockResource = handler.lockResource;
|
|
5005
|
+
this.analyticsResource = handler.analyticsResource;
|
|
5006
|
+
try {
|
|
5007
|
+
await this.runConsolidation();
|
|
5008
|
+
} finally {
|
|
5009
|
+
this.config.resource = oldResource;
|
|
5010
|
+
this.config.field = oldField;
|
|
5011
|
+
this.transactionResource = oldTransactionResource;
|
|
5012
|
+
this.targetResource = oldTargetResource;
|
|
5013
|
+
this.lockResource = oldLockResource;
|
|
5014
|
+
this.analyticsResource = oldAnalyticsResource;
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
4854
5017
|
async runConsolidation() {
|
|
4855
5018
|
const startTime = Date.now();
|
|
4856
5019
|
if (this.config.verbose) {
|
|
@@ -5048,7 +5211,7 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5048
5211
|
if (this.config.enableAnalytics && transactionsToUpdate.length > 0) {
|
|
5049
5212
|
await this.updateAnalytics(transactionsToUpdate);
|
|
5050
5213
|
}
|
|
5051
|
-
if (this.targetResource.cache && typeof this.targetResource.cache.delete === "function") {
|
|
5214
|
+
if (this.targetResource && this.targetResource.cache && typeof this.targetResource.cache.delete === "function") {
|
|
5052
5215
|
try {
|
|
5053
5216
|
const cacheKey = await this.targetResource.cacheKeyFor({ id: originalId });
|
|
5054
5217
|
await this.targetResource.cache.delete(cacheKey);
|
|
@@ -5201,6 +5364,33 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5201
5364
|
await this.runGarbageCollection();
|
|
5202
5365
|
}, gcIntervalMs);
|
|
5203
5366
|
}
|
|
5367
|
+
startGarbageCollectionTimerForHandler(handler, resourceName, fieldName) {
|
|
5368
|
+
const gcIntervalMs = this.config.gcInterval * 1e3;
|
|
5369
|
+
handler.gcTimer = setInterval(async () => {
|
|
5370
|
+
await this.runGarbageCollectionForHandler(handler, resourceName, fieldName);
|
|
5371
|
+
}, gcIntervalMs);
|
|
5372
|
+
}
|
|
5373
|
+
async runGarbageCollectionForHandler(handler, resourceName, fieldName) {
|
|
5374
|
+
const oldResource = this.config.resource;
|
|
5375
|
+
const oldField = this.config.field;
|
|
5376
|
+
const oldTransactionResource = this.transactionResource;
|
|
5377
|
+
const oldTargetResource = this.targetResource;
|
|
5378
|
+
const oldLockResource = this.lockResource;
|
|
5379
|
+
this.config.resource = resourceName;
|
|
5380
|
+
this.config.field = fieldName;
|
|
5381
|
+
this.transactionResource = handler.transactionResource;
|
|
5382
|
+
this.targetResource = handler.targetResource;
|
|
5383
|
+
this.lockResource = handler.lockResource;
|
|
5384
|
+
try {
|
|
5385
|
+
await this.runGarbageCollection();
|
|
5386
|
+
} finally {
|
|
5387
|
+
this.config.resource = oldResource;
|
|
5388
|
+
this.config.field = oldField;
|
|
5389
|
+
this.transactionResource = oldTransactionResource;
|
|
5390
|
+
this.targetResource = oldTargetResource;
|
|
5391
|
+
this.lockResource = oldLockResource;
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5204
5394
|
/**
|
|
5205
5395
|
* Delete old applied transactions based on retention policy
|
|
5206
5396
|
* Uses distributed locking to prevent multiple containers from running GC simultaneously
|
|
@@ -5503,12 +5693,20 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5503
5693
|
* @returns {Promise<Array>} Analytics data
|
|
5504
5694
|
*/
|
|
5505
5695
|
async getAnalytics(resourceName, field, options = {}) {
|
|
5506
|
-
|
|
5696
|
+
const fieldHandlers = this.fieldHandlers.get(resourceName);
|
|
5697
|
+
if (!fieldHandlers) {
|
|
5698
|
+
throw new Error(`No eventual consistency configured for resource: ${resourceName}`);
|
|
5699
|
+
}
|
|
5700
|
+
const handler = fieldHandlers.get(field);
|
|
5701
|
+
if (!handler) {
|
|
5702
|
+
throw new Error(`No eventual consistency configured for field: ${resourceName}.${field}`);
|
|
5703
|
+
}
|
|
5704
|
+
if (!handler.analyticsResource) {
|
|
5507
5705
|
throw new Error("Analytics not enabled for this plugin");
|
|
5508
5706
|
}
|
|
5509
5707
|
const { period = "day", date, startDate, endDate, month, year, breakdown = false } = options;
|
|
5510
5708
|
const [ok, err, allAnalytics] = await tryFn(
|
|
5511
|
-
() =>
|
|
5709
|
+
() => handler.analyticsResource.list()
|
|
5512
5710
|
);
|
|
5513
5711
|
if (!ok || !allAnalytics) {
|
|
5514
5712
|
return [];
|
|
@@ -5734,12 +5932,20 @@ class EventualConsistencyPlugin extends Plugin {
|
|
|
5734
5932
|
* @returns {Promise<Array>} Top records
|
|
5735
5933
|
*/
|
|
5736
5934
|
async getTopRecords(resourceName, field, options = {}) {
|
|
5737
|
-
|
|
5935
|
+
const fieldHandlers = this.fieldHandlers.get(resourceName);
|
|
5936
|
+
if (!fieldHandlers) {
|
|
5937
|
+
throw new Error(`No eventual consistency configured for resource: ${resourceName}`);
|
|
5938
|
+
}
|
|
5939
|
+
const handler = fieldHandlers.get(field);
|
|
5940
|
+
if (!handler) {
|
|
5941
|
+
throw new Error(`No eventual consistency configured for field: ${resourceName}.${field}`);
|
|
5942
|
+
}
|
|
5943
|
+
if (!handler.transactionResource) {
|
|
5738
5944
|
throw new Error("Transaction resource not initialized");
|
|
5739
5945
|
}
|
|
5740
5946
|
const { period = "day", date, metric = "transactionCount", limit = 10 } = options;
|
|
5741
5947
|
const [ok, err, transactions] = await tryFn(
|
|
5742
|
-
() =>
|
|
5948
|
+
() => handler.transactionResource.list()
|
|
5743
5949
|
);
|
|
5744
5950
|
if (!ok || !transactions) {
|
|
5745
5951
|
return [];
|
|
@@ -11847,7 +12053,7 @@ class Database extends EventEmitter {
|
|
|
11847
12053
|
this.id = idGenerator(7);
|
|
11848
12054
|
this.version = "1";
|
|
11849
12055
|
this.s3dbVersion = (() => {
|
|
11850
|
-
const [ok, err, version] = tryFn(() => true ? "10.0.
|
|
12056
|
+
const [ok, err, version] = tryFn(() => true ? "10.0.13" : "latest");
|
|
11851
12057
|
return ok ? version : "latest";
|
|
11852
12058
|
})();
|
|
11853
12059
|
this.resources = {};
|