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.es.js
CHANGED
|
@@ -23916,7 +23916,7 @@ ${errorDetails}`,
|
|
|
23916
23916
|
data = await this.applyVersionMapping(data, objectVersion, this.version);
|
|
23917
23917
|
}
|
|
23918
23918
|
data = await this.executeHooks("afterGet", data);
|
|
23919
|
-
this.
|
|
23919
|
+
this._emitStandardized("get", "fetched", data, data.id);
|
|
23920
23920
|
const value = data;
|
|
23921
23921
|
return value;
|
|
23922
23922
|
}
|
|
@@ -24626,7 +24626,7 @@ ${errorDetails}`,
|
|
|
24626
24626
|
await this.executeHooks("beforeDelete", objectData);
|
|
24627
24627
|
const key = this.getResourceKey(id);
|
|
24628
24628
|
const [ok2, err2, response] = await tryFn(() => this.client.deleteObject(key));
|
|
24629
|
-
this.
|
|
24629
|
+
this._emitStandardized("delete", "deleted", {
|
|
24630
24630
|
...objectData,
|
|
24631
24631
|
$before: { ...objectData },
|
|
24632
24632
|
$after: null
|
|
@@ -24754,7 +24754,7 @@ ${errorDetails}`,
|
|
|
24754
24754
|
}
|
|
24755
24755
|
const count = await this.client.count({ prefix });
|
|
24756
24756
|
await this.executeHooks("afterCount", { count, partition, partitionValues });
|
|
24757
|
-
this.
|
|
24757
|
+
this._emitStandardized("count", "counted", count);
|
|
24758
24758
|
return count;
|
|
24759
24759
|
}
|
|
24760
24760
|
/**
|
|
@@ -24777,7 +24777,7 @@ ${errorDetails}`,
|
|
|
24777
24777
|
const result = await this.insert(attributes);
|
|
24778
24778
|
return result;
|
|
24779
24779
|
});
|
|
24780
|
-
this.
|
|
24780
|
+
this._emitStandardized("insertMany", "inserted-many", objects.length);
|
|
24781
24781
|
return results;
|
|
24782
24782
|
}
|
|
24783
24783
|
/**
|
|
@@ -24812,7 +24812,7 @@ ${errorDetails}`,
|
|
|
24812
24812
|
return response;
|
|
24813
24813
|
});
|
|
24814
24814
|
await this.executeHooks("afterDeleteMany", { ids, results });
|
|
24815
|
-
this.
|
|
24815
|
+
this._emitStandardized("deleteMany", "deleted-many", ids.length);
|
|
24816
24816
|
return results;
|
|
24817
24817
|
}
|
|
24818
24818
|
async deleteAll() {
|
|
@@ -24821,7 +24821,7 @@ ${errorDetails}`,
|
|
|
24821
24821
|
}
|
|
24822
24822
|
const prefix = `resource=${this.name}/data`;
|
|
24823
24823
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24824
|
-
this.
|
|
24824
|
+
this._emitStandardized("deleteAll", "deleted-all", {
|
|
24825
24825
|
version: this.version,
|
|
24826
24826
|
prefix,
|
|
24827
24827
|
deletedCount
|
|
@@ -24838,7 +24838,7 @@ ${errorDetails}`,
|
|
|
24838
24838
|
}
|
|
24839
24839
|
const prefix = `resource=${this.name}`;
|
|
24840
24840
|
const deletedCount = await this.client.deleteAll({ prefix });
|
|
24841
|
-
this.
|
|
24841
|
+
this._emitStandardized("deleteAllData", "deleted-all-data", {
|
|
24842
24842
|
resource: this.name,
|
|
24843
24843
|
prefix,
|
|
24844
24844
|
deletedCount
|
|
@@ -24908,7 +24908,7 @@ ${errorDetails}`,
|
|
|
24908
24908
|
const idPart = parts.find((part) => part.startsWith("id="));
|
|
24909
24909
|
return idPart ? idPart.replace("id=", "") : null;
|
|
24910
24910
|
}).filter(Boolean);
|
|
24911
|
-
this.
|
|
24911
|
+
this._emitStandardized("listIds", "listed-ids", ids.length);
|
|
24912
24912
|
return ids;
|
|
24913
24913
|
}
|
|
24914
24914
|
/**
|
|
@@ -24950,12 +24950,12 @@ ${errorDetails}`,
|
|
|
24950
24950
|
const [ok, err, ids] = await tryFn(() => this.listIds({ limit, offset }));
|
|
24951
24951
|
if (!ok) throw err;
|
|
24952
24952
|
const results = await this.processListResults(ids, "main");
|
|
24953
|
-
this.
|
|
24953
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
24954
24954
|
return results;
|
|
24955
24955
|
}
|
|
24956
24956
|
async listPartition({ partition, partitionValues, limit, offset = 0 }) {
|
|
24957
24957
|
if (!this.config.partitions?.[partition]) {
|
|
24958
|
-
this.
|
|
24958
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 0 });
|
|
24959
24959
|
return [];
|
|
24960
24960
|
}
|
|
24961
24961
|
const partitionDef = this.config.partitions[partition];
|
|
@@ -24965,7 +24965,7 @@ ${errorDetails}`,
|
|
|
24965
24965
|
const ids = this.extractIdsFromKeys(keys).slice(offset);
|
|
24966
24966
|
const filteredIds = limit ? ids.slice(0, limit) : ids;
|
|
24967
24967
|
const results = await this.processPartitionResults(filteredIds, partition, partitionDef, keys);
|
|
24968
|
-
this.
|
|
24968
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: results.length, errors: 0 });
|
|
24969
24969
|
return results;
|
|
24970
24970
|
}
|
|
24971
24971
|
/**
|
|
@@ -25010,7 +25010,7 @@ ${errorDetails}`,
|
|
|
25010
25010
|
}
|
|
25011
25011
|
return this.handleResourceError(err, id, context);
|
|
25012
25012
|
});
|
|
25013
|
-
this.
|
|
25013
|
+
this._emitStandardized("list", "listed", { count: results.length, errors: 0 });
|
|
25014
25014
|
return results;
|
|
25015
25015
|
}
|
|
25016
25016
|
/**
|
|
@@ -25073,10 +25073,10 @@ ${errorDetails}`,
|
|
|
25073
25073
|
*/
|
|
25074
25074
|
handleListError(error, { partition, partitionValues }) {
|
|
25075
25075
|
if (error.message.includes("Partition '") && error.message.includes("' not found")) {
|
|
25076
|
-
this.
|
|
25076
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
25077
25077
|
return [];
|
|
25078
25078
|
}
|
|
25079
|
-
this.
|
|
25079
|
+
this._emitStandardized("list", "listed", { partition, partitionValues, count: 0, errors: 1 });
|
|
25080
25080
|
return [];
|
|
25081
25081
|
}
|
|
25082
25082
|
/**
|
|
@@ -25109,7 +25109,7 @@ ${errorDetails}`,
|
|
|
25109
25109
|
throw err;
|
|
25110
25110
|
});
|
|
25111
25111
|
const finalResults = await this.executeHooks("afterGetMany", results);
|
|
25112
|
-
this.
|
|
25112
|
+
this._emitStandardized("getMany", "fetched-many", ids.length);
|
|
25113
25113
|
return finalResults;
|
|
25114
25114
|
}
|
|
25115
25115
|
/**
|
|
@@ -25195,7 +25195,7 @@ ${errorDetails}`,
|
|
|
25195
25195
|
hasTotalItems: totalItems !== null
|
|
25196
25196
|
}
|
|
25197
25197
|
};
|
|
25198
|
-
this.
|
|
25198
|
+
this._emitStandardized("page", "paginated", result2);
|
|
25199
25199
|
return result2;
|
|
25200
25200
|
});
|
|
25201
25201
|
if (ok) return result;
|
|
@@ -25265,7 +25265,7 @@ ${errorDetails}`,
|
|
|
25265
25265
|
contentType
|
|
25266
25266
|
}));
|
|
25267
25267
|
if (!ok2) throw err2;
|
|
25268
|
-
this.
|
|
25268
|
+
this._emitStandardized("setContent", "content-set", { id, contentType, contentLength: buffer.length }, id);
|
|
25269
25269
|
return updatedData;
|
|
25270
25270
|
}
|
|
25271
25271
|
/**
|
|
@@ -25294,7 +25294,7 @@ ${errorDetails}`,
|
|
|
25294
25294
|
}
|
|
25295
25295
|
const buffer = Buffer.from(await response.Body.transformToByteArray());
|
|
25296
25296
|
const contentType = response.ContentType || null;
|
|
25297
|
-
this.
|
|
25297
|
+
this._emitStandardized("content", "content-fetched", { id, contentLength: buffer.length, contentType }, id);
|
|
25298
25298
|
return {
|
|
25299
25299
|
buffer,
|
|
25300
25300
|
contentType
|
|
@@ -25326,7 +25326,7 @@ ${errorDetails}`,
|
|
|
25326
25326
|
metadata: existingMetadata
|
|
25327
25327
|
}));
|
|
25328
25328
|
if (!ok2) throw err2;
|
|
25329
|
-
this.
|
|
25329
|
+
this._emitStandardized("deleteContent", "content-deleted", id, id);
|
|
25330
25330
|
return response;
|
|
25331
25331
|
}
|
|
25332
25332
|
/**
|
|
@@ -25638,7 +25638,7 @@ ${errorDetails}`,
|
|
|
25638
25638
|
const data = await this.get(id);
|
|
25639
25639
|
data._partition = partitionName;
|
|
25640
25640
|
data._partitionValues = partitionValues;
|
|
25641
|
-
this.
|
|
25641
|
+
this._emitStandardized("getFromPartition", "partition-fetched", data, data.id);
|
|
25642
25642
|
return data;
|
|
25643
25643
|
}
|
|
25644
25644
|
/**
|
|
@@ -25887,6 +25887,123 @@ ${errorDetails}`,
|
|
|
25887
25887
|
}
|
|
25888
25888
|
return out;
|
|
25889
25889
|
}
|
|
25890
|
+
// ============================================================================
|
|
25891
|
+
// STATE MACHINE METHODS
|
|
25892
|
+
// ============================================================================
|
|
25893
|
+
/**
|
|
25894
|
+
* Send an event to trigger a state transition
|
|
25895
|
+
* @param {string} id - Entity ID
|
|
25896
|
+
* @param {string} event - Event name
|
|
25897
|
+
* @param {Object} [eventData] - Event data
|
|
25898
|
+
* @returns {Promise<Object>} Transition result
|
|
25899
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25900
|
+
* @example
|
|
25901
|
+
* await orders.state('order-123', 'CONFIRM', { confirmedBy: 'user-456' });
|
|
25902
|
+
*/
|
|
25903
|
+
async state(id, event, eventData) {
|
|
25904
|
+
if (!this._stateMachine) {
|
|
25905
|
+
throw new Error(
|
|
25906
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25907
|
+
);
|
|
25908
|
+
}
|
|
25909
|
+
return this._stateMachine.send(id, event, eventData);
|
|
25910
|
+
}
|
|
25911
|
+
/**
|
|
25912
|
+
* Get current state of an entity
|
|
25913
|
+
* @param {string} id - Entity ID
|
|
25914
|
+
* @returns {Promise<string>} Current state
|
|
25915
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25916
|
+
* @example
|
|
25917
|
+
* const currentState = await orders.getState('order-123');
|
|
25918
|
+
*/
|
|
25919
|
+
async getState(id) {
|
|
25920
|
+
if (!this._stateMachine) {
|
|
25921
|
+
throw new Error(
|
|
25922
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25923
|
+
);
|
|
25924
|
+
}
|
|
25925
|
+
return this._stateMachine.getState(id);
|
|
25926
|
+
}
|
|
25927
|
+
/**
|
|
25928
|
+
* Check if a transition is valid
|
|
25929
|
+
* @param {string} id - Entity ID
|
|
25930
|
+
* @param {string} event - Event name
|
|
25931
|
+
* @returns {Promise<boolean>} True if transition is valid
|
|
25932
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25933
|
+
* @example
|
|
25934
|
+
* const canConfirm = await orders.canTransition('order-123', 'CONFIRM');
|
|
25935
|
+
*/
|
|
25936
|
+
async canTransition(id, event) {
|
|
25937
|
+
if (!this._stateMachine) {
|
|
25938
|
+
throw new Error(
|
|
25939
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25940
|
+
);
|
|
25941
|
+
}
|
|
25942
|
+
return this._stateMachine.canTransition(id, event);
|
|
25943
|
+
}
|
|
25944
|
+
/**
|
|
25945
|
+
* Get all valid events for the current state
|
|
25946
|
+
* @param {string} id - Entity ID
|
|
25947
|
+
* @returns {Promise<Array<string>>} Array of valid event names
|
|
25948
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25949
|
+
* @example
|
|
25950
|
+
* const events = await orders.getValidEvents('order-123');
|
|
25951
|
+
* // Returns: ['SHIP', 'CANCEL']
|
|
25952
|
+
*/
|
|
25953
|
+
async getValidEvents(id) {
|
|
25954
|
+
if (!this._stateMachine) {
|
|
25955
|
+
throw new Error(
|
|
25956
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25957
|
+
);
|
|
25958
|
+
}
|
|
25959
|
+
return this._stateMachine.getValidEvents(id);
|
|
25960
|
+
}
|
|
25961
|
+
/**
|
|
25962
|
+
* Initialize entity with initial state
|
|
25963
|
+
* @param {string} id - Entity ID
|
|
25964
|
+
* @param {Object} [context] - Initial context data
|
|
25965
|
+
* @returns {Promise<void>}
|
|
25966
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25967
|
+
* @example
|
|
25968
|
+
* await orders.initializeState('order-456', { customerId: 'user-123' });
|
|
25969
|
+
*/
|
|
25970
|
+
async initializeState(id, context) {
|
|
25971
|
+
if (!this._stateMachine) {
|
|
25972
|
+
throw new Error(
|
|
25973
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25974
|
+
);
|
|
25975
|
+
}
|
|
25976
|
+
return this._stateMachine.initializeEntity(id, context);
|
|
25977
|
+
}
|
|
25978
|
+
/**
|
|
25979
|
+
* Get transition history for an entity
|
|
25980
|
+
* @param {string} id - Entity ID
|
|
25981
|
+
* @param {Object} [options] - Query options
|
|
25982
|
+
* @param {number} [options.limit=100] - Maximum number of transitions
|
|
25983
|
+
* @param {Date} [options.fromDate] - Filter from date
|
|
25984
|
+
* @param {Date} [options.toDate] - Filter to date
|
|
25985
|
+
* @returns {Promise<Array<Object>>} Transition history
|
|
25986
|
+
* @throws {Error} If no state machine is configured for this resource
|
|
25987
|
+
* @example
|
|
25988
|
+
* const history = await orders.getStateHistory('order-123', { limit: 50 });
|
|
25989
|
+
*/
|
|
25990
|
+
async getStateHistory(id, options) {
|
|
25991
|
+
if (!this._stateMachine) {
|
|
25992
|
+
throw new Error(
|
|
25993
|
+
`No state machine configured for resource '${this.name}'. Ensure StateMachinePlugin is installed and configured for this resource.`
|
|
25994
|
+
);
|
|
25995
|
+
}
|
|
25996
|
+
return this._stateMachine.getTransitionHistory(id, options);
|
|
25997
|
+
}
|
|
25998
|
+
/**
|
|
25999
|
+
* Internal method to attach state machine instance
|
|
26000
|
+
* This is called by StateMachinePlugin during initialization
|
|
26001
|
+
* @private
|
|
26002
|
+
* @param {Object} stateMachine - State machine instance
|
|
26003
|
+
*/
|
|
26004
|
+
_attachStateMachine(stateMachine) {
|
|
26005
|
+
this._stateMachine = stateMachine;
|
|
26006
|
+
}
|
|
25890
26007
|
}
|
|
25891
26008
|
function validateResourceConfig(config) {
|
|
25892
26009
|
const errors = [];
|
|
@@ -26047,7 +26164,7 @@ class Database extends EventEmitter {
|
|
|
26047
26164
|
})();
|
|
26048
26165
|
this.version = "1";
|
|
26049
26166
|
this.s3dbVersion = (() => {
|
|
26050
|
-
const [ok, err, version] = tryFn(() => true ? "13.2.
|
|
26167
|
+
const [ok, err, version] = tryFn(() => true ? "13.2.2" : "latest");
|
|
26051
26168
|
return ok ? version : "latest";
|
|
26052
26169
|
})();
|
|
26053
26170
|
this._resourcesMap = {};
|
|
@@ -30597,6 +30714,7 @@ class StateMachinePlugin extends Plugin {
|
|
|
30597
30714
|
// entityId -> currentState
|
|
30598
30715
|
});
|
|
30599
30716
|
}
|
|
30717
|
+
await this._attachStateMachinesToResources();
|
|
30600
30718
|
await this._setupTriggers();
|
|
30601
30719
|
this.emit("db:plugin:initialized", { machines: Array.from(this.machines.keys()) });
|
|
30602
30720
|
}
|
|
@@ -31438,6 +31556,58 @@ class StateMachinePlugin extends Plugin {
|
|
|
31438
31556
|
}
|
|
31439
31557
|
}
|
|
31440
31558
|
}
|
|
31559
|
+
/**
|
|
31560
|
+
* Attach state machine instances to their associated resources
|
|
31561
|
+
* This enables the resource API: resource.state(id, event)
|
|
31562
|
+
* @private
|
|
31563
|
+
*/
|
|
31564
|
+
async _attachStateMachinesToResources() {
|
|
31565
|
+
for (const [machineName, machineConfig] of Object.entries(this.config.stateMachines)) {
|
|
31566
|
+
const resourceConfig = machineConfig.config || machineConfig;
|
|
31567
|
+
if (!resourceConfig.resource) {
|
|
31568
|
+
if (this.config.verbose) {
|
|
31569
|
+
console.log(`[StateMachinePlugin] Machine '${machineName}' has no resource configured, skipping attachment`);
|
|
31570
|
+
}
|
|
31571
|
+
continue;
|
|
31572
|
+
}
|
|
31573
|
+
let resource;
|
|
31574
|
+
if (typeof resourceConfig.resource === "string") {
|
|
31575
|
+
resource = this.database.resources[resourceConfig.resource];
|
|
31576
|
+
if (!resource) {
|
|
31577
|
+
console.warn(
|
|
31578
|
+
`[StateMachinePlugin] Resource '${resourceConfig.resource}' not found for machine '${machineName}'. Resource API will not be available.`
|
|
31579
|
+
);
|
|
31580
|
+
continue;
|
|
31581
|
+
}
|
|
31582
|
+
} else {
|
|
31583
|
+
resource = resourceConfig.resource;
|
|
31584
|
+
}
|
|
31585
|
+
const machineProxy = {
|
|
31586
|
+
send: async (id, event, eventData) => {
|
|
31587
|
+
return this.send(machineName, id, event, eventData);
|
|
31588
|
+
},
|
|
31589
|
+
getState: async (id) => {
|
|
31590
|
+
return this.getState(machineName, id);
|
|
31591
|
+
},
|
|
31592
|
+
canTransition: async (id, event) => {
|
|
31593
|
+
return this.canTransition(machineName, id, event);
|
|
31594
|
+
},
|
|
31595
|
+
getValidEvents: async (id) => {
|
|
31596
|
+
return this.getValidEvents(machineName, id);
|
|
31597
|
+
},
|
|
31598
|
+
initializeEntity: async (id, context) => {
|
|
31599
|
+
return this.initializeEntity(machineName, id, context);
|
|
31600
|
+
},
|
|
31601
|
+
getTransitionHistory: async (id, options) => {
|
|
31602
|
+
return this.getTransitionHistory(machineName, id, options);
|
|
31603
|
+
}
|
|
31604
|
+
};
|
|
31605
|
+
resource._attachStateMachine(machineProxy);
|
|
31606
|
+
if (this.config.verbose) {
|
|
31607
|
+
console.log(`[StateMachinePlugin] Attached machine '${machineName}' to resource '${resource.name}'`);
|
|
31608
|
+
}
|
|
31609
|
+
}
|
|
31610
|
+
}
|
|
31441
31611
|
async start() {
|
|
31442
31612
|
if (this.config.verbose) {
|
|
31443
31613
|
console.log(`[StateMachinePlugin] Started with ${this.machines.size} state machines`);
|