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.
- package/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -575,10 +575,34 @@
|
|
|
575
575
|
|
|
576
576
|
import { Plugin } from "./plugin.class.js";
|
|
577
577
|
import tryFn from "../concerns/try-fn.js";
|
|
578
|
+
import { resolveResourceNames } from "./concerns/resource-names.js";
|
|
579
|
+
import { PluginError } from '../errors.js';
|
|
578
580
|
|
|
579
581
|
export class MetricsPlugin extends Plugin {
|
|
580
582
|
constructor(options = {}) {
|
|
581
583
|
super();
|
|
584
|
+
const resourceNamesOption = options.resourceNames || {};
|
|
585
|
+
const legacyResourceOption = options.resources || {};
|
|
586
|
+
const resourceOverrides = {
|
|
587
|
+
metrics: resourceNamesOption.metrics ?? legacyResourceOption.metrics,
|
|
588
|
+
errors: resourceNamesOption.errors ?? legacyResourceOption.errors,
|
|
589
|
+
performance: resourceNamesOption.performance ?? legacyResourceOption.performance
|
|
590
|
+
};
|
|
591
|
+
this._resourceDescriptors = {
|
|
592
|
+
metrics: {
|
|
593
|
+
defaultName: 'plg_metrics',
|
|
594
|
+
override: resourceOverrides.metrics
|
|
595
|
+
},
|
|
596
|
+
errors: {
|
|
597
|
+
defaultName: 'plg_metrics_errors',
|
|
598
|
+
override: resourceOverrides.errors
|
|
599
|
+
},
|
|
600
|
+
performance: {
|
|
601
|
+
defaultName: 'plg_metrics_performance',
|
|
602
|
+
override: resourceOverrides.performance
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
this.resourceNames = this._resolveResourceNames();
|
|
582
606
|
this.config = {
|
|
583
607
|
collectPerformance: options.collectPerformance !== false,
|
|
584
608
|
collectErrors: options.collectErrors !== false,
|
|
@@ -617,12 +641,22 @@ export class MetricsPlugin extends Plugin {
|
|
|
617
641
|
this.metricsServer = null; // Standalone HTTP server (if needed)
|
|
618
642
|
}
|
|
619
643
|
|
|
644
|
+
_resolveResourceNames() {
|
|
645
|
+
return resolveResourceNames('metrics', this._resourceDescriptors, {
|
|
646
|
+
namespace: this.namespace
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
onNamespaceChanged() {
|
|
651
|
+
this.resourceNames = this._resolveResourceNames();
|
|
652
|
+
}
|
|
653
|
+
|
|
620
654
|
async onInstall() {
|
|
621
655
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') return;
|
|
622
656
|
|
|
623
657
|
const [ok, err] = await tryFn(async () => {
|
|
624
658
|
const [ok1, err1, metricsResource] = await tryFn(() => this.database.createResource({
|
|
625
|
-
name:
|
|
659
|
+
name: this.resourceNames.metrics,
|
|
626
660
|
attributes: {
|
|
627
661
|
id: 'string|required',
|
|
628
662
|
type: 'string|required', // 'operation', 'error', 'performance'
|
|
@@ -641,10 +675,12 @@ export class MetricsPlugin extends Plugin {
|
|
|
641
675
|
},
|
|
642
676
|
behavior: 'body-overflow'
|
|
643
677
|
}));
|
|
644
|
-
this.metricsResource = ok1
|
|
678
|
+
this.metricsResource = ok1
|
|
679
|
+
? metricsResource
|
|
680
|
+
: this.database.resources[this.resourceNames.metrics];
|
|
645
681
|
|
|
646
682
|
const [ok2, err2, errorsResource] = await tryFn(() => this.database.createResource({
|
|
647
|
-
name:
|
|
683
|
+
name: this.resourceNames.errors,
|
|
648
684
|
attributes: {
|
|
649
685
|
id: 'string|required',
|
|
650
686
|
resourceName: 'string|required',
|
|
@@ -659,10 +695,12 @@ export class MetricsPlugin extends Plugin {
|
|
|
659
695
|
},
|
|
660
696
|
behavior: 'body-overflow'
|
|
661
697
|
}));
|
|
662
|
-
this.errorsResource = ok2
|
|
698
|
+
this.errorsResource = ok2
|
|
699
|
+
? errorsResource
|
|
700
|
+
: this.database.resources[this.resourceNames.errors];
|
|
663
701
|
|
|
664
702
|
const [ok3, err3, performanceResource] = await tryFn(() => this.database.createResource({
|
|
665
|
-
name:
|
|
703
|
+
name: this.resourceNames.performance,
|
|
666
704
|
attributes: {
|
|
667
705
|
id: 'string|required',
|
|
668
706
|
resourceName: 'string|required',
|
|
@@ -677,13 +715,15 @@ export class MetricsPlugin extends Plugin {
|
|
|
677
715
|
},
|
|
678
716
|
behavior: 'body-overflow'
|
|
679
717
|
}));
|
|
680
|
-
this.performanceResource = ok3
|
|
718
|
+
this.performanceResource = ok3
|
|
719
|
+
? performanceResource
|
|
720
|
+
: this.database.resources[this.resourceNames.performance];
|
|
681
721
|
});
|
|
682
722
|
if (!ok) {
|
|
683
723
|
// Resources might already exist
|
|
684
|
-
this.metricsResource = this.database.resources.
|
|
685
|
-
this.errorsResource = this.database.resources.
|
|
686
|
-
this.performanceResource = this.database.resources.
|
|
724
|
+
this.metricsResource = this.database.resources[this.resourceNames.metrics];
|
|
725
|
+
this.errorsResource = this.database.resources[this.resourceNames.errors];
|
|
726
|
+
this.performanceResource = this.database.resources[this.resourceNames.performance];
|
|
687
727
|
}
|
|
688
728
|
|
|
689
729
|
// Use database hooks for automatic resource discovery
|
|
@@ -728,7 +768,7 @@ export class MetricsPlugin extends Plugin {
|
|
|
728
768
|
installDatabaseHooks() {
|
|
729
769
|
// Use the new database hooks system for automatic resource discovery
|
|
730
770
|
this.database.addHook('afterCreateResource', (resource) => {
|
|
731
|
-
if (
|
|
771
|
+
if (!this.isInternalResource(resource.name)) {
|
|
732
772
|
this.installResourceHooks(resource);
|
|
733
773
|
}
|
|
734
774
|
});
|
|
@@ -739,10 +779,14 @@ export class MetricsPlugin extends Plugin {
|
|
|
739
779
|
this.database.removeHook('afterCreateResource', this.installResourceHooks.bind(this));
|
|
740
780
|
}
|
|
741
781
|
|
|
782
|
+
isInternalResource(resourceName) {
|
|
783
|
+
return Object.values(this.resourceNames).includes(resourceName);
|
|
784
|
+
}
|
|
785
|
+
|
|
742
786
|
installMetricsHooks() {
|
|
743
787
|
// Only hook into non-metrics resources
|
|
744
788
|
for (const resource of Object.values(this.database.resources)) {
|
|
745
|
-
if (
|
|
789
|
+
if (this.isInternalResource(resource.name)) {
|
|
746
790
|
continue; // Skip metrics resources to avoid recursion
|
|
747
791
|
}
|
|
748
792
|
|
|
@@ -753,7 +797,7 @@ export class MetricsPlugin extends Plugin {
|
|
|
753
797
|
this.database._createResource = this.database.createResource;
|
|
754
798
|
this.database.createResource = async function (...args) {
|
|
755
799
|
const resource = await this._createResource(...args);
|
|
756
|
-
if (this.plugins?.metrics && !
|
|
800
|
+
if (this.plugins?.metrics && !this.plugins.metrics.isInternalResource(resource.name)) {
|
|
757
801
|
this.plugins.metrics.installResourceHooks(resource);
|
|
758
802
|
}
|
|
759
803
|
return resource;
|
|
@@ -1305,9 +1349,13 @@ export class MetricsPlugin extends Plugin {
|
|
|
1305
1349
|
// INTEGRATED mode: requires API Plugin
|
|
1306
1350
|
else if (mode === 'integrated') {
|
|
1307
1351
|
if (!apiPlugin || !apiPlugin.server) {
|
|
1308
|
-
throw new
|
|
1309
|
-
|
|
1310
|
-
|
|
1352
|
+
throw new PluginError('[Metrics Plugin] prometheus.mode=integrated requires API Plugin to be active', {
|
|
1353
|
+
pluginName: 'MetricsPlugin',
|
|
1354
|
+
operation: '_setupPrometheusExporter',
|
|
1355
|
+
statusCode: 400,
|
|
1356
|
+
retriable: false,
|
|
1357
|
+
suggestion: 'Install and start the API plugin or switch prometheus.mode to "standalone" or "auto".'
|
|
1358
|
+
});
|
|
1311
1359
|
}
|
|
1312
1360
|
await this._setupIntegratedMetrics(apiPlugin);
|
|
1313
1361
|
}
|
|
@@ -1406,4 +1454,4 @@ export class MetricsPlugin extends Plugin {
|
|
|
1406
1454
|
console.error('[Metrics Plugin] Standalone metrics server error:', err);
|
|
1407
1455
|
});
|
|
1408
1456
|
}
|
|
1409
|
-
}
|
|
1457
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Provides common functionality for training, prediction, and persistence
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createRequire } from 'module';
|
|
8
9
|
import {
|
|
9
10
|
TrainingError,
|
|
10
11
|
PredictionError,
|
|
@@ -13,11 +14,20 @@ import {
|
|
|
13
14
|
InsufficientDataError,
|
|
14
15
|
TensorFlowDependencyError
|
|
15
16
|
} from '../ml.errors.js';
|
|
17
|
+
import { PluginError } from '../../errors.js';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
16
20
|
|
|
17
21
|
export class BaseModel {
|
|
18
22
|
constructor(config = {}) {
|
|
19
23
|
if (this.constructor === BaseModel) {
|
|
20
|
-
throw new
|
|
24
|
+
throw new PluginError('BaseModel is an abstract class and cannot be instantiated directly', {
|
|
25
|
+
pluginName: 'MLPlugin',
|
|
26
|
+
operation: 'baseModel:constructor',
|
|
27
|
+
statusCode: 500,
|
|
28
|
+
retriable: false,
|
|
29
|
+
suggestion: 'Extend BaseModel and instantiate the concrete subclass instead.'
|
|
30
|
+
});
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
this.config = {
|
|
@@ -68,21 +78,15 @@ export class BaseModel {
|
|
|
68
78
|
}
|
|
69
79
|
|
|
70
80
|
try {
|
|
71
|
-
//
|
|
81
|
+
// Use CommonJS require with createRequire (works reliably in both Node.js and Jest ESM mode)
|
|
82
|
+
// This avoids TensorFlow.js internal ESM compatibility issues in Jest
|
|
72
83
|
this.tf = require('@tensorflow/tfjs-node');
|
|
73
84
|
this._tfValidated = true;
|
|
74
|
-
} catch (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this._tfValidated = true;
|
|
80
|
-
} catch (importError) {
|
|
81
|
-
throw new TensorFlowDependencyError(
|
|
82
|
-
'TensorFlow.js is not installed. Run: pnpm add @tensorflow/tfjs-node',
|
|
83
|
-
{ originalError: importError.message }
|
|
84
|
-
);
|
|
85
|
-
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new TensorFlowDependencyError(
|
|
87
|
+
'TensorFlow.js is not installed. Run: pnpm add @tensorflow/tfjs-node',
|
|
88
|
+
{ originalError: error.message }
|
|
89
|
+
);
|
|
86
90
|
}
|
|
87
91
|
}
|
|
88
92
|
|
|
@@ -92,7 +96,13 @@ export class BaseModel {
|
|
|
92
96
|
* @abstract
|
|
93
97
|
*/
|
|
94
98
|
buildModel() {
|
|
95
|
-
throw new
|
|
99
|
+
throw new PluginError('buildModel() must be implemented by subclass', {
|
|
100
|
+
pluginName: 'MLPlugin',
|
|
101
|
+
operation: 'baseModel:buildModel',
|
|
102
|
+
statusCode: 500,
|
|
103
|
+
retriable: false,
|
|
104
|
+
suggestion: 'Override buildModel() in your model subclass to define the underlying ML architecture.'
|
|
105
|
+
});
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { BaseModel } from './base-model.class.js';
|
|
9
|
-
import { ModelConfigError } from '../ml.errors.js';
|
|
9
|
+
import { ModelConfigError, ModelNotTrainedError } from '../ml.errors.js';
|
|
10
10
|
|
|
11
11
|
export class RegressionModel extends BaseModel {
|
|
12
12
|
constructor(config = {}) {
|
package/src/plugins/ml.errors.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PluginError } from '../errors.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Machine Learning Plugin Errors
|
|
3
5
|
*
|
|
@@ -7,25 +9,18 @@
|
|
|
7
9
|
/**
|
|
8
10
|
* Base ML Error
|
|
9
11
|
*/
|
|
10
|
-
export class MLError extends
|
|
12
|
+
export class MLError extends PluginError {
|
|
11
13
|
constructor(message, context = {}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
toJSON() {
|
|
23
|
-
return {
|
|
24
|
-
name: this.name,
|
|
25
|
-
message: this.message,
|
|
26
|
-
context: this.context,
|
|
27
|
-
stack: this.stack
|
|
14
|
+
const merged = {
|
|
15
|
+
pluginName: context.pluginName || 'MLPlugin',
|
|
16
|
+
operation: context.operation || 'unknown',
|
|
17
|
+
statusCode: context.statusCode ?? 500,
|
|
18
|
+
retriable: context.retriable ?? false,
|
|
19
|
+
suggestion: context.suggestion ?? 'Review ML plugin configuration and datasets before retrying.',
|
|
20
|
+
...context
|
|
28
21
|
};
|
|
22
|
+
super(message, merged);
|
|
23
|
+
this.name = 'MLError';
|
|
29
24
|
}
|
|
30
25
|
}
|
|
31
26
|
|
|
@@ -35,7 +30,12 @@ export class MLError extends Error {
|
|
|
35
30
|
*/
|
|
36
31
|
export class ModelConfigError extends MLError {
|
|
37
32
|
constructor(message, context = {}) {
|
|
38
|
-
super(message,
|
|
33
|
+
super(message, {
|
|
34
|
+
statusCode: context.statusCode ?? 400,
|
|
35
|
+
retriable: context.retriable ?? false,
|
|
36
|
+
suggestion: context.suggestion ?? 'Validate layer definitions, optimizer, and loss function values.',
|
|
37
|
+
...context
|
|
38
|
+
});
|
|
39
39
|
this.name = 'ModelConfigError';
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -46,7 +46,11 @@ export class ModelConfigError extends MLError {
|
|
|
46
46
|
*/
|
|
47
47
|
export class TrainingError extends MLError {
|
|
48
48
|
constructor(message, context = {}) {
|
|
49
|
-
super(message,
|
|
49
|
+
super(message, {
|
|
50
|
+
retriable: context.retriable ?? true,
|
|
51
|
+
suggestion: context.suggestion ?? 'Inspect training logs, data shapes, and GPU availability, then retry.',
|
|
52
|
+
...context
|
|
53
|
+
});
|
|
50
54
|
this.name = 'TrainingError';
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -57,7 +61,11 @@ export class TrainingError extends MLError {
|
|
|
57
61
|
*/
|
|
58
62
|
export class PredictionError extends MLError {
|
|
59
63
|
constructor(message, context = {}) {
|
|
60
|
-
super(message,
|
|
64
|
+
super(message, {
|
|
65
|
+
retriable: context.retriable ?? true,
|
|
66
|
+
suggestion: context.suggestion ?? 'Verify the model is loaded and input tensors match the expected schema.',
|
|
67
|
+
...context
|
|
68
|
+
});
|
|
61
69
|
this.name = 'PredictionError';
|
|
62
70
|
}
|
|
63
71
|
}
|
|
@@ -68,7 +76,12 @@ export class PredictionError extends MLError {
|
|
|
68
76
|
*/
|
|
69
77
|
export class ModelNotFoundError extends MLError {
|
|
70
78
|
constructor(message, context = {}) {
|
|
71
|
-
super(message,
|
|
79
|
+
super(message, {
|
|
80
|
+
statusCode: 404,
|
|
81
|
+
retriable: false,
|
|
82
|
+
suggestion: context.suggestion ?? 'Train the model or load it from storage before invoking inference.',
|
|
83
|
+
...context
|
|
84
|
+
});
|
|
72
85
|
this.name = 'ModelNotFoundError';
|
|
73
86
|
}
|
|
74
87
|
}
|
|
@@ -79,7 +92,12 @@ export class ModelNotFoundError extends MLError {
|
|
|
79
92
|
*/
|
|
80
93
|
export class ModelNotTrainedError extends MLError {
|
|
81
94
|
constructor(message, context = {}) {
|
|
82
|
-
super(message,
|
|
95
|
+
super(message, {
|
|
96
|
+
statusCode: 409,
|
|
97
|
+
retriable: false,
|
|
98
|
+
suggestion: context.suggestion ?? 'Run train() for this model or load a trained checkpoint.',
|
|
99
|
+
...context
|
|
100
|
+
});
|
|
83
101
|
this.name = 'ModelNotTrainedError';
|
|
84
102
|
}
|
|
85
103
|
}
|
|
@@ -90,7 +108,12 @@ export class ModelNotTrainedError extends MLError {
|
|
|
90
108
|
*/
|
|
91
109
|
export class DataValidationError extends MLError {
|
|
92
110
|
constructor(message, context = {}) {
|
|
93
|
-
super(message,
|
|
111
|
+
super(message, {
|
|
112
|
+
statusCode: 422,
|
|
113
|
+
retriable: false,
|
|
114
|
+
suggestion: context.suggestion ?? 'Normalize input data and ensure required features are provided.',
|
|
115
|
+
...context
|
|
116
|
+
});
|
|
94
117
|
this.name = 'DataValidationError';
|
|
95
118
|
}
|
|
96
119
|
}
|
|
@@ -101,7 +124,12 @@ export class DataValidationError extends MLError {
|
|
|
101
124
|
*/
|
|
102
125
|
export class InsufficientDataError extends MLError {
|
|
103
126
|
constructor(message, context = {}) {
|
|
104
|
-
super(message,
|
|
127
|
+
super(message, {
|
|
128
|
+
statusCode: 400,
|
|
129
|
+
retriable: false,
|
|
130
|
+
suggestion: context.suggestion ?? 'Collect more samples, reduce batch size, or adjust minimumRecords configuration.',
|
|
131
|
+
...context
|
|
132
|
+
});
|
|
105
133
|
this.name = 'InsufficientDataError';
|
|
106
134
|
}
|
|
107
135
|
}
|
|
@@ -112,7 +140,11 @@ export class InsufficientDataError extends MLError {
|
|
|
112
140
|
*/
|
|
113
141
|
export class TensorFlowDependencyError extends MLError {
|
|
114
142
|
constructor(message = 'TensorFlow.js is not installed. Run: pnpm add @tensorflow/tfjs-node', context = {}) {
|
|
115
|
-
super(message,
|
|
143
|
+
super(message, {
|
|
144
|
+
retriable: false,
|
|
145
|
+
suggestion: context.suggestion ?? 'Install @tensorflow/tfjs-node or @tensorflow/tfjs to enable ML features.',
|
|
146
|
+
...context
|
|
147
|
+
});
|
|
116
148
|
this.name = 'TensorFlowDependencyError';
|
|
117
149
|
}
|
|
118
150
|
}
|
package/src/plugins/ml.plugin.js
CHANGED
|
@@ -284,7 +284,13 @@ export class MLPlugin extends Plugin {
|
|
|
284
284
|
const mlPlugin = resource.database?._mlPlugin;
|
|
285
285
|
|
|
286
286
|
if (!mlPlugin) {
|
|
287
|
-
throw new
|
|
287
|
+
throw new MLError('MLPlugin is not installed on this database instance', {
|
|
288
|
+
pluginName: 'MLPlugin',
|
|
289
|
+
operation: 'Resource.ml accessor',
|
|
290
|
+
statusCode: 400,
|
|
291
|
+
retriable: false,
|
|
292
|
+
suggestion: 'Install MLPlugin via db.usePlugin(new MLPlugin(...)) before calling resource.ml.* methods.'
|
|
293
|
+
});
|
|
288
294
|
}
|
|
289
295
|
|
|
290
296
|
return {
|
|
@@ -437,7 +443,13 @@ export class MLPlugin extends Plugin {
|
|
|
437
443
|
Resource.prototype.predict = async function(input, targetAttribute) {
|
|
438
444
|
const mlPlugin = this.database?._mlPlugin;
|
|
439
445
|
if (!mlPlugin) {
|
|
440
|
-
throw new
|
|
446
|
+
throw new MLError('MLPlugin is not installed on this database instance', {
|
|
447
|
+
pluginName: 'MLPlugin',
|
|
448
|
+
operation: 'Resource.predict',
|
|
449
|
+
statusCode: 400,
|
|
450
|
+
retriable: false,
|
|
451
|
+
suggestion: 'Install MLPlugin via db.usePlugin(new MLPlugin(...)) before calling resource.predict().'
|
|
452
|
+
});
|
|
441
453
|
}
|
|
442
454
|
|
|
443
455
|
return await mlPlugin._resourcePredict(this.name, input, targetAttribute);
|
|
@@ -449,7 +461,13 @@ export class MLPlugin extends Plugin {
|
|
|
449
461
|
Resource.prototype.trainModel = async function(targetAttribute, options = {}) {
|
|
450
462
|
const mlPlugin = this.database?._mlPlugin;
|
|
451
463
|
if (!mlPlugin) {
|
|
452
|
-
throw new
|
|
464
|
+
throw new MLError('MLPlugin is not installed on this database instance', {
|
|
465
|
+
pluginName: 'MLPlugin',
|
|
466
|
+
operation: 'Resource.trainModel',
|
|
467
|
+
statusCode: 400,
|
|
468
|
+
retriable: false,
|
|
469
|
+
suggestion: 'Install MLPlugin via db.usePlugin(new MLPlugin(...)) before calling resource.trainModel().'
|
|
470
|
+
});
|
|
453
471
|
}
|
|
454
472
|
|
|
455
473
|
return await mlPlugin._resourceTrainModel(this.name, targetAttribute, options);
|
|
@@ -461,7 +479,13 @@ export class MLPlugin extends Plugin {
|
|
|
461
479
|
Resource.prototype.listModels = function() {
|
|
462
480
|
const mlPlugin = this.database?._mlPlugin;
|
|
463
481
|
if (!mlPlugin) {
|
|
464
|
-
throw new
|
|
482
|
+
throw new MLError('MLPlugin is not installed on this database instance', {
|
|
483
|
+
pluginName: 'MLPlugin',
|
|
484
|
+
operation: 'Resource.listModels',
|
|
485
|
+
statusCode: 400,
|
|
486
|
+
retriable: false,
|
|
487
|
+
suggestion: 'Install MLPlugin via db.usePlugin(new MLPlugin(...)) before calling resource.listModels().'
|
|
488
|
+
});
|
|
465
489
|
}
|
|
466
490
|
|
|
467
491
|
return mlPlugin._resourceListModels(this.name);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Namespace Detection and Logging
|
|
3
|
+
*
|
|
4
|
+
* Provides standardized namespace detection and console warnings for all plugins.
|
|
5
|
+
*
|
|
6
|
+
* @module concerns/plugin-namespace
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* List all existing namespaces for a plugin by scanning storage
|
|
11
|
+
*
|
|
12
|
+
* @param {Object} storage - Plugin storage instance
|
|
13
|
+
* @param {string} pluginPrefix - Plugin prefix (e.g., 'recon', 'scheduler', 'cache')
|
|
14
|
+
* @returns {Promise<string[]>} Array of namespace strings, sorted alphabetically
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const storage = plugin.getStorage();
|
|
18
|
+
* const namespaces = await listPluginNamespaces(storage, 'recon');
|
|
19
|
+
* // ['aggressive', 'default', 'stealth', 'uptime']
|
|
20
|
+
*/
|
|
21
|
+
export async function listPluginNamespaces(storage, pluginPrefix) {
|
|
22
|
+
if (!storage) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Get base key for plugin: plugin=<pluginPrefix>/
|
|
28
|
+
const baseKey = storage.getPluginKey(null);
|
|
29
|
+
|
|
30
|
+
// List all keys under plugin prefix
|
|
31
|
+
const allKeys = await storage.list(baseKey);
|
|
32
|
+
|
|
33
|
+
// Extract unique namespaces from keys like: plugin=<pluginPrefix>/<namespace>/...
|
|
34
|
+
const namespaces = new Set();
|
|
35
|
+
const prefix = baseKey.endsWith('/') ? baseKey : `${baseKey}/`;
|
|
36
|
+
|
|
37
|
+
for (const key of allKeys) {
|
|
38
|
+
// Remove prefix and extract first segment (namespace)
|
|
39
|
+
const relativePath = key.replace(prefix, '');
|
|
40
|
+
const parts = relativePath.split('/');
|
|
41
|
+
|
|
42
|
+
if (parts.length > 0 && parts[0]) {
|
|
43
|
+
namespaces.add(parts[0]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return Array.from(namespaces).sort();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
// If no keys exist yet or storage error, return empty array
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Emit console warnings about namespace detection and usage
|
|
56
|
+
*
|
|
57
|
+
* Standardized format for all plugins:
|
|
58
|
+
* - Lists detected namespaces (if any)
|
|
59
|
+
* - Warns which namespace is being used
|
|
60
|
+
*
|
|
61
|
+
* @param {string} pluginName - Plugin name for logging (e.g., 'ReconPlugin', 'SchedulerPlugin')
|
|
62
|
+
* @param {string} currentNamespace - The namespace being used by this instance
|
|
63
|
+
* @param {string[]} existingNamespaces - Array of detected namespaces
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* warnNamespaceUsage('ReconPlugin', 'uptime', ['default', 'stealth']);
|
|
67
|
+
* // Console output:
|
|
68
|
+
* // [ReconPlugin] Detected 2 existing namespace(s): default, stealth
|
|
69
|
+
* // [ReconPlugin] Using namespace: "uptime"
|
|
70
|
+
*/
|
|
71
|
+
export function warnNamespaceUsage(pluginName, currentNamespace, existingNamespaces = []) {
|
|
72
|
+
if (existingNamespaces.length > 0) {
|
|
73
|
+
console.warn(
|
|
74
|
+
`[${pluginName}] Detected ${existingNamespaces.length} existing namespace(s): ${existingNamespaces.join(', ')}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const namespaceDisplay = currentNamespace === '' ? '(none)' : `"${currentNamespace}"`;
|
|
79
|
+
console.warn(`[${pluginName}] Using namespace: ${namespaceDisplay}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Complete namespace detection and warning flow
|
|
84
|
+
*
|
|
85
|
+
* Convenience method that combines listing and warning in one call.
|
|
86
|
+
*
|
|
87
|
+
* @param {Object} storage - Plugin storage instance
|
|
88
|
+
* @param {string} pluginName - Plugin name for logging
|
|
89
|
+
* @param {string} pluginPrefix - Plugin prefix for storage scanning
|
|
90
|
+
* @param {string} currentNamespace - The namespace being used
|
|
91
|
+
* @returns {Promise<string[]>} Array of detected namespaces
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* const namespaces = await detectAndWarnNamespaces(
|
|
95
|
+
* plugin.getStorage(),
|
|
96
|
+
* 'ReconPlugin',
|
|
97
|
+
* 'recon',
|
|
98
|
+
* 'uptime'
|
|
99
|
+
* );
|
|
100
|
+
* // Console output:
|
|
101
|
+
* // [ReconPlugin] Detected 2 existing namespace(s): default, stealth
|
|
102
|
+
* // [ReconPlugin] Using namespace: "uptime"
|
|
103
|
+
* // Returns: ['default', 'stealth']
|
|
104
|
+
*/
|
|
105
|
+
export async function detectAndWarnNamespaces(storage, pluginName, pluginPrefix, currentNamespace) {
|
|
106
|
+
const existingNamespaces = await listPluginNamespaces(storage, pluginPrefix);
|
|
107
|
+
warnNamespaceUsage(pluginName, currentNamespace, existingNamespaces);
|
|
108
|
+
return existingNamespaces;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get namespaced resource name
|
|
113
|
+
*
|
|
114
|
+
* Generates consistent resource names across all plugins.
|
|
115
|
+
* Pattern: plg_<namespace>_<plugin>_<resource>
|
|
116
|
+
* Empty namespace: plg_<plugin>_<resource>
|
|
117
|
+
*
|
|
118
|
+
* @param {string} baseResourceName - Base resource name (e.g., 'plg_recon_hosts')
|
|
119
|
+
* @param {string} namespace - Namespace to apply (empty string = no namespace)
|
|
120
|
+
* @param {string} pluginPrefix - Plugin prefix (e.g., 'plg_recon')
|
|
121
|
+
* @returns {string} Namespaced resource name
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* getNamespacedResourceName('plg_recon_hosts', '', 'plg_recon');
|
|
125
|
+
* // 'plg_recon_hosts'
|
|
126
|
+
*
|
|
127
|
+
* getNamespacedResourceName('plg_recon_hosts', 'uptime', 'plg_recon');
|
|
128
|
+
* // 'plg_uptime_recon_hosts'
|
|
129
|
+
*
|
|
130
|
+
* getNamespacedResourceName('plg_scheduler_jobs', 'prod', 'plg_scheduler');
|
|
131
|
+
* // 'plg_prod_scheduler_jobs'
|
|
132
|
+
*/
|
|
133
|
+
export function getNamespacedResourceName(baseResourceName, namespace, pluginPrefix) {
|
|
134
|
+
if (!namespace) {
|
|
135
|
+
return baseResourceName;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Insert namespace after 'plg_' prefix
|
|
139
|
+
// Example: 'plg_recon_hosts' → 'plg_uptime_recon_hosts'
|
|
140
|
+
return baseResourceName.replace('plg_', `plg_${namespace}_`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validate namespace string
|
|
145
|
+
*
|
|
146
|
+
* Ensures namespace follows naming conventions:
|
|
147
|
+
* - Alphanumeric + hyphens + underscores only
|
|
148
|
+
* - 1-50 characters
|
|
149
|
+
* - Empty string is allowed (no namespace)
|
|
150
|
+
*
|
|
151
|
+
* @param {string} namespace - Namespace to validate
|
|
152
|
+
* @throws {Error} If namespace is invalid
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* validateNamespace('uptime'); // OK
|
|
156
|
+
* validateNamespace('client-acme'); // OK
|
|
157
|
+
* validateNamespace('prod_env_2'); // OK
|
|
158
|
+
* validateNamespace(''); // OK (no namespace)
|
|
159
|
+
* validateNamespace('invalid space'); // Throws
|
|
160
|
+
* validateNamespace('a'.repeat(51)); // Throws
|
|
161
|
+
*/
|
|
162
|
+
export function validateNamespace(namespace) {
|
|
163
|
+
// Empty string is allowed (no namespace)
|
|
164
|
+
if (namespace === '') {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!namespace || typeof namespace !== 'string') {
|
|
169
|
+
throw new Error('Namespace must be a string');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (namespace.length > 50) {
|
|
173
|
+
throw new Error('Namespace must be 50 characters or less');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Allow alphanumeric, hyphens, and underscores only
|
|
177
|
+
const validPattern = /^[a-zA-Z0-9_-]+$/;
|
|
178
|
+
if (!validPattern.test(namespace)) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'Namespace can only contain alphanumeric characters, hyphens, and underscores'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Get namespace from config with validation
|
|
187
|
+
*
|
|
188
|
+
* Extracts and validates namespace from plugin config.
|
|
189
|
+
* Defaults to empty string if not specified (no namespace).
|
|
190
|
+
*
|
|
191
|
+
* @param {Object} config - Plugin configuration
|
|
192
|
+
* @param {string} defaultNamespace - Default namespace if not specified (default: '')
|
|
193
|
+
* @returns {string} Validated namespace
|
|
194
|
+
* @throws {Error} If namespace is invalid
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* getValidatedNamespace({ namespace: 'uptime' });
|
|
198
|
+
* // 'uptime'
|
|
199
|
+
*
|
|
200
|
+
* getValidatedNamespace({});
|
|
201
|
+
* // ''
|
|
202
|
+
*
|
|
203
|
+
* getValidatedNamespace({ namespace: 'invalid space' });
|
|
204
|
+
* // Throws Error
|
|
205
|
+
*/
|
|
206
|
+
export function getValidatedNamespace(config = {}, defaultNamespace = '') {
|
|
207
|
+
const namespace = (config.namespace !== undefined && config.namespace !== null) ? config.namespace : defaultNamespace;
|
|
208
|
+
validateNamespace(namespace);
|
|
209
|
+
return namespace;
|
|
210
|
+
}
|