@ciq-dev/neoiq-foundation-node 1.0.3-0 → 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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { AutoInstrumentationConfigSchema, FeaturesConfigSchema, FoundationConfigSchema, LoggingConfigSchema, OtelConfigSchema, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultCacheConfigSchema, VaultConfigSchema, context, getActiveSpan, getDefaultOtelEndpoint, getTraceContext, getTracer, isTracingEnabled, parseConfig, propagation, setupTracing, shutdownTracing, trace } from "./tracing-C3oniP3X.mjs";
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,111 +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 the optional peer dependency: npm install node-vault");
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
- client = null;
755
+ logger;
756
+ address;
757
+ authMethod;
758
+ role;
759
+ mountPoint;
760
+ tokenPath;
761
+ k8sAuthMount;
762
+ namespace;
746
763
  clientPromise;
747
764
  authenticated = false;
748
- cache = new Map();
749
- config;
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
- k8sAuthMount: "kubernetes",
761
- ...options.config
762
- };
763
768
  this.logger = options.logger;
764
- this.cacheEnabled = this.config.cache?.enabled !== false;
765
- this.cacheTtlMs = (this.config.cache?.ttlSeconds ?? 300) * 1e3;
766
- this.clientPromise = this.initClient();
767
- }
768
- async initClient() {
769
- const nodeVault = await loadNodeVault();
770
- const client = nodeVault({
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({
771
779
  apiVersion: "v1",
772
- endpoint: this.config.address,
773
- namespace: this.config.namespace
774
- });
775
- this.client = client;
776
- return client;
780
+ endpoint: this.address,
781
+ namespace: this.namespace
782
+ }));
777
783
  }
