@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/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,109 @@ 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
+ let start = 0;
745
+ let end = value.length;
746
+ while (start < end && value[start] === "/") start += 1;
747
+ while (end > start && value[end - 1] === "/") end -= 1;
748
+ return value.slice(start, end);
749
+ }
750
+ function stripTrailingSlashes(value) {
751
+ let end = value.length;
752
+ while (end > 0 && value[end - 1] === "/") end -= 1;
753
+ return value.slice(0, end);
754
+ }
736
755
  async function loadNodeVault() {
737
756
  try {
738
757
  const mod = await import("node-vault");
739
758
  return mod.default ?? mod;
740
759
  } catch {
741
- throw new Error("node-vault is not installed. Install the optional peer dependency: npm install node-vault");
760
+ throw new Error("node-vault is not installed. Install it as a peer dependency: npm install node-vault");
742
761
  }
743
762
  }
744
763
  var VaultClient = class {
745
- client = null;
764
+ logger;
765
+ address;
766
+ authMethod;
767
+ role;
768
+ mountPoint;
769
+ tokenPath;
770
+ k8sAuthMount;
771
+ namespace;
746
772
  clientPromise;
747
773
  authenticated = false;
748
- cache = new Map();
749
- config;
750
- logger;
751
- cacheEnabled;
752
- cacheTtlMs;
774
+ /** In-flight auth so concurrent getSecret() calls don't all trigger kubernetesLogin. */
775
+ authPromise = null;
753
776
  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
777
  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({
778
+ const cfg = options.config;
779
+ this.address = stripTrailingSlashes(cfg.address ?? DEFAULTS.address);
780
+ this.authMethod = cfg.authMethod ?? DEFAULTS.authMethod;
781
+ this.role = cfg.role;
782
+ this.mountPoint = stripSlashes(cfg.mountPoint ?? DEFAULTS.mountPoint);
783
+ this.tokenPath = cfg.tokenPath ?? DEFAULTS.tokenPath;
784
+ this.k8sAuthMount = cfg.k8sAuthMount ?? DEFAULTS.k8sAuthMount;
785
+ this.namespace = cfg.namespace;
786
+ const factory = options.nodeVaultFactory ? Promise.resolve(options.nodeVaultFactory) : loadNodeVault();
787
+ this.clientPromise = factory.then((create) => create({
771
788
  apiVersion: "v1",
772
- endpoint: this.config.address,
773
- namespace: this.config.namespace
774
- });
775
- this.client = client;
776
- return client;
789
+ endpoint: this.address,
790
+ namespace: this.namespace
791
+ }));
777
792
  }
