s3db.js 13.6.1 → 14.0.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.
Files changed (189) hide show
  1. package/README.md +56 -15
  2. package/dist/s3db.cjs +72446 -39022
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72172 -38790
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +85 -50
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/route-context.js +601 -0
  34. package/src/plugins/api/index.js +168 -40
  35. package/src/plugins/api/routes/auth-routes.js +198 -30
  36. package/src/plugins/api/routes/resource-routes.js +19 -4
  37. package/src/plugins/api/server/health-manager.class.js +163 -0
  38. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  39. package/src/plugins/api/server/router.class.js +472 -0
  40. package/src/plugins/api/server.js +280 -1303
  41. package/src/plugins/api/utils/custom-routes.js +17 -5
  42. package/src/plugins/api/utils/guards.js +76 -17
  43. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  44. package/src/plugins/api/utils/openapi-generator.js +7 -6
  45. package/src/plugins/audit.plugin.js +30 -8
  46. package/src/plugins/backup.plugin.js +110 -14
  47. package/src/plugins/cache/cache.class.js +22 -5
  48. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  49. package/src/plugins/cache/memory-cache.class.js +211 -57
  50. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  51. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  52. package/src/plugins/cache/redis-cache.class.js +552 -0
  53. package/src/plugins/cache/s3-cache.class.js +17 -8
  54. package/src/plugins/cache.plugin.js +176 -61
  55. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  56. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  57. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  58. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  59. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  60. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  62. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/index.js +29 -8
  66. package/src/plugins/cloud-inventory/registry.js +64 -42
  67. package/src/plugins/cloud-inventory.plugin.js +240 -138
  68. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  69. package/src/plugins/concerns/resource-names.js +100 -0
  70. package/src/plugins/consumers/index.js +10 -2
  71. package/src/plugins/consumers/sqs-consumer.js +12 -2
  72. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  73. package/src/plugins/cookie-farm.errors.js +73 -0
  74. package/src/plugins/cookie-farm.plugin.js +869 -0
  75. package/src/plugins/costs.plugin.js +7 -1
  76. package/src/plugins/eventual-consistency/analytics.js +94 -19
  77. package/src/plugins/eventual-consistency/config.js +15 -7
  78. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  79. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  80. package/src/plugins/eventual-consistency/helpers.js +39 -14
  81. package/src/plugins/eventual-consistency/install.js +21 -2
  82. package/src/plugins/eventual-consistency/utils.js +32 -10
  83. package/src/plugins/fulltext.plugin.js +38 -11
  84. package/src/plugins/geo.plugin.js +61 -9
  85. package/src/plugins/identity/concerns/config.js +61 -0
  86. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  87. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  88. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  89. package/src/plugins/identity/concerns/token-generator.js +29 -4
  90. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  91. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  92. package/src/plugins/identity/drivers/index.js +18 -0
  93. package/src/plugins/identity/drivers/password-driver.js +122 -0
  94. package/src/plugins/identity/email-service.js +17 -2
  95. package/src/plugins/identity/index.js +413 -69
  96. package/src/plugins/identity/oauth2-server.js +413 -30
  97. package/src/plugins/identity/oidc-discovery.js +16 -8
  98. package/src/plugins/identity/rsa-keys.js +115 -35
  99. package/src/plugins/identity/server.js +166 -45
  100. package/src/plugins/identity/session-manager.js +53 -7
  101. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  102. package/src/plugins/identity/ui/routes.js +363 -255
  103. package/src/plugins/importer/index.js +153 -20
  104. package/src/plugins/index.js +9 -2
  105. package/src/plugins/kubernetes-inventory/index.js +6 -0
  106. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  107. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  108. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  109. package/src/plugins/metrics.plugin.js +64 -16
  110. package/src/plugins/ml/base-model.class.js +25 -15
  111. package/src/plugins/ml/regression-model.class.js +1 -1
  112. package/src/plugins/ml.errors.js +57 -25
  113. package/src/plugins/ml.plugin.js +28 -4
  114. package/src/plugins/namespace.js +210 -0
  115. package/src/plugins/plugin.class.js +180 -8
  116. package/src/plugins/puppeteer/console-monitor.js +729 -0
  117. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  118. package/src/plugins/puppeteer/network-monitor.js +816 -0
  119. package/src/plugins/puppeteer/performance-manager.js +746 -0
  120. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  121. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  122. package/src/plugins/puppeteer.errors.js +81 -0
  123. package/src/plugins/puppeteer.plugin.js +1327 -0
  124. package/src/plugins/queue-consumer.plugin.js +69 -14
  125. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  126. package/src/plugins/recon/concerns/command-runner.js +148 -0
  127. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  128. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  129. package/src/plugins/recon/concerns/process-manager.js +338 -0
  130. package/src/plugins/recon/concerns/report-generator.js +478 -0
  131. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  132. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  133. package/src/plugins/recon/config/defaults.js +321 -0
  134. package/src/plugins/recon/config/resources.js +370 -0
  135. package/src/plugins/recon/index.js +778 -0
  136. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  137. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  138. package/src/plugins/recon/managers/storage-manager.js +745 -0
  139. package/src/plugins/recon/managers/target-manager.js +274 -0
  140. package/src/plugins/recon/stages/asn-stage.js +314 -0
  141. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  142. package/src/plugins/recon/stages/dns-stage.js +107 -0
  143. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  144. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  145. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  146. package/src/plugins/recon/stages/http-stage.js +89 -0
  147. package/src/plugins/recon/stages/latency-stage.js +148 -0
  148. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  149. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  150. package/src/plugins/recon/stages/ports-stage.js +169 -0
  151. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  152. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  153. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  154. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  155. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  156. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  157. package/src/plugins/recon/stages/whois-stage.js +349 -0
  158. package/src/plugins/recon.plugin.js +75 -0
  159. package/src/plugins/recon.plugin.js.backup +2635 -0
  160. package/src/plugins/relation.errors.js +87 -14
  161. package/src/plugins/replicator.plugin.js +514 -137
  162. package/src/plugins/replicators/base-replicator.class.js +89 -1
  163. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  164. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  165. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  166. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  167. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  168. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  169. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  170. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  171. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  172. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  173. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  174. package/src/plugins/s3-queue.plugin.js +464 -65
  175. package/src/plugins/scheduler.plugin.js +20 -6
  176. package/src/plugins/state-machine.plugin.js +40 -9
  177. package/src/plugins/tfstate/base-driver.js +28 -4
  178. package/src/plugins/tfstate/errors.js +65 -10
  179. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  180. package/src/plugins/tfstate/index.js +163 -90
  181. package/src/plugins/tfstate/s3-driver.js +64 -6
  182. package/src/plugins/ttl.plugin.js +72 -17
  183. package/src/plugins/vector/distances.js +18 -12
  184. package/src/plugins/vector/kmeans.js +26 -4
  185. package/src/resource.class.js +115 -19
  186. package/src/testing/factory.class.js +20 -3
  187. package/src/testing/seeder.class.js +7 -1
  188. package/src/clients/memory-client.md +0 -917
  189. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -80,7 +80,13 @@ import { SchedulerError } from "./scheduler.errors.js";
