s3db.js 13.2.1 → 13.2.2
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 +191 -21
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +191 -21
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/plugins/state-machine.plugin.js +68 -0
- package/src/resource.class.js +151 -20
package/dist/s3db.cjs.js
CHANGED
|
@@ -23939,7 +23939,7 @@ ${errorDetails}`,
|
|
|
23939
23939
|
data = await this.applyVersionMapping(data, objectVersion, this.version);
|
|
23940
23940
|
}
|
|
23941
23941
|
data = await this.executeHooks("afterGet", data);
|
|
23942
|
-
this.
|
|
23942
|
+
this._emitStandardized("get", "fetched", data, data.id);
|
|
23943
23943
|
const value = data;
|
|
23944
23944
|
return value;
|
|
23945
23945
|
}
|
|
@@ -24649,7 +24649,7 @@ ${errorDetails}`,
|
|
|
24649
24649
|
await this.executeHooks("beforeDelete", objectData);
|
|
24650
24650
|
const key = this.getResourceKey(id);
|
|
24651
24651
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
24652
|
-
this.
|
|
24652
|
+
this._emitStandardized("delete", "deleted", {
|
|
24653
24653
|
...objectData,
|
|
24654
24654
|
$before: { ...objectData },
|
|
24655
24655
|
$after: null
|
|
@@ -24777,7 +24777,7 @@ ${errorDetails}`,
|
|
|
24777
24777
|
}
|
|
24778
24778
|
const count = await this.client.count({ prefix });
|
|
24779
24779
|
await this.executeHooks("afterCount", { count, partition, partitionValues });
|
|
24780
|
-
this.
|
|
24780
|
+
this._emitStandardized("count", "counted", count);
|
|
24781
24781
|
return count;
|
|
24782
24782
|
}
|
|
24783
24783
|
/**
|
|
@@ -24800,7 +24800,7 @@ ${errorDetails}`,
|
|
|
24800
24800
|
const result = await this.insert(attributes);
|
|
24801
24801
|
return result;
|
|
24802
24802
|
});
|
|
24803
|
-
this.
|
|
24803
|
+
this._emitStandardized("insertMany", "inserted-many", objects.length);
|
|
24804
24804
|
return results;
|
|
24805
24805
|
}
|
|
24806
24806
|
/**
|
|
@@ -24835,7 +24835,7 @@ ${errorDetails}`,
|
|
|
24835
24835
|
return response;
|
|
24836
24836
|
});
|
|
24837
24837
|
await this.executeHooks("afterDeleteMany", { ids, results });
|
|
24838
|
-
this.
|
|
24838
|
+
this._emitStandardized("deleteMany", "deleted-many", ids.length);
|
|
24839
24839
|
return results;
|
|
24840
24840
|
}
|
|
24841
24841
|
async deleteAll() {
|
|
@@ -24844,7 +24844,7 @@ ${errorDetails}`,
|
|
|
24844
24844
|
}
|
|
24845
24845
|
const prefix = `resource=${this.name}/data`;
|
|
24846
24846
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24847
|
-
this.
|
|
24847
|
+
this._emitStandardized("deleteAll", "deleted-all", {
|
|
24848
24848
|
version: this.version,
|
|
24849
24849
|
prefix,
|
|
24850
24850
|
deletedCount
|
|
@@ -24861,7 +24861,7 @@ ${errorDetails}`,
|
|
|
24861
24861
|
}
|
|
24862
24862
|
const prefix = `resource=${this.name}`;
|
|
24863
24863
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24864
|
-
this.
|
|
24864
|
+
this._emitStandardized("deleteAllData", "deleted-all-data", {
|
|
24865
24865
|
resource: this.name,
|
|
24866
24866
|
prefix,
|
|
24867
24867
|
deletedCount
|
|
@@ -24931,7 +24931,7 @@ ${errorDetails}`,
|
|
|
24931
24931
|
const idPart = parts.find((part) => part.startsWith("id="));
|
|
24932
24932
|
return idPart ? idPart.replace("id=", "") : null;
|
|
24933
24933
|
}).filter(Boolean);
|
|
24934
|
-
this.
|
|
24934
|
+
this._emitStandardized("listIds", "listed-ids", ids.length);
|
|
24935
24935
|
return ids;
|
|
24936
24936
|
}
|
|
24937
24937
|
/**
|
|
@@ -24973,12 +24973,12 @@ ${errorDetails}`,
|
|
|
24973
24973
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
24974
24974
|
if (!ok) throw err;
|
|
24975
24975
|
const results = await this.processListResults(ids, "main");
|
|
24976
|
-
this.
|
|
24976
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24977
24977
|
return results;
|
|
24978
24978
|
}
|
|
24979
24979
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
24980
24980
|
if (!this.config.partitions?.[partition]) {
|
|
24981
|
-
this.
|
|
24981
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
|
|
24982
24982
|
return [];
|
|
24983
24983
|
}
|
|
24984
24984
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -24988,7 +24988,7 @@ ${errorDetails}`,
|
|
|
24988
24988
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
24989
24989
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
24990
24990
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
24991
|
-
this.
|
|
24991
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
|
|
24992
24992
|
return results;
|
|
24993
24993
|
}
|
|
24994
24994
|
/**
|
|
@@ -25033,7 +25033,7 @@ ${errorDetails}`,
|
|
|
25033
25033
|
}
|
|
25034
25034
|
return this.handleResourceError(err, id, context);
|
|
25035
25035
|
});
|
|
25036
|
-
this.
|
|
25036
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
25037
25037
|
return results;
|
|
25038
25038
|
}
|
|
25039
25039
|
/**
|
|
@@ -25096,10 +25096,10 @@ ${errorDetails}`,
|
|
|
25096
25096
|
*/
|
|
25097
25097
|
handleListError(error, { partition, partitionValues }) {
|
|
25098
25098
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
25099
|
-
this.
|
|
25099
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
25100
25100
|
return [];
|
|
25101
25101
|
}
|
|
25102
|
-
this.
|
|
25102
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
25103
25103
|
return [];
|
|
25104
25104
|
}
|
|
25105
25105
|
/**
|
|
@@ -25132,7 +25132,7 @@ ${errorDetails}`,
|
|
|
25132
25132
|
throw err;
|
|
25133
25133
|
});
|
|
25134
25134
|
const finalResults = await this.executeHooks("afterGetMany", results);
|
|
25135
|
-
this.
|
|
25135
|
+
this._emitStandardized("getMany", "fetched-many", ids.length);
|
|
25136
25136
|
return finalResults;
|
|
25137
25137
|
}
|
|
25138
25138
|
/**
|
|
@@ -25218,7 +25218,7 @@ ${errorDetails}`,
|
|
|
25218
25218
|
hasTotalItems: totalItems !== null
|
|
25219
25219
|
}
|
|
25220
25220
|
};
|
|
25221
|
-
this.
|
|
25221
|
+
this._emitStandardized("page", "paginated", result2);
|
|
25222
25222
|
return result2;
|
|
25223
25223
|
});
|
|
25224
25224
|
if (ok) return result;
|
|
@@ -25288,7 +25288,7 @@ ${errorDetails}`,
|
|
|
25288
25288
|
contentType
|
|
25289
25289
|
}));
|
|
25290
25290
|
if (!ok2) throw err2;
|
|
25291
|
-
this.
|
|
25291
|
+
this._emitStandardized("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
25292
25292
|
return updatedData;
|
|
25293
25293
|
}
|
|
25294
25294
|
/**
|
|
@@ -25317,7 +25317,7 @@ ${errorDetails}`,
|
|
|
25317
25317
|
}
|
|
25318
25318
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
25319
25319
|
const contentType = response.ContentType || null;
|
|
25320
|
-
this.
|
|
25320
|
+
this._emitStandardized("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
25321
25321
|
return {
|
|
25322
25322
|
buffer,
|
|
25323
25323
|
contentType
|
|
@@ -25349,7 +25349,7 @@ ${errorDetails}`,
|
|
|
25349
25349
|
metadata: existingMetadata
|
|
25350
25350
|
}));
|
|
25351
25351
|
if (!ok2) throw err2;
|
|
25352
|
-
this.
|
|
25352
|
+
this._emitStandardized("deleteContent", "content-deleted", id, id);
|
|
25353
25353
|
return response;
|
|
25354
25354
|
}
|
|
25355
25355
|
/**
|
|
@@ -25661,7 +25661,7 @@ ${errorDetails}`,
|
|
|
25661
25661
|
const data = await this.get(id);
|
|
25662
25662
|
data._partition = partitionName;
|
|
25663
25663
|
data._partitionValues = partitionValues;
|
|
25664
|
-
this.
|
|
25664
|
+
this._emitStandardized("getFromPartition", "partition-fetched", data, data.id);
|
|
25665
25665
|
return data;
|
|
25666
25666
|
}
|
|
25667
25667
|
/**
|
|
@@ -25910,6 +25910,123 @@ ${errorDetails}`,
|
|
|
25910
25910
|
}
|
|
25911
25911
|
return out;
|
|
25912
25912
|
}
|
|
25913
|
+
// ============================================================================
|
|
25914
|
+
// STATE MACHINE METHODS
|
|
25915
|
+
// ============================================================================
|
|
25916
|
+
/**
|
|
25917
|
+
* Send an event to trigger a state transition
|
|
25918
|
+
* @param {string} id - Entity ID
|
|
25919
|
+
* @param {string} event - Event name
|
|
25920
|
+
* @param {Object} [eventData] - Event data
|
|
25921
|
+
* @returns {Promise<Object>} Transition result
|
|
25922
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25923
|
+
* @example
|
|
25924
|
+
* await orders.state('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
25925
|
+
*/
|
|
25926
|
+
async state(id, event, eventData) {
|
|
25927
|
+
if (!this._stateMachine) {
|
|
25928
|
+
throw new Error(
|
|
25929
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25930
|
+
);
|
|
25931
|
+
}
|
|
25932
|
+
return this._stateMachine.send(id, event, eventData);
|
|
25933
|
+
}
|
|
25934
|
+
/**
|
|
25935
|
+
* Get current state of an entity
|
|
25936
|
+
* @param {string} id - Entity ID
|
|
25937
|
+
* @returns {Promise<string>} Current state
|
|
25938
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25939
|
+
* @example
|
|
25940
|
+
* const currentState = await orders.getState('order-123');
|
|
25941
|
+
*/
|
|
25942
|
+
async getState(id) {
|
|
25943
|
+
if (!this._stateMachine) {
|
|
25944
|
+
throw new Error(
|
|
25945
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25946
|
+
);
|
|
25947
|
+
}
|
|
25948
|
+
return this._stateMachine.getState(id);
|
|
25949
|
+
}
|
|
25950
|
+
/**
|
|
25951
|
+
* Check if a transition is valid
|
|
25952
|
+
* @param {string} id - Entity ID
|
|
25953
|
+
* @param {string} event - Event name
|
|
25954
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
25955
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25956
|
+
* @example
|
|
25957
|
+
* const canConfirm = await orders.canTransition('order-123', 'CONFIRM');
|
|
25958
|
+
*/
|
|
25959
|
+
async canTransition(id, event) {
|
|
25960
|
+
if (!this._stateMachine) {
|
|
25961
|
+
throw new Error(
|
|
25962
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25963
|
+
);
|
|
25964
|
+
}
|
|
25965
|
+
return this._stateMachine.canTransition(id, event);
|
|
25966
|
+
}
|
|
25967
|
+
/**
|
|
25968
|
+
* Get all valid events for the current state
|
|
25969
|
+
* @param {string} id - Entity ID
|
|
25970
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
25971
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25972
|
+
* @example
|
|
25973
|
+
* const events = await orders.getValidEvents('order-123');
|
|
25974
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
25975
|
+
*/
|
|
25976
|
+
async getValidEvents(id) {
|
|
25977
|
+
if (!this._stateMachine) {
|
|
25978
|
+
throw new Error(
|
|
25979
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25980
|
+
);
|
|
25981
|
+
}
|
|
25982
|
+
return this._stateMachine.getValidEvents(id);
|
|
25983
|
+
}
|
|
25984
|
+
/**
|
|
25985
|
+
* Initialize entity with initial state
|
|
25986
|
+
* @param {string} id - Entity ID
|
|
25987
|
+
* @param {Object} [context] - Initial context data
|
|
25988
|
+
* @returns {Promise<void>}
|
|
25989
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25990
|
+
* @example
|
|
25991
|
+
* await orders.initializeState('order-456', { customerId: 'user-123' });
|
|
25992
|
+
*/
|
|
25993
|
+
async initializeState(id, context) {
|
|
25994
|
+
if (!this._stateMachine) {
|
|
25995
|
+
throw new Error(
|
|
25996
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25997
|
+
);
|
|
25998
|
+
}
|
|
25999
|
+
return this._stateMachine.initializeEntity(id, context);
|
|
26000
|
+
}
|
|
26001
|
+
/**
|
|
26002
|
+
* Get transition history for an entity
|
|
26003
|
+
* @param {string} id - Entity ID
|
|
26004
|
+
* @param {Object} [options] - Query options
|
|
26005
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
26006
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
26007
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
26008
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
26009
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
26010
|
+
* @example
|
|
26011
|
+
* const history = await orders.getStateHistory('order-123', { limit: 50 });
|
|
26012
|
+
*/
|
|
26013
|
+
async getStateHistory(id, options) {
|
|
26014
|
+
if (!this._stateMachine) {
|
|
26015
|
+
throw new Error(
|
|
26016
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
26017
|
+
);
|
|
26018
|
+
}
|
|
26019
|
+
return this._stateMachine.getTransitionHistory(id, options);
|
|
26020
|
+
}
|
|
26021
|
+
/**
|
|
26022
|
+
* Internal method to attach state machine instance
|
|
26023
|
+
* This is called by StateMachinePlugin during initialization
|
|
26024
|
+
* @private
|
|
26025
|
+
* @param {Object} stateMachine - State machine instance
|
|
26026
|
+
*/
|
|
26027
|
+
_attachStateMachine(stateMachine) {
|
|
26028
|
+
this._stateMachine = stateMachine;
|
|
26029
|
+
}
|
|
25913
26030
|
}
|
|
25914
26031
|
function validateResourceConfig(config) {
|
|
25915
26032
|
const errors = [];
|
|
@@ -26070,7 +26187,7 @@ class Database extends EventEmitter {
|
|
|
26070
26187
|
})();
|
|
26071
26188
|
this.version = "1";
|
|
26072
26189
|
this.s3dbVersion = (() => {
|
|
26073
|
-
const [ok, err, version] = tryFn(() => true ? "13.2.
|
|
26190
|
+
const [ok, err, version] = tryFn(() => true ? "13.2.2" : "latest");
|
|
26074
26191
|
return ok ? version : "latest";
|
|
26075
26192
|
})();
|
|
26076
26193
|
this._resourcesMap = {};
|
|
@@ -30620,6 +30737,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30620
30737
|
// entityId -> currentState
|
|
30621
30738
|
});
|
|
30622
30739
|
}
|
|
30740
|
+
await this._attachStateMachinesToResources();
|
|
30623
30741
|
await this._setupTriggers();
|
|
30624
30742
|
this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
|
|
30625
30743
|
}
|
|
@@ -31461,6 +31579,58 @@ class StateMachinePlugin extends Plugin {
|
|
|
31461
31579
|
}
|
|
31462
31580
|
}
|
|
31463
31581
|
}
|
|
31582
|
+
/**
|
|
31583
|
+
* Attach state machine instances to their associated resources
|
|
31584
|
+
* This enables the resource API: resource.state(id, event)
|
|
31585
|
+
* @private
|
|
31586
|
+
*/
|
|
31587
|
+
async _attachStateMachinesToResources() {
|
|
31588
|
+
for (const [machineName, machineConfig] of Object.entries(this.config.stateMachines)) {
|
|
31589
|
+
const resourceConfig = machineConfig.config || machineConfig;
|
|
31590
|
+
if (!resourceConfig.resource) {
|
|
31591
|
+
if (this.config.verbose) {
|
|
31592
|
+
console.log(`[StateMachinePlugin] Machine '${machineName}' has no resource configured, skipping attachment`);
|
|
31593
|
+
}
|
|
31594
|
+
continue;
|
|
31595
|
+
}
|
|
31596
|
+
let resource;
|
|
31597
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31598
|
+
resource = this.database.resources[resourceConfig.resource];
|
|
31599
|
+
if (!resource) {
|
|
31600
|
+
console.warn(
|
|
31601
|
+
`[StateMachinePlugin] Resource '${resourceConfig.resource}' not found for machine '${machineName}'. Resource API will not be available.`
|
|
31602
|
+
);
|
|
31603
|
+
continue;
|
|
31604
|
+
}
|
|
31605
|
+
} else {
|
|
31606
|
+
resource = resourceConfig.resource;
|
|
31607
|
+
}
|
|
31608
|
+
const machineProxy = {
|
|
31609
|
+
send: async (id, event, eventData) => {
|
|
31610
|
+
return this.send(machineName, id, event, eventData);
|
|
31611
|
+
},
|
|
31612
|
+
getState: async (id) => {
|
|
31613
|
+
return this.getState(machineName, id);
|
|
31614
|
+
},
|
|
31615
|
+
canTransition: async (id, event) => {
|
|
31616
|
+
return this.canTransition(machineName, id, event);
|
|
31617
|
+
},
|
|
31618
|
+
getValidEvents: async (id) => {
|
|
31619
|
+
return this.getValidEvents(machineName, id);
|
|
31620
|
+
},
|
|
31621
|
+
initializeEntity: async (id, context) => {
|
|
31622
|
+
return this.initializeEntity(machineName, id, context);
|
|
31623
|
+
},
|
|
31624
|
+
getTransitionHistory: async (id, options) => {
|
|
31625
|
+
return this.getTransitionHistory(machineName, id, options);
|
|
31626
|
+
}
|
|
31627
|
+
};
|
|
31628
|
+
resource._attachStateMachine(machineProxy);
|
|
31629
|
+
if (this.config.verbose) {
|
|
31630
|
+
console.log(`[StateMachinePlugin] Attached machine '${machineName}' to resource '${resource.name}'`);
|
|
31631
|
+
}
|
|
31632
|
+
}
|
|
31633
|
+
}
|
|
31464
31634
|
async start() {
|
|
31465
31635
|
if (this.config.verbose) {
|
|
31466
31636
|
console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
|