@ciq-dev/neoiq-foundation-node 1.0.3-0 → 1.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap.js +1 -1
- package/dist/bootstrap.js.map +1 -1
- package/dist/bootstrap.mjs +1 -1
- package/dist/bootstrap.mjs.map +1 -1
- package/dist/index.d.mts +94 -119
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +94 -119
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -117
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -117
- package/dist/index.mjs.map +1 -1
- package/dist/{tracing-C3oniP3X.mjs → tracing-DUFkYSRe.mjs} +12 -21
- package/dist/tracing-DUFkYSRe.mjs.map +1 -0
- package/dist/{tracing-C0FkkeKE.js → tracing-MhJ1fEY_.js} +11 -26
- package/dist/tracing-MhJ1fEY_.js.map +1 -0
- package/package.json +1 -12
- package/dist/tracing-C0FkkeKE.js.map +0 -1
- package/dist/tracing-C3oniP3X.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const require_tracing = require('./tracing-
|
|
2
|
+
const require_tracing = require('./tracing-MhJ1fEY_.js');
|
|
3
3
|
const __opentelemetry_resources = require_tracing.__toESM(require("@opentelemetry/resources"));
|
|
4
4
|
const __opentelemetry_semantic_conventions = require_tracing.__toESM(require("@opentelemetry/semantic-conventions"));
|
|
5
5
|
const __opentelemetry_api = require_tracing.__toESM(require("@opentelemetry/api"));
|
|
@@ -734,111 +734,109 @@ function createHttpClient(options) {
|
|
|
734
734
|
|
|
735
735
|
//#endregion
|
|
736
736
|
//#region src/features/vault.ts
|
|
737
|
+
const DEFAULTS = {
|
|
738
|
+
address: "https://vault.beta.commerceiq.ai",
|
|
739
|
+
authMethod: "kubernetes",
|
|
740
|
+
mountPoint: "secret",
|
|
741
|
+
tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
|
742
|
+
k8sAuthMount: "kubernetes"
|
|
743
|
+
};
|
|
744
|
+
function stripSlashes(value) {
|
|
745
|
+
let start = 0;
|
|
746
|
+
let end = value.length;
|
|
747
|
+
while (start < end && value[start] === "/") start += 1;
|
|
748
|
+
while (end > start && value[end - 1] === "/") end -= 1;
|
|
749
|
+
return value.slice(start, end);
|
|
750
|
+
}
|
|
751
|
+
function stripTrailingSlashes(value) {
|
|
752
|
+
let end = value.length;
|
|
753
|
+
while (end > 0 && value[end - 1] === "/") end -= 1;
|
|
754
|
+
return value.slice(0, end);
|
|
755
|
+
}
|
|
737
756
|
async function loadNodeVault() {
|
|
738
757
|
try {
|
|
739
758
|
const mod = await import("node-vault");
|
|
740
759
|
return mod.default ?? mod;
|
|
741
760
|
} catch {
|
|
742
|
-
throw new Error("node-vault is not installed. Install
|
|
761
|
+
throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
|
|
743
762
|
}
|
|
744
763
|
}
|
|
745
764
|
var VaultClient = class {
|
|
746
|
-
|
|
765
|
+
logger;
|
|
766
|
+
address;
|
|
767
|
+
authMethod;
|
|
768
|
+
role;
|
|
769
|
+
mountPoint;
|
|
770
|
+
tokenPath;
|
|
771
|
+
k8sAuthMount;
|
|
772
|
+
namespace;
|
|
747
773
|
clientPromise;
|
|
748
774
|
authenticated = false;
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
logger;
|
|
752
|
-
cacheEnabled;
|
|
753
|
-
cacheTtlMs;
|
|
775
|
+
/** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
|
|
776
|
+
authPromise = null;
|
|
754
777
|
constructor(options) {
|
|
755
|
-
this.config = {
|
|
756
|
-
address: "https://vault.beta.commerceiq.ai/",
|
|
757
|
-
authMethod: "kubernetes",
|
|
758
|
-
role: "",
|
|
759
|
-
mountPoint: "secret",
|
|
760
|
-
tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
|
761
|
-
k8sAuthMount: "kubernetes",
|
|
762
|
-
...options.config
|
|
763
|
-
};
|
|
764
778
|
this.logger = options.logger;
|
|
765
|
-
|
|
766
|
-
this.
|
|
767
|
-
this.
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
779
|
+
const cfg = options.config;
|
|
780
|
+
this.address = stripTrailingSlashes(cfg.address ?? DEFAULTS.address);
|
|
781
|
+
this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
|
|
782
|
+
this.role = cfg.role;
|
|
783
|
+
this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
|
|
784
|
+
this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
|
|
785
|
+
this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
|
|
786
|
+
this.namespace = cfg.namespace;
|
|
787
|
+
const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
|
|
788
|
+
this.clientPromise = factory.then((create) => create({
|
|
772
789
|
apiVersion: "v1",
|
|
773
|
-
endpoint: this.
|
|
774
|
-
namespace: this.
|
|
775
|
-
});
|
|
776
|
-
this.client = client;
|
|
777
|
-
return client;
|
|
790
|
+
endpoint: this.address,
|
|
791
|
+
namespace: this.namespace
|
|
792
|
+
}));
|
|
778
793
|
}
|
|
779
794
|
async authenticate() {
|
|
780
795
|
if (this.authenticated) return;
|
|
796
|
+
if (this.authPromise) return this.authPromise;
|
|
797
|
+
this.authPromise = this.doAuthenticate().finally(() => {
|
|
798
|
+
if (!this.authenticated) this.authPromise = null;
|
|
799
|
+
});
|
|
800
|
+
return this.authPromise;
|
|
801
|
+
}
|
|
802
|
+
async doAuthenticate() {
|
|
781
803
|
const client = await this.clientPromise;
|
|
782
|
-
if (this.
|
|
804
|
+
if (this.authMethod === "token") {
|
|
783
805
|
const token = process.env.VAULT_TOKEN;
|
|
784
|
-
if (!token) throw new Error("VAULT_TOKEN
|
|
806
|
+
if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
|
|
785
807
|
client.token = token;
|
|
786
808
|
this.authenticated = true;
|
|
787
809
|
this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
|
|
788
810
|
return;
|
|
789
811
|
}
|
|
790
|
-
|
|
791
|
-
if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
|
|
812
|
+
if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
|
|
792
813
|
let jwt;
|
|
793
814
|
try {
|
|
794
|
-
jwt = await (0, node_fs_promises.readFile)(this.
|
|
815
|
+
jwt = await (0, node_fs_promises.readFile)(this.tokenPath, "utf8");
|
|
795
816
|
} catch (err) {
|
|
796
|
-
const
|
|
797
|
-
throw new Error(`Failed to read
|
|
817
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
818
|
+
throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
|
|
798
819
|
}
|
|
799
820
|
const result = await client.kubernetesLogin({
|
|
800
|
-
role,
|
|
821
|
+
role: this.role,
|
|
801
822
|
jwt,
|
|
802
|
-
mount_point: this.
|
|
823
|
+
mount_point: this.k8sAuthMount
|
|
803
824
|
});
|
|
804
825
|
client.token = result.auth.client_token;
|
|
805
826
|
this.authenticated = true;
|
|
806
827
|
this.logger.info({
|
|
807
828
|
authMethod: "kubernetes",
|
|
808
|
-
role,
|
|
829
|
+
role: this.role,
|
|
809
830
|
leaseDuration: result.auth.lease_duration
|
|
810
831
|
}, "Vault authenticated via Kubernetes service account");
|
|
811
832
|
}
|
|
812
833
|
async getSecret(path) {
|
|
813
|
-
const cacheKey = path;
|
|
814
|
-
if (this.cacheEnabled) {
|
|
815
|
-
const cached = this.cache.get(cacheKey);
|
|
816
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
817
|
-
this.logger.debug({
|
|
818
|
-
path,
|
|
819
|
-
cache: "hit"
|
|
820
|
-
}, "Vault secret cache hit");
|
|
821
|
-
return cached.data;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
834
|
await this.authenticate();
|
|
825
835
|
const client = await this.clientPromise;
|
|
826
|
-
const fullPath = `${this.
|
|
836
|
+
const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
|
|
827
837
|
this.logger.debug({ path: fullPath }, "Reading secret from Vault");
|
|
828
838
|
const response = await client.read(fullPath);
|
|
829
|
-
|
|
830
|
-
if (this.cacheEnabled) {
|
|
831
|
-
this.cache.set(cacheKey, {
|
|
832
|
-
data,
|
|
833
|
-
expiresAt: Date.now() + this.cacheTtlMs
|
|
834
|
-
});
|
|
835
|
-
this.logger.debug({
|
|
836
|
-
path,
|
|
837
|
-
cache: "miss",
|
|
838
|
-
ttlMs: this.cacheTtlMs
|
|
839
|
-
}, "Vault secret cached");
|
|
840
|
-
}
|
|
841
|
-
return data;
|
|
839
|
+
return response.data.data;
|
|
842
840
|
}
|
|
843
841
|
async getSecretValue(path, key) {
|
|
844
842
|
const secrets = await this.getSecret(path);
|
|
@@ -847,10 +845,6 @@ var VaultClient = class {
|
|
|
847
845
|
isAuthenticated() {
|
|
848
846
|
return this.authenticated;
|
|
849
847
|
}
|
|
850
|
-
clearCache() {
|
|
851
|
-
this.cache.clear();
|
|
852
|
-
this.logger.debug({}, "Vault secret cache cleared");
|
|
853
|
-
}
|
|
854
848
|
async checkHealth() {
|
|
855
849
|
try {
|
|
856
850
|
await this.authenticate();
|
|
@@ -881,12 +875,11 @@ function warnDeprecation(oldPath, newPath, logger) {
|
|
|
881
875
|
function createFoundation(input) {
|
|
882
876
|
const startTime = Date.now();
|
|
883
877
|
const config = require_tracing.parseConfig(input);
|
|
884
|
-
const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig
|
|
878
|
+
const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
|
|
885
879
|
const features = {
|
|
886
880
|
tracing: featuresConfig.tracing ?? true,
|
|
887
881
|
metrics: featuresConfig.metrics ?? true,
|
|
888
|
-
logging: featuresConfig.logging ?? true
|
|
889
|
-
vault: vaultConfig.enabled ?? false
|
|
882
|
+
logging: featuresConfig.logging ?? true
|
|
890
883
|
};
|
|
891
884
|
const contextManager = createContextManager();
|
|
892
885
|
let logger;
|
|
@@ -964,22 +957,6 @@ function createFoundation(input) {
|
|
|
964
957
|
metricsError = err.message;
|
|
965
958
|
logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
|
|
966
959
|
}
|
|
967
|
-
let vaultClient = null;
|
|
968
|
-
let vaultError;
|
|
969
|
-
if (features.vault) try {
|
|
970
|
-
vaultClient = createVaultClient({
|
|
971
|
-
config: vaultConfig,
|
|
972
|
-
logger
|
|
973
|
-
});
|
|
974
|
-
logger.info({
|
|
975
|
-
feature: "vault",
|
|
976
|
-
address: vaultConfig.address,
|
|
977
|
-
authMethod: vaultConfig.authMethod
|
|
978
|
-
}, "Vault client created (lazy auth on first secret read)");
|
|
979
|
-
} catch (err) {
|
|
980
|
-
vaultError = err.message;
|
|
981
|
-
logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
|
|
982
|
-
}
|
|
983
960
|
logger.info({
|
|
984
961
|
serviceName,
|
|
985
962
|
serviceVersion,
|
|
@@ -1047,45 +1024,50 @@ function createFoundation(input) {
|
|
|
1047
1024
|
...options,
|
|
1048
1025
|
foundation
|
|
1049
1026
|
}) };
|
|
1050
|
-
|
|
1027
|
+
let secrets = null;
|
|
1028
|
+
if (config.vault?.enabled) try {
|
|
1029
|
+
secrets = createVaultClient({
|
|
1030
|
+
config: config.vault,
|
|
1031
|
+
logger
|
|
1032
|
+
});
|
|
1033
|
+
logger.info({
|
|
1034
|
+
address: config.vault.address,
|
|
1035
|
+
authMethod: config.vault.authMethod
|
|
1036
|
+
}, "Vault secrets module initialized");
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
|
|
1039
|
+
}
|
|
1051
1040
|
const buildHealthStatus = () => {
|
|
1052
1041
|
const tracingUp = !features.tracing || !tracingError && require_tracing.isTracingEnabled();
|
|
1053
1042
|
const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
|
|
1054
1043
|
const loggingUp = !loggingError;
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1057
|
-
const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
|
|
1044
|
+
const allUp = tracingUp && metricsUp && loggingUp;
|
|
1045
|
+
const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
|
|
1058
1046
|
let status = "healthy";
|
|
1059
1047
|
if (!allUp) status = allDown ? "unhealthy" : "degraded";
|
|
1060
|
-
const components = {
|
|
1061
|
-
tracing: {
|
|
1062
|
-
enabled: features.tracing,
|
|
1063
|
-
status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
|
|
1064
|
-
message: tracingError
|
|
1065
|
-
},
|
|
1066
|
-
metrics: {
|
|
1067
|
-
enabled: features.metrics,
|
|
1068
|
-
status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
|
|
1069
|
-
message: metricsError
|
|
1070
|
-
},
|
|
1071
|
-
logging: {
|
|
1072
|
-
enabled: features.logging,
|
|
1073
|
-
status: loggingError ? "down" : "up",
|
|
1074
|
-
message: loggingError
|
|
1075
|
-
}
|
|
1076
|
-
};
|
|
1077
|
-
if (features.vault) components.vault = {
|
|
1078
|
-
enabled: true,
|
|
1079
|
-
status: vaultError ? "down" : "up",
|
|
1080
|
-
message: vaultError
|
|
1081
|
-
};
|
|
1082
1048
|
return {
|
|
1083
1049
|
status,
|
|
1084
1050
|
timestamp: new Date().toISOString(),
|
|
1085
1051
|
service: serviceName,
|
|
1086
1052
|
version: serviceVersion,
|
|
1087
1053
|
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
1088
|
-
components
|
|
1054
|
+
components: {
|
|
1055
|
+
tracing: {
|
|
1056
|
+
enabled: features.tracing,
|
|
1057
|
+
status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
|
|
1058
|
+
message: tracingError
|
|
1059
|
+
},
|
|
1060
|
+
metrics: {
|
|
1061
|
+
enabled: features.metrics,
|
|
1062
|
+
status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
|
|
1063
|
+
message: metricsError
|
|
1064
|
+
},
|
|
1065
|
+
logging: {
|
|
1066
|
+
enabled: features.logging,
|
|
1067
|
+
status: loggingError ? "down" : "up",
|
|
1068
|
+
message: loggingError
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1089
1071
|
};
|
|
1090
1072
|
};
|
|
1091
1073
|
const shutdownFn = async () => {
|
|
@@ -1093,7 +1075,6 @@ function createFoundation(input) {
|
|
|
1093
1075
|
const promises = [];
|
|
1094
1076
|
if (features.tracing && require_tracing.isTracingEnabled()) promises.push(require_tracing.shutdownTracing());
|
|
1095
1077
|
if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
|
|
1096
|
-
if (vaultClient) vaultClient.clearCache();
|
|
1097
1078
|
await Promise.all(promises);
|
|
1098
1079
|
logger.info({}, "Foundation shutdown complete");
|
|
1099
1080
|
};
|
|
@@ -1126,7 +1107,7 @@ function createFoundation(input) {
|
|
|
1126
1107
|
observability,
|
|
1127
1108
|
http: httpModule,
|
|
1128
1109
|
lifecycle,
|
|
1129
|
-
secrets
|
|
1110
|
+
secrets,
|
|
1130
1111
|
logger,
|
|
1131
1112
|
context: contextManager,
|
|
1132
1113
|
fastifyPlugin: createObservabilityPlugin({
|
|
@@ -1484,7 +1465,6 @@ exports.RedactionConfigSchema = require_tracing.RedactionConfigSchema
|
|
|
1484
1465
|
exports.RequestLoggingConfigSchema = require_tracing.RequestLoggingConfigSchema
|
|
1485
1466
|
exports.ShutdownConfigSchema = require_tracing.ShutdownConfigSchema
|
|
1486
1467
|
exports.SpanStatusCode = __opentelemetry_api.SpanStatusCode
|
|
1487
|
-
exports.VaultCacheConfigSchema = require_tracing.VaultCacheConfigSchema
|
|
1488
1468
|
exports.VaultClient = VaultClient
|
|
1489
1469
|
exports.VaultConfigSchema = require_tracing.VaultConfigSchema
|
|
1490
1470
|
exports.buildPinoRedactConfig = buildPinoRedactConfig
|