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.
Files changed (42) hide show
  1. package/dist/s3db.cjs.js +1177 -128
  2. package/dist/s3db.cjs.js.map +1 -1
  3. package/dist/s3db.es.js +1172 -129
  4. package/dist/s3db.es.js.map +1 -1
  5. package/package.json +1 -1
  6. package/src/behaviors/enforce-limits.js +28 -4
  7. package/src/behaviors/index.js +6 -1
  8. package/src/client.class.js +11 -1
  9. package/src/concerns/partition-queue.js +7 -1
  10. package/src/concerns/plugin-storage.js +75 -13
  11. package/src/database.class.js +19 -4
  12. package/src/errors.js +306 -27
  13. package/src/partition-drivers/base-partition-driver.js +12 -2
  14. package/src/partition-drivers/index.js +7 -1
  15. package/src/partition-drivers/memory-partition-driver.js +20 -5
  16. package/src/partition-drivers/sqs-partition-driver.js +6 -1
  17. package/src/plugins/audit.errors.js +46 -0
  18. package/src/plugins/backup/base-backup-driver.class.js +36 -6
  19. package/src/plugins/backup/filesystem-backup-driver.class.js +55 -7
  20. package/src/plugins/backup/index.js +40 -9
  21. package/src/plugins/backup/multi-backup-driver.class.js +69 -9
  22. package/src/plugins/backup/s3-backup-driver.class.js +48 -6
  23. package/src/plugins/backup.errors.js +45 -0
  24. package/src/plugins/cache/cache.class.js +8 -1
  25. package/src/plugins/cache.errors.js +47 -0
  26. package/src/plugins/cache.plugin.js +8 -1
  27. package/src/plugins/fulltext.errors.js +46 -0
  28. package/src/plugins/fulltext.plugin.js +15 -3
  29. package/src/plugins/metrics.errors.js +46 -0
  30. package/src/plugins/queue-consumer.plugin.js +31 -4
  31. package/src/plugins/queue.errors.js +46 -0
  32. package/src/plugins/replicator.errors.js +46 -0
  33. package/src/plugins/replicator.plugin.js +40 -5
  34. package/src/plugins/replicators/base-replicator.class.js +19 -3
  35. package/src/plugins/replicators/index.js +9 -3
  36. package/src/plugins/replicators/s3db-replicator.class.js +38 -8
  37. package/src/plugins/scheduler.errors.js +46 -0
  38. package/src/plugins/scheduler.plugin.js +79 -19
  39. package/src/plugins/state-machine.errors.js +47 -0
  40. package/src/plugins/state-machine.plugin.js +86 -17
  41. package/src/stream/index.js +6 -1
  42. 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 Error('StateMachinePlugin: At least one state machine must be defined');
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 Error(`StateMachinePlugin: Machine '${machineName}' must have states defined`);
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 Error(`StateMachinePlugin: Machine '${machineName}' must have an initialState`);
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 Error(`StateMachinePlugin: Initial state '${machine.initialState}' not found in machine '${machineName}'`);
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 Error(`State machine '${machineId}' not found`);
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 Error(`Event '${event}' not valid for state '${currentState}' in machine '${machineId}'`);
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 Error(`Transition blocked by guard '${guardName}': ${guardErr?.message || 'Guard returned false'}`);
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 Error(`State machine '${machineId}' not found`);
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 Error(`State machine '${machineId}' not found`);
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 Error(`State machine '${machineId}' not found`);
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 Error(`Failed to initialize entity state: ${err.message}`);
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 Error(`State machine '${machineId}' not found`);
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`;
@@ -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 Error('streamToString: stream is undefined'));
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 Error("Resource is required for ResourceReader");
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;