s3db.js 13.6.0 → 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 (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  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 +94 -49
  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/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -5,8 +5,10 @@ import crypto from 'crypto';
5
5
  import { Plugin } from "./plugin.class.js";
6
6
  import S3Cache from "./cache/s3-cache.class.js";
7
7
  import MemoryCache from "./cache/memory-cache.class.js";
8
+ import RedisCache from "./cache/redis-cache.class.js";
8
9
  import { FilesystemCache } from "./cache/filesystem-cache.class.js";
9
10
  import { PartitionAwareFilesystemCache } from "./cache/partition-aware-filesystem-cache.class.js";
11
+ import MultiTierCache from "./cache/multi-tier-cache.class.js";
10
12
  import tryFn from "../concerns/try-fn.js";
11
13
  import { CacheError } from "./cache.errors.js";
12
14
 
@@ -96,10 +98,21 @@ export class CachePlugin extends Plugin {
96
98
  constructor(options = {}) {
97
99
  super(options);
98
100
 
101
+ // Detect multi-tier mode (drivers array)
102
+ const isMultiTier = Array.isArray(options.drivers) && options.drivers.length > 0;
103
+
99
104
  // Clean, consolidated configuration
100
105
  this.config = {
101
- // Driver configuration
106
+ // Driver configuration (single-tier or multi-tier)
102
107
  driver: options.driver || 's3',
108
+ drivers: options.drivers, // Array of driver configs for multi-tier
109
+ isMultiTier,
110
+
111
+ // Multi-tier specific options
112
+ promoteOnHit: options.promoteOnHit !== false,
113
+ strategy: options.strategy || 'write-through', // 'write-through' | 'lazy-promotion'
114
+ fallbackOnError: options.fallbackOnError !== false,
115
+
103
116
  config: {
104
117
  ttl: options.ttl,
105
118
  maxSize: options.maxSize,
@@ -139,30 +152,16 @@ export class CachePlugin extends Plugin {
139
152
  }
140
153
 
141
154
  async onInstall() {
142
- // Initialize cache driver
143
- if (this.config.driver && typeof this.config.driver === 'object') {
155
+ // Initialize cache driver (multi-tier or single-tier)
156
+ if (this.config.isMultiTier) {
157
+ // Multi-tier mode: create multiple drivers and wrap with MultiTierCache
158
+ this.driver = await this._createMultiTierDriver();
159
+ } else if (this.config.driver && typeof this.config.driver === 'object') {
144
160
  // Use custom driver instance if provided
145
161
  this.driver = this.config.driver;
146
- } else if (this.config.driver === 'memory') {
147
- this.driver = new MemoryCache(this.config.config);
148
- } else if (this.config.driver === 'filesystem') {
149
- // Use partition-aware filesystem cache if enabled
150
- if (this.config.partitionAware) {
151
- this.driver = new PartitionAwareFilesystemCache({
152
- partitionStrategy: this.config.partitionStrategy,
153
- trackUsage: this.config.trackUsage,
154
- preloadRelated: this.config.preloadRelated,
155
- ...this.config.config
156
- });
157
- } else {
158
- this.driver = new FilesystemCache(this.config.config);
159
- }
160
162
  } else {
161
- // Default to S3Cache
162
- this.driver = new S3Cache({
163
- client: this.database.client,
164
- ...this.config.config
165
- });
163
+ // Single-tier mode: create single driver
164
+ this.driver = await this._createSingleDriver(this.config.driver, this.config.config);
166
165
  }
167
166
 
168
167
  // Use database hooks instead of method overwriting
@@ -192,6 +191,66 @@ export class CachePlugin extends Plugin {
192
191
  // Cleanup if needed
193
192
  }
194
193
 
194
+ /**
195
+ * Create a single cache driver instance
196
+ * @private
197
+ */
198
+ async _createSingleDriver(driverName, config) {
199
+ if (driverName === 'memory') {
200
+ return new MemoryCache(config);
201
+ } else if (driverName === 'redis') {
202
+ return new RedisCache(config);
203
+ } else if (driverName === 'filesystem') {
204
+ // Use partition-aware filesystem cache if enabled
205
+ if (this.config.partitionAware) {
206
+ return new PartitionAwareFilesystemCache({
207
+ partitionStrategy: this.config.partitionStrategy,
208
+ trackUsage: this.config.trackUsage,
209
+ preloadRelated: this.config.preloadRelated,
210
+ ...config
211
+ });
212
+ } else {
213
+ return new FilesystemCache(config);
214
+ }
215
+ } else {
216
+ // Default to S3Cache
217
+ return new S3Cache({
218
+ client: this.database.client,
219
+ ...config
220
+ });
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Create multi-tier cache driver
226
+ * @private
227
+ */
228
+ async _createMultiTierDriver() {
229
+ const driverInstances = [];
230
+
231
+ // Create each driver instance
232
+ for (const driverConfig of this.config.drivers) {
233
+ const driverInstance = await this._createSingleDriver(
234
+ driverConfig.driver,
235
+ driverConfig.config || {}
236
+ );
237
+
238
+ driverInstances.push({
239
+ driver: driverInstance,
240
+ name: driverConfig.name || `L${driverInstances.length + 1}-${driverConfig.driver}`
241
+ });
242
+ }
243
+
244
+ // Wrap with MultiTierCache
245
+ return new MultiTierCache({
246
+ drivers: driverInstances,
247
+ promoteOnHit: this.config.promoteOnHit,
248
+ strategy: this.config.strategy,
249
+ fallbackOnError: this.config.fallbackOnError,
250
+ verbose: this.config.verbose
251
+ });
252
+ }
253
+
195
254
  // Remove the old installDatabaseProxy method
196
255
  installResourceHooks() {
197
256
  for (const resource of Object.values(this.database.resources)) {
@@ -204,11 +263,11 @@ export class CachePlugin extends Plugin {
204
263
  }
205
264
 
206
265
  shouldCacheResource(resourceName) {
207
- // Get resource metadata to check createdBy
208
- const resourceMetadata = this.database.savedMetadata?.resources?.[resourceName];
266
+ // Use $schema for reliable access to createdBy
267
+ const resource = this.database.resources[resourceName];
209
268
 
210
269
  // Skip plugin-created resources by default (unless explicitly included)
211
- if (resourceMetadata?.createdBy && resourceMetadata.createdBy !== 'user' && !this.config.include) {
270
+ if (resource?.$schema?.createdBy && resource.$schema.createdBy !== 'user' && !this.config.include) {
212
271
  return false;
213
272
  }
214
273
 
@@ -233,18 +292,58 @@ export class CachePlugin extends Plugin {
233
292
  installResourceHooksForResource(resource) {
234
293
  if (!this.driver) return;
235
294
 
236
- // Add cache methods to resource
237
- Object.defineProperty(resource, 'cache', {
238
- value: this.driver,
239
- writable: true,
240
- configurable: true,
241
- enumerable: false
242
- });
243
- resource.cacheKeyFor = async (options = {}) => {
295
+ const driver = this.driver;
296
+ const instanceKey = this.instanceName || this.slug;
297
+
298
+ resource.cacheInstances = resource.cacheInstances || {};
299
+ resource.cacheInstances[instanceKey] = driver;
300
+
301
+ if (!Object.prototype.hasOwnProperty.call(resource, 'cache')) {
302
+ Object.defineProperty(resource, 'cache', {
303
+ value: driver,
304
+ writable: true,
305
+ configurable: true,
306
+ enumerable: false
307
+ });
308
+ }
309
+
310
+ if (typeof resource.getCacheDriver !== 'function') {
311
+ Object.defineProperty(resource, 'getCacheDriver', {
312
+ value: (name = null) => {
313
+ if (!name) {
314
+ return resource.cache;
315
+ }
316
+ return resource.cacheInstances?.[name] || null;
317
+ },
318
+ writable: true,
319
+ configurable: true,
320
+ enumerable: false
321
+ });
322
+ }
323
+ const computeCacheKey = async (options = {}) => {
244
324
  const { action, params = {}, partition, partitionValues } = options;
245
325
  return this.generateCacheKey(resource, action, params, partition, partitionValues);
246
326
  };
247
327
 
328
+ resource.cacheKeyResolvers = resource.cacheKeyResolvers || {};
329
+ resource.cacheKeyResolvers[instanceKey] = computeCacheKey;
330
+
331
+ if (!resource.cacheKeyFor) {
332
+ resource.cacheKeyFor = computeCacheKey;
333
+ }
334
+
335
+ if (typeof resource.getCacheKeyResolver !== 'function') {
336
+ Object.defineProperty(resource, 'getCacheKeyResolver', {
337
+ value: (name = null) => {
338
+ if (!name) return resource.cacheKeyFor;
339
+ return resource.cacheKeyResolvers?.[name] || null;
340
+ },
341
+ writable: true,
342
+ configurable: true,
343
+ enumerable: false
344
+ });
345
+ }
346
+
248
347
  // Add partition-aware methods if using PartitionAwareFilesystemCache
249
348
  if (this.driver instanceof PartitionAwareFilesystemCache) {
250
349
  resource.clearPartitionCache = async (partition, partitionValues = {}) => {
@@ -272,6 +371,7 @@ export class CachePlugin extends Plugin {
272
371
 
273
372
  for (const method of cacheMethods) {
274
373
  resource.useMiddleware(method, async (ctx, next) => {
374
+ const resolveCacheKey = resource.cacheKeyResolvers?.[instanceKey] || computeCacheKey;
275
375
  // Check for skipCache option in the last argument
276
376
  let skipCache = false;
277
377
  const lastArg = ctx.args[ctx.args.length - 1];
@@ -287,17 +387,17 @@ export class CachePlugin extends Plugin {
287
387
  // Build cache key
288
388
  let key;
289
389
  if (method === 'getMany') {
290
- key = await resource.cacheKeyFor({ action: method, params: { ids: ctx.args[0] } });
390
+ key = await resolveCacheKey({ action: method, params: { ids: ctx.args[0] } });
291
391
  } else if (method === 'page') {
292
392
  const { offset, size, partition, partitionValues } = ctx.args[0] || {};
293
- key = await resource.cacheKeyFor({ action: method, params: { offset, size }, partition, partitionValues });
393
+ key = await resolveCacheKey({ action: method, params: { offset, size }, partition, partitionValues });
294
394
  } else if (method === 'list' || method === 'listIds' || method === 'count') {
295
395
  const { partition, partitionValues } = ctx.args[0] || {};
296
- key = await resource.cacheKeyFor({ action: method, partition, partitionValues });
396
+ key = await resolveCacheKey({ action: method, partition, partitionValues });
297
397
  } else if (method === 'query') {
298
398
  const filter = ctx.args[0] || {};
299
399
  const options = ctx.args[1] || {};
300
- key = await resource.cacheKeyFor({
400
+ key = await resolveCacheKey({
301
401
  action: method,
302
402
  params: { filter, options: { limit: options.limit, offset: options.offset } },
303
403
  partition: options.partition,
@@ -305,16 +405,16 @@ export class CachePlugin extends Plugin {
305
405
  });
306
406
  } else if (method === 'getFromPartition') {
307
407
  const { id, partitionName, partitionValues } = ctx.args[0] || {};
308
- key = await resource.cacheKeyFor({
408
+ key = await resolveCacheKey({
309
409
  action: method,
310
410
  params: { id, partitionName },
311
411
  partition: partitionName,
312
412
  partitionValues
313
413
  });
314
414
  } else if (method === 'getAll') {
315
- key = await resource.cacheKeyFor({ action: method });
415
+ key = await resolveCacheKey({ action: method });
316
416
  } else if (['get', 'exists', 'content', 'hasContent'].includes(method)) {
317
- key = await resource.cacheKeyFor({ action: method, params: { id: ctx.args[0] } });
417
+ key = await resolveCacheKey({ action: method, params: { id: ctx.args[0] } });
318
418
  }
319
419
 
320
420
  // Try cache with partition awareness
@@ -336,7 +436,7 @@ export class CachePlugin extends Plugin {
336
436
  partitionValues = pValues;
337
437
  }
338
438
 
339
- const [ok, err, result] = await tryFn(() => resource.cache._get(key, {
439
+ const [ok, err, result] = await tryFn(() => driver._get(key, {
340
440
  resource: resource.name,
341
441
  action: method,
342
442
  partition,
@@ -358,7 +458,7 @@ export class CachePlugin extends Plugin {
358
458
 
359
459
  // Store with partition context
360
460
  this.stats.writes++;
361
- await resource.cache._set(key, freshResult, {
461
+ await driver._set(key, freshResult, {
362
462
  resource: resource.name,
363
463
  action: method,
364
464
  partition,
@@ -368,7 +468,7 @@ export class CachePlugin extends Plugin {
368
468
  return freshResult;
369
469
  } else {
370
470
  // Standard cache behavior
371
- const [ok, err, result] = await tryFn(() => resource.cache.get(key));
471
+ const [ok, err, result] = await tryFn(() => driver.get(key));
372
472
  if (ok && result !== null && result !== undefined) {
373
473
  this.stats.hits++;
374
474
  return result;
@@ -382,7 +482,7 @@ export class CachePlugin extends Plugin {
382
482
  this.stats.misses++;
383
483
  const freshResult = await next();
384
484
  this.stats.writes++;
385
- await resource.cache.set(key, freshResult);
485
+ await driver.set(key, freshResult);
386
486
  return freshResult;
387
487
  }
388
488
  });
@@ -421,7 +521,8 @@ export class CachePlugin extends Plugin {
421
521
  }
422
522
 
423
523
  async clearCacheForResource(resource, data) {
424
- if (!resource.cache) return; // Skip if no cache is available
524
+ const driver = this._getDriverForResource(resource);
525
+ if (!driver) return; // Skip if no cache is available
425
526
 
426
527
  const keyPrefix = `resource=${resource.name}`;
427
528
 
@@ -431,7 +532,7 @@ export class CachePlugin extends Plugin {
431
532
  const itemSpecificMethods = ['get', 'exists', 'content', 'hasContent'];
432
533
  for (const method of itemSpecificMethods) {
433
534
  const specificKey = await this.generateCacheKey(resource, method, { id: data.id });
434
- const [ok, err] = await this.clearCacheWithRetry(resource.cache, specificKey);
535
+ const [ok, err] = await this.clearCacheWithRetry(driver, specificKey);
435
536
 
436
537
  if (!ok) {
437
538
  this.emit('plg:cache:clear-error', {
@@ -448,12 +549,12 @@ export class CachePlugin extends Plugin {
448
549
  }
449
550
 
450
551
  // Clear partition-specific caches if this resource has partitions
451
- if (this.config.includePartitions === true && resource.config?.partitions && Object.keys(resource.config.partitions).length > 0) {
552
+ if (this.config.includePartitions === true && resource.$schema.partitions && Object.keys(resource.$schema.partitions).length > 0) {
452
553
  const partitionValues = this.getPartitionValues(data, resource);
453
554
  for (const [partitionName, values] of Object.entries(partitionValues)) {
454
555
  if (values && Object.keys(values).length > 0 && Object.values(values).some(v => v !== null && v !== undefined)) {
455
556
  const partitionKeyPrefix = join(keyPrefix, `partition=${partitionName}`);
456
- const [ok, err] = await this.clearCacheWithRetry(resource.cache, partitionKeyPrefix);
557
+ const [ok, err] = await this.clearCacheWithRetry(driver, partitionKeyPrefix);
457
558
 
458
559
  if (!ok) {
459
560
  this.emit('plg:cache:clear-error', {
@@ -472,7 +573,7 @@ export class CachePlugin extends Plugin {
472
573
  }
473
574
 
474
575
  // Clear aggregate caches more broadly to ensure all variants are cleared
475
- const [ok, err] = await this.clearCacheWithRetry(resource.cache, keyPrefix);
576
+ const [ok, err] = await this.clearCacheWithRetry(driver, keyPrefix);
476
577
 
477
578
  if (!ok) {
478
579
  this.emit('plg:cache:clear-error', {
@@ -489,8 +590,8 @@ export class CachePlugin extends Plugin {
489
590
  const aggregateMethods = ['count', 'list', 'listIds', 'getAll', 'page', 'query'];
490
591
  for (const method of aggregateMethods) {
491
592
  // Try multiple key patterns to ensure we catch all variations
492
- await this.clearCacheWithRetry(resource.cache, `${keyPrefix}/action=${method}`);
493
- await this.clearCacheWithRetry(resource.cache, `resource=${resource.name}/action=${method}`);
593
+ await this.clearCacheWithRetry(driver, `${keyPrefix}/action=${method}`);
594
+ await this.clearCacheWithRetry(driver, `resource=${resource.name}/action=${method}`);
494
595
  }
495
596
  }
496
597
  }
@@ -523,6 +624,14 @@ export class CachePlugin extends Plugin {
523
624
  return [false, lastError];
524
625
  }
525
626
 
627
+ _getDriverForResource(resource) {
628
+ const instanceKey = this.instanceName || this.slug;
629
+ if (resource?.cacheInstances && instanceKey && resource.cacheInstances[instanceKey]) {
630
+ return resource.cacheInstances[instanceKey];
631
+ }
632
+ return this.driver;
633
+ }
634
+
526
635
  async generateCacheKey(resource, action, params = {}, partition = null, partitionValues = null) {
527
636
  const keyParts = [
528
637
  `resource=${resource.name}`,
@@ -562,10 +671,15 @@ export class CachePlugin extends Plugin {
562
671
  async getCacheStats() {
563
672
  if (!this.driver) return null;
564
673
 
674
+ const driverStats = typeof this.driver.getStats === 'function'
675
+ ? this.driver.getStats()
676
+ : null;
677
+
565
678
  return {
566
679
  size: await this.driver.size(),
567
680
  keys: await this.driver.keys(),
568
- driver: this.driver.constructor.name
681
+ driver: this.driver.constructor.name,
682
+ stats: driverStats
569
683
  };
570
684
  }
571
685
 
@@ -573,10 +687,11 @@ export class CachePlugin extends Plugin {
573
687
  if (!this.driver) return;
574
688
 
575
689
  for (const resource of Object.values(this.database.resources)) {
576
- if (resource.cache) {
577
- const keyPrefix = `resource=${resource.name}`;
578
- await resource.cache.clear(keyPrefix);
579
- }
690
+ const driver = this._getDriverForResource(resource);
691
+ if (!driver) continue;
692
+
693
+ const keyPrefix = `resource=${resource.name}`;
694
+ await driver.clear(keyPrefix);
580
695
  }
581
696
  }
582
697
 
@@ -596,7 +711,7 @@ export class CachePlugin extends Plugin {
596
711
 
597
712
  // Use partition-aware warming if available
598
713
  if (this.driver instanceof PartitionAwareFilesystemCache && resource.warmPartitionCache) {
599
- const partitionNames = resource.config.partitions ? Object.keys(resource.config.partitions) : [];
714
+ const partitionNames = resource.$schema.partitions ? Object.keys(resource.$schema.partitions) : [];
600
715
  return await resource.warmPartitionCache(partitionNames, options);
601
716
  }
602
717
 
@@ -628,8 +743,8 @@ export class CachePlugin extends Plugin {
628
743
  }
629
744
 
630
745
  // Warm partition caches if enabled
631
- if (includePartitions && resource.config.partitions && sampledRecords.length > 0) {
632
- for (const [partitionName, partitionDef] of Object.entries(resource.config.partitions)) {
746
+ if (includePartitions && resource.$schema.partitions && sampledRecords.length > 0) {
747
+ for (const [partitionName, partitionDef] of Object.entries(resource.$schema.partitions)) {
633
748
  if (partitionDef.fields) {
634
749
  // Get unique partition values from sample
635
750
  const partitionValuesSet = new Set();
@@ -653,8 +768,8 @@ export class CachePlugin extends Plugin {
653
768
  return {
654
769
  resourceName,
655
770
  recordsSampled: sampledRecords.length,
656
- partitionsWarmed: includePartitions && resource.config.partitions
657
- ? Object.keys(resource.config.partitions).length
771
+ partitionsWarmed: includePartitions && resource.$schema.partitions
772
+ ? Object.keys(resource.$schema.partitions).length
658
773
  : 0
659
774
  };
660
775
  }
@@ -1,4 +1,5 @@
1
1
  import { BaseCloudDriver } from './base-driver.js';
2
+ import { PluginError } from '../../../errors.js';
2
3
 
3
4
  /**
4
5
  * Production-ready Alibaba Cloud (Aliyun) inventory driver using @alicloud SDK.
@@ -57,7 +58,13 @@ export class AlibabaInventoryDriver extends BaseCloudDriver {
57
58
  this._accessKeySecret = credentials.accessKeySecret || process.env.ALIBABA_CLOUD_ACCESS_KEY_SECRET;
58
59
 
59
60
  if (!this._accessKeyId || !this._accessKeySecret) {
60
- throw new Error('Alibaba Cloud AccessKeyId and AccessKeySecret are required. Provide via credentials or env vars.');
61
+ throw new PluginError('Alibaba Cloud AccessKeyId and AccessKeySecret are required. Provide via credentials or env vars.', {
62
+ pluginName: 'CloudInventoryPlugin',
63
+ operation: 'alibaba:initCredentials',
64
+ statusCode: 400,
65
+ retriable: false,
66
+ suggestion: 'Pass credentials.accessKeyId/accessKeySecret or set ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.'
67
+ });
61
68
  }
62
69
 
63
70
  this.logger('info', 'Alibaba Cloud credentials initialized', {
@@ -87,7 +87,7 @@ import {
87
87
  } from '@aws-sdk/client-api-gateway';
88
88
  import {
89
89
  ApiGatewayV2Client,
90
- paginateGetApis,
90
+ GetApisCommand,
91
91
  GetTagsCommand as GetAPIGatewayV2TagsCommand
92
92
  } from '@aws-sdk/client-apigatewayv2';
93
93
  import {
@@ -138,8 +138,8 @@ import {
138
138
  } from '@aws-sdk/client-sfn';
139
139
  import {
140
140
  EventBridgeClient,
141
- paginateListEventBuses,
142
- paginateListRules,
141
+ ListEventBusesCommand,
142
+ ListRulesCommand,
143
143
  ListTagsForResourceCommand as ListEventBridgeTagsCommand
144
144
  } from '@aws-sdk/client-eventbridge';
145
145
  import {
@@ -171,12 +171,12 @@ import {
171
171
  } from '@aws-sdk/client-acm';
172
172
  import {
173
173
  WAFClient,
174
- paginateListWebACLs as paginateListWAFWebACLs,
174
+ ListWebACLsCommand as ListWAFWebACLsCommand,
175
175
  ListTagsForResourceCommand as ListWAFTagsCommand
176
176
  } from '@aws-sdk/client-waf';
177
177
  import {
178
178
  WAFV2Client,
179
- paginateListWebACLs as paginateListWAFV2WebACLs,
179
+ ListWebACLsCommand as ListWAFV2WebACLsCommand,
180
180
  ListTagsForResourceCommand as ListWAFV2TagsCommand
181
181
  } from '@aws-sdk/client-wafv2';
182
182
  import {
@@ -287,9 +287,22 @@ function extractEc2Tags(instance) {
287
287
  return Object.keys(tags).length ? tags : null;
288
288
  }
289
289
 
290
+ /**
291
+ * @typedef {Object} AwsClientMap
292
+ * @property {Map<string, any>} ec2
293
+ * @property {any} s3
294
+ * @property {Map<string, any>} rds
295
+ * @property {any} iam
296
+ * @property {Map<string, any>} lambda
297
+ * @property {any} sts
298
+ * @property {Map<string, any>} ecs
299
+ * @property {Map<string, any>} eks
300
+ */
301
+
290
302
  export class AwsInventoryDriver extends BaseCloudDriver {
291
303
  constructor(options = {}) {
292
304
  super({ ...options, driver: options.driver || 'aws' });
305
+ /** @type {AwsClientMap} */
293
306
  this._clients = {
294
307
  ec2: new Map(),
295
308
  s3: null,
@@ -952,9 +965,12 @@ export class AwsInventoryDriver extends BaseCloudDriver {
952
965
 
953
966
  // HTTP/WebSocket APIs (v2)
954
967
  const v2Client = this._getApiGatewayV2Client(region);
955
- const v2Paginator = paginateGetApis({ client: v2Client }, {});
956
- for await (const page of v2Paginator) {
957
- const apis = page.Items || [];
968
+ let nextToken;
969
+ do {
970
+ const response = await v2Client.send(new GetApisCommand({ NextToken: nextToken }));
971
+ const apis = response.Items || [];
972
+ nextToken = response.NextToken;
973
+
958
974
  for (const api of apis) {
959
975
  const tags = await this._safeGetAPIGatewayV2Tags(v2Client, api.ApiId);
960
976
  const type = api.ProtocolType?.toLowerCase() || 'http';
@@ -970,7 +986,7 @@ export class AwsInventoryDriver extends BaseCloudDriver {
970
986
  configuration: sanitizeConfiguration(api)
971
987
  };
972
988
  }
973
- }
989
+ } while (nextToken);
974
990
  }
975
991
  }
976
992
 
@@ -1197,9 +1213,12 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1197
1213
  const client = this._getEventBridgeClient(region);
1198
1214
 
1199
1215
  // Event Buses
1200
- const busPaginator = paginateListEventBuses({ client }, {});
1201
- for await (const page of busPaginator) {
1202
- const buses = page.EventBuses || [];
1216
+ let nextBusToken;
1217
+ do {
1218
+ const busResponse = await client.send(new ListEventBusesCommand({ NextToken: nextBusToken }));
1219
+ const buses = busResponse.EventBuses || [];
1220
+ nextBusToken = busResponse.NextToken;
1221
+
1203
1222
  for (const bus of buses) {
1204
1223
  const tags = await this._safeListEventBridgeTags(client, bus.Arn);
1205
1224
  yield {
@@ -1214,12 +1233,15 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1214
1233
  configuration: sanitizeConfiguration(bus)
1215
1234
  };
1216
1235
  }
1217
- }
1236
+ } while (nextBusToken);
1218
1237
 
1219
1238
  // Rules
1220
- const rulePaginator = paginateListRules({ client }, {});
1221
- for await (const page of rulePaginator) {
1222
- const rules = page.Rules || [];
1239
+ let nextRuleToken;
1240
+ do {
1241
+ const ruleResponse = await client.send(new ListRulesCommand({ NextToken: nextRuleToken }));
1242
+ const rules = ruleResponse.Rules || [];
1243
+ nextRuleToken = ruleResponse.NextToken;
1244
+
1223
1245
  for (const rule of rules) {
1224
1246
  const tags = await this._safeListEventBridgeTags(client, rule.Arn);
1225
1247
  yield {
@@ -1234,7 +1256,7 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1234
1256
  configuration: sanitizeConfiguration(rule)
1235
1257
  };
1236
1258
  }
1237
- }
1259
+ } while (nextRuleToken);
1238
1260
  }
1239
1261
  }
1240
1262
 
@@ -1380,9 +1402,12 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1380
1402
  async *_collectWAFResources() {
1381
1403
  // WAF Classic (global)
1382
1404
  const wafClient = this._getWafClient();
1383
- const classicPaginator = paginateListWAFWebACLs({ client: wafClient }, {});
1384
- for await (const page of classicPaginator) {
1385
- const webACLs = page.WebACLs || [];
1405
+ let nextMarker;
1406
+ do {
1407
+ const response = await wafClient.send(new ListWAFWebACLsCommand({ NextMarker: nextMarker }));
1408
+ const webACLs = response.WebACLs || [];
1409
+ nextMarker = response.NextMarker;
1410
+
1386
1411
  for (const acl of webACLs) {
1387
1412
  const tags = await this._safeListWAFTags(wafClient, acl.WebACLId);
1388
1413
  yield {
@@ -1397,7 +1422,7 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1397
1422
  configuration: sanitizeConfiguration(acl)
1398
1423
  };
1399
1424
  }
1400
- }
1425
+ } while (nextMarker);
1401
1426
  }
1402
1427
 
1403
1428
  async *_collectWAFV2Resources() {
@@ -1405,9 +1430,12 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1405
1430
  const client = this._getWafv2Client(region);
1406
1431
 
1407
1432
  // Regional WebACLs
1408
- const regionalPaginator = paginateListWAFV2WebACLs({ client }, { Scope: 'REGIONAL' });
1409
- for await (const page of regionalPaginator) {
1410
- const webACLs = page.WebACLs || [];
1433
+ let nextMarker;
1434
+ do {
1435
+ const response = await client.send(new ListWAFV2WebACLsCommand({ Scope: 'REGIONAL', NextMarker: nextMarker }));
1436
+ const webACLs = response.WebACLs || [];
1437
+ nextMarker = response.NextMarker;
1438
+
1411
1439
  for (const acl of webACLs) {
1412
1440
  const tags = await this._safeListWAFV2Tags(client, acl.ARN);
1413
1441
  yield {
@@ -1422,14 +1450,17 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1422
1450
  configuration: sanitizeConfiguration(acl)
1423
1451
  };
1424
1452
  }
1425
- }
1453
+ } while (nextMarker);
1426
1454
  }
1427
1455
 
1428
1456
  // CloudFront WebACLs (us-east-1 only)
1429
1457
  const cfClient = this._getWafv2Client(GLOBAL_REGION);
1430
- const cfPaginator = paginateListWAFV2WebACLs({ client: cfClient }, { Scope: 'CLOUDFRONT' });
1431
- for await (const page of cfPaginator) {
1432
- const webACLs = page.WebACLs || [];
1458
+ let cfNextMarker;
1459
+ do {
1460
+ const cfResponse = await cfClient.send(new ListWAFV2WebACLsCommand({ Scope: 'CLOUDFRONT', NextMarker: cfNextMarker }));
1461
+ const webACLs = cfResponse.WebACLs || [];
1462
+ cfNextMarker = cfResponse.NextMarker;
1463
+
1433
1464
  for (const acl of webACLs) {
1434
1465
  const tags = await this._safeListWAFV2Tags(cfClient, acl.ARN);
1435
1466
  yield {
@@ -1444,7 +1475,7 @@ export class AwsInventoryDriver extends BaseCloudDriver {
1444
1475
  configuration: sanitizeConfiguration(acl)
1445
1476
  };
1446
1477
  }
1447
- }
1478
+ } while (cfNextMarker);
1448
1479
  }
1449
1480
 
1450
1481
  async *_collectCognitoUserPools() {
@@ -1,4 +1,5 @@
1
1
  import { BaseCloudDriver } from './base-driver.js';
2
+ import { PluginError } from '../../../errors.js';
2
3
 
3
4
  /**
4
5
  * Production-ready Microsoft Azure inventory driver using official @azure SDK.
@@ -69,7 +70,13 @@ export class AzureInventoryDriver extends BaseCloudDriver {
69
70
  this._subscriptionId = credentials.subscriptionId || this.config?.subscriptionId;
70
71
 
71
72
  if (!this._subscriptionId) {
72
- throw new Error('Azure subscription ID is required. Provide via credentials.subscriptionId or config.subscriptionId.');
73
+ throw new PluginError('Azure subscription ID is required. Provide via credentials.subscriptionId or config.subscriptionId.', {
74
+ pluginName: 'CloudInventoryPlugin',
75
+ operation: 'azure:initCredential',
76
+ statusCode: 400,
77
+ retriable: false,
78
+ suggestion: 'Set credentials.subscriptionId or config.subscriptionId before initializing the Azure inventory driver.'
79
+ });
73
80
  }
74
81
 
75
82
  this.logger('info', 'Azure credential initialized', {