s3db.js 13.2.1 → 13.3.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/dist/s3db.cjs.js +248 -80
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +248 -80
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/clients/memory-client.class.js +5 -3
- package/src/database.class.js +6 -3
- package/src/plugins/audit.plugin.js +3 -3
- package/src/plugins/cache/cache.class.js +2 -2
- package/src/plugins/costs.plugin.js +3 -2
- package/src/plugins/replicator.plugin.js +10 -10
- package/src/plugins/replicators/base-replicator.class.js +1 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +3 -3
- package/src/plugins/replicators/planetscale-replicator.class.js +1 -1
- package/src/plugins/replicators/postgres-replicator.class.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +3 -3
- package/src/plugins/replicators/turso-replicator.class.js +1 -1
- package/src/plugins/replicators/webhook-replicator.class.js +3 -3
- package/src/plugins/s3-queue.plugin.js +1 -1
- package/src/plugins/state-machine.plugin.js +68 -0
- package/src/resource.class.js +172 -47
package/dist/s3db.cjs.js
CHANGED
|
@@ -5186,7 +5186,7 @@ class AuditPlugin extends Plugin {
|
|
|
5186
5186
|
async onStop() {
|
|
5187
5187
|
}
|
|
5188
5188
|
setupResourceAuditing(resource) {
|
|
5189
|
-
resource.on("
|
|
5189
|
+
resource.on("inserted", async (data) => {
|
|
5190
5190
|
const partitionValues = this.config.includePartitions ? this.getPartitionValues(data, resource) : null;
|
|
5191
5191
|
await this.logAudit({
|
|
5192
5192
|
resourceName: resource.name,
|
|
@@ -5198,7 +5198,7 @@ class AuditPlugin extends Plugin {
|
|
|
5198
5198
|
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
5199
5199
|
});
|
|
5200
5200
|
});
|
|
5201
|
-
resource.on("
|
|
5201
|
+
resource.on("updated", async (data) => {
|
|
5202
5202
|
let oldData = data.$before;
|
|
5203
5203
|
if (this.config.includeData && !oldData) {
|
|
5204
5204
|
const [ok, err, fetched] = await tryFn(() => resource.get(data.id));
|
|
@@ -5215,7 +5215,7 @@ class AuditPlugin extends Plugin {
|
|
|
5215
5215
|
partitionValues: partitionValues ? JSON.stringify(partitionValues) : null
|
|
5216
5216
|
});
|
|
5217
5217
|
});
|
|
5218
|
-
resource.on("
|
|
5218
|
+
resource.on("deleted", async (data) => {
|
|
5219
5219
|
let oldData = data;
|
|
5220
5220
|
if (this.config.includeData && !oldData) {
|
|
5221
5221
|
const [ok, err, fetched] = await tryFn(() => resource.get(data.id));
|
|
@@ -7320,13 +7320,13 @@ class Cache extends EventEmitter {
|
|
|
7320
7320
|
async get(key) {
|
|
7321
7321
|
this.validateKey(key);
|
|
7322
7322
|
const data = await this._get(key);
|
|
7323
|
-
this.emit("
|
|
7323
|
+
this.emit("fetched", data);
|
|
7324
7324
|
return data;
|
|
7325
7325
|
}
|
|
7326
7326
|
async del(key) {
|
|
7327
7327
|
this.validateKey(key);
|
|
7328
7328
|
const data = await this._del(key);
|
|
7329
|
-
this.emit("
|
|
7329
|
+
this.emit("deleted", data);
|
|
7330
7330
|
return data;
|
|
7331
7331
|
}
|
|
7332
7332
|
async delete(key) {
|
|
@@ -9141,8 +9141,7 @@ class CostsPlugin extends Plugin {
|
|
|
9141
9141
|
}
|
|
9142
9142
|
async onStart() {
|
|
9143
9143
|
if (this.client) {
|
|
9144
|
-
this.client.on("
|
|
9145
|
-
this.client.on("command.error", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
9144
|
+
this.client.on("cl:response", (name, response, input) => this.addRequest(name, this.map[name], response, input));
|
|
9146
9145
|
}
|
|
9147
9146
|
}
|
|
9148
9147
|
addRequest(name, method, response = {}, input = {}) {
|
|
@@ -17724,7 +17723,7 @@ class BaseReplicator extends EventEmitter {
|
|
|
17724
17723
|
*/
|
|
17725
17724
|
async initialize(database) {
|
|
17726
17725
|
this.database = database;
|
|
17727
|
-
this.emit("initialized", { replicator: this.name });
|
|
17726
|
+
this.emit("db:plugin:initialized", { replicator: this.name });
|
|
17728
17727
|
}
|
|
17729
17728
|
/**
|
|
17730
17729
|
* Replicate data to the target
|
|
@@ -18295,7 +18294,7 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
18295
18294
|
if (this.schemaSync.enabled) {
|
|
18296
18295
|
await this.syncSchemas(database);
|
|
18297
18296
|
}
|
|
18298
|
-
this.emit("initialized", {
|
|
18297
|
+
this.emit("db:plugin:initialized", {
|
|
18299
18298
|
replicator: this.name,
|
|
18300
18299
|
projectId: this.projectId,
|
|
18301
18300
|
datasetId: this.datasetId,
|
|
@@ -18602,7 +18601,7 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
18602
18601
|
if (errors.length > 0) {
|
|
18603
18602
|
console.warn(`[BigqueryReplicator] Replication completed with errors for ${resourceName}:`, errors);
|
|
18604
18603
|
}
|
|
18605
|
-
this.emit("replicated", {
|
|
18604
|
+
this.emit("plg:replicator:replicated", {
|
|
18606
18605
|
replicator: this.name,
|
|
18607
18606
|
resourceName,
|
|
18608
18607
|
operation,
|
|
@@ -18623,7 +18622,7 @@ class BigqueryReplicator extends BaseReplicator {
|
|
|
18623
18622
|
if (this.config.verbose) {
|
|
18624
18623
|
console.warn(`[BigqueryReplicator] Replication failed for ${resourceName}: ${err.message}`);
|
|
18625
18624
|
}
|
|
18626
|
-
this.emit("
|
|
18625
|
+
this.emit("plg:replicator:error", {
|
|
18627
18626
|
replicator: this.name,
|
|
18628
18627
|
resourceName,
|
|
18629
18628
|
operation,
|
|
@@ -19898,7 +19897,7 @@ ${createSQL}`);
|
|
|
19898
19897
|
}
|
|
19899
19898
|
}
|
|
19900
19899
|
const success = errors.length === 0;
|
|
19901
|
-
this.emit("replicated", {
|
|
19900
|
+
this.emit("plg:replicator:replicated", {
|
|
19902
19901
|
replicator: this.name,
|
|
19903
19902
|
resourceName,
|
|
19904
19903
|
operation,
|
|
@@ -20043,7 +20042,7 @@ class PostgresReplicator extends BaseReplicator {
|
|
|
20043
20042
|
if (this.schemaSync.enabled) {
|
|
20044
20043
|
await this.syncSchemas(database);
|
|
20045
20044
|
}
|
|
20046
|
-
this.emit("initialized", {
|
|
20045
|
+
this.emit("db:plugin:initialized", {
|
|
20047
20046
|
replicator: this.name,
|
|
20048
20047
|
database: this.database || "postgres",
|
|
20049
20048
|
resources: Object.keys(this.resources)
|
|
@@ -20249,7 +20248,7 @@ ${createSQL}`);
|
|
|
20249
20248
|
if (errors.length > 0) {
|
|
20250
20249
|
console.warn(`[PostgresReplicator] Replication completed with errors for ${resourceName}:`, errors);
|
|
20251
20250
|
}
|
|
20252
|
-
this.emit("replicated", {
|
|
20251
|
+
this.emit("plg:replicator:replicated", {
|
|
20253
20252
|
replicator: this.name,
|
|
20254
20253
|
resourceName,
|
|
20255
20254
|
operation,
|
|
@@ -20270,7 +20269,7 @@ ${createSQL}`);
|
|
|
20270
20269
|
if (this.config.verbose) {
|
|
20271
20270
|
console.warn(`[PostgresReplicator] Replication failed for ${resourceName}: ${err.message}`);
|
|
20272
20271
|
}
|
|
20273
|
-
this.emit("
|
|
20272
|
+
this.emit("plg:replicator:error", {
|
|
20274
20273
|
replicator: this.name,
|
|
20275
20274
|
resourceName,
|
|
20276
20275
|
operation,
|
|
@@ -23939,7 +23938,7 @@ ${errorDetails}`,
|
|
|
23939
23938
|
data = await this.applyVersionMapping(data, objectVersion, this.version);
|
|
23940
23939
|
}
|
|
23941
23940
|
data = await this.executeHooks("afterGet", data);
|
|
23942
|
-
this.
|
|
23941
|
+
this._emitStandardized("fetched", data, data.id);
|
|
23943
23942
|
const value = data;
|
|
23944
23943
|
return value;
|
|
23945
23944
|
}
|
|
@@ -24649,26 +24648,6 @@ ${errorDetails}`,
|
|
|
24649
24648
|
await this.executeHooks("beforeDelete", objectData);
|
|
24650
24649
|
const key = this.getResourceKey(id);
|
|
24651
24650
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
24652
|
-
this._emitWithDeprecation("delete", "deleted", {
|
|
24653
|
-
...objectData,
|
|
24654
|
-
$before: { ...objectData },
|
|
24655
|
-
$after: null
|
|
24656
|
-
}, id);
|
|
24657
|
-
if (deleteError) {
|
|
24658
|
-
throw mapAwsError(deleteError, {
|
|
24659
|
-
bucket: this.client.config.bucket,
|
|
24660
|
-
key,
|
|
24661
|
-
resourceName: this.name,
|
|
24662
|
-
operation: "delete",
|
|
24663
|
-
id
|
|
24664
|
-
});
|
|
24665
|
-
}
|
|
24666
|
-
if (!ok2) throw mapAwsError(err2, {
|
|
24667
|
-
key,
|
|
24668
|
-
resourceName: this.name,
|
|
24669
|
-
operation: "delete",
|
|
24670
|
-
id
|
|
24671
|
-
});
|
|
24672
24651
|
if (this.config.partitions && Object.keys(this.config.partitions).length > 0 && objectData) {
|
|
24673
24652
|
if (this.config.strictPartitions) {
|
|
24674
24653
|
await this.deletePartitionReferences(objectData);
|
|
@@ -24701,11 +24680,30 @@ ${errorDetails}`,
|
|
|
24701
24680
|
for (const hook of nonPartitionHooks) {
|
|
24702
24681
|
afterDeleteData = await hook(afterDeleteData);
|
|
24703
24682
|
}
|
|
24704
|
-
return response;
|
|
24705
24683
|
} else {
|
|
24706
24684
|
await this.executeHooks("afterDelete", objectData);
|
|
24707
|
-
return response;
|
|
24708
24685
|
}
|
|
24686
|
+
this._emitStandardized("deleted", {
|
|
24687
|
+
...objectData,
|
|
24688
|
+
$before: { ...objectData },
|
|
24689
|
+
$after: null
|
|
24690
|
+
}, id);
|
|
24691
|
+
if (deleteError) {
|
|
24692
|
+
throw mapAwsError(deleteError, {
|
|
24693
|
+
bucket: this.client.config.bucket,
|
|
24694
|
+
key,
|
|
24695
|
+
resourceName: this.name,
|
|
24696
|
+
operation: "delete",
|
|
24697
|
+
id
|
|
24698
|
+
});
|
|
24699
|
+
}
|
|
24700
|
+
if (!ok2) throw mapAwsError(err2, {
|
|
24701
|
+
key,
|
|
24702
|
+
resourceName: this.name,
|
|
24703
|
+
operation: "delete",
|
|
24704
|
+
id
|
|
24705
|
+
});
|
|
24706
|
+
return response;
|
|
24709
24707
|
}
|
|
24710
24708
|
/**
|
|
24711
24709
|
* Insert or update a resource object (upsert operation)
|
|
@@ -24777,7 +24775,7 @@ ${errorDetails}`,
|
|
|
24777
24775
|
}
|
|
24778
24776
|
const count = await this.client.count({ prefix });
|
|
24779
24777
|
await this.executeHooks("afterCount", { count, partition, partitionValues });
|
|
24780
|
-
this.
|
|
24778
|
+
this._emitStandardized("count", count);
|
|
24781
24779
|
return count;
|
|
24782
24780
|
}
|
|
24783
24781
|
/**
|
|
@@ -24800,7 +24798,7 @@ ${errorDetails}`,
|
|
|
24800
24798
|
const result = await this.insert(attributes);
|
|
24801
24799
|
return result;
|
|
24802
24800
|
});
|
|
24803
|
-
this.
|
|
24801
|
+
this._emitStandardized("inserted-many", objects.length);
|
|
24804
24802
|
return results;
|
|
24805
24803
|
}
|
|
24806
24804
|
/**
|
|
@@ -24835,7 +24833,7 @@ ${errorDetails}`,
|
|
|
24835
24833
|
return response;
|
|
24836
24834
|
});
|
|
24837
24835
|
await this.executeHooks("afterDeleteMany", { ids, results });
|
|
24838
|
-
this.
|
|
24836
|
+
this._emitStandardized("deleted-many", ids.length);
|
|
24839
24837
|
return results;
|
|
24840
24838
|
}
|
|
24841
24839
|
async deleteAll() {
|
|
@@ -24844,7 +24842,7 @@ ${errorDetails}`,
|
|
|
24844
24842
|
}
|
|
24845
24843
|
const prefix = `resource=${this.name}/data`;
|
|
24846
24844
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24847
|
-
this.
|
|
24845
|
+
this._emitStandardized("deleted-all", {
|
|
24848
24846
|
version: this.version,
|
|
24849
24847
|
prefix,
|
|
24850
24848
|
deletedCount
|
|
@@ -24861,7 +24859,7 @@ ${errorDetails}`,
|
|
|
24861
24859
|
}
|
|
24862
24860
|
const prefix = `resource=${this.name}`;
|
|
24863
24861
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24864
|
-
this.
|
|
24862
|
+
this._emitStandardized("deleted-all-data", {
|
|
24865
24863
|
resource: this.name,
|
|
24866
24864
|
prefix,
|
|
24867
24865
|
deletedCount
|
|
@@ -24931,7 +24929,7 @@ ${errorDetails}`,
|
|
|
24931
24929
|
const idPart = parts.find((part) => part.startsWith("id="));
|
|
24932
24930
|
return idPart ? idPart.replace("id=", "") : null;
|
|
24933
24931
|
}).filter(Boolean);
|
|
24934
|
-
this.
|
|
24932
|
+
this._emitStandardized("listed-ids", ids.length);
|
|
24935
24933
|
return ids;
|
|
24936
24934
|
}
|
|
24937
24935
|
/**
|
|
@@ -24973,12 +24971,12 @@ ${errorDetails}`,
|
|
|
24973
24971
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
24974
24972
|
if (!ok) throw err;
|
|
24975
24973
|
const results = await this.processListResults(ids, "main");
|
|
24976
|
-
this.
|
|
24974
|
+
this._emitStandardized("list", { count: results.length, errors: 0 });
|
|
24977
24975
|
return results;
|
|
24978
24976
|
}
|
|
24979
24977
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
24980
24978
|
if (!this.config.partitions?.[partition]) {
|
|
24981
|
-
this.
|
|
24979
|
+
this._emitStandardized("list", { partition, partitionValues, count: 0, errors: 0 });
|
|
24982
24980
|
return [];
|
|
24983
24981
|
}
|
|
24984
24982
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -24988,7 +24986,7 @@ ${errorDetails}`,
|
|
|
24988
24986
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
24989
24987
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
24990
24988
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
24991
|
-
this.
|
|
24989
|
+
this._emitStandardized("list", { partition, partitionValues, count: results.length, errors: 0 });
|
|
24992
24990
|
return results;
|
|
24993
24991
|
}
|
|
24994
24992
|
/**
|
|
@@ -25033,7 +25031,7 @@ ${errorDetails}`,
|
|
|
25033
25031
|
}
|
|
25034
25032
|
return this.handleResourceError(err, id, context);
|
|
25035
25033
|
});
|
|
25036
|
-
this.
|
|
25034
|
+
this._emitStandardized("list", { count: results.length, errors: 0 });
|
|
25037
25035
|
return results;
|
|
25038
25036
|
}
|
|
25039
25037
|
/**
|
|
@@ -25096,10 +25094,10 @@ ${errorDetails}`,
|
|
|
25096
25094
|
*/
|
|
25097
25095
|
handleListError(error, { partition, partitionValues }) {
|
|
25098
25096
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
25099
|
-
this.
|
|
25097
|
+
this._emitStandardized("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
25100
25098
|
return [];
|
|
25101
25099
|
}
|
|
25102
|
-
this.
|
|
25100
|
+
this._emitStandardized("list", { partition, partitionValues, count: 0, errors: 1 });
|
|
25103
25101
|
return [];
|
|
25104
25102
|
}
|
|
25105
25103
|
/**
|
|
@@ -25132,7 +25130,7 @@ ${errorDetails}`,
|
|
|
25132
25130
|
throw err;
|
|
25133
25131
|
});
|
|
25134
25132
|
const finalResults = await this.executeHooks("afterGetMany", results);
|
|
25135
|
-
this.
|
|
25133
|
+
this._emitStandardized("fetched-many", ids.length);
|
|
25136
25134
|
return finalResults;
|
|
25137
25135
|
}
|
|
25138
25136
|
/**
|
|
@@ -25218,7 +25216,7 @@ ${errorDetails}`,
|
|
|
25218
25216
|
hasTotalItems: totalItems !== null
|
|
25219
25217
|
}
|
|
25220
25218
|
};
|
|
25221
|
-
this.
|
|
25219
|
+
this._emitStandardized("paginated", result2);
|
|
25222
25220
|
return result2;
|
|
25223
25221
|
});
|
|
25224
25222
|
if (ok) return result;
|
|
@@ -25288,7 +25286,7 @@ ${errorDetails}`,
|
|
|
25288
25286
|
contentType
|
|
25289
25287
|
}));
|
|
25290
25288
|
if (!ok2) throw err2;
|
|
25291
|
-
this.
|
|
25289
|
+
this._emitStandardized("content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
25292
25290
|
return updatedData;
|
|
25293
25291
|
}
|
|
25294
25292
|
/**
|
|
@@ -25317,7 +25315,7 @@ ${errorDetails}`,
|
|
|
25317
25315
|
}
|
|
25318
25316
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
25319
25317
|
const contentType = response.ContentType || null;
|
|
25320
|
-
this.
|
|
25318
|
+
this._emitStandardized("content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
25321
25319
|
return {
|
|
25322
25320
|
buffer,
|
|
25323
25321
|
contentType
|
|
@@ -25349,7 +25347,7 @@ ${errorDetails}`,
|
|
|
25349
25347
|
metadata: existingMetadata
|
|
25350
25348
|
}));
|
|
25351
25349
|
if (!ok2) throw err2;
|
|
25352
|
-
this.
|
|
25350
|
+
this._emitStandardized("content-deleted", id, id);
|
|
25353
25351
|
return response;
|
|
25354
25352
|
}
|
|
25355
25353
|
/**
|
|
@@ -25661,7 +25659,7 @@ ${errorDetails}`,
|
|
|
25661
25659
|
const data = await this.get(id);
|
|
25662
25660
|
data._partition = partitionName;
|
|
25663
25661
|
data._partitionValues = partitionValues;
|
|
25664
|
-
this.
|
|
25662
|
+
this._emitStandardized("partition-fetched", data, data.id);
|
|
25665
25663
|
return data;
|
|
25666
25664
|
}
|
|
25667
25665
|
/**
|
|
@@ -25910,6 +25908,120 @@ ${errorDetails}`,
|
|
|
25910
25908
|
}
|
|
25911
25909
|
return out;
|
|
25912
25910
|
}
|
|
25911
|
+
// ============================================================================
|
|
25912
|
+
// STATE MACHINE METHODS
|
|
25913
|
+
// ============================================================================
|
|
25914
|
+
/**
|
|
25915
|
+
* State machine accessor object
|
|
25916
|
+
* Provides namespaced access to state machine operations
|
|
25917
|
+
* @type {Object}
|
|
25918
|
+
* @property {Function} send - Trigger state transition
|
|
25919
|
+
* @property {Function} get - Get current state
|
|
25920
|
+
* @property {Function} canTransition - Check if transition is valid
|
|
25921
|
+
* @property {Function} getValidEvents - Get valid events for current state
|
|
25922
|
+
* @property {Function} initialize - Initialize entity with initial state
|
|
25923
|
+
* @property {Function} history - Get transition history
|
|
25924
|
+
* @example
|
|
25925
|
+
* await orders.state.send('order-123', 'CONFIRM');
|
|
25926
|
+
* const state = await orders.state.get('order-123');
|
|
25927
|
+
* const canShip = await orders.state.canTransition('order-123', 'SHIP');
|
|
25928
|
+
*/
|
|
25929
|
+
get state() {
|
|
25930
|
+
const resource = this;
|
|
25931
|
+
const throwIfNoStateMachine = () => {
|
|
25932
|
+
if (!resource._stateMachine) {
|
|
25933
|
+
throw new Error(
|
|
25934
|
+
`No state machine configured for resource '${resource.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25935
|
+
);
|
|
25936
|
+
}
|
|
25937
|
+
};
|
|
25938
|
+
return {
|
|
25939
|
+
/**
|
|
25940
|
+
* Trigger a state transition
|
|
25941
|
+
* @param {string} id - Entity ID
|
|
25942
|
+
* @param {string} event - Event name
|
|
25943
|
+
* @param {Object} [eventData] - Event data
|
|
25944
|
+
* @returns {Promise<Object>} Transition result
|
|
25945
|
+
* @example
|
|
25946
|
+
* await orders.state.send('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
25947
|
+
*/
|
|
25948
|
+
send: async (id, event, eventData) => {
|
|
25949
|
+
throwIfNoStateMachine();
|
|
25950
|
+
return resource._stateMachine.send(id, event, eventData);
|
|
25951
|
+
},
|
|
25952
|
+
/**
|
|
25953
|
+
* Get current state of an entity
|
|
25954
|
+
* @param {string} id - Entity ID
|
|
25955
|
+
* @returns {Promise<string>} Current state
|
|
25956
|
+
* @example
|
|
25957
|
+
* const currentState = await orders.state.get('order-123');
|
|
25958
|
+
*/
|
|
25959
|
+
get: async (id) => {
|
|
25960
|
+
throwIfNoStateMachine();
|
|
25961
|
+
return resource._stateMachine.getState(id);
|
|
25962
|
+
},
|
|
25963
|
+
/**
|
|
25964
|
+
* Check if a transition is valid
|
|
25965
|
+
* @param {string} id - Entity ID
|
|
25966
|
+
* @param {string} event - Event name
|
|
25967
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
25968
|
+
* @example
|
|
25969
|
+
* const canConfirm = await orders.state.canTransition('order-123', 'CONFIRM');
|
|
25970
|
+
*/
|
|
25971
|
+
canTransition: async (id, event) => {
|
|
25972
|
+
throwIfNoStateMachine();
|
|
25973
|
+
return resource._stateMachine.canTransition(id, event);
|
|
25974
|
+
},
|
|
25975
|
+
/**
|
|
25976
|
+
* Get all valid events for the current state
|
|
25977
|
+
* @param {string} id - Entity ID
|
|
25978
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
25979
|
+
* @example
|
|
25980
|
+
* const events = await orders.state.getValidEvents('order-123');
|
|
25981
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
25982
|
+
*/
|
|
25983
|
+
getValidEvents: async (id) => {
|
|
25984
|
+
throwIfNoStateMachine();
|
|
25985
|
+
return resource._stateMachine.getValidEvents(id);
|
|
25986
|
+
},
|
|
25987
|
+
/**
|
|
25988
|
+
* Initialize entity with initial state
|
|
25989
|
+
* @param {string} id - Entity ID
|
|
25990
|
+
* @param {Object} [context] - Initial context data
|
|
25991
|
+
* @returns {Promise<void>}
|
|
25992
|
+
* @example
|
|
25993
|
+
* await orders.state.initialize('order-456', { customerId: 'user-123' });
|
|
25994
|
+
*/
|
|
25995
|
+
initialize: async (id, context) => {
|
|
25996
|
+
throwIfNoStateMachine();
|
|
25997
|
+
return resource._stateMachine.initializeEntity(id, context);
|
|
25998
|
+
},
|
|
25999
|
+
/**
|
|
26000
|
+
* Get transition history for an entity
|
|
26001
|
+
* @param {string} id - Entity ID
|
|
26002
|
+
* @param {Object} [options] - Query options
|
|
26003
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
26004
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
26005
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
26006
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
26007
|
+
* @example
|
|
26008
|
+
* const history = await orders.state.history('order-123', { limit: 50 });
|
|
26009
|
+
*/
|
|
26010
|
+
history: async (id, options) => {
|
|
26011
|
+
throwIfNoStateMachine();
|
|
26012
|
+
return resource._stateMachine.getTransitionHistory(id, options);
|
|
26013
|
+
}
|
|
26014
|
+
};
|
|
26015
|
+
}
|
|
26016
|
+
/**
|
|
26017
|
+
* Internal method to attach state machine instance
|
|
26018
|
+
* This is called by StateMachinePlugin during initialization
|
|
26019
|
+
* @private
|
|
26020
|
+
* @param {Object} stateMachine - State machine instance
|
|
26021
|
+
*/
|
|
26022
|
+
_attachStateMachine(stateMachine) {
|
|
26023
|
+
this._stateMachine = stateMachine;
|
|
26024
|
+
}
|
|
25913
26025
|
}
|
|
25914
26026
|
function validateResourceConfig(config) {
|
|
25915
26027
|
const errors = [];
|
|
@@ -26070,7 +26182,7 @@ class Database extends EventEmitter {
|
|
|
26070
26182
|
})();
|
|
26071
26183
|
this.version = "1";
|
|
26072
26184
|
this.s3dbVersion = (() => {
|
|
26073
|
-
const [ok, err, version] = tryFn(() => true ? "13.
|
|
26185
|
+
const [ok, err, version] = tryFn(() => true ? "13.3.0" : "latest");
|
|
26074
26186
|
return ok ? version : "latest";
|
|
26075
26187
|
})();
|
|
26076
26188
|
this._resourcesMap = {};
|
|
@@ -26915,7 +27027,7 @@ class Database extends EventEmitter {
|
|
|
26915
27027
|
if (!existingVersionData || existingVersionData.hash !== newHash) {
|
|
26916
27028
|
await this.uploadMetadataFile();
|
|
26917
27029
|
}
|
|
26918
|
-
this.emit("
|
|
27030
|
+
this.emit("s3db.resourceUpdated", name);
|
|
26919
27031
|
return existingResource;
|
|
26920
27032
|
}
|
|
26921
27033
|
const existingMetadata = this.savedMetadata?.resources?.[name];
|
|
@@ -26952,7 +27064,7 @@ class Database extends EventEmitter {
|
|
|
26952
27064
|
this._applyMiddlewares(resource, middlewares);
|
|
26953
27065
|
}
|
|
26954
27066
|
await this.uploadMetadataFile();
|
|
26955
|
-
this.emit("
|
|
27067
|
+
this.emit("s3db.resourceCreated", name);
|
|
26956
27068
|
return resource;
|
|
26957
27069
|
}
|
|
26958
27070
|
/**
|
|
@@ -27078,6 +27190,7 @@ class Database extends EventEmitter {
|
|
|
27078
27190
|
return !!this.savedMetadata;
|
|
27079
27191
|
}
|
|
27080
27192
|
async disconnect() {
|
|
27193
|
+
await this.emit("disconnected", /* @__PURE__ */ new Date());
|
|
27081
27194
|
await tryFn(async () => {
|
|
27082
27195
|
if (this.pluginList && this.pluginList.length > 0) {
|
|
27083
27196
|
for (const plugin of this.pluginList) {
|
|
@@ -27228,7 +27341,7 @@ class Database extends EventEmitter {
|
|
|
27228
27341
|
for (const hook of hooks) {
|
|
27229
27342
|
const [ok, error] = await tryFn(() => hook({ database: this, ...context }));
|
|
27230
27343
|
if (!ok) {
|
|
27231
|
-
this.emit("
|
|
27344
|
+
this.emit("hookError", { event, error, context });
|
|
27232
27345
|
if (this.strictHooks) {
|
|
27233
27346
|
throw new DatabaseError(`Hook execution failed for event '${event}': ${error.message}`, {
|
|
27234
27347
|
event,
|
|
@@ -27787,7 +27900,7 @@ class SqsReplicator extends BaseReplicator {
|
|
|
27787
27900
|
region: this.region,
|
|
27788
27901
|
credentials: this.config.credentials
|
|
27789
27902
|
});
|
|
27790
|
-
this.emit("initialized", {
|
|
27903
|
+
this.emit("db:plugin:initialized", {
|
|
27791
27904
|
replicator: this.name,
|
|
27792
27905
|
queueUrl: this.queueUrl,
|
|
27793
27906
|
queues: this.queues,
|
|
@@ -27817,7 +27930,7 @@ class SqsReplicator extends BaseReplicator {
|
|
|
27817
27930
|
});
|
|
27818
27931
|
const result2 = await this.sqsClient.send(command);
|
|
27819
27932
|
results.push({ queueUrl, messageId: result2.MessageId });
|
|
27820
|
-
this.emit("replicated", {
|
|
27933
|
+
this.emit("plg:replicator:replicated", {
|
|
27821
27934
|
replicator: this.name,
|
|
27822
27935
|
resource,
|
|
27823
27936
|
operation,
|
|
@@ -27833,7 +27946,7 @@ class SqsReplicator extends BaseReplicator {
|
|
|
27833
27946
|
if (this.config.verbose) {
|
|
27834
27947
|
console.warn(`[SqsReplicator] Replication failed for ${resource}: ${err.message}`);
|
|
27835
27948
|
}
|
|
27836
|
-
this.emit("
|
|
27949
|
+
this.emit("plg:replicator:error", {
|
|
27837
27950
|
replicator: this.name,
|
|
27838
27951
|
resource,
|
|
27839
27952
|
operation,
|
|
@@ -28230,7 +28343,7 @@ ${createSQL}`);
|
|
|
28230
28343
|
}
|
|
28231
28344
|
}
|
|
28232
28345
|
const success = errors.length === 0;
|
|
28233
|
-
this.emit("replicated", {
|
|
28346
|
+
this.emit("plg:replicator:replicated", {
|
|
28234
28347
|
replicator: this.name,
|
|
28235
28348
|
resourceName,
|
|
28236
28349
|
operation,
|
|
@@ -28516,7 +28629,7 @@ class WebhookReplicator extends BaseReplicator {
|
|
|
28516
28629
|
});
|
|
28517
28630
|
throw error;
|
|
28518
28631
|
}
|
|
28519
|
-
this.emit("initialized", {
|
|
28632
|
+
this.emit("db:plugin:initialized", {
|
|
28520
28633
|
replicator: this.name,
|
|
28521
28634
|
url: this.url,
|
|
28522
28635
|
method: this.method,
|
|
@@ -28536,7 +28649,7 @@ class WebhookReplicator extends BaseReplicator {
|
|
|
28536
28649
|
const payload = this.createPayload(resource, operation, transformedData, id, beforeData);
|
|
28537
28650
|
const response = await this._makeRequest(payload);
|
|
28538
28651
|
if (response.success) {
|
|
28539
|
-
this.emit("replicated", {
|
|
28652
|
+
this.emit("plg:replicator:replicated", {
|
|
28540
28653
|
replicator: this.name,
|
|
28541
28654
|
resource,
|
|
28542
28655
|
operation,
|
|
@@ -28553,7 +28666,7 @@ class WebhookReplicator extends BaseReplicator {
|
|
|
28553
28666
|
if (this.config.verbose) {
|
|
28554
28667
|
console.warn(`[WebhookReplicator] Replication failed for ${resource}: ${err.message}`);
|
|
28555
28668
|
}
|
|
28556
|
-
this.emit("
|
|
28669
|
+
this.emit("plg:replicator:error", {
|
|
28557
28670
|
replicator: this.name,
|
|
28558
28671
|
resource,
|
|
28559
28672
|
operation,
|
|
@@ -28832,13 +28945,13 @@ class ReplicatorPlugin extends Plugin {
|
|
|
28832
28945
|
}
|
|
28833
28946
|
};
|
|
28834
28947
|
this.eventHandlers.set(resource.name, {
|
|
28835
|
-
|
|
28836
|
-
|
|
28837
|
-
|
|
28948
|
+
inserted: insertHandler,
|
|
28949
|
+
updated: updateHandler,
|
|
28950
|
+
deleted: deleteHandler
|
|
28838
28951
|
});
|
|
28839
|
-
resource.on("
|
|
28840
|
-
resource.on("
|
|
28841
|
-
resource.on("
|
|
28952
|
+
resource.on("inserted", insertHandler);
|
|
28953
|
+
resource.on("updated", updateHandler);
|
|
28954
|
+
resource.on("deleted", deleteHandler);
|
|
28842
28955
|
this.eventListenersInstalled.add(resource.name);
|
|
28843
28956
|
}
|
|
28844
28957
|
async onInstall() {
|
|
@@ -29228,9 +29341,9 @@ class ReplicatorPlugin extends Plugin {
|
|
|
29228
29341
|
const resource = this.database.resources[resourceName];
|
|
29229
29342
|
const handlers = this.eventHandlers.get(resourceName);
|
|
29230
29343
|
if (resource && handlers) {
|
|
29231
|
-
resource.off("
|
|
29232
|
-
resource.off("
|
|
29233
|
-
resource.off("
|
|
29344
|
+
resource.off("inserted", handlers.inserted);
|
|
29345
|
+
resource.off("updated", handlers.updated);
|
|
29346
|
+
resource.off("deleted", handlers.deleted);
|
|
29234
29347
|
}
|
|
29235
29348
|
}
|
|
29236
29349
|
}
|
|
@@ -29359,7 +29472,7 @@ class S3QueuePlugin extends Plugin {
|
|
|
29359
29472
|
createdAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
29360
29473
|
};
|
|
29361
29474
|
await plugin.queueResource.insert(queueEntry);
|
|
29362
|
-
plugin.emit("message
|
|
29475
|
+
plugin.emit("plg:s3-queue:message-enqueued", { id: record.id, queueId: queueEntry.id });
|
|
29363
29476
|
return record;
|
|
29364
29477
|
};
|
|
29365
29478
|
resource.queueStats = async function() {
|
|
@@ -30620,6 +30733,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30620
30733
|
// entityId -> currentState
|
|
30621
30734
|
});
|
|
30622
30735
|
}
|
|
30736
|
+
await this._attachStateMachinesToResources();
|
|
30623
30737
|
await this._setupTriggers();
|
|
30624
30738
|
this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
|
|
30625
30739
|
}
|
|
@@ -31461,6 +31575,58 @@ class StateMachinePlugin extends Plugin {
|
|
|
31461
31575
|
}
|
|
31462
31576
|
}
|
|
31463
31577
|
}
|
|
31578
|
+
/**
|
|
31579
|
+
* Attach state machine instances to their associated resources
|
|
31580
|
+
* This enables the resource API: resource.state(id, event)
|
|
31581
|
+
* @private
|
|
31582
|
+
*/
|
|
31583
|
+
async _attachStateMachinesToResources() {
|
|
31584
|
+
for (const [machineName, machineConfig] of Object.entries(this.config.stateMachines)) {
|
|
31585
|
+
const resourceConfig = machineConfig.config || machineConfig;
|
|
31586
|
+
if (!resourceConfig.resource) {
|
|
31587
|
+
if (this.config.verbose) {
|
|
31588
|
+
console.log(`[StateMachinePlugin] Machine '${machineName}' has no resource configured, skipping attachment`);
|
|
31589
|
+
}
|
|
31590
|
+
continue;
|
|
31591
|
+
}
|
|
31592
|
+
let resource;
|
|
31593
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31594
|
+
resource = this.database.resources[resourceConfig.resource];
|
|
31595
|
+
if (!resource) {
|
|
31596
|
+
console.warn(
|
|
31597
|
+
`[StateMachinePlugin] Resource '${resourceConfig.resource}' not found for machine '${machineName}'. Resource API will not be available.`
|
|
31598
|
+
);
|
|
31599
|
+
continue;
|
|
31600
|
+
}
|
|
31601
|
+
} else {
|
|
31602
|
+
resource = resourceConfig.resource;
|
|
31603
|
+
}
|
|
31604
|
+
const machineProxy = {
|
|
31605
|
+
send: async (id, event, eventData) => {
|
|
31606
|
+
return this.send(machineName, id, event, eventData);
|
|
31607
|
+
},
|
|
31608
|
+
getState: async (id) => {
|
|
31609
|
+
return this.getState(machineName, id);
|
|
31610
|
+
},
|
|
31611
|
+
canTransition: async (id, event) => {
|
|
31612
|
+
return this.canTransition(machineName, id, event);
|
|
31613
|
+
},
|
|
31614
|
+
getValidEvents: async (id) => {
|
|
31615
|
+
return this.getValidEvents(machineName, id);
|
|
31616
|
+
},
|
|
31617
|
+
initializeEntity: async (id, context) => {
|
|
31618
|
+
return this.initializeEntity(machineName, id, context);
|
|
31619
|
+
},
|
|
31620
|
+
getTransitionHistory: async (id, options) => {
|
|
31621
|
+
return this.getTransitionHistory(machineName, id, options);
|
|
31622
|
+
}
|
|
31623
|
+
};
|
|
31624
|
+
resource._attachStateMachine(machineProxy);
|
|
31625
|
+
if (this.config.verbose) {
|
|
31626
|
+
console.log(`[StateMachinePlugin] Attached machine '${machineName}' to resource '${resource.name}'`);
|
|
31627
|
+
}
|
|
31628
|
+
}
|
|
31629
|
+
}
|
|
31464
31630
|
async start() {
|
|
31465
31631
|
if (this.config.verbose) {
|
|
31466
31632
|
console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
|
|
@@ -43725,6 +43891,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43725
43891
|
const commandName = command.constructor.name;
|
|
43726
43892
|
const input = command.input || {};
|
|
43727
43893
|
this.emit("cl:request", commandName, input);
|
|
43894
|
+
this.emit("command.request", commandName, input);
|
|
43728
43895
|
let response;
|
|
43729
43896
|
try {
|
|
43730
43897
|
switch (commandName) {
|
|
@@ -43753,6 +43920,7 @@ class MemoryClient extends EventEmitter {
|
|
|
43753
43920
|
throw new Error(`Unsupported command: ${commandName}`);
|
|
43754
43921
|
}
|
|
43755
43922
|
this.emit("cl:response", commandName, response, input);
|
|
43923
|
+
this.emit("command.response", commandName, response, input);
|
|
43756
43924
|
return response;
|
|
43757
43925
|
} catch (error) {
|
|
43758
43926
|
const mappedError = mapAwsError(error, {
|
|
@@ -44048,13 +44216,13 @@ class MemoryClient extends EventEmitter {
|
|
|
44048
44216
|
if (keys.length > 0) {
|
|
44049
44217
|
const result = await this.deleteObjects(keys);
|
|
44050
44218
|
totalDeleted = result.Deleted.length;
|
|
44051
|
-
this.emit("
|
|
44219
|
+
this.emit("deleteAll", {
|
|
44052
44220
|
prefix,
|
|
44053
44221
|
batch: totalDeleted,
|
|
44054
44222
|
total: totalDeleted
|
|
44055
44223
|
});
|
|
44056
44224
|
}
|
|
44057
|
-
this.emit("
|
|
44225
|
+
this.emit("deleteAllComplete", {
|
|
44058
44226
|
prefix,
|
|
44059
44227
|
totalDeleted
|
|
44060
44228
|
});
|
|
@@ -44101,7 +44269,7 @@ class MemoryClient extends EventEmitter {
|
|
|
44101
44269
|
});
|
|
44102
44270
|
}
|
|
44103
44271
|
}
|
|
44104
|
-
this.emit("
|
|
44272
|
+
this.emit("moveAllObjects", { results, errors });
|
|
44105
44273
|
if (errors.length > 0) {
|
|
44106
44274
|
const error = new Error("Some objects could not be moved");
|
|
44107
44275
|
error.context = {
|