s3db.js 10.0.12 → 10.0.14

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 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: fieldPlugin } = plugin._resolveFieldAndPlugin(resource, field, value);
4666
- await fieldPlugin.createTransaction(fieldPlugin, {
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
- operation: "set",
4677
+ field: handler.field,
4669
4678
  value,
4670
- source: "set"
4671
- });
4672
- if (fieldPlugin.config.mode === "sync") {
4673
- return await fieldPlugin._syncModeConsolidate(fieldPlugin, id, field);
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: fieldPlugin } = plugin._resolveFieldAndPlugin(resource, field, amount);
4679
- await fieldPlugin.createTransaction(fieldPlugin, {
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
- operation: "add",
4719
+ field: handler.field,
4682
4720
  value: amount,
4683
- source: "add"
4684
- });
4685
- if (fieldPlugin.config.mode === "sync") {
4686
- return await fieldPlugin._syncModeConsolidate(fieldPlugin, id, field);
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 currentValue = await fieldPlugin.getConsolidatedValue(fieldPlugin, id);
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: fieldPlugin } = plugin._resolveFieldAndPlugin(resource, field, amount);
4693
- await fieldPlugin.createTransaction(fieldPlugin, {
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
- operation: "sub",
4763
+ field: handler.field,
4696
4764
  value: amount,
4697
- source: "sub"
4698
- });
4699
- if (fieldPlugin.config.mode === "sync") {
4700
- return await fieldPlugin._syncModeConsolidate(fieldPlugin, id, field);
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 currentValue = await fieldPlugin.getConsolidatedValue(fieldPlugin, id);
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 fieldPlugin = resource._eventualConsistencyPlugins[field];
4710
- if (!fieldPlugin) {
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
- return await fieldPlugin.consolidateRecord(fieldPlugin, id);
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 fieldPlugin = resource._eventualConsistencyPlugins[field];
4720
- if (!fieldPlugin) {
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
- return await fieldPlugin.getConsolidatedValue(fieldPlugin, id, options);
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) {
@@ -4950,10 +5113,6 @@ class EventualConsistencyPlugin extends Plugin {
4950
5113
  return recordOk && record ? record[this.config.field] || 0 : 0;
4951
5114
  }
4952
5115
  try {
4953
- const [recordOk, recordErr, record] = await tryFn(
4954
- () => this.targetResource.get(originalId)
4955
- );
4956
- const currentValue = recordOk && record ? record[this.config.field] || 0 : 0;
4957
5116
  const [ok, err, transactions] = await tryFn(
4958
5117
  () => this.transactionResource.query({
4959
5118
  originalId,
@@ -4961,16 +5120,119 @@ class EventualConsistencyPlugin extends Plugin {
4961
5120
  })
4962
5121
  );
4963
5122
  if (!ok || !transactions || transactions.length === 0) {
5123
+ const [recordOk, recordErr, record] = await tryFn(
5124
+ () => this.targetResource.get(originalId)
5125
+ );
5126
+ const currentValue2 = recordOk && record ? record[this.config.field] || 0 : 0;
4964
5127
  if (this.config.verbose) {
4965
5128
  console.log(
4966
5129
  `[EventualConsistency] ${this.config.resource}.${this.config.field} - No pending transactions for ${originalId}, skipping`
4967
5130
  );
4968
5131
  }
4969
- return currentValue;
5132
+ return currentValue2;
5133
+ }
5134
+ const [appliedOk, appliedErr, appliedTransactions] = await tryFn(
5135
+ () => this.transactionResource.query({
5136
+ originalId,
5137
+ applied: true
5138
+ })
5139
+ );
5140
+ let currentValue = 0;
5141
+ if (appliedOk && appliedTransactions && appliedTransactions.length > 0) {
5142
+ const [recordExistsOk, recordExistsErr, recordExists] = await tryFn(
5143
+ () => this.targetResource.get(originalId)
5144
+ );
5145
+ if (!recordExistsOk || !recordExists) {
5146
+ if (this.config.verbose) {
5147
+ console.log(
5148
+ `[EventualConsistency] ${this.config.resource}.${this.config.field} - Record ${originalId} doesn't exist, deleting ${appliedTransactions.length} old applied transactions`
5149
+ );
5150
+ }
5151
+ const { results, errors } = await promisePool.PromisePool.for(appliedTransactions).withConcurrency(10).process(async (txn) => {
5152
+ const [deleted] = await tryFn(() => this.transactionResource.delete(txn.id));
5153
+ return deleted;
5154
+ });
5155
+ if (this.config.verbose && errors && errors.length > 0) {
5156
+ console.warn(
5157
+ `[EventualConsistency] ${this.config.resource}.${this.config.field} - Failed to delete ${errors.length} old applied transactions`
5158
+ );
5159
+ }
5160
+ currentValue = 0;
5161
+ } else {
5162
+ appliedTransactions.sort(
5163
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
5164
+ );
5165
+ const hasSetInApplied = appliedTransactions.some((t) => t.operation === "set");
5166
+ if (!hasSetInApplied) {
5167
+ const recordValue = recordExists[this.config.field] || 0;
5168
+ let appliedDelta = 0;
5169
+ for (const t of appliedTransactions) {
5170
+ if (t.operation === "add") appliedDelta += t.value;
5171
+ else if (t.operation === "sub") appliedDelta -= t.value;
5172
+ }
5173
+ const baseValue = recordValue - appliedDelta;
5174
+ const hasExistingAnchor = appliedTransactions.some((t) => t.source === "anchor");
5175
+ if (baseValue !== 0 && !hasExistingAnchor) {
5176
+ const firstTransactionDate = new Date(appliedTransactions[0].timestamp);
5177
+ const cohortInfo = this.getCohortInfo(firstTransactionDate);
5178
+ const anchorTransaction = {
5179
+ id: idGenerator(),
5180
+ originalId,
5181
+ field: this.config.field,
5182
+ value: baseValue,
5183
+ operation: "set",
5184
+ timestamp: new Date(firstTransactionDate.getTime() - 1).toISOString(),
5185
+ // 1ms before first txn to ensure it's first
5186
+ cohortDate: cohortInfo.date,
5187
+ cohortHour: cohortInfo.hour,
5188
+ cohortMonth: cohortInfo.month,
5189
+ source: "anchor",
5190
+ applied: true
5191
+ };
5192
+ await this.transactionResource.insert(anchorTransaction);
5193
+ appliedTransactions.unshift(anchorTransaction);
5194
+ }
5195
+ }
5196
+ currentValue = this.config.reducer(appliedTransactions);
5197
+ }
5198
+ } else {
5199
+ const [recordOk, recordErr, record] = await tryFn(
5200
+ () => this.targetResource.get(originalId)
5201
+ );
5202
+ currentValue = recordOk && record ? record[this.config.field] || 0 : 0;
5203
+ if (currentValue !== 0) {
5204
+ let anchorTimestamp;
5205
+ if (transactions && transactions.length > 0) {
5206
+ const firstPendingDate = new Date(transactions[0].timestamp);
5207
+ anchorTimestamp = new Date(firstPendingDate.getTime() - 1).toISOString();
5208
+ } else {
5209
+ anchorTimestamp = (/* @__PURE__ */ new Date()).toISOString();
5210
+ }
5211
+ const cohortInfo = this.getCohortInfo(new Date(anchorTimestamp));
5212
+ const anchorTransaction = {
5213
+ id: idGenerator(),
5214
+ originalId,
5215
+ field: this.config.field,
5216
+ value: currentValue,
5217
+ operation: "set",
5218
+ timestamp: anchorTimestamp,
5219
+ cohortDate: cohortInfo.date,
5220
+ cohortHour: cohortInfo.hour,
5221
+ cohortMonth: cohortInfo.month,
5222
+ source: "anchor",
5223
+ applied: true
5224
+ };
5225
+ await this.transactionResource.insert(anchorTransaction);
5226
+ if (this.config.verbose) {
5227
+ console.log(
5228
+ `[EventualConsistency] ${this.config.resource}.${this.config.field} - Created anchor transaction for ${originalId} with base value ${currentValue}`
5229
+ );
5230
+ }
5231
+ }
4970
5232
  }
4971
5233
  if (this.config.verbose) {
4972
5234
  console.log(
4973
- `[EventualConsistency] ${this.config.resource}.${this.config.field} - Consolidating ${originalId}: ${transactions.length} pending transactions (current: ${currentValue})`
5235
+ `[EventualConsistency] ${this.config.resource}.${this.config.field} - Consolidating ${originalId}: ${transactions.length} pending transactions (current: ${currentValue} from ${appliedOk && appliedTransactions?.length > 0 ? "applied transactions" : "record"})`
4974
5236
  );
4975
5237
  }
4976
5238
  transactions.sort(
@@ -5048,7 +5310,7 @@ class EventualConsistencyPlugin extends Plugin {
5048
5310
  if (this.config.enableAnalytics && transactionsToUpdate.length > 0) {
5049
5311
  await this.updateAnalytics(transactionsToUpdate);
5050
5312
  }
5051
- if (this.targetResource.cache && typeof this.targetResource.cache.delete === "function") {
5313
+ if (this.targetResource && this.targetResource.cache && typeof this.targetResource.cache.delete === "function") {
5052
5314
  try {
5053
5315
  const cacheKey = await this.targetResource.cacheKeyFor({ id: originalId });
5054
5316
  await this.targetResource.cache.delete(cacheKey);
@@ -5201,6 +5463,33 @@ class EventualConsistencyPlugin extends Plugin {
5201
5463
  await this.runGarbageCollection();
5202
5464
  }, gcIntervalMs);
5203
5465
  }
5466
+ startGarbageCollectionTimerForHandler(handler, resourceName, fieldName) {
5467
+ const gcIntervalMs = this.config.gcInterval * 1e3;
5468
+ handler.gcTimer = setInterval(async () => {
5469
+ await this.runGarbageCollectionForHandler(handler, resourceName, fieldName);
5470
+ }, gcIntervalMs);
5471
+ }
5472
+ async runGarbageCollectionForHandler(handler, resourceName, fieldName) {
5473
+ const oldResource = this.config.resource;
5474
+ const oldField = this.config.field;
5475
+ const oldTransactionResource = this.transactionResource;
5476
+ const oldTargetResource = this.targetResource;
5477
+ const oldLockResource = this.lockResource;
5478
+ this.config.resource = resourceName;
5479
+ this.config.field = fieldName;
5480
+ this.transactionResource = handler.transactionResource;
5481
+ this.targetResource = handler.targetResource;
5482
+ this.lockResource = handler.lockResource;
5483
+ try {
5484
+ await this.runGarbageCollection();
5485
+ } finally {
5486
+ this.config.resource = oldResource;
5487
+ this.config.field = oldField;
5488
+ this.transactionResource = oldTransactionResource;
5489
+ this.targetResource = oldTargetResource;
5490
+ this.lockResource = oldLockResource;
5491
+ }
5492
+ }
5204
5493
  /**
5205
5494
  * Delete old applied transactions based on retention policy
5206
5495
  * Uses distributed locking to prevent multiple containers from running GC simultaneously
@@ -5503,12 +5792,20 @@ class EventualConsistencyPlugin extends Plugin {
5503
5792
  * @returns {Promise<Array>} Analytics data
5504
5793
  */
5505
5794
  async getAnalytics(resourceName, field, options = {}) {
5506
- if (!this.analyticsResource) {
5795
+ const fieldHandlers = this.fieldHandlers.get(resourceName);
5796
+ if (!fieldHandlers) {
5797
+ throw new Error(`No eventual consistency configured for resource: ${resourceName}`);
5798
+ }
5799
+ const handler = fieldHandlers.get(field);
5800
+ if (!handler) {
5801
+ throw new Error(`No eventual consistency configured for field: ${resourceName}.${field}`);
5802
+ }
5803
+ if (!handler.analyticsResource) {
5507
5804
  throw new Error("Analytics not enabled for this plugin");
5508
5805
  }
5509
5806
  const { period = "day", date, startDate, endDate, month, year, breakdown = false } = options;
5510
5807
  const [ok, err, allAnalytics] = await tryFn(
5511
- () => this.analyticsResource.list()
5808
+ () => handler.analyticsResource.list()
5512
5809
  );
5513
5810
  if (!ok || !allAnalytics) {
5514
5811
  return [];
@@ -5734,12 +6031,20 @@ class EventualConsistencyPlugin extends Plugin {
5734
6031
  * @returns {Promise<Array>} Top records
5735
6032
  */
5736
6033
  async getTopRecords(resourceName, field, options = {}) {
5737
- if (!this.transactionResource) {
6034
+ const fieldHandlers = this.fieldHandlers.get(resourceName);
6035
+ if (!fieldHandlers) {
6036
+ throw new Error(`No eventual consistency configured for resource: ${resourceName}`);
6037
+ }
6038
+ const handler = fieldHandlers.get(field);
6039
+ if (!handler) {
6040
+ throw new Error(`No eventual consistency configured for field: ${resourceName}.${field}`);
6041
+ }
6042
+ if (!handler.transactionResource) {
5738
6043
  throw new Error("Transaction resource not initialized");
5739
6044
  }
5740
6045
  const { period = "day", date, metric = "transactionCount", limit = 10 } = options;
5741
6046
  const [ok, err, transactions] = await tryFn(
5742
- () => this.transactionResource.list()
6047
+ () => handler.transactionResource.list()
5743
6048
  );
5744
6049
  if (!ok || !transactions) {
5745
6050
  return [];
@@ -11847,7 +12152,7 @@ class Database extends EventEmitter {
11847
12152
  this.id = idGenerator(7);
11848
12153
  this.version = "1";
11849
12154
  this.s3dbVersion = (() => {
11850
- const [ok, err, version] = tryFn(() => true ? "10.0.12" : "latest");
12155
+ const [ok, err, version] = tryFn(() => true ? "10.0.14" : "latest");
11851
12156
  return ok ? version : "latest";
11852
12157
  })();
11853
12158
  this.resources = {};