s3db.js 11.2.3 → 11.2.4
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 +1177 -128
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.es.js +1172 -129
- package/dist/s3db.es.js.map +1 -1
- package/package.json +1 -1
- package/src/behaviors/enforce-limits.js +28 -4
- package/src/behaviors/index.js +6 -1
- package/src/client.class.js +11 -1
- package/src/concerns/partition-queue.js +7 -1
- package/src/concerns/plugin-storage.js +75 -13
- package/src/database.class.js +19 -4
- package/src/errors.js +306 -27
- package/src/partition-drivers/base-partition-driver.js +12 -2
- package/src/partition-drivers/index.js +7 -1
- package/src/partition-drivers/memory-partition-driver.js +20 -5
- package/src/partition-drivers/sqs-partition-driver.js +6 -1
- package/src/plugins/audit.errors.js +46 -0
- package/src/plugins/backup/base-backup-driver.class.js +36 -6
- package/src/plugins/backup/filesystem-backup-driver.class.js +55 -7
- package/src/plugins/backup/index.js +40 -9
- package/src/plugins/backup/multi-backup-driver.class.js +69 -9
- package/src/plugins/backup/s3-backup-driver.class.js +48 -6
- package/src/plugins/backup.errors.js +45 -0
- package/src/plugins/cache/cache.class.js +8 -1
- package/src/plugins/cache.errors.js +47 -0
- package/src/plugins/cache.plugin.js +8 -1
- package/src/plugins/fulltext.errors.js +46 -0
- package/src/plugins/fulltext.plugin.js +15 -3
- package/src/plugins/metrics.errors.js +46 -0
- package/src/plugins/queue-consumer.plugin.js +31 -4
- package/src/plugins/queue.errors.js +46 -0
- package/src/plugins/replicator.errors.js +46 -0
- package/src/plugins/replicator.plugin.js +40 -5
- package/src/plugins/replicators/base-replicator.class.js +19 -3
- package/src/plugins/replicators/index.js +9 -3
- package/src/plugins/replicators/s3db-replicator.class.js +38 -8
- package/src/plugins/scheduler.errors.js +46 -0
- package/src/plugins/scheduler.plugin.js +79 -19
- package/src/plugins/state-machine.errors.js +47 -0
- package/src/plugins/state-machine.plugin.js +86 -17
- package/src/stream/index.js +6 -1
- package/src/stream/resource-reader.class.js +6 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { S3dbError } from '../errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StateMachineError - Errors related to state machine operations
|
|
5
|
+
*
|
|
6
|
+
* Used for state machine operations including:
|
|
7
|
+
* - State transitions
|
|
8
|
+
* - State validation
|
|
9
|
+
* - Transition conditions
|
|
10
|
+
* - State machine configuration
|
|
11
|
+
* - Workflow execution
|
|
12
|
+
*
|
|
13
|
+
* @extends S3dbError
|
|
14
|
+
*/
|
|
15
|
+
export class StateMachineError extends S3dbError {
|
|
16
|
+
constructor(message, details = {}) {
|
|
17
|
+
const { currentState, targetState, resourceName, operation = 'unknown', ...rest } = details;
|
|
18
|
+
|
|
19
|
+
let description = details.description;
|
|
20
|
+
if (!description) {
|
|
21
|
+
description = `
|
|
22
|
+
State Machine Operation Error
|
|
23
|
+
|
|
24
|
+
Operation: ${operation}
|
|
25
|
+
${currentState ? `Current State: ${currentState}` : ''}
|
|
26
|
+
${targetState ? `Target State: ${targetState}` : ''}
|
|
27
|
+
${resourceName ? `Resource: ${resourceName}` : ''}
|
|
28
|
+
|
|
29
|
+
Common causes:
|
|
30
|
+
1. Invalid state transition
|
|
31
|
+
2. State machine not configured
|
|
32
|
+
3. Transition conditions not met
|
|
33
|
+
4. State not defined in configuration
|
|
34
|
+
5. Missing transition handler
|
|
35
|
+
|
|
36
|
+
Solution:
|
|
37
|
+
Check state machine configuration and valid transitions.
|
|
38
|
+
|
|
39
|
+
Docs: https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/state-machine.md
|
|
40
|
+
`.trim();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
super(message, { ...rest, currentState, targetState, resourceName, operation, description });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default StateMachineError;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Plugin from "./plugin.class.js";
|
|
2
2
|
import tryFn from "../concerns/try-fn.js";
|
|
3
|
+
import { StateMachineError } from "./state-machine.errors.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* StateMachinePlugin - Finite State Machine Management
|
|
@@ -120,20 +121,39 @@ export class StateMachinePlugin extends Plugin {
|
|
|
120
121
|
|
|
121
122
|
_validateConfiguration() {
|
|
122
123
|
if (!this.config.stateMachines || Object.keys(this.config.stateMachines).length === 0) {
|
|
123
|
-
throw new
|
|
124
|
+
throw new StateMachineError('At least one state machine must be defined', {
|
|
125
|
+
operation: 'validateConfiguration',
|
|
126
|
+
machineCount: 0,
|
|
127
|
+
suggestion: 'Provide at least one state machine in the stateMachines configuration'
|
|
128
|
+
});
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
for (const [machineName, machine] of Object.entries(this.config.stateMachines)) {
|
|
127
132
|
if (!machine.states || Object.keys(machine.states).length === 0) {
|
|
128
|
-
throw new
|
|
133
|
+
throw new StateMachineError(`Machine '${machineName}' must have states defined`, {
|
|
134
|
+
operation: 'validateConfiguration',
|
|
135
|
+
machineId: machineName,
|
|
136
|
+
suggestion: 'Define at least one state in the states configuration'
|
|
137
|
+
});
|
|
129
138
|
}
|
|
130
|
-
|
|
139
|
+
|
|
131
140
|
if (!machine.initialState) {
|
|
132
|
-
throw new
|
|
141
|
+
throw new StateMachineError(`Machine '${machineName}' must have an initialState`, {
|
|
142
|
+
operation: 'validateConfiguration',
|
|
143
|
+
machineId: machineName,
|
|
144
|
+
availableStates: Object.keys(machine.states),
|
|
145
|
+
suggestion: 'Specify an initialState property matching one of the defined states'
|
|
146
|
+
});
|
|
133
147
|
}
|
|
134
|
-
|
|
148
|
+
|
|
135
149
|
if (!machine.states[machine.initialState]) {
|
|
136
|
-
throw new
|
|
150
|
+
throw new StateMachineError(`Initial state '${machine.initialState}' not found in machine '${machineName}'`, {
|
|
151
|
+
operation: 'validateConfiguration',
|
|
152
|
+
machineId: machineName,
|
|
153
|
+
initialState: machine.initialState,
|
|
154
|
+
availableStates: Object.keys(machine.states),
|
|
155
|
+
suggestion: 'Set initialState to one of the defined states'
|
|
156
|
+
});
|
|
137
157
|
}
|
|
138
158
|
}
|
|
139
159
|
}
|
|
@@ -200,14 +220,27 @@ export class StateMachinePlugin extends Plugin {
|
|
|
200
220
|
async send(machineId, entityId, event, context = {}) {
|
|
201
221
|
const machine = this.machines.get(machineId);
|
|
202
222
|
if (!machine) {
|
|
203
|
-
throw new
|
|
223
|
+
throw new StateMachineError(`State machine '${machineId}' not found`, {
|
|
224
|
+
operation: 'send',
|
|
225
|
+
machineId,
|
|
226
|
+
availableMachines: Array.from(this.machines.keys()),
|
|
227
|
+
suggestion: 'Check machine ID or use getMachines() to list available machines'
|
|
228
|
+
});
|
|
204
229
|
}
|
|
205
230
|
|
|
206
231
|
const currentState = await this.getState(machineId, entityId);
|
|
207
232
|
const stateConfig = machine.config.states[currentState];
|
|
208
|
-
|
|
233
|
+
|
|
209
234
|
if (!stateConfig || !stateConfig.on || !stateConfig.on[event]) {
|
|
210
|
-
throw new
|
|
235
|
+
throw new StateMachineError(`Event '${event}' not valid for state '${currentState}' in machine '${machineId}'`, {
|
|
236
|
+
operation: 'send',
|
|
237
|
+
machineId,
|
|
238
|
+
entityId,
|
|
239
|
+
event,
|
|
240
|
+
currentState,
|
|
241
|
+
validEvents: stateConfig && stateConfig.on ? Object.keys(stateConfig.on) : [],
|
|
242
|
+
suggestion: 'Use getValidEvents() to check which events are valid for the current state'
|
|
243
|
+
});
|
|
211
244
|
}
|
|
212
245
|
|
|
213
246
|
const targetState = stateConfig.on[event];
|
|
@@ -218,12 +251,21 @@ export class StateMachinePlugin extends Plugin {
|
|
|
218
251
|
const guard = this.config.guards[guardName];
|
|
219
252
|
|
|
220
253
|
if (guard) {
|
|
221
|
-
const [guardOk, guardErr, guardResult] = await tryFn(() =>
|
|
254
|
+
const [guardOk, guardErr, guardResult] = await tryFn(() =>
|
|
222
255
|
guard(context, event, { database: this.database, machineId, entityId })
|
|
223
256
|
);
|
|
224
|
-
|
|
257
|
+
|
|
225
258
|
if (!guardOk || !guardResult) {
|
|
226
|
-
throw new
|
|
259
|
+
throw new StateMachineError(`Transition blocked by guard '${guardName}'`, {
|
|
260
|
+
operation: 'send',
|
|
261
|
+
machineId,
|
|
262
|
+
entityId,
|
|
263
|
+
event,
|
|
264
|
+
currentState,
|
|
265
|
+
guardName,
|
|
266
|
+
guardError: guardErr?.message || 'Guard returned false',
|
|
267
|
+
suggestion: 'Check guard conditions or modify the context to satisfy guard requirements'
|
|
268
|
+
});
|
|
227
269
|
}
|
|
228
270
|
}
|
|
229
271
|
}
|
|
@@ -363,7 +405,12 @@ export class StateMachinePlugin extends Plugin {
|
|
|
363
405
|
async getState(machineId, entityId) {
|
|
364
406
|
const machine = this.machines.get(machineId);
|
|
365
407
|
if (!machine) {
|
|
366
|
-
throw new
|
|
408
|
+
throw new StateMachineError(`State machine '${machineId}' not found`, {
|
|
409
|
+
operation: 'getState',
|
|
410
|
+
machineId,
|
|
411
|
+
availableMachines: Array.from(this.machines.keys()),
|
|
412
|
+
suggestion: 'Check machine ID or use getMachines() to list available machines'
|
|
413
|
+
});
|
|
367
414
|
}
|
|
368
415
|
|
|
369
416
|
// Check in-memory cache first
|
|
@@ -397,7 +444,12 @@ export class StateMachinePlugin extends Plugin {
|
|
|
397
444
|
async getValidEvents(machineId, stateOrEntityId) {
|
|
398
445
|
const machine = this.machines.get(machineId);
|
|
399
446
|
if (!machine) {
|
|
400
|
-
throw new
|
|
447
|
+
throw new StateMachineError(`State machine '${machineId}' not found`, {
|
|
448
|
+
operation: 'getValidEvents',
|
|
449
|
+
machineId,
|
|
450
|
+
availableMachines: Array.from(this.machines.keys()),
|
|
451
|
+
suggestion: 'Check machine ID or use getMachines() to list available machines'
|
|
452
|
+
});
|
|
401
453
|
}
|
|
402
454
|
|
|
403
455
|
let state;
|
|
@@ -458,7 +510,12 @@ export class StateMachinePlugin extends Plugin {
|
|
|
458
510
|
async initializeEntity(machineId, entityId, context = {}) {
|
|
459
511
|
const machine = this.machines.get(machineId);
|
|
460
512
|
if (!machine) {
|
|
461
|
-
throw new
|
|
513
|
+
throw new StateMachineError(`State machine '${machineId}' not found`, {
|
|
514
|
+
operation: 'initializeEntity',
|
|
515
|
+
machineId,
|
|
516
|
+
availableMachines: Array.from(this.machines.keys()),
|
|
517
|
+
suggestion: 'Check machine ID or use getMachines() to list available machines'
|
|
518
|
+
});
|
|
462
519
|
}
|
|
463
520
|
|
|
464
521
|
const initialState = machine.config.initialState;
|
|
@@ -483,7 +540,14 @@ export class StateMachinePlugin extends Plugin {
|
|
|
483
540
|
|
|
484
541
|
// Only throw if error is NOT "already exists"
|
|
485
542
|
if (!ok && err && !err.message?.includes('already exists')) {
|
|
486
|
-
throw new
|
|
543
|
+
throw new StateMachineError('Failed to initialize entity state', {
|
|
544
|
+
operation: 'initializeEntity',
|
|
545
|
+
machineId,
|
|
546
|
+
entityId,
|
|
547
|
+
initialState,
|
|
548
|
+
original: err,
|
|
549
|
+
suggestion: 'Check state resource configuration and database permissions'
|
|
550
|
+
});
|
|
487
551
|
}
|
|
488
552
|
}
|
|
489
553
|
|
|
@@ -519,7 +583,12 @@ export class StateMachinePlugin extends Plugin {
|
|
|
519
583
|
visualize(machineId) {
|
|
520
584
|
const machine = this.machines.get(machineId);
|
|
521
585
|
if (!machine) {
|
|
522
|
-
throw new
|
|
586
|
+
throw new StateMachineError(`State machine '${machineId}' not found`, {
|
|
587
|
+
operation: 'visualize',
|
|
588
|
+
machineId,
|
|
589
|
+
availableMachines: Array.from(this.machines.keys()),
|
|
590
|
+
suggestion: 'Check machine ID or use getMachines() to list available machines'
|
|
591
|
+
});
|
|
523
592
|
}
|
|
524
593
|
|
|
525
594
|
let dot = `digraph ${machineId} {\n`;
|
package/src/stream/index.js
CHANGED
|
@@ -3,10 +3,15 @@ export * from "./resource-writer.class.js"
|
|
|
3
3
|
export * from "./resource-ids-reader.class.js"
|
|
4
4
|
export * from "./resource-ids-page-reader.class.js"
|
|
5
5
|
|
|
6
|
+
import { StreamError } from '../errors.js';
|
|
7
|
+
|
|
6
8
|
export function streamToString(stream) {
|
|
7
9
|
return new Promise((resolve, reject) => {
|
|
8
10
|
if (!stream) {
|
|
9
|
-
return reject(new
|
|
11
|
+
return reject(new StreamError('Stream is undefined', {
|
|
12
|
+
operation: 'streamToString',
|
|
13
|
+
suggestion: 'Ensure a valid stream is passed to streamToString()'
|
|
14
|
+
}));
|
|
10
15
|
}
|
|
11
16
|
const chunks = [];
|
|
12
17
|
stream.on('data', (chunk) => chunks.push(chunk));
|
|
@@ -4,13 +4,18 @@ import { PromisePool } from "@supercharge/promise-pool";
|
|
|
4
4
|
|
|
5
5
|
import { ResourceIdsPageReader } from "./resource-ids-page-reader.class.js"
|
|
6
6
|
import tryFn from "../concerns/try-fn.js";
|
|
7
|
+
import { StreamError } from '../errors.js';
|
|
7
8
|
|
|
8
9
|
export class ResourceReader extends EventEmitter {
|
|
9
10
|
constructor({ resource, batchSize = 10, concurrency = 5 }) {
|
|
10
11
|
super()
|
|
11
12
|
|
|
12
13
|
if (!resource) {
|
|
13
|
-
throw new
|
|
14
|
+
throw new StreamError('Resource is required for ResourceReader', {
|
|
15
|
+
operation: 'constructor',
|
|
16
|
+
resource: resource?.name,
|
|
17
|
+
suggestion: 'Pass a valid Resource instance when creating ResourceReader'
|
|
18
|
+
});
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
this.resource = resource;
|