778
784
  async authenticate() {
779
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() {
780
793
  const client = await this.clientPromise;
781
- if (this.config.authMethod === "token") {
794
+ if (this.authMethod === "token") {
782
795
  const token = process.env.VAULT_TOKEN;
783
- if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
796
+ if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
784
797
  client.token = token;
785
798
  this.authenticated = true;
786
799
  this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
787
800
  return;
788
801
  }
789
- const role = this.config.role;
790
- 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)");
791
803
  let jwt;
792
804
  try {
793
- jwt = await readFile(this.config.tokenPath, "utf8");
805
+ jwt = await readFile(this.tokenPath, "utf8");
794
806
  } catch (err) {
795
- const message = err instanceof Error ? err.message : String(err);
796
- throw new Error(`Failed to read Kubernetes service account token from ${this.config.tokenPath}: ${message}`);
807
+ const msg = err instanceof Error ? err.message : String(err);
808
+ throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
797
809
  }
798
810
  const result = await client.kubernetesLogin({
799
- role,
811
+ role: this.role,
800
812
  jwt,
801
- mount_point: this.config.k8sAuthMount
813
+ mount_point: this.k8sAuthMount
802
814
  });
803
815
  client.token = result.auth.client_token;
804
816
  this.authenticated = true;
805
817
  this.logger.info({
806
818
  authMethod: "kubernetes",
807
- role,
819
+ role: this.role,
808
820
  leaseDuration: result.auth.lease_duration
809
821
  }, "Vault authenticated via Kubernetes service account");
810
822
  }
811
823
  async getSecret(path) {
812
- const cacheKey = path;
813
- if (this.cacheEnabled) {
814
- const cached = this.cache.get(cacheKey);
815
- if (cached && cached.expiresAt > Date.now()) {
816
- this.logger.debug({
817
- path,
818
- cache: "hit"
819
- }, "Vault secret cache hit");
820
- return cached.data;
821
- }
822
- }
823
824
  await this.authenticate();
824
825
  const client = await this.clientPromise;
825
- const fullPath = `${this.config.mountPoint}/data/${path}`;
826
+ const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
826
827
  this.logger.debug({ path: fullPath }, "Reading secret from Vault");
827
828
  const response = await client.read(fullPath);
828
- const data = response.data.data;
829
- if (this.cacheEnabled) {
830
- this.cache.set(cacheKey, {
831
- data,
832
- expiresAt: Date.now() + this.cacheTtlMs
833
- });
834
- this.logger.debug({
835
- path,
836
- cache: "miss",
837
- ttlMs: this.cacheTtlMs
838
- }, "Vault secret cached");
839
- }
840
- return data;
829
+ return response.data.data;
841
830
  }
842
831
  async getSecretValue(path, key) {
843
832
  const secrets = await this.getSecret(path);
@@ -846,10 +835,6 @@ var VaultClient = class {
846
835
  isAuthenticated() {
847
836
  return this.authenticated;
848
837
  }
849
- clearCache() {
850
- this.cache.clear();
851
- this.logger.debug({}, "Vault secret cache cleared");
852
- }
853
838
  async checkHealth() {
854
839
  try {
855
840
  await this.authenticate();
@@ -880,12 +865,11 @@ function warnDeprecation(oldPath, newPath, logger) {
880
865
  function createFoundation(input) {
881
866
  const startTime = Date.now();
882
867
  const config = parseConfig(input);
883
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
868
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
884
869
  const features = {
885
870
  tracing: featuresConfig.tracing ?? true,
886
871
  metrics: featuresConfig.metrics ?? true,
887
- logging: featuresConfig.logging ?? true,
888
- vault: vaultConfig.enabled ?? false
872
+ logging: featuresConfig.logging ?? true
889
873
  };
890
874
  const contextManager = createContextManager();
891
875
  let logger;
@@ -963,22 +947,6 @@ function createFoundation(input) {
963
947
  metricsError = err.message;
964
948
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
965
949
  }
966
- let vaultClient = null;
967
- let vaultError;
968
- if (features.vault) try {
969
- vaultClient = createVaultClient({
970
- config: vaultConfig,
971
- logger
972
- });
973
- logger.info({
974
- feature: "vault",
975
- address: vaultConfig.address,
976
- authMethod: vaultConfig.authMethod
977
- }, "Vault client created (lazy auth on first secret read)");
978
- } catch (err) {
979
- vaultError = err.message;
980
- logger.error({ error: vaultError }, "Vault setup failed - continuing without vault");
981
- }
982
950
  logger.info({
983
951
  serviceName,
984
952
  serviceVersion,
@@ -1046,45 +1014,50 @@ function createFoundation(input) {
1046
1014
  ...options,
1047
1015
  foundation
1048
1016
  }) };
1049
- const secretsModule = vaultClient;
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
+ }
1050
1030
  const buildHealthStatus = () => {
1051
1031
  const tracingUp = !features.tracing || !tracingError && isTracingEnabled();
1052
1032
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
1053
1033
  const loggingUp = !loggingError;
1054
- const vaultUp = !features.vault || !vaultError && !!vaultClient;
1055
- const allUp = tracingUp && metricsUp && loggingUp && vaultUp;
1056
- 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;
1057
1036
  let status = "healthy";
1058
1037
  if (!allUp) status = allDown ? "unhealthy" : "degraded";
1059
- const components = {
1060
- tracing: {
1061
- enabled: features.tracing,
1062
- status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1063
- message: tracingError
1064
- },
1065
- metrics: {
1066
- enabled: features.metrics,
1067
- status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1068
- message: metricsError
1069
- },
1070
- logging: {
1071
- enabled: features.logging,
1072
- status: loggingError ? "down" : "up",
1073
- message: loggingError
1074
- }
1075
- };
1076
- if (features.vault) components.vault = {
1077
- enabled: true,
1078
- status: vaultError ? "down" : "up",
1079
- message: vaultError
1080
- };
1081
1038
  return {
1082
1039
  status,
1083
1040
  timestamp: new Date().toISOString(),
1084
1041
  service: serviceName,
1085
1042
  version: serviceVersion,
1086
1043
  uptime: Math.floor((Date.now() - startTime) / 1e3),
1087
- 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
+ }
1088
1061
  };
1089
1062
  };
1090
1063
  const shutdownFn = async () => {
@@ -1092,7 +1065,6 @@ function createFoundation(input) {
1092
1065
  const promises = [];
1093
1066
  if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());
1094
1067
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1095
- if (vaultClient) vaultClient.clearCache();
1096
1068
  await Promise.all(promises);
1097
1069
  logger.info({}, "Foundation shutdown complete");
1098
1070
  };
@@ -1125,7 +1097,7 @@ function createFoundation(input) {
1125
1097
  observability,
1126
1098
  http: httpModule,
1127
1099
  lifecycle,
1128
- secrets: secretsModule,
1100
+ secrets,
1129
1101
  logger,
1130
1102
  context: contextManager,
1131
1103
  fastifyPlugin: createObservabilityPlugin({
@@ -1471,5 +1443,5 @@ var InMemoryObjectStore = class {
1471
1443
  };
1472
1444
 
1473
1445
  //#endregion
1474
- export { AutoInstrumentationConfigSchema, AwsS3ObjectStore, FeaturesConfigSchema, FoundationConfigSchema, InMemoryObjectStore, LoggingConfigSchema, OtelConfigSchema, REDACT_PATHS, RedactionConfigSchema, RequestLoggingConfigSchema, ShutdownConfigSchema, SpanStatusCode, VaultCacheConfigSchema, 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 };
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 };
1475
1447
  //# sourceMappingURL=index.mjs.map