80
80
  * if (backupPlugin) {
81
81
  * return await backupPlugin.backup('incremental');
82
82
  * }
83
- * throw new Error('BackupPlugin not available');
83
+ * throw new PluginError('BackupPlugin not available', {
84
+ * pluginName: 'SchedulerPlugin',
85
+ * operation: 'backupJob',
86
+ * statusCode: 500,
87
+ * retriable: false,
88
+ * suggestion: 'Ensure BackupPlugin is installed before scheduling backup jobs.'
89
+ * });
84
90
  * },
85
91
  * retries: 2
86
92
  * },
@@ -94,7 +100,13 @@ import { SchedulerError } from "./scheduler.errors.js";
94
100
  * if (backupPlugin) {
95
101
  * return await backupPlugin.backup('full');
96
102
  * }
97
- * throw new Error('BackupPlugin not available');
103
+ * throw new PluginError('BackupPlugin not available', {
104
+ * pluginName: 'SchedulerPlugin',
105
+ * operation: 'backupJob',
106
+ * statusCode: 500,
107
+ * retriable: false,
108
+ * suggestion: 'Ensure BackupPlugin is installed before scheduling backup jobs.'
109
+ * });
98
110
  * }
99
111
  * },
100
112
  *
@@ -414,8 +426,8 @@ export class SchedulerPlugin extends Plugin {
414
426
 
415
427
  // Acquire distributed lock with TTL to prevent concurrent execution across instances
416
428
  const storage = this.getStorage();
417
- const lockKey = `job-${jobName}`;
418
- const lock = await storage.acquireLock(lockKey, {
429
+ const lockName = `job-${jobName}`;
430
+ const lock = await storage.acquireLock(lockName, {
419
431
  ttl: Math.ceil(job.timeout / 1000) + 60, // Job timeout + 60 seconds buffer
420
432
  timeout: 0, // Don't wait if locked
421
433
  workerId: process.pid ? String(process.pid) : 'unknown'
@@ -571,7 +583,9 @@ export class SchedulerPlugin extends Plugin {
571
583
  }
572
584
  } finally {
573
585
  // Always release the distributed lock
574
- await tryFn(() => storage.releaseLock(lockKey));
586
+ if (lock) {
587
+ await tryFn(() => storage.releaseLock(lock));
588
+ }
575
589
  }
576
590
  }
577
591
 
@@ -921,4 +935,4 @@ export class SchedulerPlugin extends Plugin {
921
935
  this.activeJobs.clear();
922
936
  this.removeAllListeners();
923
937
  }
924
- }
938
+ }
@@ -1,5 +1,6 @@
1
1
  import { Plugin } from "./plugin.class.js";
2
2
  import tryFn from "../concerns/try-fn.js";
3
+ import { resolveResourceNames } from "./concerns/resource-names.js";
3
4
  import { StateMachineError } from "./state-machine.errors.js";
4
5
  import { ErrorClassifier } from "../concerns/error-classifier.js";
5
6
 
@@ -102,13 +103,27 @@ export class StateMachinePlugin extends Plugin {
102
103
  constructor(options = {}) {
103
104
  super();
104
105
 
106
+ const resourceNamesOption = options.resourceNames || {};
107
+
108
+ this._resourceDescriptors = {
109
+ transitionLog: {
110
+ defaultName: 'plg_state_transitions',
111
+ override: resourceNamesOption.transitionLog || options.transitionLogResource
112
+ },
113
+ states: {
114
+ defaultName: 'plg_entity_states',
115
+ override: resourceNamesOption.states || options.stateResource
116
+ }
117
+ };
118
+ this.resourceNames = this._resolveResourceNames();
119
+
105
120
  this.config = {
106
121
  stateMachines: options.stateMachines || {},
107
122
  actions: options.actions || {},
108
123
  guards: options.guards || {},
109
124
  persistTransitions: options.persistTransitions !== false,
110
- transitionLogResource: options.transitionLogResource || 'plg_state_transitions',
111
- stateResource: options.stateResource || 'plg_entity_states',
125
+ transitionLogResource: this.resourceNames.transitionLog,
126
+ stateResource: this.resourceNames.states,
112
127
  retryAttempts: options.retryAttempts || 3,
113
128
  retryDelay: options.retryDelay || 100,
114
129
  verbose: options.verbose || false,
@@ -138,6 +153,20 @@ export class StateMachinePlugin extends Plugin {
138
153
  this._validateConfiguration();
139
154
  }
140
155
 
156
+ _resolveResourceNames() {
157
+ return resolveResourceNames('state_machine', this._resourceDescriptors, {
158
+ namespace: this.namespace
159
+ });
160
+ }
161
+
162
+ onNamespaceChanged() {
163
+ this.resourceNames = this._resolveResourceNames();
164
+ if (this.config) {
165
+ this.config.transitionLogResource = this.resourceNames.transitionLog;
166
+ this.config.stateResource = this.resourceNames.states;
167
+ }
168
+ }
169
+
141
170
  /**
142
171
  * Wait for all pending event handlers to complete
143
172
  * Useful when working with async events (asyncEvents: true)
@@ -290,7 +319,7 @@ export class StateMachinePlugin extends Plugin {
290
319
  }
291
320
 
292
321
  // Acquire distributed lock to prevent concurrent transitions
293
- const lockName = await this._acquireTransitionLock(machineId, entityId);
322
+ const lock = await this._acquireTransitionLock(machineId, entityId);
294
323
 
295
324
  try {
296
325
  const currentState = await this.getState(machineId, entityId);
@@ -366,7 +395,7 @@ export class StateMachinePlugin extends Plugin {
366
395
  };
367
396
  } finally {
368
397
  // Always release lock, even if transition fails
369
- await this._releaseTransitionLock(lockName);
398
+ await this._releaseTransitionLock(lock);
370
399
  }
371
400
  }
372
401
 
@@ -610,19 +639,21 @@ export class StateMachinePlugin extends Plugin {
610
639
  });
611
640
  }
612
641
 
613
- return lockName;
642
+ return lock;
614
643
  }
615
644
 
616
645
  /**
617
646
  * Release distributed lock for transition
618
647
  * @private
619
648
  */
620
- async _releaseTransitionLock(lockName) {
649
+ async _releaseTransitionLock(lock) {
650
+ if (!lock) return;
651
+
621
652
  const storage = this.getStorage();
622
- const [ok, err] = await tryFn(() => storage.releaseLock(lockName));
653
+ const [ok, err] = await tryFn(() => storage.releaseLock(lock));
623
654
 
624
655
  if (!ok && this.config.verbose) {
625
- console.warn(`[StateMachinePlugin] Failed to release lock '${lockName}':`, err.message);
656
+ console.warn(`[StateMachinePlugin] Failed to release lock '${lock?.name}':`, err.message);
626
657
  }
627
658
  }
628
659
 
@@ -1500,4 +1531,4 @@ export class StateMachinePlugin extends Plugin {
1500
1531
  this.machines.clear();
1501
1532
  this.removeAllListeners();
1502
1533
  }
1503
- }
1534
+ }
@@ -1,3 +1,5 @@
1
+ import { TfStateError } from './errors.js';
2
+
1
3
  /**
2
4
  * Base Driver Class for TfState Plugin
3
5
  *
@@ -14,7 +16,12 @@ export class TfStateDriver {
14
16
  * Called during plugin installation
15
17
  */
16
18
  async initialize() {
17
- throw new Error('Driver must implement initialize()');
19
+ throw new TfStateError('Driver must implement initialize()', {
20
+ operation: 'initialize',
21
+ statusCode: 501,
22
+ retriable: false,
23
+ suggestion: 'Extend TfStateDriver and implement initialize() to configure backend connections.'
24
+ });
18
25
  }
19
26
 
20
27
  /**
@@ -22,7 +29,12 @@ export class TfStateDriver {
22
29
  * @returns {Promise<Array>} Array of state file metadata { path, lastModified, size }
23
30
  */
24
31
  async listStateFiles() {
25
- throw new Error('Driver must implement listStateFiles()');
32
+ throw new TfStateError('Driver must implement listStateFiles()', {
33
+ operation: 'listStateFiles',
34
+ statusCode: 501,
35
+ retriable: false,
36
+ suggestion: 'Override listStateFiles() to return available Terraform state metadata.'
37
+ });
26
38
  }
27
39
 
28
40
  /**
@@ -31,7 +43,13 @@ export class TfStateDriver {
31
43
  * @returns {Promise<Object>} Parsed state file content
32
44
  */
33
45
  async readStateFile(path) {
34
- throw new Error('Driver must implement readStateFile()');
46
+ throw new TfStateError('Driver must implement readStateFile()', {
47
+ operation: 'readStateFile',
48
+ statusCode: 501,
49
+ retriable: false,
50
+ suggestion: 'Override readStateFile(path) to load and parse the Terraform state JSON.',
51
+ path
52
+ });
35
53
  }
36
54
 
37
55
  /**
@@ -40,7 +58,13 @@ export class TfStateDriver {
40
58
  * @returns {Promise<Object>} Metadata { path, lastModified, size, etag }
41
59
  */
42
60
  async getStateFileMetadata(path) {
43
- throw new Error('Driver must implement getStateFileMetadata()');
61
+ throw new TfStateError('Driver must implement getStateFileMetadata()', {
62
+ operation: 'getStateFileMetadata',
63
+ statusCode: 501,
64
+ retriable: false,
65
+ suggestion: 'Override getStateFileMetadata(path) to return lastModified, size, and ETag information.',
66
+ path
67
+ });
44
68
  }
45
69
 
46
70
  /**
@@ -1,3 +1,5 @@
1
+ import { PluginError } from '../../errors.js';
2
+
1
3
  /**
2
4
  * TfStatePlugin Error Classes
3
5
  * Custom errors for Terraform/OpenTofu state operations
@@ -6,12 +8,19 @@
6
8
  /**
7
9
  * Base error for all Terraform/OpenTofu state operations
8
10
  */
9
- export class TfStateError extends Error {
11
+ export class TfStateError extends PluginError {
10
12
  constructor(message, context = {}) {
11
- super(message);
13
+ const merged = {
14
+ pluginName: context.pluginName || 'TfStatePlugin',
15
+ operation: context.operation || 'unknown',
16
+ statusCode: context.statusCode ?? 500,
17
+ retriable: context.retriable ?? false,
18
+ suggestion: context.suggestion ?? 'Verify Terraform/OpenTofu configuration and state storage before retrying.',
19
+ ...context
20
+ };
21
+ super(message, merged);
12
22
  this.name = 'TfStateError';
13
23
  this.context = context;
14
- Error.captureStackTrace(this, this.constructor);
15
24
  }
16
25
  }
17
26
 
@@ -20,7 +29,14 @@ export class TfStateError extends Error {
20
29
  */
21
30
  export class InvalidStateFileError extends TfStateError {
22
31
  constructor(filePath, reason, context = {}) {
23
- super(`Invalid Tfstate file "${filePath}": ${reason}`, context);
32
+ super(`Invalid Tfstate file "${filePath}": ${reason}`, {
33
+ statusCode: context.statusCode ?? 422,
34
+ retriable: false,
35
+ suggestion: context.suggestion ?? 'Validate Terraform state integrity or re-run terraform state pull.',
36
+ filePath,
37
+ reason,
38
+ ...context
39
+ });
24
40
  this.name = 'InvalidStateFileError';
25
41
  this.filePath = filePath;
26
42
  this.reason = reason;
@@ -34,7 +50,14 @@ export class UnsupportedStateVersionError extends TfStateError {
34
50
  constructor(version, supportedVersions, context = {}) {
35
51
  super(
36
52
  `Tfstate version ${version} is not supported. Supported versions: ${supportedVersions.join(', ')}`,
37
- context
53
+ {
54
+ statusCode: context.statusCode ?? 400,
55
+ retriable: false,
56
+ suggestion: context.suggestion ?? `Upgrade/downgrade Terraform state to one of the supported versions: ${supportedVersions.join(', ')}.`,
57
+ version,
58
+ supportedVersions,
59
+ ...context
60
+ }
38
61
  );
39
62
  this.name = 'UnsupportedStateVersionError';
40
63
  this.version = version;
@@ -47,7 +70,13 @@ export class UnsupportedStateVersionError extends TfStateError {
47
70
  */
48
71
  export class StateFileNotFoundError extends TfStateError {
49
72
  constructor(filePath, context = {}) {
50
- super(`Tfstate file not found: ${filePath}`, context);
73
+ super(`Tfstate file not found: ${filePath}`, {
74
+ statusCode: context.statusCode ?? 404,
75
+ retriable: false,
76
+ suggestion: context.suggestion ?? 'Ensure the state file exists at the configured path/bucket.',
77
+ filePath,
78
+ ...context
79
+ });
51
80
  this.name = 'StateFileNotFoundError';
52
81
  this.filePath = filePath;
53
82
  }
@@ -60,7 +89,13 @@ export class ResourceExtractionError extends TfStateError {
60
89
  constructor(resourceAddress, originalError, context = {}) {
61
90
  super(
62
91
  `Failed to extract resource "${resourceAddress}": ${originalError.message}`,
63
- context
92
+ {
93
+ retriable: context.retriable ?? false,
94
+ suggestion: context.suggestion ?? 'Check resource address and state structure; rerun extraction after fixing the state.',
95
+ resourceAddress,
96
+ originalError,
97
+ ...context
98
+ }
64
99
  );
65
100
  this.name = 'ResourceExtractionError';
66
101
  this.resourceAddress = resourceAddress;
@@ -75,7 +110,14 @@ export class StateDiffError extends TfStateError {
75
110
  constructor(oldSerial, newSerial, originalError, context = {}) {
76
111
  super(
77
112
  `Failed to calculate diff between state serials ${oldSerial} and ${newSerial}: ${originalError.message}`,
78
- context
113
+ {
114
+ retriable: context.retriable ?? true,
115
+ suggestion: context.suggestion ?? 'Refresh the latest state snapshots and retry the diff operation.',
116
+ oldSerial,
117
+ newSerial,
118
+ originalError,
119
+ ...context
120
+ }
79
121
  );
80
122
  this.name = 'StateDiffError';
81
123
  this.oldSerial = oldSerial;
@@ -89,7 +131,13 @@ export class StateDiffError extends TfStateError {
89
131
  */
90
132
  export class FileWatchError extends TfStateError {
91
133
  constructor(path, originalError, context = {}) {
92
- super(`Failed to watch path "${path}": ${originalError.message}`, context);
134
+ super(`Failed to watch path "${path}": ${originalError.message}`, {
135
+ retriable: context.retriable ?? true,
136
+ suggestion: context.suggestion ?? 'Verify filesystem permissions and that the watch path exists.',
137
+ path,
138
+ originalError,
139
+ ...context
140
+ });
93
141
  this.name = 'FileWatchError';
94
142
  this.path = path;
95
143
  this.originalError = originalError;
@@ -103,7 +151,14 @@ export class ResourceFilterError extends TfStateError {
103
151
  constructor(filterExpression, originalError, context = {}) {
104
152
  super(
105
153
  `Failed to apply resource filter "${filterExpression}": ${originalError.message}`,
106
- context
154
+ {
155
+ statusCode: context.statusCode ?? 400,
156
+ retriable: context.retriable ?? false,
157
+ suggestion: context.suggestion ?? 'Validate the filter expression syntax and ensure referenced resources exist.',
158
+ filterExpression,
159
+ originalError,
160
+ ...context
161
+ }
107
162
  );
108
163
  this.name = 'ResourceFilterError';
109
164
  this.filterExpression = filterExpression;
@@ -6,8 +6,9 @@
6
6
  */
7
7
  import { TfStateDriver } from './base-driver.js';
8
8
  import { readFile, stat } from 'fs/promises';
9
- import { join, relative } from 'path';
9
+ import { join } from 'path';
10
10
  import { glob } from 'glob';
11
+ import { TfStateError, StateFileNotFoundError } from './errors.js';
11
12
 
12
13
  export class FilesystemTfStateDriver extends TfStateDriver {
13
14
  constructor(config = {}) {
@@ -23,10 +24,23 @@ export class FilesystemTfStateDriver extends TfStateDriver {
23
24
  try {
24
25
  const stats = await stat(this.basePath);
25
26
  if (!stats.isDirectory()) {
26
- throw new Error(`Base path is not a directory: ${this.basePath}`);
27
+ throw new TfStateError(`Base path is not a directory: ${this.basePath}`, {
28
+ operation: 'initialize',
29
+ statusCode: 400,
30
+ retriable: false,
31
+ suggestion: 'Update the TfState filesystem driver configuration to point to a directory containing .tfstate files.',
32
+ basePath: this.basePath
33
+ });
27
34
  }
28
35
  } catch (error) {
29
- throw new Error(`Invalid base path: ${this.basePath} - ${error.message}`);
36
+ throw new TfStateError(`Invalid base path: ${this.basePath}`, {
37
+ operation: 'initialize',
38
+ statusCode: 400,
39
+ retriable: false,
40
+ suggestion: 'Ensure the basePath exists and is readable by the current process.',
41
+ basePath: this.basePath,
42
+ original: error
43
+ });
30
44
  }
31
45
  }
32
46
 
@@ -60,7 +74,15 @@ export class FilesystemTfStateDriver extends TfStateDriver {
60
74
 
61
75
  return stateFiles;
62
76
  } catch (error) {
63
- throw new Error(`Failed to list state files: ${error.message}`);
77
+ throw new TfStateError('Failed to list Terraform state files', {
78
+ operation: 'listStateFiles',
79
+ statusCode: 500,
80
+ retriable: false,
81
+ suggestion: 'Verify filesystem permissions and glob selector pattern.',
82
+ selector: this.selector,
83
+ basePath: this.basePath,
84
+ original: error
85
+ });
64
86
  }
65
87
  }
66
88
 
@@ -77,9 +99,20 @@ export class FilesystemTfStateDriver extends TfStateDriver {
77
99
  return JSON.parse(content);
78
100
  } catch (error) {
79
101
  if (error.code === 'ENOENT') {
80
- throw new Error(`State file not found: ${path}`);
102
+ throw new StateFileNotFoundError(path, {
103
+ operation: 'readStateFile',
104
+ retriable: false,
105
+ suggestion: 'Ensure the Terraform state file exists at the specified path.',
106
+ original: error
107
+ });
81
108
  }
82
- throw new Error(`Failed to read state file ${path}: ${error.message}`);
109
+ throw new TfStateError(`Failed to read state file ${path}`, {
110
+ operation: 'readStateFile',
111
+ retriable: false,
112
+ suggestion: 'Validate file permissions and state file contents (must be valid JSON).',
113
+ path,
114
+ original: error
115
+ });
83
116
  }
84
117
  }
85
118
 
@@ -103,9 +136,20 @@ export class FilesystemTfStateDriver extends TfStateDriver {
103
136
  };
104
137
  } catch (error) {
105
138
  if (error.code === 'ENOENT') {
106
- throw new Error(`State file not found: ${path}`);
139
+ throw new StateFileNotFoundError(path, {
140
+ operation: 'getStateFileMetadata',
141
+ retriable: false,
142
+ suggestion: 'Ensure the Terraform state file exists at the specified path.',
143
+ original: error
144
+ });
107
145
  }
108
- throw new Error(`Failed to get metadata for ${path}: ${error.message}`);
146
+ throw new TfStateError(`Failed to get metadata for ${path}`, {
147
+ operation: 'getStateFileMetadata',
148
+ retriable: false,
149
+ suggestion: 'Check filesystem permissions and path configuration for TfStatePlugin.',
150
+ path,
151
+ original: error
152
+ });
109
153
  }
110
154
  }
111
155