s3db.js 9.3.0 → 10.0.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 +71 -13
- package/dist/s3db.cjs.js +466 -8
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +466 -9
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/client.class.js +2 -2
- package/src/concerns/high-performance-inserter.js +285 -0
- package/src/concerns/partition-queue.js +171 -0
- package/src/errors.js +10 -2
- package/src/partition-drivers/base-partition-driver.js +96 -0
- package/src/partition-drivers/index.js +60 -0
- package/src/partition-drivers/memory-partition-driver.js +274 -0
- package/src/partition-drivers/sqs-partition-driver.js +332 -0
- package/src/partition-drivers/sync-partition-driver.js +38 -0
- package/src/plugins/backup.plugin.js +1 -1
- package/src/plugins/backup.plugin.js.backup +1 -1
- package/src/plugins/eventual-consistency.plugin.js +609 -0
- package/src/plugins/index.js +1 -0
- package/PLUGINS.md +0 -5036
package/dist/s3db.es.js
CHANGED
|
@@ -419,8 +419,14 @@ function mapAwsError(err, context = {}) {
|
|
|
419
419
|
suggestion = "Check if the object metadata is present and valid.";
|
|
420
420
|
return new MissingMetadata({ ...context, original: err, metadata, commandName, commandInput, suggestion });
|
|
421
421
|
}
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
const errorDetails = [
|
|
423
|
+
`Unknown error: ${err.message || err.toString()}`,
|
|
424
|
+
err.code && `Code: ${err.code}`,
|
|
425
|
+
err.statusCode && `Status: ${err.statusCode}`,
|
|
426
|
+
err.stack && `Stack: ${err.stack.split("\n")[0]}`
|
|
427
|
+
].filter(Boolean).join(" | ");
|
|
428
|
+
suggestion = `Check the error details and AWS documentation. Original error: ${err.message || err.toString()}`;
|
|
429
|
+
return new UnknownError(errorDetails, { ...context, original: err, metadata, commandName, commandInput, suggestion });
|
|
424
430
|
}
|
|
425
431
|
class ConnectionStringError extends S3dbError {
|
|
426
432
|
constructor(message, details = {}) {
|
|
@@ -1899,7 +1905,7 @@ class BackupPlugin extends Plugin {
|
|
|
1899
1905
|
include: options.include || null,
|
|
1900
1906
|
exclude: options.exclude || [],
|
|
1901
1907
|
backupMetadataResource: options.backupMetadataResource || "backup_metadata",
|
|
1902
|
-
tempDir: options.tempDir || "
|
|
1908
|
+
tempDir: options.tempDir || "/tmp/s3db/backups",
|
|
1903
1909
|
verbose: options.verbose || false,
|
|
1904
1910
|
// Hooks
|
|
1905
1911
|
onBackupStart: options.onBackupStart || null,
|
|
@@ -4046,6 +4052,457 @@ const CostsPlugin = {
|
|
|
4046
4052
|
}
|
|
4047
4053
|
};
|
|
4048
4054
|
|
|
4055
|
+
class EventualConsistencyPlugin extends Plugin {
|
|
4056
|
+
constructor(options = {}) {
|
|
4057
|
+
super(options);
|
|
4058
|
+
if (!options.resource) {
|
|
4059
|
+
throw new Error("EventualConsistencyPlugin requires 'resource' option");
|
|
4060
|
+
}
|
|
4061
|
+
if (!options.field) {
|
|
4062
|
+
throw new Error("EventualConsistencyPlugin requires 'field' option");
|
|
4063
|
+
}
|
|
4064
|
+
this.config = {
|
|
4065
|
+
resource: options.resource,
|
|
4066
|
+
field: options.field,
|
|
4067
|
+
cohort: {
|
|
4068
|
+
interval: options.cohort?.interval || "24h",
|
|
4069
|
+
timezone: options.cohort?.timezone || "UTC",
|
|
4070
|
+
...options.cohort
|
|
4071
|
+
},
|
|
4072
|
+
reducer: options.reducer || ((transactions) => {
|
|
4073
|
+
let baseValue = 0;
|
|
4074
|
+
for (const t of transactions) {
|
|
4075
|
+
if (t.operation === "set") {
|
|
4076
|
+
baseValue = t.value;
|
|
4077
|
+
} else if (t.operation === "add") {
|
|
4078
|
+
baseValue += t.value;
|
|
4079
|
+
} else if (t.operation === "sub") {
|
|
4080
|
+
baseValue -= t.value;
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
return baseValue;
|
|
4084
|
+
}),
|
|
4085
|
+
consolidationInterval: options.consolidationInterval || 36e5,
|
|
4086
|
+
// 1 hour default
|
|
4087
|
+
autoConsolidate: options.autoConsolidate !== false,
|
|
4088
|
+
batchTransactions: options.batchTransactions || false,
|
|
4089
|
+
batchSize: options.batchSize || 100,
|
|
4090
|
+
mode: options.mode || "async",
|
|
4091
|
+
// 'async' or 'sync'
|
|
4092
|
+
...options
|
|
4093
|
+
};
|
|
4094
|
+
this.transactionResource = null;
|
|
4095
|
+
this.targetResource = null;
|
|
4096
|
+
this.consolidationTimer = null;
|
|
4097
|
+
this.pendingTransactions = /* @__PURE__ */ new Map();
|
|
4098
|
+
}
|
|
4099
|
+
async onSetup() {
|
|
4100
|
+
this.targetResource = this.database.resources[this.config.resource];
|
|
4101
|
+
if (!this.targetResource) {
|
|
4102
|
+
this.deferredSetup = true;
|
|
4103
|
+
this.watchForResource();
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
4106
|
+
await this.completeSetup();
|
|
4107
|
+
}
|
|
4108
|
+
watchForResource() {
|
|
4109
|
+
const hookCallback = async ({ resource, config }) => {
|
|
4110
|
+
if (config.name === this.config.resource && this.deferredSetup) {
|
|
4111
|
+
this.targetResource = resource;
|
|
4112
|
+
this.deferredSetup = false;
|
|
4113
|
+
await this.completeSetup();
|
|
4114
|
+
}
|
|
4115
|
+
};
|
|
4116
|
+
this.database.addHook("afterCreateResource", hookCallback);
|
|
4117
|
+
}
|
|
4118
|
+
async completeSetup() {
|
|
4119
|
+
if (!this.targetResource) return;
|
|
4120
|
+
const transactionResourceName = `${this.config.resource}_transactions_${this.config.field}`;
|
|
4121
|
+
const partitionConfig = this.createPartitionConfig();
|
|
4122
|
+
const [ok, err, transactionResource] = await tryFn(
|
|
4123
|
+
() => this.database.createResource({
|
|
4124
|
+
name: transactionResourceName,
|
|
4125
|
+
attributes: {
|
|
4126
|
+
id: "string|required",
|
|
4127
|
+
originalId: "string|required",
|
|
4128
|
+
field: "string|required",
|
|
4129
|
+
value: "number|required",
|
|
4130
|
+
operation: "string|required",
|
|
4131
|
+
// 'set', 'add', or 'sub'
|
|
4132
|
+
timestamp: "string|required",
|
|
4133
|
+
cohortDate: "string|required",
|
|
4134
|
+
// For partitioning
|
|
4135
|
+
cohortMonth: "string|optional",
|
|
4136
|
+
// For monthly partitioning
|
|
4137
|
+
source: "string|optional",
|
|
4138
|
+
applied: "boolean|optional"
|
|
4139
|
+
// Track if transaction was applied
|
|
4140
|
+
},
|
|
4141
|
+
behavior: "body-overflow",
|
|
4142
|
+
timestamps: true,
|
|
4143
|
+
partitions: partitionConfig,
|
|
4144
|
+
asyncPartitions: true
|
|
4145
|
+
// Use async partitions for better performance
|
|
4146
|
+
})
|
|
4147
|
+
);
|
|
4148
|
+
if (!ok && !this.database.resources[transactionResourceName]) {
|
|
4149
|
+
throw new Error(`Failed to create transaction resource: ${err?.message}`);
|
|
4150
|
+
}
|
|
4151
|
+
this.transactionResource = ok ? transactionResource : this.database.resources[transactionResourceName];
|
|
4152
|
+
this.addHelperMethods();
|
|
4153
|
+
if (this.config.autoConsolidate) {
|
|
4154
|
+
this.startConsolidationTimer();
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
async onStart() {
|
|
4158
|
+
if (this.deferredSetup) {
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
this.emit("eventual-consistency.started", {
|
|
4162
|
+
resource: this.config.resource,
|
|
4163
|
+
field: this.config.field,
|
|
4164
|
+
cohort: this.config.cohort
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
async onStop() {
|
|
4168
|
+
if (this.consolidationTimer) {
|
|
4169
|
+
clearInterval(this.consolidationTimer);
|
|
4170
|
+
this.consolidationTimer = null;
|
|
4171
|
+
}
|
|
4172
|
+
await this.flushPendingTransactions();
|
|
4173
|
+
this.emit("eventual-consistency.stopped", {
|
|
4174
|
+
resource: this.config.resource,
|
|
4175
|
+
field: this.config.field
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
createPartitionConfig() {
|
|
4179
|
+
const partitions = {
|
|
4180
|
+
byDay: {
|
|
4181
|
+
fields: {
|
|
4182
|
+
cohortDate: "string"
|
|
4183
|
+
}
|
|
4184
|
+
},
|
|
4185
|
+
byMonth: {
|
|
4186
|
+
fields: {
|
|
4187
|
+
cohortMonth: "string"
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
};
|
|
4191
|
+
return partitions;
|
|
4192
|
+
}
|
|
4193
|
+
addHelperMethods() {
|
|
4194
|
+
const resource = this.targetResource;
|
|
4195
|
+
const defaultField = this.config.field;
|
|
4196
|
+
const plugin = this;
|
|
4197
|
+
if (!resource._eventualConsistencyPlugins) {
|
|
4198
|
+
resource._eventualConsistencyPlugins = {};
|
|
4199
|
+
}
|
|
4200
|
+
resource._eventualConsistencyPlugins[defaultField] = plugin;
|
|
4201
|
+
resource.set = async (id, fieldOrValue, value) => {
|
|
4202
|
+
const hasMultipleFields = Object.keys(resource._eventualConsistencyPlugins).length > 1;
|
|
4203
|
+
if (hasMultipleFields && value === void 0) {
|
|
4204
|
+
throw new Error(`Multiple fields have eventual consistency. Please specify the field: set(id, field, value)`);
|
|
4205
|
+
}
|
|
4206
|
+
const field = value !== void 0 ? fieldOrValue : defaultField;
|
|
4207
|
+
const actualValue = value !== void 0 ? value : fieldOrValue;
|
|
4208
|
+
const fieldPlugin = resource._eventualConsistencyPlugins[field];
|
|
4209
|
+
if (!fieldPlugin) {
|
|
4210
|
+
throw new Error(`No eventual consistency plugin found for field "${field}"`);
|
|
4211
|
+
}
|
|
4212
|
+
await fieldPlugin.createTransaction({
|
|
4213
|
+
originalId: id,
|
|
4214
|
+
operation: "set",
|
|
4215
|
+
value: actualValue,
|
|
4216
|
+
source: "set"
|
|
4217
|
+
});
|
|
4218
|
+
if (fieldPlugin.config.mode === "sync") {
|
|
4219
|
+
const consolidatedValue = await fieldPlugin.consolidateRecord(id);
|
|
4220
|
+
await resource.update(id, {
|
|
4221
|
+
[field]: consolidatedValue
|
|
4222
|
+
});
|
|
4223
|
+
return consolidatedValue;
|
|
4224
|
+
}
|
|
4225
|
+
return actualValue;
|
|
4226
|
+
};
|
|
4227
|
+
resource.add = async (id, fieldOrAmount, amount) => {
|
|
4228
|
+
const hasMultipleFields = Object.keys(resource._eventualConsistencyPlugins).length > 1;
|
|
4229
|
+
if (hasMultipleFields && amount === void 0) {
|
|
4230
|
+
throw new Error(`Multiple fields have eventual consistency. Please specify the field: add(id, field, amount)`);
|
|
4231
|
+
}
|
|
4232
|
+
const field = amount !== void 0 ? fieldOrAmount : defaultField;
|
|
4233
|
+
const actualAmount = amount !== void 0 ? amount : fieldOrAmount;
|
|
4234
|
+
const fieldPlugin = resource._eventualConsistencyPlugins[field];
|
|
4235
|
+
if (!fieldPlugin) {
|
|
4236
|
+
throw new Error(`No eventual consistency plugin found for field "${field}"`);
|
|
4237
|
+
}
|
|
4238
|
+
await fieldPlugin.createTransaction({
|
|
4239
|
+
originalId: id,
|
|
4240
|
+
operation: "add",
|
|
4241
|
+
value: actualAmount,
|
|
4242
|
+
source: "add"
|
|
4243
|
+
});
|
|
4244
|
+
if (fieldPlugin.config.mode === "sync") {
|
|
4245
|
+
const consolidatedValue = await fieldPlugin.consolidateRecord(id);
|
|
4246
|
+
await resource.update(id, {
|
|
4247
|
+
[field]: consolidatedValue
|
|
4248
|
+
});
|
|
4249
|
+
return consolidatedValue;
|
|
4250
|
+
}
|
|
4251
|
+
const currentValue = await fieldPlugin.getConsolidatedValue(id);
|
|
4252
|
+
return currentValue + actualAmount;
|
|
4253
|
+
};
|
|
4254
|
+
resource.sub = async (id, fieldOrAmount, amount) => {
|
|
4255
|
+
const hasMultipleFields = Object.keys(resource._eventualConsistencyPlugins).length > 1;
|
|
4256
|
+
if (hasMultipleFields && amount === void 0) {
|
|
4257
|
+
throw new Error(`Multiple fields have eventual consistency. Please specify the field: sub(id, field, amount)`);
|
|
4258
|
+
}
|
|
4259
|
+
const field = amount !== void 0 ? fieldOrAmount : defaultField;
|
|
4260
|
+
const actualAmount = amount !== void 0 ? amount : fieldOrAmount;
|
|
4261
|
+
const fieldPlugin = resource._eventualConsistencyPlugins[field];
|
|
4262
|
+
if (!fieldPlugin) {
|
|
4263
|
+
throw new Error(`No eventual consistency plugin found for field "${field}"`);
|
|
4264
|
+
}
|
|
4265
|
+
await fieldPlugin.createTransaction({
|
|
4266
|
+
originalId: id,
|
|
4267
|
+
operation: "sub",
|
|
4268
|
+
value: actualAmount,
|
|
4269
|
+
source: "sub"
|
|
4270
|
+
});
|
|
4271
|
+
if (fieldPlugin.config.mode === "sync") {
|
|
4272
|
+
const consolidatedValue = await fieldPlugin.consolidateRecord(id);
|
|
4273
|
+
await resource.update(id, {
|
|
4274
|
+
[field]: consolidatedValue
|
|
4275
|
+
});
|
|
4276
|
+
return consolidatedValue;
|
|
4277
|
+
}
|
|
4278
|
+
const currentValue = await fieldPlugin.getConsolidatedValue(id);
|
|
4279
|
+
return currentValue - actualAmount;
|
|
4280
|
+
};
|
|
4281
|
+
resource.consolidate = async (id, field) => {
|
|
4282
|
+
const hasMultipleFields = Object.keys(resource._eventualConsistencyPlugins).length > 1;
|
|
4283
|
+
if (hasMultipleFields && !field) {
|
|
4284
|
+
throw new Error(`Multiple fields have eventual consistency. Please specify the field: consolidate(id, field)`);
|
|
4285
|
+
}
|
|
4286
|
+
const actualField = field || defaultField;
|
|
4287
|
+
const fieldPlugin = resource._eventualConsistencyPlugins[actualField];
|
|
4288
|
+
if (!fieldPlugin) {
|
|
4289
|
+
throw new Error(`No eventual consistency plugin found for field "${actualField}"`);
|
|
4290
|
+
}
|
|
4291
|
+
return await fieldPlugin.consolidateRecord(id);
|
|
4292
|
+
};
|
|
4293
|
+
resource.getConsolidatedValue = async (id, fieldOrOptions, options) => {
|
|
4294
|
+
if (typeof fieldOrOptions === "string") {
|
|
4295
|
+
const field = fieldOrOptions;
|
|
4296
|
+
const fieldPlugin = resource._eventualConsistencyPlugins[field] || plugin;
|
|
4297
|
+
return await fieldPlugin.getConsolidatedValue(id, options || {});
|
|
4298
|
+
} else {
|
|
4299
|
+
return await plugin.getConsolidatedValue(id, fieldOrOptions || {});
|
|
4300
|
+
}
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
async createTransaction(data) {
|
|
4304
|
+
const now = /* @__PURE__ */ new Date();
|
|
4305
|
+
const cohortInfo = this.getCohortInfo(now);
|
|
4306
|
+
const transaction = {
|
|
4307
|
+
id: `txn-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`,
|
|
4308
|
+
originalId: data.originalId,
|
|
4309
|
+
field: this.config.field,
|
|
4310
|
+
value: data.value || 0,
|
|
4311
|
+
operation: data.operation || "set",
|
|
4312
|
+
timestamp: now.toISOString(),
|
|
4313
|
+
cohortDate: cohortInfo.date,
|
|
4314
|
+
cohortMonth: cohortInfo.month,
|
|
4315
|
+
source: data.source || "unknown",
|
|
4316
|
+
applied: false
|
|
4317
|
+
};
|
|
4318
|
+
if (this.config.batchTransactions) {
|
|
4319
|
+
this.pendingTransactions.set(transaction.id, transaction);
|
|
4320
|
+
if (this.pendingTransactions.size >= this.config.batchSize) {
|
|
4321
|
+
await this.flushPendingTransactions();
|
|
4322
|
+
}
|
|
4323
|
+
} else {
|
|
4324
|
+
await this.transactionResource.insert(transaction);
|
|
4325
|
+
}
|
|
4326
|
+
return transaction;
|
|
4327
|
+
}
|
|
4328
|
+
async flushPendingTransactions() {
|
|
4329
|
+
if (this.pendingTransactions.size === 0) return;
|
|
4330
|
+
const transactions = Array.from(this.pendingTransactions.values());
|
|
4331
|
+
this.pendingTransactions.clear();
|
|
4332
|
+
for (const transaction of transactions) {
|
|
4333
|
+
await this.transactionResource.insert(transaction);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
getCohortInfo(date) {
|
|
4337
|
+
const tz = this.config.cohort.timezone;
|
|
4338
|
+
const offset = this.getTimezoneOffset(tz);
|
|
4339
|
+
const localDate = new Date(date.getTime() + offset);
|
|
4340
|
+
const year = localDate.getFullYear();
|
|
4341
|
+
const month = String(localDate.getMonth() + 1).padStart(2, "0");
|
|
4342
|
+
const day = String(localDate.getDate()).padStart(2, "0");
|
|
4343
|
+
return {
|
|
4344
|
+
date: `${year}-${month}-${day}`,
|
|
4345
|
+
month: `${year}-${month}`
|
|
4346
|
+
};
|
|
4347
|
+
}
|
|
4348
|
+
getTimezoneOffset(timezone) {
|
|
4349
|
+
const offsets = {
|
|
4350
|
+
"UTC": 0,
|
|
4351
|
+
"America/New_York": -5 * 36e5,
|
|
4352
|
+
"America/Chicago": -6 * 36e5,
|
|
4353
|
+
"America/Denver": -7 * 36e5,
|
|
4354
|
+
"America/Los_Angeles": -8 * 36e5,
|
|
4355
|
+
"America/Sao_Paulo": -3 * 36e5,
|
|
4356
|
+
"Europe/London": 0,
|
|
4357
|
+
"Europe/Paris": 1 * 36e5,
|
|
4358
|
+
"Europe/Berlin": 1 * 36e5,
|
|
4359
|
+
"Asia/Tokyo": 9 * 36e5,
|
|
4360
|
+
"Asia/Shanghai": 8 * 36e5,
|
|
4361
|
+
"Australia/Sydney": 10 * 36e5
|
|
4362
|
+
};
|
|
4363
|
+
return offsets[timezone] || 0;
|
|
4364
|
+
}
|
|
4365
|
+
startConsolidationTimer() {
|
|
4366
|
+
const interval = this.config.consolidationInterval;
|
|
4367
|
+
this.consolidationTimer = setInterval(async () => {
|
|
4368
|
+
await this.runConsolidation();
|
|
4369
|
+
}, interval);
|
|
4370
|
+
}
|
|
4371
|
+
async runConsolidation() {
|
|
4372
|
+
try {
|
|
4373
|
+
const [ok, err, transactions] = await tryFn(
|
|
4374
|
+
() => this.transactionResource.query({
|
|
4375
|
+
applied: false
|
|
4376
|
+
})
|
|
4377
|
+
);
|
|
4378
|
+
if (!ok) {
|
|
4379
|
+
console.error("Consolidation failed to query transactions:", err);
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
const uniqueIds = [...new Set(transactions.map((t) => t.originalId))];
|
|
4383
|
+
for (const id of uniqueIds) {
|
|
4384
|
+
await this.consolidateRecord(id);
|
|
4385
|
+
}
|
|
4386
|
+
this.emit("eventual-consistency.consolidated", {
|
|
4387
|
+
resource: this.config.resource,
|
|
4388
|
+
field: this.config.field,
|
|
4389
|
+
recordCount: uniqueIds.length
|
|
4390
|
+
});
|
|
4391
|
+
} catch (error) {
|
|
4392
|
+
console.error("Consolidation error:", error);
|
|
4393
|
+
this.emit("eventual-consistency.consolidation-error", error);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
async consolidateRecord(originalId) {
|
|
4397
|
+
const [recordOk, recordErr, record] = await tryFn(
|
|
4398
|
+
() => this.targetResource.get(originalId)
|
|
4399
|
+
);
|
|
4400
|
+
const currentValue = recordOk && record ? record[this.config.field] || 0 : 0;
|
|
4401
|
+
const [ok, err, transactions] = await tryFn(
|
|
4402
|
+
() => this.transactionResource.query({
|
|
4403
|
+
originalId,
|
|
4404
|
+
applied: false
|
|
4405
|
+
})
|
|
4406
|
+
);
|
|
4407
|
+
if (!ok || !transactions || transactions.length === 0) {
|
|
4408
|
+
return currentValue;
|
|
4409
|
+
}
|
|
4410
|
+
transactions.sort(
|
|
4411
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
4412
|
+
);
|
|
4413
|
+
const hasSetOperation = transactions.some((t) => t.operation === "set");
|
|
4414
|
+
if (currentValue !== 0 && !hasSetOperation) {
|
|
4415
|
+
transactions.unshift({
|
|
4416
|
+
id: "__synthetic__",
|
|
4417
|
+
// Synthetic ID that we'll skip when marking as applied
|
|
4418
|
+
operation: "set",
|
|
4419
|
+
value: currentValue,
|
|
4420
|
+
timestamp: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
4421
|
+
// Very old timestamp to ensure it's first
|
|
4422
|
+
});
|
|
4423
|
+
}
|
|
4424
|
+
const consolidatedValue = this.config.reducer(transactions);
|
|
4425
|
+
const [updateOk, updateErr] = await tryFn(
|
|
4426
|
+
() => this.targetResource.update(originalId, {
|
|
4427
|
+
[this.config.field]: consolidatedValue
|
|
4428
|
+
})
|
|
4429
|
+
);
|
|
4430
|
+
if (updateOk) {
|
|
4431
|
+
for (const txn of transactions) {
|
|
4432
|
+
if (txn.id !== "__synthetic__") {
|
|
4433
|
+
await this.transactionResource.update(txn.id, {
|
|
4434
|
+
applied: true
|
|
4435
|
+
});
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
return consolidatedValue;
|
|
4440
|
+
}
|
|
4441
|
+
async getConsolidatedValue(originalId, options = {}) {
|
|
4442
|
+
const includeApplied = options.includeApplied || false;
|
|
4443
|
+
const startDate = options.startDate;
|
|
4444
|
+
const endDate = options.endDate;
|
|
4445
|
+
const query = { originalId };
|
|
4446
|
+
if (!includeApplied) {
|
|
4447
|
+
query.applied = false;
|
|
4448
|
+
}
|
|
4449
|
+
const [ok, err, transactions] = await tryFn(
|
|
4450
|
+
() => this.transactionResource.query(query)
|
|
4451
|
+
);
|
|
4452
|
+
if (!ok || !transactions || transactions.length === 0) {
|
|
4453
|
+
const [recordOk, recordErr, record] = await tryFn(
|
|
4454
|
+
() => this.targetResource.get(originalId)
|
|
4455
|
+
);
|
|
4456
|
+
if (recordOk && record) {
|
|
4457
|
+
return record[this.config.field] || 0;
|
|
4458
|
+
}
|
|
4459
|
+
return 0;
|
|
4460
|
+
}
|
|
4461
|
+
let filtered = transactions;
|
|
4462
|
+
if (startDate || endDate) {
|
|
4463
|
+
filtered = transactions.filter((t) => {
|
|
4464
|
+
const timestamp = new Date(t.timestamp);
|
|
4465
|
+
if (startDate && timestamp < new Date(startDate)) return false;
|
|
4466
|
+
if (endDate && timestamp > new Date(endDate)) return false;
|
|
4467
|
+
return true;
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
filtered.sort(
|
|
4471
|
+
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
4472
|
+
);
|
|
4473
|
+
return this.config.reducer(filtered);
|
|
4474
|
+
}
|
|
4475
|
+
// Helper method to get cohort statistics
|
|
4476
|
+
async getCohortStats(cohortDate) {
|
|
4477
|
+
const [ok, err, transactions] = await tryFn(
|
|
4478
|
+
() => this.transactionResource.query({
|
|
4479
|
+
cohortDate
|
|
4480
|
+
})
|
|
4481
|
+
);
|
|
4482
|
+
if (!ok) return null;
|
|
4483
|
+
const stats = {
|
|
4484
|
+
date: cohortDate,
|
|
4485
|
+
transactionCount: transactions.length,
|
|
4486
|
+
totalValue: 0,
|
|
4487
|
+
byOperation: { set: 0, add: 0, sub: 0 },
|
|
4488
|
+
byOriginalId: {}
|
|
4489
|
+
};
|
|
4490
|
+
for (const txn of transactions) {
|
|
4491
|
+
stats.totalValue += txn.value || 0;
|
|
4492
|
+
stats.byOperation[txn.operation] = (stats.byOperation[txn.operation] || 0) + 1;
|
|
4493
|
+
if (!stats.byOriginalId[txn.originalId]) {
|
|
4494
|
+
stats.byOriginalId[txn.originalId] = {
|
|
4495
|
+
count: 0,
|
|
4496
|
+
value: 0
|
|
4497
|
+
};
|
|
4498
|
+
}
|
|
4499
|
+
stats.byOriginalId[txn.originalId].count++;
|
|
4500
|
+
stats.byOriginalId[txn.originalId].value += txn.value || 0;
|
|
4501
|
+
}
|
|
4502
|
+
return stats;
|
|
4503
|
+
}
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4049
4506
|
class FullTextPlugin extends Plugin {
|
|
4050
4507
|
constructor(options = {}) {
|
|
4051
4508
|
super();
|
|
@@ -5830,10 +6287,10 @@ class Client extends EventEmitter {
|
|
|
5830
6287
|
// Enabled for better performance
|
|
5831
6288
|
keepAliveMsecs: 1e3,
|
|
5832
6289
|
// 1 second keep-alive
|
|
5833
|
-
maxSockets:
|
|
5834
|
-
//
|
|
5835
|
-
maxFreeSockets:
|
|
5836
|
-
//
|
|
6290
|
+
maxSockets: httpClientOptions.maxSockets || 500,
|
|
6291
|
+
// High concurrency support
|
|
6292
|
+
maxFreeSockets: httpClientOptions.maxFreeSockets || 100,
|
|
6293
|
+
// Better connection reuse
|
|
5837
6294
|
timeout: 6e4,
|
|
5838
6295
|
// 60 second timeout
|
|
5839
6296
|
...httpClientOptions
|
|
@@ -9696,7 +10153,7 @@ class Database extends EventEmitter {
|
|
|
9696
10153
|
this.id = idGenerator(7);
|
|
9697
10154
|
this.version = "1";
|
|
9698
10155
|
this.s3dbVersion = (() => {
|
|
9699
|
-
const [ok, err, version] = tryFn(() => true ? "
|
|
10156
|
+
const [ok, err, version] = tryFn(() => true ? "10.0.0" : "latest");
|
|
9700
10157
|
return ok ? version : "latest";
|
|
9701
10158
|
})();
|
|
9702
10159
|
this.resources = {};
|
|
@@ -12765,5 +13222,5 @@ class StateMachinePlugin extends Plugin {
|
|
|
12765
13222
|
}
|
|
12766
13223
|
}
|
|
12767
13224
|
|
|
12768
|
-
export { AVAILABLE_BEHAVIORS, AuditPlugin, AuthenticationError, BackupPlugin, BaseError, CachePlugin, Client, ConnectionString, ConnectionStringError, CostsPlugin, CryptoError, DEFAULT_BEHAVIOR, Database, DatabaseError, EncryptionError, ErrorMap, FullTextPlugin, InvalidResourceItem, MetricsPlugin, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, PartitionError, PermissionError, Plugin, PluginObject, ReplicatorPlugin, Resource, ResourceError, ResourceIdsPageReader, ResourceIdsReader, ResourceNotFound, ResourceReader, ResourceWriter, Database as S3db, S3dbError, SchedulerPlugin, Schema, SchemaError, StateMachinePlugin, UnknownError, ValidationError, Validator, behaviors, calculateAttributeNamesSize, calculateAttributeSizes, calculateEffectiveLimit, calculateSystemOverhead, calculateTotalSize, calculateUTF8Bytes, clearUTF8Cache, clearUTF8Memo, clearUTF8Memory, decode, decodeDecimal, decrypt, S3db as default, encode, encodeDecimal, encrypt, getBehavior, getSizeBreakdown, idGenerator, mapAwsError, md5, passwordGenerator, sha256, streamToString, transformValue, tryFn, tryFnSync };
|
|
13225
|
+
export { AVAILABLE_BEHAVIORS, AuditPlugin, AuthenticationError, BackupPlugin, BaseError, CachePlugin, Client, ConnectionString, ConnectionStringError, CostsPlugin, CryptoError, DEFAULT_BEHAVIOR, Database, DatabaseError, EncryptionError, ErrorMap, EventualConsistencyPlugin, FullTextPlugin, InvalidResourceItem, MetricsPlugin, MissingMetadata, NoSuchBucket, NoSuchKey, NotFound, PartitionError, PermissionError, Plugin, PluginObject, ReplicatorPlugin, Resource, ResourceError, ResourceIdsPageReader, ResourceIdsReader, ResourceNotFound, ResourceReader, ResourceWriter, Database as S3db, S3dbError, SchedulerPlugin, Schema, SchemaError, StateMachinePlugin, UnknownError, ValidationError, Validator, behaviors, calculateAttributeNamesSize, calculateAttributeSizes, calculateEffectiveLimit, calculateSystemOverhead, calculateTotalSize, calculateUTF8Bytes, clearUTF8Cache, clearUTF8Memo, clearUTF8Memory, decode, decodeDecimal, decrypt, S3db as default, encode, encodeDecimal, encrypt, getBehavior, getSizeBreakdown, idGenerator, mapAwsError, md5, passwordGenerator, sha256, streamToString, transformValue, tryFn, tryFnSync };
|
|
12769
13226
|
//# sourceMappingURL=s3db.es.js.map
|