778
793
  async authenticate() {
779
794
  if (this.authenticated) return;
795
+ if (this.authPromise) return this.authPromise;
796
+ this.authPromise = this.doAuthenticate().finally(() => {
797
+ if (!this.authenticated) this.authPromise = null;
798
+ });
799
+ return this.authPromise;
800
+ }
801
+ async doAuthenticate() {
780
802
  const client = await this.clientPromise;
781
- if (this.config.authMethod === "token") {
803
+ if (this.authMethod === "token") {
782
804
  const token = process.env.VAULT_TOKEN;
783
- if (!token) throw new Error("VAULT_TOKEN environment variable is required for token-based authentication");
805
+ if (!token) throw new Error("VAULT_TOKEN env var is required for token auth");
784
806
  client.token = token;
785
807
  this.authenticated = true;
786
808
  this.logger.info({ authMethod: "token" }, "Vault authenticated via token");
787
809
  return;
788
810
  }
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.");
811
+ if (!this.role) throw new Error("Vault role is required for Kubernetes auth (set vault.role or VAULT_AUTH_ROLE)");
791
812
  let jwt;
792
813
  try {
793
- jwt = await readFile(this.config.tokenPath, "utf8");
814
+ jwt = await readFile(this.tokenPath, "utf8");
794
815
  } 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}`);
816
+ const msg = err instanceof Error ? err.message : String(err);
817
+ throw new Error(`Failed to read service-account token at ${this.tokenPath}: ${msg}`);
797
818
  }
798
819
  const result = await client.kubernetesLogin({
799
- role,
820
+ role: this.role,
800
821
  jwt,
801
- mount_point: this.config.k8sAuthMount
822
+ mount_point: this.k8sAuthMount
802
823
  });
803
824
  client.token = result.auth.client_token;
804
825
  this.authenticated = true;
805
826
  this.logger.info({
806
827
  authMethod: "kubernetes",
807
- role,
828
+ role: this.role,
808
829
  leaseDuration: result.auth.lease_duration
809
830
  }, "Vault authenticated via Kubernetes service account");
810
831
  }
811
832
  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
833
  await this.authenticate();
824
834
  const client = await this.clientPromise;
825
- const fullPath = `${this.config.mountPoint}/data/${path}`;
835
+ const fullPath = `${this.mountPoint}/data/${stripSlashes(path)}`;
826
836
  this.logger.debug({ path: fullPath }, "Reading secret from Vault");
827
837
  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;
838
+ return response.data.data;
841
839
  }
842
840
  async getSecretValue(path, key) {
843
841
  const secrets = await this.getSecret(path);
@@ -846,10 +844,6 @@ var VaultClient = class {
846
844
  isAuthenticated() {
847
845
  return this.authenticated;
848
846
  }
849
- clearCache() {
850
- this.cache.clear();
851
- this.logger.debug({}, "Vault secret cache cleared");
852
- }
853
847
  async checkHealth() {
854
848
  try {
855
849
  await this.authenticate();
@@ -880,12 +874,11 @@ function warnDeprecation(oldPath, newPath, logger) {
880
874
  function createFoundation(input) {
881
875
  const startTime = Date.now();
882
876
  const config = parseConfig(input);
883
- const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig, vault: vaultConfig } = config;
877
+ const { serviceName, serviceVersion, environment, features: featuresConfig, otel, logging: loggingConfig, requestLogging: requestLoggingConfig, redaction: redactionConfig, shutdown: shutdownConfig } = config;
884
878
  const features = {
885
879
  tracing: featuresConfig.tracing ?? true,
886
880
  metrics: featuresConfig.metrics ?? true,
887
- logging: featuresConfig.logging ?? true,
888
- vault: vaultConfig.enabled ?? false
881
+ logging: featuresConfig.logging ?? true
889
882
  };
890
883
  const contextManager = createContextManager();
891
884
  let logger;
@@ -963,22 +956,6 @@ function createFoundation(input) {
963
956
  metricsError = err.message;
964
957
  logger.error({ error: metricsError }, "Metrics setup failed - continuing without metrics");
965
958
  }
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
959
  logger.info({
983
960
  serviceName,
984
961
  serviceVersion,
@@ -1046,45 +1023,50 @@ function createFoundation(input) {
1046
1023
  ...options,
1047
1024
  foundation
1048
1025
  }) };
1049
- const secretsModule = vaultClient;
1026
+ let secrets = null;
1027
+ if (config.vault?.enabled) try {
1028
+ secrets = createVaultClient({
1029
+ config: config.vault,
1030
+ logger
1031
+ });
1032
+ logger.info({
1033
+ address: config.vault.address,
1034
+ authMethod: config.vault.authMethod
1035
+ }, "Vault secrets module initialized");
1036
+ } catch (err) {
1037
+ logger.error({ error: err.message }, "Vault initialization failed - continuing without secrets");
1038
+ }
1050
1039
  const buildHealthStatus = () => {
1051
1040
  const tracingUp = !features.tracing || !tracingError && isTracingEnabled();
1052
1041
  const metricsUp = !features.metrics || !metricsError && isMetricsEnabled();
1053
1042
  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;
1043
+ const allUp = tracingUp && metricsUp && loggingUp;
1044
+ const allDown = (!tracingUp || !features.tracing) && (!metricsUp || !features.metrics) && !loggingUp;
1057
1045
  let status = "healthy";
1058
1046
  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
1047
  return {
1082
1048
  status,
1083
1049
  timestamp: new Date().toISOString(),
1084
1050
  service: serviceName,
1085
1051
  version: serviceVersion,
1086
1052
  uptime: Math.floor((Date.now() - startTime) / 1e3),
1087
- components
1053
+ components: {
1054
+ tracing: {
1055
+ enabled: features.tracing,
1056
+ status: !features.tracing ? "disabled" : tracingError ? "down" : "up",
1057
+ message: tracingError
1058
+ },
1059
+ metrics: {
1060
+ enabled: features.metrics,
1061
+ status: !features.metrics ? "disabled" : metricsError ? "down" : "up",
1062
+ message: metricsError
1063
+ },
1064
+ logging: {
1065
+ enabled: features.logging,
1066
+ status: loggingError ? "down" : "up",
1067
+ message: loggingError
1068
+ }
1069
+ }
1088
1070
  };
1089
1071
  };
1090
1072
  const shutdownFn = async () => {
@@ -1092,7 +1074,6 @@ function createFoundation(input) {
1092
1074
  const promises = [];
1093
1075
  if (features.tracing && isTracingEnabled()) promises.push(shutdownTracing());
1094
1076
  if (features.metrics && isMetricsEnabled()) promises.push(shutdownMetrics());
1095
- if (vaultClient) vaultClient.clearCache();
1096
1077
  await Promise.all(promises);
1097
1078
  logger.info({}, "Foundation shutdown complete");
1098
1079
  };
@@ -1125,7 +1106,7 @@ function createFoundation(input) {
1125
1106
  observability,
1126
1107
  http: httpModule,
1127
1108
  lifecycle,
1128
- secrets: secretsModule,
1109
+ secrets,
1129
1110
  logger,
1130
1111
  context: contextManager,
1131
1112
  fastifyPlugin: createObservabilityPlugin({
@@ -1471,5 +1452,5 @@ var InMemoryObjectStore = class {
1471
1452
  };
1472
1453
 
1473
1454
  //#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 };
1455
+ 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
1456
  //# sourceMappingURL=index.mjs.map