@ciq-dev/neoiq-foundation-node 1.0.2 → 1.1.0-beta.0
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 +97 -114
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +97 -114
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -116
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +90 -116
- package/dist/index.mjs.map +1 -1
- package/dist/{tracing-BwTyDuEv.mjs → tracing-DUFkYSRe.mjs} +12 -20
- package/dist/tracing-DUFkYSRe.mjs.map +1 -0
- package/dist/{tracing-CMXNnEXN.js → tracing-MhJ1fEY_.js} +11 -25
- package/dist/tracing-MhJ1fEY_.js.map +1 -0
- package/package.json +1 -12
- package/dist/tracing-BwTyDuEv.mjs.map +0 -1
- package/dist/tracing-CMXNnEXN.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AutoInstrumentationConfigSchema, FeaturesConfigSchema, FoundationConfigSchema, LoggingConfigSchema, OtelConfigSchema, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode,
|
|
1
|
+
import { AutoInstrumentationConfigSchema, FeaturesConfigSchema, FoundationConfigSchema, LoggingConfigSchema, OtelConfigSchema, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultConfigSchema, context, getActiveSpan, getDefaultOtelEndpoint, getTraceContext, getTracer, isTracingEnabled, parseConfig, propagation, setupTracing, shutdownTracing, trace } from "./tracing-DUFkYSRe.mjs";
|
|
2
2
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
3
3
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
|
4
4
|
import { SpanStatusCode as SpanStatusCode$1, context as context$1, metrics, propagation as propagation$1, trace as trace$1 } from "@opentelemetry/api";
|
|
@@ -733,109 +733,100 @@ function createHttpClient(options) {
|
|
|
733
733
|
|
|
734
734
|
//#endregion
|
|
735
735
|
//#region src/features/vault.ts
|
|
736
|
+
const DEFAULTS = {
|
|
737
|
+
address: "https://vault.beta.commerceiq.ai",
|
|
738
|
+
authMethod: "kubernetes",
|
|
739
|
+
mountPoint: "secret",
|
|
740
|
+
tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
|
741
|
+
k8sAuthMount: "kubernetes"
|
|
742
|
+
};
|
|
743
|
+
function stripSlashes(value) {
|
|
744
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
745
|
+
}
|
|
736
746
|
async function loadNodeVault() {
|
|
737
747
|
try {
|
|
738
748
|
const mod = await import("node-vault");
|
|
739
749
|
return mod.default ?? mod;
|
|
740
750
|
} catch {
|
|
741
|
-
throw new Error("node-vault is not installed. Install
|
|
751
|
+
throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
|
|
742
752
|
}
|
|
743
753
|
}
|
|
744
754
|
var VaultClient = class {
|
|
745
|
-
|
|
755
|
+
logger;
|
|
756
|
+
address;
|
|
757
|
+
authMethod;
|
|
758
|
+
role;
|
|
759
|
+
mountPoint;
|
|
760
|
+
tokenPath;
|
|
761
|
+
k8sAuthMount;
|
|
762
|
+
namespace;
|
|
746
763
|
clientPromise;
|
|
747
764
|
authenticated = false;
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
logger;
|
|
751
|
-
cacheEnabled;
|
|
752
|
-
cacheTtlMs;
|
|
765
|
+
/** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
|
|
766
|
+
authPromise = null;
|
|
753
767
|
constructor(options) {
|
|
754
|
-
this.config = {
|
|
755
|
-
address: "https://vault.beta.commerceiq.ai/",
|
|
756
|
-
authMethod: "kubernetes",
|
|
757
|
-
role: "",
|
|
758
|
-
mountPoint: "secret",
|
|
759
|
-
tokenPath: "/var/run/secrets/kubernetes.io/serviceaccount/token",
|
|
760
|
-
...options.config
|
|
761
|
-
};
|
|
762
768
|
this.logger = options.logger;
|
|
763
|
-
|
|
764
|
-
this.
|
|
765
|
-
this.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
769
|
+
const cfg = options.config;
|
|
770
|
+
this.address = (cfg.address ?? DEFAULTS.address).replace(/\/+$/, "");
|
|
771
|
+
this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
|
|
772
|
+
this.role = cfg.role;
|
|
773
|
+
this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
|
|
774
|
+
this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
|
|
775
|
+
this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
|
|
776
|
+
this.namespace = cfg.namespace;
|
|
777
|
+
const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
|
|
778
|
+
this.clientPromise = factory.then((create) => create({
|
|
770
779
|
apiVersion: "v1",
|
|
771
|
-
endpoint: this.
|
|
772
|
-
namespace: this.
|
|
773
|
-
});
|
|
774
|
-
this.client = client;
|
|
775
|
-
return client;
|
|
780
|
+
endpoint: this.address,
|
|
781
|
+
namespace: this.namespace
|
|
782
|
+
}));
|
|
776
783
|
}
|
|
777
784
|
async authenticate() {
|
|
778
785
|
if (this.authenticated) return;
|
|
786
|
+
if (this.authPromise) return this.authPromise;
|
|
787
|
+
this.authPromise = this.doAuthenticate().finally(() => {
|
|
788
|
+
if (!this.authenticated) this.authPromise = null;
|
|
789
|
+
});
|
|
790
|
+
return this.authPromise;
|
|
791
|
+
}
|
|
792
|
+
async doAuthenticate() {
|
|
779
793
|
const client = await this.clientPromise;
|
|
780
|
-
if (this.
|
|
794
|
+
if (this.authMethod === "token") {
|
|
781
795
|
const token = process.env.VAULT_TOKEN;
|
|
782
|
-
if (!token) throw new Error("VAULT_TOKEN
|
|
796
|
+
if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
|
|
783
797
|
client.token = token;
|
|
784
798
|
this.authenticated = true;
|
|
785
799
|
this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
|
|
786
800
|
return;
|
|
787
801
|
}
|
|
788
|
-
|
|
789
|
-
if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
|
|
802
|
+
if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
|
|
790
803
|
let jwt;
|
|
791
804
|
try {
|
|
792
|
-
jwt = await readFile(this.
|
|
805
|
+
jwt = await readFile(this.tokenPath, "utf8");
|
|
793
806
|
} catch (err) {
|
|
794
|
-
const
|
|
795
|
-
throw new Error(`Failed to read
|
|
807
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
808
|
+
throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
|
|
796
809
|
}
|
|
797
810
|
const result = await client.kubernetesLogin({
|
|
798
|
-
role,
|
|
799
|
-
jwt
|
|
811
|
+
role: this.role,
|
|
812
|
+
jwt,
|
|
813
|
+
mount_point: this.k8sAuthMount
|
|
800
814
|
});
|
|
801
815
|
client.token = result.auth.client_token;
|
|
802
816
|
this.authenticated = true;
|
|
803
817
|
this.logger.info({
|
|
804
818
|
authMethod: "kubernetes",
|
|
805
|
-
role,
|
|
819
|
+
role: this.role,
|
|
806
820
|
leaseDuration: result.auth.lease_duration
|
|
807
821
|
}, "Vault authenticated via Kubernetes service account");
|
|
808
822
|
}
|
|
809
823
|
async getSecret(path) {
|
|
810
|
-
const cacheKey = path;
|
|
811
|
-
if (this.cacheEnabled) {
|
|
812
|
-
const cached = this.cache.get(cacheKey);
|
|
813
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
814
|
-
this.logger.debug({
|
|
815
|
-
path,
|
|
816
|
-
cache: "hit"
|
|
817
|
-
}, "Vault secret cache hit");
|
|
818
|
-
return cached.data;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
824
|
await this.authenticate();
|
|
822
825
|
const client = await this.clientPromise;
|
|
823
|
-
const fullPath = `${this.
|
|
826
|
+
const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
|
|
824
827
|
this.logger.debug({ path: fullPath }, "Reading secret from Vault");
|
|
825
828
|
const response = await client.read(fullPath);
|
|
826
|
-
|
|
827
|
-
if (this.cacheEnabled) {
|
|
828
|
-
this.cache.set(cacheKey, {
|
|
829
|
-
data,
|
|
830
|
-
expiresAt: Date.now() + this.cacheTtlMs
|
|
831
|
-
});
|
|
832
|
-
this.logger.debug({
|
|
833
|
-
path,
|
|
834
|
-
cache: "miss",
|
|
835
|
-
ttlMs: this.cacheTtlMs
|
|
836
|
-
}, "Vault secret cached");
|
|
837
|
-
}
|
|
838
|
-
return data;
|
|
829
|
+
return response.data.data;
|
|
839
830
|
}
|
|
840
831
|
async getSecretValue(path, key) {
|
|
841
832
|
const secrets = await this.getSecret(path);
|
|
@@ -844,10 +835,6 @@ var VaultClient = class {
|
|
|
844
835
|
isAuthenticated() {
|
|
845
836
|
return this.authenticated;
|
|
846
837
|
}
|
|
847
|
-
clearCache() {
|
|
848
|
-
this.cache.clear();
|
|
849
|
-
this.logger.debug({}, "Vault secret cache cleared");
|
|
850
|
-
}
|
|
851
838
|
async checkHealth() {
|
|
852
839
|
try {
|
|
853
840
|
await this.authenticate();
|
|
@@ -878,12 +865,11 @@ function warnDeprecation(oldPath, newPath, logger) {
|
|
|
878
865
|
function createFoundation(input) {
|
|
879
866
|
const startTime = Date.now();
|
|
880
867
|
const config = parseConfig(input);
|
|
881
|
-
const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig
|
|
868
|
+
const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
|
|
882
869
|
const features = {
|
|
883
870
|
tracing: featuresConfig.tracing ?? true,
|
|
884
871
|
metrics: featuresConfig.metrics ?? true,
|
|
885
|
-
logging: featuresConfig.logging ?? true
|
|
886
|
-
vault: vaultConfig.enabled ?? false
|
|
872
|
+
logging: featuresConfig.logging ?? true
|
|
887
873
|
};
|
|
888
874
|
const contextManager = createContextManager();
|
|
889
875
|
let logger;
|
|
@@ -961,22 +947,6 @@ function createFoundation(input) {
|
|
|
961
947
|
metricsError = err.message;
|
|
962
948
|
logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
|
|
963
949
|
}
|
|
964
|
-
let vaultClient = null;
|
|
965
|
-
let vaultError;
|
|
966
|
-
if (features.vault) try {
|
|
967
|
-
vaultClient = createVaultClient({
|
|
968
|
-
config: vaultConfig,
|
|
969
|
-
logger
|
|
970
|
-
});
|
|
971
|
-
logger.info({
|
|
972
|
-
feature: "vault",
|
|
973
|
-
address: vaultConfig.address,
|
|
974
|
-
authMethod: vaultConfig.authMethod
|
|
975
|
-
}, "Vault client created (lazy auth on first secret read)");
|
|
976
|
-
} catch (err) {
|
|
977
|
-
vaultError = err.message;
|
|
978
|
-
logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
|
|
979
|
-
}
|
|
980
950
|
logger.info({
|
|
981
951
|
serviceName,
|
|
982
952
|
serviceVersion,
|
|
@@ -1044,45 +1014,50 @@ function createFoundation(input) {
|
|
|
1044
1014
|
...options,
|
|
1045
1015
|
foundation
|
|
1046
1016
|
}) };
|
|
1047
|
-
|
|
1017
|
+
let secrets = null;
|
|
1018
|
+
if (config.vault?.enabled) try {
|
|
1019
|
+
secrets = createVaultClient({
|
|
1020
|
+
config: config.vault,
|
|
1021
|
+
logger
|
|
1022
|
+
});
|
|
1023
|
+
logger.info({
|
|
1024
|
+
address: config.vault.address,
|
|
1025
|
+
authMethod: config.vault.authMethod
|
|
1026
|
+
}, "Vault secrets module initialized");
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
|
|
1029
|
+
}
|
|
1048
1030
|
const buildHealthStatus = () => {
|
|
1049
1031
|
const tracingUp = !features.tracing || !tracingError && isTracingEnabled();
|
|
1050
1032
|
const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
|
|
1051
1033
|
const loggingUp = !loggingError;
|
|
1052
|
-
const
|
|
1053
|
-
const
|
|
1054
|
-
const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
|
|
1034
|
+
const allUp = tracingUp && metricsUp && loggingUp;
|
|
1035
|
+
const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
|
|
1055
1036
|
let status = "healthy";
|
|
1056
1037
|
if (!allUp) status = allDown ? "unhealthy" : "degraded";
|
|
1057
|
-
const components = {
|
|
1058
|
-
tracing: {
|
|
1059
|
-
enabled: features.tracing,
|
|
1060
|
-
status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
|
|
1061
|
-
message: tracingError
|
|
1062
|
-
},
|
|
1063
|
-
metrics: {
|
|
1064
|
-
enabled: features.metrics,
|
|
1065
|
-
status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
|
|
1066
|
-
message: metricsError
|
|
1067
|
-
},
|
|
1068
|
-
logging: {
|
|
1069
|
-
enabled: features.logging,
|
|
1070
|
-
status: loggingError ? "down" : "up",
|
|
1071
|
-
message: loggingError
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
if (features.vault) components.vault = {
|
|
1075
|
-
enabled: true,
|
|
1076
|
-
status: vaultError ? "down" : "up",
|
|
1077
|
-
message: vaultError
|
|
1078
|
-
};
|
|
1079
1038
|
return {
|
|
1080
1039
|
status,
|
|
1081
1040
|
timestamp: new Date().toISOString(),
|
|
1082
1041
|
service: serviceName,
|
|
1083
1042
|
version: serviceVersion,
|
|
1084
1043
|
uptime: Math.floor((Date.now() - startTime) / 1e3),
|
|
1085
|
-
components
|
|
1044
|
+
components: {
|
|
1045
|
+
tracing: {
|
|
1046
|
+
enabled: features.tracing,
|
|
1047
|
+
status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
|
|
1048
|
+
message: tracingError
|
|
1049
|
+
},
|
|
1050
|
+
metrics: {
|
|
1051
|
+
enabled: features.metrics,
|
|
1052
|
+
status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
|
|
1053
|
+
message: metricsError
|
|
1054
|
+
},
|
|
1055
|
+
logging: {
|
|
1056
|
+
enabled: features.logging,
|
|
1057
|
+
status: loggingError ? "down" : "up",
|
|
1058
|
+
message: loggingError
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1086
1061
|
};
|
|
1087
1062
|
};
|
|
1088
1063
|
const shutdownFn = async () => {
|
|
@@ -1090,7 +1065,6 @@ function createFoundation(input) {
|
|
|
1090
1065
|
const promises = [];
|
|
1091
1066
|
if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());
|
|
1092
1067
|
if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
|
|
1093
|
-
if (vaultClient) vaultClient.clearCache();
|
|
1094
1068
|
await Promise.all(promises);
|
|
1095
1069
|
logger.info({}, "Foundation shutdown complete");
|
|
1096
1070
|
};
|
|
@@ -1123,7 +1097,7 @@ function createFoundation(input) {
|
|
|
1123
1097
|
observability,
|
|
1124
1098
|
http: httpModule,
|
|
1125
1099
|
lifecycle,
|
|
1126
|
-
secrets
|
|
1100
|
+
secrets,
|
|
1127
1101
|
logger,
|
|
1128
1102
|
context: contextManager,
|
|
1129
1103
|
fastifyPlugin: createObservabilityPlugin({
|
|
@@ -1469,5 +1443,5 @@ var InMemoryObjectStore = class {
|
|
|
1469
1443
|
};
|
|
1470
1444
|
|
|
1471
1445
|
//#endregion
|
|
1472
|
-
export { AutoInstrumentationConfigSchema, AwsS3ObjectStore, FeaturesConfigSchema, FoundationConfigSchema, InMemoryObjectStore, LoggingConfigSchema, OtelConfigSchema, REDACT_PATHS, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode,
|
|
1446
|
+
export { AutoInstrumentationConfigSchema, AwsS3ObjectStore, FeaturesConfigSchema, FoundationConfigSchema, InMemoryObjectStore, LoggingConfigSchema, OtelConfigSchema, REDACT_PATHS, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultClient, VaultConfigSchema, buildPinoRedactConfig, context, createContextManager, createFallbackLogger, createFoundation, createHttpClient, createLogger, createObservabilityPlugin, createVaultClient, getActiveSpan, getDefaultOtelEndpoint, getGlobalLogger, getMeter, getTraceContext, getTracer, isMetricsEnabled, isTracingEnabled, metrics, parseConfig, propagation, sanitizeBody, setGlobalLogger, setupMetrics, setupObservability, setupTracing, shutdownMetrics, shutdownTracing, trace };
|
|
1473
1447
|
//# sourceMappingURL=index.mjs.map
|