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