@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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const require_tracing = require('./tracing-C0FkkeKE.js');
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,100 @@ 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
+ return value.replace(/^\/+|\/+$/g, "");
746
+ }
737
747
  async function loadNodeVault() {
738
748
  try {
739
749
  const mod = await import("node-vault");
740
750
  return mod.default ?? mod;
741
751
  } catch {
742
- throw new Error("node-vault is not installed. Install the optional peer dependency: npm install node-vault");
752
+ throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
743
753
  }
744
754
  }
745
755
  var VaultClient = class {
746
- client = null;
756
+ logger;
757
+ address;
758
+ authMethod;
759
+ role;
760
+ mountPoint;
761
+ tokenPath;
762
+ k8sAuthMount;
763
+ namespace;
747
764
  clientPromise;
748
765
  authenticated = false;
749
- cache = new Map();
750
- config;
751
- logger;
752
- cacheEnabled;
753
- cacheTtlMs;
766
+ /** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
767
+ authPromise = null;
754
768
  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
769
  this.logger = options.logger;
765
- this.cacheEnabled = this.config.cache?.enabled !== false;
766
- this.cacheTtlMs = (this.config.cache?.ttlSeconds ?? 300) * 1e3;
767
- this.clientPromise = this.initClient();
768
- }
769
- async initClient() {
770
- const nodeVault = await loadNodeVault();
771
- const client = nodeVault({
770
+ const cfg = options.config;
771
+ this.address = (cfg.address ?? DEFAULTS.address).replace(/\/+$/, "");
772
+ this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
773
+ this.role = cfg.role;
774
+ this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
775
+ this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
776
+ this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
777
+ this.namespace = cfg.namespace;
778
+ const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
779
+ this.clientPromise = factory.then((create) => create({
772
780
  apiVersion: "v1",
773
- endpoint: this.config.address,
774
- namespace: this.config.namespace
775
- });
776
- this.client = client;
777
- return client;
781
+ endpoint: this.address,
782
+ namespace: this.namespace
783
+ }));
778
784
  }
779
785
  async authenticate() {
780
786
  if (this.authenticated) return;
787
+ if (this.authPromise) return this.authPromise;
788
+ this.authPromise = this.doAuthenticate().finally(() => {
789
+ if (!this.authenticated) this.authPromise = null;
790
+ });
791
+ return this.authPromise;
792
+ }
793
+ async doAuthenticate() {
781
794
  const client = await this.clientPromise;
782
- if (this.config.authMethod === "token") {
795
+ if (this.authMethod === "token") {
783
796
  const token = process.env.VAULT_TOKEN;
784
- if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
797
+ if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
785
798
  client.token = token;
786
799
  this.authenticated = true;
787
800
  this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
788
801
  return;
789
802
  }
790
- const role = this.config.role;
791
- if (!role) throw new Error("Vault role is required for Kubernetes authentication. Set vault.role in config or VAULT_AUTH_ROLE env var.");
803
+ if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
792
804
  let jwt;
793
805
  try {
794
- jwt = await (0, node_fs_promises.readFile)(this.config.tokenPath, "utf8");
806
+ jwt = await (0, node_fs_promises.readFile)(this.tokenPath, "utf8");
795
807
  } catch (err) {
796
- const message = err instanceof Error ? err.message : String(err);
797
- throw new Error(`Failed to read Kubernetes service account token from ${this.config.tokenPath}: ${message}`);
808
+ const msg = err instanceof Error ? err.message : String(err);
809
+ throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
798
810
  }
799
811
  const result = await client.kubernetesLogin({
800
- role,
812
+ role: this.role,
801
813
  jwt,
802
- mount_point: this.config.k8sAuthMount
814
+ mount_point: this.k8sAuthMount
803
815
  });
804
816
  client.token = result.auth.client_token;
805
817
  this.authenticated = true;
806
818
  this.logger.info({
807
819
  authMethod: "kubernetes",
808
- role,
820
+ role: this.role,
809
821
  leaseDuration: result.auth.lease_duration
810
822
  }, "Vault authenticated via Kubernetes service account");
811
823
  }
812
824
  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
825
  await this.authenticate();
825
826
  const client = await this.clientPromise;
826
- const fullPath = `${this.config.mountPoint}/data/${path}`;
827
+ const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
827
828
  this.logger.debug({ path: fullPath }, "Reading secret from Vault");
828
829
  const response = await client.read(fullPath);
829
- const data = response.data.data;
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;
830
+ return response.data.data;
842
831
  }
843
832
  async getSecretValue(path, key) {
844
833
  const secrets = await this.getSecret(path);
@@ -847,10 +836,6 @@ var VaultClient = class {
847
836
  isAuthenticated() {
848
837
  return this.authenticated;
849
838
  }
850
- clearCache() {
851
- this.cache.clear();
852
- this.logger.debug({}, "Vault secret cache cleared");
853
- }
854
839
  async checkHealth() {
855
840
  try {
856
841
  await this.authenticate();
@@ -881,12 +866,11 @@ function warnDeprecation(oldPath, newPath, logger) {
881
866
  function createFoundation(input) {
882
867
  const startTime = Date.now();
883
868
  const config = require_tracing.parseConfig(input);
884
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
869
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
885
870
  const features = {
886
871
  tracing: featuresConfig.tracing ?? true,
887
872
  metrics: featuresConfig.metrics ?? true,
888
- logging: featuresConfig.logging ?? true,
889
- vault: vaultConfig.enabled ?? false
873
+ logging: featuresConfig.logging ?? true
890
874
  };
891
875
  const contextManager = createContextManager();
892
876
  let logger;
@@ -964,22 +948,6 @@ function createFoundation(input) {
964
948
  metricsError = err.message;
965
949
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
966
950
  }
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
951
  logger.info({
984
952
  serviceName,
985
953
  serviceVersion,
@@ -1047,45 +1015,50 @@ function createFoundation(input) {
1047
1015
  ...options,
1048
1016
  foundation
1049
1017
  }) };
1050
- const secretsModule = vaultClient;
1018
+ let secrets = null;
1019
+ if (config.vault?.enabled) try {
1020
+ secrets = createVaultClient({
1021
+ config: config.vault,
1022
+ logger
1023
+ });
1024
+ logger.info({
1025
+ address: config.vault.address,
1026
+ authMethod: config.vault.authMethod
1027
+ }, "Vault secrets module initialized");
1028
+ } catch (err) {
1029
+ logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
1030
+ }
1051
1031
  const buildHealthStatus = () => {
1052
1032
  const tracingUp = !features.tracing || !tracingError && require_tracing.isTracingEnabled();
1053
1033
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
1054
1034
  const loggingUp = !loggingError;
1055
- const vaultUp = !features.vault || !vaultError && !!vaultClient;
1056
- const allUp = tracingUp && metricsUp && loggingUp && vaultUp;
1057
- const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && (!vaultUp || !features.vault) && !loggingUp;
1035
+ const allUp = tracingUp && metricsUp && loggingUp;
1036
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
1058
1037
  let status = "healthy";
1059
1038
  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
1039
  return {
1083
1040
  status,
1084
1041
  timestamp: new Date().toISOString(),
1085
1042
  service: serviceName,
1086
1043
  version: serviceVersion,
1087
1044
  uptime: Math.floor((Date.now() - startTime) / 1e3),
1088
- components
1045
+ components: {
1046
+ tracing: {
1047
+ enabled: features.tracing,
1048
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1049
+ message: tracingError
1050
+ },
1051
+ metrics: {
1052
+ enabled: features.metrics,
1053
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1054
+ message: metricsError
1055
+ },
1056
+ logging: {
1057
+ enabled: features.logging,
1058
+ status: loggingError ? "down" : "up",
1059
+ message: loggingError
1060
+ }
1061
+ }
1089
1062
  };
1090
1063
  };
1091
1064
  const shutdownFn = async () => {
@@ -1093,7 +1066,6 @@ function createFoundation(input) {
1093
1066
  const promises = [];
1094
1067
  if (features.tracing && require_tracing.isTracingEnabled()) promises.push(require_tracing.shutdownTracing());
1095
1068
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1096
- if (vaultClient) vaultClient.clearCache();
1097
1069
  await Promise.all(promises);
1098
1070
  logger.info({}, "Foundation shutdown complete");
1099
1071
  };
@@ -1126,7 +1098,7 @@ function createFoundation(input) {
1126
1098
  observability,
1127
1099
  http: httpModule,
1128
1100
  lifecycle,
1129
- secrets: secretsModule,
1101
+ secrets,
1130
1102
  logger,
1131
1103
  context: contextManager,
1132
1104
  fastifyPlugin: createObservabilityPlugin({
@@ -1484,7 +1456,6 @@ exports.RedactionConfigSchema = require_tracing.RedactionConfigSchema
1484
1456
  exports.RequestLoggingConfigSchema = require_tracing.RequestLoggingConfigSchema
1485
1457
  exports.ShutdownConfigSchema = require_tracing.ShutdownConfigSchema
1486
1458
  exports.SpanStatusCode = __opentelemetry_api.SpanStatusCode
1487
- exports.VaultCacheConfigSchema = require_tracing.VaultCacheConfigSchema
1488
1459
  exports.VaultClient = VaultClient
1489
1460
  exports.VaultConfigSchema = require_tracing.VaultConfigSchema
1490
1461
  exports.buildPinoRedactConfig = buildPinoRedactConfig