@blokjs/runner 0.2.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.
Files changed (307) hide show
  1. package/dist/Blok.d.ts +19 -0
  2. package/dist/Blok.js +184 -0
  3. package/dist/Blok.js.map +1 -0
  4. package/dist/BlokResponse.d.ts +16 -0
  5. package/dist/BlokResponse.js +28 -0
  6. package/dist/BlokResponse.js.map +1 -0
  7. package/dist/Configuration.d.ts +37 -0
  8. package/dist/Configuration.js +248 -0
  9. package/dist/Configuration.js.map +1 -0
  10. package/dist/ConfigurationResolver.d.ts +7 -0
  11. package/dist/ConfigurationResolver.js +15 -0
  12. package/dist/ConfigurationResolver.js.map +1 -0
  13. package/dist/DefaultLogger.d.ts +65 -0
  14. package/dist/DefaultLogger.js +101 -0
  15. package/dist/DefaultLogger.js.map +1 -0
  16. package/dist/LocalStorage.d.ts +7 -0
  17. package/dist/LocalStorage.js +56 -0
  18. package/dist/LocalStorage.js.map +1 -0
  19. package/dist/MemoryUsage.d.ts +22 -0
  20. package/dist/MemoryUsage.js +83 -0
  21. package/dist/MemoryUsage.js.map +1 -0
  22. package/dist/NodeMap.d.ts +7 -0
  23. package/dist/NodeMap.js +13 -0
  24. package/dist/NodeMap.js.map +1 -0
  25. package/dist/ResolverBase.d.ts +8 -0
  26. package/dist/ResolverBase.js +18 -0
  27. package/dist/ResolverBase.js.map +1 -0
  28. package/dist/Runner.d.ts +25 -0
  29. package/dist/Runner.js +32 -0
  30. package/dist/Runner.js.map +1 -0
  31. package/dist/RunnerNode.d.ts +9 -0
  32. package/dist/RunnerNode.js +8 -0
  33. package/dist/RunnerNode.js.map +1 -0
  34. package/dist/RunnerNodeBase.d.ts +4 -0
  35. package/dist/RunnerNodeBase.js +3 -0
  36. package/dist/RunnerNodeBase.js.map +1 -0
  37. package/dist/RunnerSteps.d.ts +14 -0
  38. package/dist/RunnerSteps.js +110 -0
  39. package/dist/RunnerSteps.js.map +1 -0
  40. package/dist/RuntimeAdapterNode.d.ts +19 -0
  41. package/dist/RuntimeAdapterNode.js +87 -0
  42. package/dist/RuntimeAdapterNode.js.map +1 -0
  43. package/dist/RuntimeRegistry.d.ts +61 -0
  44. package/dist/RuntimeRegistry.js +87 -0
  45. package/dist/RuntimeRegistry.js.map +1 -0
  46. package/dist/TriggerBase.d.ts +119 -0
  47. package/dist/TriggerBase.js +413 -0
  48. package/dist/TriggerBase.js.map +1 -0
  49. package/dist/adapters/BunRuntimeAdapter.d.ts +38 -0
  50. package/dist/adapters/BunRuntimeAdapter.js +169 -0
  51. package/dist/adapters/BunRuntimeAdapter.js.map +1 -0
  52. package/dist/adapters/DockerRuntimeAdapter.d.ts +85 -0
  53. package/dist/adapters/DockerRuntimeAdapter.js +298 -0
  54. package/dist/adapters/DockerRuntimeAdapter.js.map +1 -0
  55. package/dist/adapters/HttpRuntimeAdapter.d.ts +58 -0
  56. package/dist/adapters/HttpRuntimeAdapter.js +152 -0
  57. package/dist/adapters/HttpRuntimeAdapter.js.map +1 -0
  58. package/dist/adapters/NodeJsRuntimeAdapter.d.ts +23 -0
  59. package/dist/adapters/NodeJsRuntimeAdapter.js +67 -0
  60. package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -0
  61. package/dist/adapters/RuntimeAdapter.d.ts +42 -0
  62. package/dist/adapters/RuntimeAdapter.js +2 -0
  63. package/dist/adapters/RuntimeAdapter.js.map +1 -0
  64. package/dist/adapters/WasmRuntimeAdapter.d.ts +69 -0
  65. package/dist/adapters/WasmRuntimeAdapter.js +279 -0
  66. package/dist/adapters/WasmRuntimeAdapter.js.map +1 -0
  67. package/dist/cache/NodeResultCache.d.ts +286 -0
  68. package/dist/cache/NodeResultCache.js +499 -0
  69. package/dist/cache/NodeResultCache.js.map +1 -0
  70. package/dist/cache/index.d.ts +1 -0
  71. package/dist/cache/index.js +2 -0
  72. package/dist/cache/index.js.map +1 -0
  73. package/dist/cost/CostEstimator.d.ts +57 -0
  74. package/dist/cost/CostEstimator.js +171 -0
  75. package/dist/cost/CostEstimator.js.map +1 -0
  76. package/dist/cost/index.d.ts +4 -0
  77. package/dist/cost/index.js +3 -0
  78. package/dist/cost/index.js.map +1 -0
  79. package/dist/cost/pricing.d.ts +24 -0
  80. package/dist/cost/pricing.js +169 -0
  81. package/dist/cost/pricing.js.map +1 -0
  82. package/dist/defineNode.d.ts +155 -0
  83. package/dist/defineNode.js +191 -0
  84. package/dist/defineNode.js.map +1 -0
  85. package/dist/graphql/GraphQLSchemaGenerator.d.ts +129 -0
  86. package/dist/graphql/GraphQLSchemaGenerator.js +425 -0
  87. package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -0
  88. package/dist/hmr/FileWatcher.d.ts +62 -0
  89. package/dist/hmr/FileWatcher.js +185 -0
  90. package/dist/hmr/FileWatcher.js.map +1 -0
  91. package/dist/hmr/HmrDevConsole.d.ts +13 -0
  92. package/dist/hmr/HmrDevConsole.js +46 -0
  93. package/dist/hmr/HmrDevConsole.js.map +1 -0
  94. package/dist/hmr/HotReloadManager.d.ts +84 -0
  95. package/dist/hmr/HotReloadManager.js +195 -0
  96. package/dist/hmr/HotReloadManager.js.map +1 -0
  97. package/dist/hmr/index.d.ts +39 -0
  98. package/dist/hmr/index.js +38 -0
  99. package/dist/hmr/index.js.map +1 -0
  100. package/dist/index.d.ts +107 -0
  101. package/dist/index.js +107 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/integrations/APMIntegration.d.ts +141 -0
  104. package/dist/integrations/APMIntegration.js +212 -0
  105. package/dist/integrations/APMIntegration.js.map +1 -0
  106. package/dist/integrations/AzureMonitorIntegration.d.ts +118 -0
  107. package/dist/integrations/AzureMonitorIntegration.js +254 -0
  108. package/dist/integrations/AzureMonitorIntegration.js.map +1 -0
  109. package/dist/integrations/CloudWatchIntegration.d.ts +135 -0
  110. package/dist/integrations/CloudWatchIntegration.js +293 -0
  111. package/dist/integrations/CloudWatchIntegration.js.map +1 -0
  112. package/dist/integrations/SentryIntegration.d.ts +153 -0
  113. package/dist/integrations/SentryIntegration.js +200 -0
  114. package/dist/integrations/SentryIntegration.js.map +1 -0
  115. package/dist/integrations/index.d.ts +19 -0
  116. package/dist/integrations/index.js +16 -0
  117. package/dist/integrations/index.js.map +1 -0
  118. package/dist/marketplace/RuntimeAutoScaler.d.ts +148 -0
  119. package/dist/marketplace/RuntimeAutoScaler.js +366 -0
  120. package/dist/marketplace/RuntimeAutoScaler.js.map +1 -0
  121. package/dist/marketplace/RuntimeCatalog.d.ts +174 -0
  122. package/dist/marketplace/RuntimeCatalog.js +339 -0
  123. package/dist/marketplace/RuntimeCatalog.js.map +1 -0
  124. package/dist/marketplace/RuntimeDiscovery.d.ts +86 -0
  125. package/dist/marketplace/RuntimeDiscovery.js +219 -0
  126. package/dist/marketplace/RuntimeDiscovery.js.map +1 -0
  127. package/dist/marketplace/RuntimeHealthMonitor.d.ts +100 -0
  128. package/dist/marketplace/RuntimeHealthMonitor.js +241 -0
  129. package/dist/marketplace/RuntimeHealthMonitor.js.map +1 -0
  130. package/dist/marketplace/RuntimeMetricsDashboard.d.ts +113 -0
  131. package/dist/marketplace/RuntimeMetricsDashboard.js +293 -0
  132. package/dist/marketplace/RuntimeMetricsDashboard.js.map +1 -0
  133. package/dist/monitoring/CircuitBreaker.d.ts +107 -0
  134. package/dist/monitoring/CircuitBreaker.js +238 -0
  135. package/dist/monitoring/CircuitBreaker.js.map +1 -0
  136. package/dist/monitoring/DistributedTracer.d.ts +125 -0
  137. package/dist/monitoring/DistributedTracer.js +230 -0
  138. package/dist/monitoring/DistributedTracer.js.map +1 -0
  139. package/dist/monitoring/HealthCheck.d.ts +54 -0
  140. package/dist/monitoring/HealthCheck.js +102 -0
  141. package/dist/monitoring/HealthCheck.js.map +1 -0
  142. package/dist/monitoring/PerformanceProfiler.d.ts +63 -0
  143. package/dist/monitoring/PerformanceProfiler.js +229 -0
  144. package/dist/monitoring/PerformanceProfiler.js.map +1 -0
  145. package/dist/monitoring/PrometheusBootstrap.d.ts +30 -0
  146. package/dist/monitoring/PrometheusBootstrap.js +71 -0
  147. package/dist/monitoring/PrometheusBootstrap.js.map +1 -0
  148. package/dist/monitoring/PrometheusMetricsBridge.d.ts +60 -0
  149. package/dist/monitoring/PrometheusMetricsBridge.js +216 -0
  150. package/dist/monitoring/PrometheusMetricsBridge.js.map +1 -0
  151. package/dist/monitoring/RateLimiter.d.ts +58 -0
  152. package/dist/monitoring/RateLimiter.js +128 -0
  153. package/dist/monitoring/RateLimiter.js.map +1 -0
  154. package/dist/monitoring/StructuredLogger.d.ts +131 -0
  155. package/dist/monitoring/StructuredLogger.js +207 -0
  156. package/dist/monitoring/StructuredLogger.js.map +1 -0
  157. package/dist/monitoring/TracingBootstrap.d.ts +69 -0
  158. package/dist/monitoring/TracingBootstrap.js +129 -0
  159. package/dist/monitoring/TracingBootstrap.js.map +1 -0
  160. package/dist/monitoring/TriggerMetricsCollector.d.ts +94 -0
  161. package/dist/monitoring/TriggerMetricsCollector.js +174 -0
  162. package/dist/monitoring/TriggerMetricsCollector.js.map +1 -0
  163. package/dist/monitoring/index.d.ts +9 -0
  164. package/dist/monitoring/index.js +10 -0
  165. package/dist/monitoring/index.js.map +1 -0
  166. package/dist/openapi/OpenAPIGenerator.d.ts +192 -0
  167. package/dist/openapi/OpenAPIGenerator.js +373 -0
  168. package/dist/openapi/OpenAPIGenerator.js.map +1 -0
  169. package/dist/openapi/index.d.ts +20 -0
  170. package/dist/openapi/index.js +20 -0
  171. package/dist/openapi/index.js.map +1 -0
  172. package/dist/security/ABAC.d.ts +224 -0
  173. package/dist/security/ABAC.js +380 -0
  174. package/dist/security/ABAC.js.map +1 -0
  175. package/dist/security/AuditLogger.d.ts +242 -0
  176. package/dist/security/AuditLogger.js +317 -0
  177. package/dist/security/AuditLogger.js.map +1 -0
  178. package/dist/security/AuthMiddleware.d.ts +163 -0
  179. package/dist/security/AuthMiddleware.js +274 -0
  180. package/dist/security/AuthMiddleware.js.map +1 -0
  181. package/dist/security/EncryptionAtRest.d.ts +206 -0
  182. package/dist/security/EncryptionAtRest.js +236 -0
  183. package/dist/security/EncryptionAtRest.js.map +1 -0
  184. package/dist/security/OAuthProvider.d.ts +334 -0
  185. package/dist/security/OAuthProvider.js +719 -0
  186. package/dist/security/OAuthProvider.js.map +1 -0
  187. package/dist/security/PIIDetector.d.ts +233 -0
  188. package/dist/security/PIIDetector.js +354 -0
  189. package/dist/security/PIIDetector.js.map +1 -0
  190. package/dist/security/RBAC.d.ts +143 -0
  191. package/dist/security/RBAC.js +285 -0
  192. package/dist/security/RBAC.js.map +1 -0
  193. package/dist/security/SecretManager.d.ts +652 -0
  194. package/dist/security/SecretManager.js +1146 -0
  195. package/dist/security/SecretManager.js.map +1 -0
  196. package/dist/security/TLSConfig.d.ts +305 -0
  197. package/dist/security/TLSConfig.js +550 -0
  198. package/dist/security/TLSConfig.js.map +1 -0
  199. package/dist/security/index.d.ts +79 -0
  200. package/dist/security/index.js +80 -0
  201. package/dist/security/index.js.map +1 -0
  202. package/dist/testing/TestHarness.d.ts +189 -0
  203. package/dist/testing/TestHarness.js +272 -0
  204. package/dist/testing/TestHarness.js.map +1 -0
  205. package/dist/testing/TestLogger.d.ts +103 -0
  206. package/dist/testing/TestLogger.js +153 -0
  207. package/dist/testing/TestLogger.js.map +1 -0
  208. package/dist/testing/WorkflowTestRunner.d.ts +172 -0
  209. package/dist/testing/WorkflowTestRunner.js +355 -0
  210. package/dist/testing/WorkflowTestRunner.js.map +1 -0
  211. package/dist/testing/index.d.ts +21 -0
  212. package/dist/testing/index.js +22 -0
  213. package/dist/testing/index.js.map +1 -0
  214. package/dist/tracing/InMemoryRunStore.d.ts +44 -0
  215. package/dist/tracing/InMemoryRunStore.js +341 -0
  216. package/dist/tracing/InMemoryRunStore.js.map +1 -0
  217. package/dist/tracing/PostgresRunStore.d.ts +82 -0
  218. package/dist/tracing/PostgresRunStore.js +640 -0
  219. package/dist/tracing/PostgresRunStore.js.map +1 -0
  220. package/dist/tracing/RunStore.d.ts +38 -0
  221. package/dist/tracing/RunStore.js +2 -0
  222. package/dist/tracing/RunStore.js.map +1 -0
  223. package/dist/tracing/RunTracker.d.ts +75 -0
  224. package/dist/tracing/RunTracker.js +374 -0
  225. package/dist/tracing/RunTracker.js.map +1 -0
  226. package/dist/tracing/SqliteRunStore.d.ts +53 -0
  227. package/dist/tracing/SqliteRunStore.js +703 -0
  228. package/dist/tracing/SqliteRunStore.js.map +1 -0
  229. package/dist/tracing/TraceRouter.d.ts +47 -0
  230. package/dist/tracing/TraceRouter.js +904 -0
  231. package/dist/tracing/TraceRouter.js.map +1 -0
  232. package/dist/tracing/TracingLogger.d.ts +21 -0
  233. package/dist/tracing/TracingLogger.js +62 -0
  234. package/dist/tracing/TracingLogger.js.map +1 -0
  235. package/dist/tracing/createStore.d.ts +30 -0
  236. package/dist/tracing/createStore.js +75 -0
  237. package/dist/tracing/createStore.js.map +1 -0
  238. package/dist/tracing/index.d.ts +13 -0
  239. package/dist/tracing/index.js +9 -0
  240. package/dist/tracing/index.js.map +1 -0
  241. package/dist/tracing/sanitize.d.ts +7 -0
  242. package/dist/tracing/sanitize.js +95 -0
  243. package/dist/tracing/sanitize.js.map +1 -0
  244. package/dist/tracing/types.d.ts +178 -0
  245. package/dist/tracing/types.js +3 -0
  246. package/dist/tracing/types.js.map +1 -0
  247. package/dist/types/Average.d.ts +11 -0
  248. package/dist/types/Average.js +2 -0
  249. package/dist/types/Average.js.map +1 -0
  250. package/dist/types/Condition.d.ts +8 -0
  251. package/dist/types/Condition.js +2 -0
  252. package/dist/types/Condition.js.map +1 -0
  253. package/dist/types/Conditions.d.ts +5 -0
  254. package/dist/types/Conditions.js +2 -0
  255. package/dist/types/Conditions.js.map +1 -0
  256. package/dist/types/Config.d.ts +12 -0
  257. package/dist/types/Config.js +2 -0
  258. package/dist/types/Config.js.map +1 -0
  259. package/dist/types/Flow.d.ts +5 -0
  260. package/dist/types/Flow.js +2 -0
  261. package/dist/types/Flow.js.map +1 -0
  262. package/dist/types/GlobalOptions.d.ts +11 -0
  263. package/dist/types/GlobalOptions.js +2 -0
  264. package/dist/types/GlobalOptions.js.map +1 -0
  265. package/dist/types/Inputs.d.ts +5 -0
  266. package/dist/types/Inputs.js +2 -0
  267. package/dist/types/Inputs.js.map +1 -0
  268. package/dist/types/JsonLikeObject.d.ts +3 -0
  269. package/dist/types/JsonLikeObject.js +2 -0
  270. package/dist/types/JsonLikeObject.js.map +1 -0
  271. package/dist/types/Mapper.d.ts +5 -0
  272. package/dist/types/Mapper.js +2 -0
  273. package/dist/types/Mapper.js.map +1 -0
  274. package/dist/types/Node.d.ts +10 -0
  275. package/dist/types/Node.js +2 -0
  276. package/dist/types/Node.js.map +1 -0
  277. package/dist/types/ParamsDictionary.d.ts +3 -0
  278. package/dist/types/ParamsDictionary.js +2 -0
  279. package/dist/types/ParamsDictionary.js.map +1 -0
  280. package/dist/types/Properties.d.ts +5 -0
  281. package/dist/types/Properties.js +2 -0
  282. package/dist/types/Properties.js.map +1 -0
  283. package/dist/types/Targets.d.ts +5 -0
  284. package/dist/types/Targets.js +2 -0
  285. package/dist/types/Targets.js.map +1 -0
  286. package/dist/types/Trigger.d.ts +5 -0
  287. package/dist/types/Trigger.js +2 -0
  288. package/dist/types/Trigger.js.map +1 -0
  289. package/dist/types/TriggerHttp.d.ts +7 -0
  290. package/dist/types/TriggerHttp.js +2 -0
  291. package/dist/types/TriggerHttp.js.map +1 -0
  292. package/dist/types/TriggerResponse.d.ts +6 -0
  293. package/dist/types/TriggerResponse.js +2 -0
  294. package/dist/types/TriggerResponse.js.map +1 -0
  295. package/dist/types/Triggers.d.ts +5 -0
  296. package/dist/types/Triggers.js +2 -0
  297. package/dist/types/Triggers.js.map +1 -0
  298. package/dist/types/TryCatch.d.ts +6 -0
  299. package/dist/types/TryCatch.js +2 -0
  300. package/dist/types/TryCatch.js.map +1 -0
  301. package/dist/visualization/NodeDependencyGraph.d.ts +76 -0
  302. package/dist/visualization/NodeDependencyGraph.js +418 -0
  303. package/dist/visualization/NodeDependencyGraph.js.map +1 -0
  304. package/dist/visualization/WorkflowVisualizer.d.ts +144 -0
  305. package/dist/visualization/WorkflowVisualizer.js +446 -0
  306. package/dist/visualization/WorkflowVisualizer.js.map +1 -0
  307. package/package.json +95 -0
@@ -0,0 +1,1146 @@
1
+ /**
2
+ * Secret Management for Blok Framework
3
+ *
4
+ * Provides a unified interface for secret management across multiple providers:
5
+ * - HashiCorp Vault (KV v2 engine via REST API)
6
+ * - AWS Secrets Manager (via @aws-sdk/client-secrets-manager)
7
+ * - GCP Secret Manager (via @google-cloud/secret-manager)
8
+ * - Environment Variables (process.env)
9
+ * - In-Memory (for testing)
10
+ *
11
+ * Features:
12
+ * - Provider chain: try providers in order, first match wins
13
+ * - Caching layer with TTL and max size (LRU eviction)
14
+ * - Audit event emission for secret access tracking
15
+ * - Template resolution for `${secret:KEY}` patterns
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * import {
20
+ * SecretManager,
21
+ * EnvironmentSecretProvider,
22
+ * InMemorySecretProvider,
23
+ * } from "@blokjs/runner";
24
+ *
25
+ * // Simple setup with environment variables
26
+ * const secrets = new SecretManager({
27
+ * providers: [
28
+ * { type: "environment", config: { prefix: "BLOK_SECRET_" } },
29
+ * ],
30
+ * cache: { enabled: true, ttlMs: 60_000, maxSize: 100 },
31
+ * auditLog: true,
32
+ * });
33
+ *
34
+ * const dbPassword = await secrets.getSecret("DB_PASSWORD");
35
+ * const connStr = await secrets.resolveTemplate(
36
+ * "postgres://user:${secret:DB_PASSWORD}@host/db"
37
+ * );
38
+ * ```
39
+ */
40
+ import { EventEmitter } from "node:events";
41
+ // ---------------------------------------------------------------------------
42
+ // EnvironmentSecretProvider
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Secret provider backed by process.env
46
+ *
47
+ * Reads environment variables, optionally with a prefix. Supports
48
+ * case-insensitive lookups when configured.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const provider = new EnvironmentSecretProvider({ prefix: "APP_" });
53
+ * // Reads process.env.APP_DATABASE_URL
54
+ * const dbUrl = await provider.get("DATABASE_URL");
55
+ * ```
56
+ */
57
+ export class EnvironmentSecretProvider {
58
+ name = "environment";
59
+ prefix;
60
+ caseSensitive;
61
+ constructor(config) {
62
+ this.prefix = config?.prefix ?? "";
63
+ this.caseSensitive = config?.caseSensitive ?? true;
64
+ }
65
+ /**
66
+ * Retrieve an environment variable value
67
+ * @param key - Variable name (without prefix)
68
+ */
69
+ async get(key) {
70
+ const envKey = this.resolveKey(key);
71
+ const value = process.env[envKey];
72
+ return value !== undefined ? value : null;
73
+ }
74
+ /**
75
+ * Set an environment variable (primarily useful for testing)
76
+ * @param key - Variable name (without prefix)
77
+ * @param value - Value to set
78
+ */
79
+ async set(key, value, _metadata) {
80
+ const envKey = this.resolveKey(key);
81
+ process.env[envKey] = value;
82
+ }
83
+ /**
84
+ * Delete an environment variable
85
+ * @param key - Variable name (without prefix)
86
+ */
87
+ async delete(key) {
88
+ const envKey = this.resolveKey(key);
89
+ delete process.env[envKey];
90
+ }
91
+ /**
92
+ * List environment variable names matching the configured prefix
93
+ * @param prefix - Additional prefix to filter by (applied after the provider prefix)
94
+ */
95
+ async list(prefix) {
96
+ const fullPrefix = this.prefix + (prefix ?? "");
97
+ const keys = Object.keys(process.env).filter((k) => {
98
+ const candidate = this.caseSensitive ? k : k.toUpperCase();
99
+ const match = this.caseSensitive ? fullPrefix : fullPrefix.toUpperCase();
100
+ return candidate.startsWith(match);
101
+ });
102
+ // Strip the provider prefix from returned keys
103
+ return keys.map((k) => k.slice(this.prefix.length));
104
+ }
105
+ /**
106
+ * Check whether an environment variable exists
107
+ * @param key - Variable name (without prefix)
108
+ */
109
+ async exists(key) {
110
+ const envKey = this.resolveKey(key);
111
+ return envKey in process.env;
112
+ }
113
+ /**
114
+ * Build the full environment variable name from a logical key
115
+ */
116
+ resolveKey(key) {
117
+ const fullKey = this.prefix + key;
118
+ if (this.caseSensitive) {
119
+ return fullKey;
120
+ }
121
+ // For case-insensitive mode, find the matching key in process.env
122
+ const upper = fullKey.toUpperCase();
123
+ const match = Object.keys(process.env).find((k) => k.toUpperCase() === upper);
124
+ return match ?? fullKey;
125
+ }
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // InMemorySecretProvider
129
+ // ---------------------------------------------------------------------------
130
+ /**
131
+ * In-memory secret provider for testing and development
132
+ *
133
+ * Stores secrets in a Map with full CRUD support. Provides stats
134
+ * for debugging and verification.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const provider = new InMemorySecretProvider();
139
+ * await provider.set("API_KEY", "test-key-123");
140
+ * const key = await provider.get("API_KEY"); // "test-key-123"
141
+ * console.log(provider.getStats()); // { size: 1, keys: ["API_KEY"] }
142
+ * ```
143
+ */
144
+ export class InMemorySecretProvider {
145
+ name = "memory";
146
+ store = new Map();
147
+ /**
148
+ * Retrieve a secret from the in-memory store
149
+ * @param key - The secret key
150
+ */
151
+ async get(key) {
152
+ const entry = this.store.get(key);
153
+ if (!entry)
154
+ return null;
155
+ // Check expiration
156
+ if (entry.metadata?.expiresAt && entry.metadata.expiresAt < Date.now()) {
157
+ this.store.delete(key);
158
+ return null;
159
+ }
160
+ return entry.value;
161
+ }
162
+ /**
163
+ * Store a secret in the in-memory store
164
+ * @param key - The secret key
165
+ * @param value - The secret value
166
+ * @param metadata - Optional metadata
167
+ */
168
+ async set(key, value, metadata) {
169
+ this.store.set(key, { value, metadata });
170
+ }
171
+ /**
172
+ * Delete a secret from the in-memory store
173
+ * @param key - The secret key
174
+ */
175
+ async delete(key) {
176
+ this.store.delete(key);
177
+ }
178
+ /**
179
+ * List all secret keys, optionally filtered by prefix
180
+ * @param prefix - Optional prefix filter
181
+ */
182
+ async list(prefix) {
183
+ const keys = Array.from(this.store.keys());
184
+ if (!prefix)
185
+ return keys;
186
+ return keys.filter((k) => k.startsWith(prefix));
187
+ }
188
+ /**
189
+ * Check whether a secret exists in the store
190
+ * @param key - The secret key
191
+ */
192
+ async exists(key) {
193
+ if (!this.store.has(key))
194
+ return false;
195
+ // Check expiration
196
+ const entry = this.store.get(key);
197
+ if (entry.metadata?.expiresAt && entry.metadata.expiresAt < Date.now()) {
198
+ this.store.delete(key);
199
+ return false;
200
+ }
201
+ return true;
202
+ }
203
+ /**
204
+ * Get debug statistics about the in-memory store
205
+ * @returns Object with size and list of keys
206
+ */
207
+ getStats() {
208
+ return {
209
+ size: this.store.size,
210
+ keys: Array.from(this.store.keys()),
211
+ };
212
+ }
213
+ /**
214
+ * Clear all secrets from the store
215
+ */
216
+ clear() {
217
+ this.store.clear();
218
+ }
219
+ }
220
+ // ---------------------------------------------------------------------------
221
+ // VaultSecretProvider
222
+ // ---------------------------------------------------------------------------
223
+ /**
224
+ * HashiCorp Vault secret provider (KV v2 engine)
225
+ *
226
+ * Communicates with Vault via its HTTP REST API using the native `fetch` API.
227
+ * Supports token-based authentication, namespaces, and configurable mount paths.
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const vault = new VaultSecretProvider({
232
+ * address: "https://vault.example.com:8200",
233
+ * token: process.env.VAULT_TOKEN,
234
+ * mountPath: "secret",
235
+ * });
236
+ *
237
+ * const dbPassword = await vault.get("database/credentials");
238
+ * ```
239
+ */
240
+ export class VaultSecretProvider {
241
+ name = "vault";
242
+ address;
243
+ token;
244
+ namespace;
245
+ mountPath;
246
+ apiVersion;
247
+ constructor(config) {
248
+ this.address = config.address.replace(/\/+$/, "");
249
+ this.token = config.token ?? "";
250
+ this.namespace = config.namespace;
251
+ this.mountPath = config.mountPath ?? "secret";
252
+ this.apiVersion = config.apiVersion ?? "v1";
253
+ }
254
+ /**
255
+ * Read a secret from Vault KV v2
256
+ * @param key - The secret path within the mount
257
+ */
258
+ async get(key) {
259
+ const url = this.buildUrl("data", key);
260
+ const response = await fetch(url, {
261
+ method: "GET",
262
+ headers: this.buildHeaders(),
263
+ });
264
+ if (response.status === 404)
265
+ return null;
266
+ if (!response.ok) {
267
+ const body = await response.text();
268
+ throw new Error(`Vault GET failed (${response.status}): ${body}`);
269
+ }
270
+ const json = (await response.json());
271
+ // KV v2 nests the actual data under data.data
272
+ const value = json.data?.data?.value;
273
+ if (typeof value === "string")
274
+ return value;
275
+ // If the secret has multiple fields, return as JSON
276
+ if (json.data?.data && typeof json.data.data === "object") {
277
+ return JSON.stringify(json.data.data);
278
+ }
279
+ return null;
280
+ }
281
+ /**
282
+ * Write a secret to Vault KV v2
283
+ * @param key - The secret path within the mount
284
+ * @param value - The secret value
285
+ * @param metadata - Optional metadata (stored as custom_metadata)
286
+ */
287
+ async set(key, value, metadata) {
288
+ const url = this.buildUrl("data", key);
289
+ const body = {
290
+ data: { value },
291
+ };
292
+ if (metadata) {
293
+ body.options = {};
294
+ if (metadata.version) {
295
+ body.options.cas = Number.parseInt(metadata.version, 10);
296
+ }
297
+ }
298
+ const response = await fetch(url, {
299
+ method: "POST",
300
+ headers: this.buildHeaders(),
301
+ body: JSON.stringify(body),
302
+ });
303
+ if (!response.ok) {
304
+ const responseBody = await response.text();
305
+ throw new Error(`Vault POST failed (${response.status}): ${responseBody}`);
306
+ }
307
+ // Set custom metadata if provided
308
+ if (metadata?.tags || metadata?.description) {
309
+ await this.setMetadata(key, metadata);
310
+ }
311
+ }
312
+ /**
313
+ * Delete a secret from Vault KV v2
314
+ * @param key - The secret path within the mount
315
+ */
316
+ async delete(key) {
317
+ const url = this.buildUrl("metadata", key);
318
+ const response = await fetch(url, {
319
+ method: "DELETE",
320
+ headers: this.buildHeaders(),
321
+ });
322
+ if (!response.ok && response.status !== 404) {
323
+ const body = await response.text();
324
+ throw new Error(`Vault DELETE failed (${response.status}): ${body}`);
325
+ }
326
+ }
327
+ /**
328
+ * List secret keys under a given path prefix
329
+ * @param prefix - Optional path prefix
330
+ */
331
+ async list(prefix) {
332
+ const path = prefix ?? "";
333
+ const url = this.buildUrl("metadata", path) + "?list=true";
334
+ const response = await fetch(url, {
335
+ method: "LIST",
336
+ headers: this.buildHeaders(),
337
+ });
338
+ if (response.status === 404)
339
+ return [];
340
+ if (!response.ok) {
341
+ const body = await response.text();
342
+ throw new Error(`Vault LIST failed (${response.status}): ${body}`);
343
+ }
344
+ const json = (await response.json());
345
+ return json.data?.keys ?? [];
346
+ }
347
+ /**
348
+ * Check whether a secret exists in Vault
349
+ * @param key - The secret path within the mount
350
+ */
351
+ async exists(key) {
352
+ const url = this.buildUrl("data", key);
353
+ const response = await fetch(url, {
354
+ method: "GET",
355
+ headers: this.buildHeaders(),
356
+ });
357
+ return response.ok;
358
+ }
359
+ /**
360
+ * Update the Vault token (e.g., after token renewal)
361
+ * @param token - The new Vault token
362
+ */
363
+ setToken(token) {
364
+ this.token = token;
365
+ }
366
+ /**
367
+ * Build the full URL for a Vault KV v2 API call
368
+ */
369
+ buildUrl(operation, path) {
370
+ const cleanPath = path.replace(/^\/+|\/+$/g, "");
371
+ return `${this.address}/${this.apiVersion}/${this.mountPath}/${operation}/${cleanPath}`;
372
+ }
373
+ /**
374
+ * Build common HTTP headers for Vault requests
375
+ */
376
+ buildHeaders() {
377
+ const headers = {
378
+ "Content-Type": "application/json",
379
+ };
380
+ if (this.token) {
381
+ headers["X-Vault-Token"] = this.token;
382
+ }
383
+ if (this.namespace) {
384
+ headers["X-Vault-Namespace"] = this.namespace;
385
+ }
386
+ return headers;
387
+ }
388
+ /**
389
+ * Set custom metadata on a secret in Vault KV v2
390
+ */
391
+ async setMetadata(key, metadata) {
392
+ const url = this.buildUrl("metadata", key);
393
+ const body = {
394
+ custom_metadata: {
395
+ ...metadata.tags,
396
+ ...(metadata.description ? { description: metadata.description } : {}),
397
+ },
398
+ };
399
+ const response = await fetch(url, {
400
+ method: "POST",
401
+ headers: this.buildHeaders(),
402
+ body: JSON.stringify(body),
403
+ });
404
+ if (!response.ok) {
405
+ // Non-fatal: metadata update failure should not break the set operation
406
+ const responseBody = await response.text();
407
+ console.warn(`Vault metadata update failed (${response.status}): ${responseBody}`);
408
+ }
409
+ }
410
+ }
411
+ // ---------------------------------------------------------------------------
412
+ // AWSSecretsProvider
413
+ // ---------------------------------------------------------------------------
414
+ /**
415
+ * AWS Secrets Manager provider
416
+ *
417
+ * Uses the `@aws-sdk/client-secrets-manager` SDK, loaded dynamically at
418
+ * first use to avoid hard dependencies.
419
+ *
420
+ * @example
421
+ * ```typescript
422
+ * const aws = new AWSSecretsProvider({
423
+ * region: "us-east-1",
424
+ * });
425
+ *
426
+ * const apiKey = await aws.get("prod/api-key");
427
+ * ```
428
+ */
429
+ export class AWSSecretsProvider {
430
+ name = "aws";
431
+ region;
432
+ accessKeyId;
433
+ secretAccessKey;
434
+ profile;
435
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
436
+ client = null;
437
+ constructor(config) {
438
+ this.region = config.region;
439
+ this.accessKeyId = config.accessKeyId;
440
+ this.secretAccessKey = config.secretAccessKey;
441
+ this.profile = config.profile;
442
+ }
443
+ /**
444
+ * Retrieve a secret from AWS Secrets Manager
445
+ * @param key - The secret name or ARN
446
+ */
447
+ async get(key) {
448
+ const client = await this.getClient();
449
+ const { GetSecretValueCommand } = await this.getSDK();
450
+ try {
451
+ const result = await client.send(new GetSecretValueCommand({ SecretId: key }));
452
+ return result.SecretString ?? null;
453
+ }
454
+ catch (err) {
455
+ if (this.isAWSError(err, "ResourceNotFoundException")) {
456
+ return null;
457
+ }
458
+ throw err;
459
+ }
460
+ }
461
+ /**
462
+ * Create or update a secret in AWS Secrets Manager
463
+ * @param key - The secret name
464
+ * @param value - The secret value
465
+ * @param metadata - Optional metadata (tags and description supported)
466
+ */
467
+ async set(key, value, metadata) {
468
+ const client = await this.getClient();
469
+ const sdk = await this.getSDK();
470
+ // Try to update first, create if it does not exist
471
+ try {
472
+ await client.send(new sdk.UpdateSecretCommand({
473
+ SecretId: key,
474
+ SecretString: value,
475
+ ...(metadata?.description ? { Description: metadata.description } : {}),
476
+ }));
477
+ }
478
+ catch (err) {
479
+ if (this.isAWSError(err, "ResourceNotFoundException")) {
480
+ const createParams = {
481
+ Name: key,
482
+ SecretString: value,
483
+ };
484
+ if (metadata?.description) {
485
+ createParams.Description = metadata.description;
486
+ }
487
+ if (metadata?.tags) {
488
+ createParams.Tags = Object.entries(metadata.tags).map(([Key, Value]) => ({
489
+ Key,
490
+ Value,
491
+ }));
492
+ }
493
+ await client.send(new sdk.CreateSecretCommand(createParams));
494
+ }
495
+ else {
496
+ throw err;
497
+ }
498
+ }
499
+ }
500
+ /**
501
+ * Delete a secret from AWS Secrets Manager
502
+ * @param key - The secret name or ARN
503
+ */
504
+ async delete(key) {
505
+ const client = await this.getClient();
506
+ const { DeleteSecretCommand } = await this.getSDK();
507
+ try {
508
+ await client.send(new DeleteSecretCommand({
509
+ SecretId: key,
510
+ ForceDeleteWithoutRecovery: true,
511
+ }));
512
+ }
513
+ catch (err) {
514
+ if (!this.isAWSError(err, "ResourceNotFoundException")) {
515
+ throw err;
516
+ }
517
+ }
518
+ }
519
+ /**
520
+ * List secrets in AWS Secrets Manager, optionally filtered by name prefix
521
+ * @param prefix - Optional name prefix filter
522
+ */
523
+ async list(prefix) {
524
+ const client = await this.getClient();
525
+ const { ListSecretsCommand } = await this.getSDK();
526
+ const secrets = [];
527
+ let nextToken;
528
+ do {
529
+ const params = {
530
+ MaxResults: 100,
531
+ ...(nextToken ? { NextToken: nextToken } : {}),
532
+ };
533
+ if (prefix) {
534
+ params.Filters = [{ Key: "name", Values: [prefix] }];
535
+ }
536
+ const result = await client.send(new ListSecretsCommand(params));
537
+ if (result.SecretList) {
538
+ for (const secret of result.SecretList) {
539
+ if (secret.Name) {
540
+ secrets.push(secret.Name);
541
+ }
542
+ }
543
+ }
544
+ nextToken = result.NextToken;
545
+ } while (nextToken);
546
+ return secrets;
547
+ }
548
+ /**
549
+ * Check whether a secret exists in AWS Secrets Manager
550
+ * @param key - The secret name or ARN
551
+ */
552
+ async exists(key) {
553
+ const client = await this.getClient();
554
+ const { DescribeSecretCommand } = await this.getSDK();
555
+ try {
556
+ await client.send(new DescribeSecretCommand({ SecretId: key }));
557
+ return true;
558
+ }
559
+ catch (err) {
560
+ if (this.isAWSError(err, "ResourceNotFoundException")) {
561
+ return false;
562
+ }
563
+ throw err;
564
+ }
565
+ }
566
+ /**
567
+ * Lazily initialize and cache the AWS SecretsManager client
568
+ */
569
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
570
+ async getClient() {
571
+ if (this.client)
572
+ return this.client;
573
+ const { SecretsManagerClient } = await this.getSDK();
574
+ const clientConfig = {
575
+ region: this.region,
576
+ };
577
+ if (this.accessKeyId && this.secretAccessKey) {
578
+ clientConfig.credentials = {
579
+ accessKeyId: this.accessKeyId,
580
+ secretAccessKey: this.secretAccessKey,
581
+ };
582
+ }
583
+ if (this.profile) {
584
+ // When a profile is specified, set the AWS_PROFILE env var so
585
+ // the SDK default credential chain picks it up.
586
+ process.env.AWS_PROFILE = this.profile;
587
+ }
588
+ this.client = new SecretsManagerClient(clientConfig);
589
+ return this.client;
590
+ }
591
+ /**
592
+ * Dynamically import the AWS Secrets Manager SDK
593
+ */
594
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
595
+ async getSDK() {
596
+ try {
597
+ // @ts-ignore -- optional peer dependency, loaded dynamically at runtime
598
+ return await import("@aws-sdk/client-secrets-manager");
599
+ }
600
+ catch {
601
+ throw new Error("AWS Secrets Manager SDK not found. Install it with: npm install @aws-sdk/client-secrets-manager");
602
+ }
603
+ }
604
+ /**
605
+ * Type-safe check for AWS SDK error names
606
+ */
607
+ isAWSError(err, code) {
608
+ return typeof err === "object" && err !== null && "name" in err && err.name === code;
609
+ }
610
+ }
611
+ // ---------------------------------------------------------------------------
612
+ // GCPSecretProvider
613
+ // ---------------------------------------------------------------------------
614
+ /**
615
+ * Google Cloud Secret Manager provider
616
+ *
617
+ * Uses the `@google-cloud/secret-manager` SDK, loaded dynamically at
618
+ * first use to avoid hard dependencies.
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * const gcp = new GCPSecretProvider({
623
+ * projectId: "my-project",
624
+ * });
625
+ *
626
+ * const apiKey = await gcp.get("api-key");
627
+ * ```
628
+ */
629
+ export class GCPSecretProvider {
630
+ name = "gcp";
631
+ projectId;
632
+ keyFile;
633
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
634
+ client = null;
635
+ constructor(config) {
636
+ this.projectId = config.projectId;
637
+ this.keyFile = config.keyFile;
638
+ }
639
+ /**
640
+ * Retrieve the latest version of a secret from GCP Secret Manager
641
+ * @param key - The secret ID
642
+ */
643
+ async get(key) {
644
+ const client = await this.getClient();
645
+ try {
646
+ const [version] = await client.accessSecretVersion({
647
+ name: `projects/${this.projectId}/secrets/${key}/versions/latest`,
648
+ });
649
+ const payload = version.payload?.data;
650
+ if (!payload)
651
+ return null;
652
+ if (typeof payload === "string")
653
+ return payload;
654
+ if (payload instanceof Uint8Array || Buffer.isBuffer(payload)) {
655
+ return Buffer.from(payload).toString("utf-8");
656
+ }
657
+ return null;
658
+ }
659
+ catch (err) {
660
+ if (this.isGCPNotFoundError(err)) {
661
+ return null;
662
+ }
663
+ throw err;
664
+ }
665
+ }
666
+ /**
667
+ * Create a secret and add a version, or add a new version to an existing secret
668
+ * @param key - The secret ID
669
+ * @param value - The secret value
670
+ * @param metadata - Optional metadata (tags mapped to GCP labels)
671
+ */
672
+ async set(key, value, metadata) {
673
+ const client = await this.getClient();
674
+ const parent = `projects/${this.projectId}`;
675
+ const secretName = `${parent}/secrets/${key}`;
676
+ // Try to create the secret resource first
677
+ try {
678
+ const createRequest = {
679
+ parent,
680
+ secretId: key,
681
+ secret: {
682
+ replication: { automatic: {} },
683
+ ...(metadata?.tags ? { labels: metadata.tags } : {}),
684
+ },
685
+ };
686
+ await client.createSecret(createRequest);
687
+ }
688
+ catch (err) {
689
+ // 6 = ALREADY_EXISTS - that is fine, we will add a version
690
+ if (!this.isGCPError(err, 6)) {
691
+ throw err;
692
+ }
693
+ }
694
+ // Add the secret version with the actual payload
695
+ await client.addSecretVersion({
696
+ parent: secretName,
697
+ payload: {
698
+ data: Buffer.from(value, "utf-8"),
699
+ },
700
+ });
701
+ }
702
+ /**
703
+ * Delete a secret from GCP Secret Manager
704
+ * @param key - The secret ID
705
+ */
706
+ async delete(key) {
707
+ const client = await this.getClient();
708
+ try {
709
+ await client.deleteSecret({
710
+ name: `projects/${this.projectId}/secrets/${key}`,
711
+ });
712
+ }
713
+ catch (err) {
714
+ if (!this.isGCPNotFoundError(err)) {
715
+ throw err;
716
+ }
717
+ }
718
+ }
719
+ /**
720
+ * List secrets in the GCP project, optionally filtered by prefix
721
+ * @param prefix - Optional prefix filter applied to secret IDs
722
+ */
723
+ async list(prefix) {
724
+ const client = await this.getClient();
725
+ const parent = `projects/${this.projectId}`;
726
+ const [secrets] = await client.listSecrets({ parent });
727
+ const names = [];
728
+ for (const secret of secrets) {
729
+ // Extract secret ID from the full resource name
730
+ const fullName = secret.name ?? "";
731
+ const parts = fullName.split("/");
732
+ const secretId = parts[parts.length - 1];
733
+ if (secretId) {
734
+ if (!prefix || secretId.startsWith(prefix)) {
735
+ names.push(secretId);
736
+ }
737
+ }
738
+ }
739
+ return names;
740
+ }
741
+ /**
742
+ * Check whether a secret exists in GCP Secret Manager
743
+ * @param key - The secret ID
744
+ */
745
+ async exists(key) {
746
+ const client = await this.getClient();
747
+ try {
748
+ await client.getSecret({
749
+ name: `projects/${this.projectId}/secrets/${key}`,
750
+ });
751
+ return true;
752
+ }
753
+ catch (err) {
754
+ if (this.isGCPNotFoundError(err)) {
755
+ return false;
756
+ }
757
+ throw err;
758
+ }
759
+ }
760
+ /**
761
+ * Lazily initialize and cache the GCP Secret Manager client
762
+ */
763
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
764
+ async getClient() {
765
+ if (this.client)
766
+ return this.client;
767
+ try {
768
+ // @ts-ignore -- optional peer dependency, loaded dynamically at runtime
769
+ const module = await import("@google-cloud/secret-manager");
770
+ const { SecretManagerServiceClient } = module;
771
+ const options = {};
772
+ if (this.keyFile) {
773
+ options.keyFilename = this.keyFile;
774
+ }
775
+ this.client = new SecretManagerServiceClient(options);
776
+ return this.client;
777
+ }
778
+ catch {
779
+ throw new Error("GCP Secret Manager SDK not found. Install it with: npm install @google-cloud/secret-manager");
780
+ }
781
+ }
782
+ /**
783
+ * Check for GCP "not found" errors (gRPC status code 5)
784
+ */
785
+ isGCPNotFoundError(err) {
786
+ return this.isGCPError(err, 5);
787
+ }
788
+ /**
789
+ * Check for specific gRPC error codes from the GCP SDK
790
+ */
791
+ isGCPError(err, code) {
792
+ return typeof err === "object" && err !== null && "code" in err && err.code === code;
793
+ }
794
+ }
795
+ // ---------------------------------------------------------------------------
796
+ // SecretManager
797
+ // ---------------------------------------------------------------------------
798
+ /** Regex for matching `${secret:KEY}` patterns in template strings */
799
+ const SECRET_TEMPLATE_REGEX = /\$\{secret:([^}]+)\}/g;
800
+ /**
801
+ * Unified Secret Manager for the Blok Framework
802
+ *
803
+ * Orchestrates multiple secret providers with a provider chain (first match
804
+ * wins), optional caching, and audit event emission.
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * const manager = new SecretManager({
809
+ * providers: [
810
+ * { type: "vault", config: { address: "https://vault:8200", token: "s.xxx" } },
811
+ * { type: "environment", config: { prefix: "BLOK_" } },
812
+ * ],
813
+ * cache: { enabled: true, ttlMs: 300_000, maxSize: 500 },
814
+ * auditLog: true,
815
+ * });
816
+ *
817
+ * manager.on("secretAccess", (event) => {
818
+ * console.log(`[audit] ${event.operation} ${event.key} via ${event.provider}`);
819
+ * });
820
+ *
821
+ * const password = await manager.getSecretOrThrow("DB_PASSWORD");
822
+ * const connStr = await manager.resolveTemplate(
823
+ * "postgres://admin:${secret:DB_PASSWORD}@db:5432/app"
824
+ * );
825
+ * ```
826
+ */
827
+ export class SecretManager extends EventEmitter {
828
+ providers = [];
829
+ cache = new Map();
830
+ cacheConfig;
831
+ auditLog;
832
+ cacheAccessOrder = [];
833
+ constructor(config) {
834
+ super();
835
+ this.cacheConfig = config.cache ?? { enabled: false, ttlMs: 0, maxSize: 0 };
836
+ this.auditLog = config.auditLog ?? false;
837
+ // Initialize providers in order
838
+ for (const providerConfig of config.providers) {
839
+ this.providers.push(this.createProvider(providerConfig));
840
+ }
841
+ }
842
+ /**
843
+ * Retrieve a secret value by key
844
+ *
845
+ * Checks the cache first (if enabled), then queries each provider
846
+ * in order until a value is found.
847
+ *
848
+ * @param key - The secret key
849
+ * @returns The secret value, or null if not found in any provider
850
+ */
851
+ async getSecret(key) {
852
+ // Check cache
853
+ if (this.cacheConfig.enabled) {
854
+ const cached = this.getCached(key);
855
+ if (cached !== undefined) {
856
+ this.emitAccess("get", key, "cache", true, true);
857
+ return cached;
858
+ }
859
+ }
860
+ // Query providers in order
861
+ for (const provider of this.providers) {
862
+ try {
863
+ const value = await provider.get(key);
864
+ if (value !== null) {
865
+ if (this.cacheConfig.enabled) {
866
+ this.setCache(key, value);
867
+ }
868
+ this.emitAccess("get", key, provider.name, true, false);
869
+ return value;
870
+ }
871
+ }
872
+ catch (err) {
873
+ this.emitAccess("get", key, provider.name, false, false, errorMessage(err));
874
+ // Continue to next provider
875
+ }
876
+ }
877
+ this.emitAccess("get", key, "none", true, false);
878
+ return null;
879
+ }
880
+ /**
881
+ * Retrieve a secret or throw if it does not exist
882
+ *
883
+ * @param key - The secret key
884
+ * @returns The secret value
885
+ * @throws Error if the secret is not found in any provider
886
+ */
887
+ async getSecretOrThrow(key) {
888
+ const value = await this.getSecret(key);
889
+ if (value === null) {
890
+ throw new Error(`Secret '${key}' not found in any provider`);
891
+ }
892
+ return value;
893
+ }
894
+ /**
895
+ * Store a secret value in the first writable provider
896
+ *
897
+ * @param key - The secret key
898
+ * @param value - The secret value
899
+ * @param metadata - Optional metadata to associate with the secret
900
+ */
901
+ async setSecret(key, value, metadata) {
902
+ let written = false;
903
+ for (const provider of this.providers) {
904
+ try {
905
+ await provider.set(key, value, metadata);
906
+ written = true;
907
+ // Update cache
908
+ if (this.cacheConfig.enabled) {
909
+ this.setCache(key, value);
910
+ }
911
+ this.emitAccess("set", key, provider.name, true, false);
912
+ break;
913
+ }
914
+ catch (err) {
915
+ this.emitAccess("set", key, provider.name, false, false, errorMessage(err));
916
+ // Continue to next provider
917
+ }
918
+ }
919
+ if (!written) {
920
+ throw new Error(`Failed to set secret '${key}' in any provider`);
921
+ }
922
+ }
923
+ /**
924
+ * Delete a secret from all providers that contain it
925
+ *
926
+ * @param key - The secret key
927
+ */
928
+ async deleteSecret(key) {
929
+ // Invalidate cache
930
+ this.cache.delete(key);
931
+ this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
932
+ for (const provider of this.providers) {
933
+ try {
934
+ await provider.delete(key);
935
+ this.emitAccess("delete", key, provider.name, true, false);
936
+ }
937
+ catch (err) {
938
+ this.emitAccess("delete", key, provider.name, false, false, errorMessage(err));
939
+ }
940
+ }
941
+ }
942
+ /**
943
+ * List secret keys across all providers, optionally filtered by prefix
944
+ *
945
+ * Merges results from all providers and deduplicates.
946
+ *
947
+ * @param prefix - Optional prefix filter
948
+ * @returns Deduplicated array of secret key names
949
+ */
950
+ async listSecrets(prefix) {
951
+ const allKeys = new Set();
952
+ for (const provider of this.providers) {
953
+ try {
954
+ const keys = await provider.list(prefix);
955
+ for (const key of keys) {
956
+ allKeys.add(key);
957
+ }
958
+ this.emitAccess("list", undefined, provider.name, true, false);
959
+ }
960
+ catch (err) {
961
+ this.emitAccess("list", undefined, provider.name, false, false, errorMessage(err));
962
+ }
963
+ }
964
+ return Array.from(allKeys);
965
+ }
966
+ /**
967
+ * Check whether a secret exists in any provider
968
+ *
969
+ * @param key - The secret key
970
+ * @returns True if the secret exists in at least one provider
971
+ */
972
+ async exists(key) {
973
+ // Check cache first
974
+ if (this.cacheConfig.enabled) {
975
+ const cached = this.getCached(key);
976
+ if (cached !== undefined) {
977
+ return true;
978
+ }
979
+ }
980
+ for (const provider of this.providers) {
981
+ try {
982
+ const found = await provider.exists(key);
983
+ if (found) {
984
+ this.emitAccess("exists", key, provider.name, true, false);
985
+ return true;
986
+ }
987
+ }
988
+ catch (err) {
989
+ this.emitAccess("exists", key, provider.name, false, false, errorMessage(err));
990
+ }
991
+ }
992
+ return false;
993
+ }
994
+ /**
995
+ * Resolve `${secret:KEY}` patterns in a template string
996
+ *
997
+ * Replaces every occurrence of `${secret:SOME_KEY}` with the actual
998
+ * secret value from the provider chain. Missing secrets are replaced
999
+ * with an empty string.
1000
+ *
1001
+ * @param template - The template string with `${secret:...}` placeholders
1002
+ * @returns The resolved string with secret values substituted
1003
+ *
1004
+ * @example
1005
+ * ```typescript
1006
+ * const resolved = await manager.resolveTemplate(
1007
+ * "mongodb://${secret:MONGO_USER}:${secret:MONGO_PASS}@host/db"
1008
+ * );
1009
+ * ```
1010
+ */
1011
+ async resolveTemplate(template) {
1012
+ const matches = [];
1013
+ let match;
1014
+ // Reset regex state
1015
+ SECRET_TEMPLATE_REGEX.lastIndex = 0;
1016
+ while ((match = SECRET_TEMPLATE_REGEX.exec(template)) !== null) {
1017
+ matches.push({ placeholder: match[0], key: match[1] });
1018
+ }
1019
+ if (matches.length === 0)
1020
+ return template;
1021
+ // Resolve all secrets in parallel
1022
+ const resolutions = await Promise.all(matches.map(async (m) => ({
1023
+ placeholder: m.placeholder,
1024
+ value: (await this.getSecret(m.key)) ?? "",
1025
+ })));
1026
+ let result = template;
1027
+ for (const resolution of resolutions) {
1028
+ result = result.split(resolution.placeholder).join(resolution.value);
1029
+ }
1030
+ return result;
1031
+ }
1032
+ /**
1033
+ * Get the list of configured providers
1034
+ * @returns Array of provider instances
1035
+ */
1036
+ getProviders() {
1037
+ return [...this.providers];
1038
+ }
1039
+ /**
1040
+ * Get current cache statistics
1041
+ * @returns Object with cache size and hit information
1042
+ */
1043
+ getCacheStats() {
1044
+ return {
1045
+ size: this.cache.size,
1046
+ maxSize: this.cacheConfig.maxSize,
1047
+ enabled: this.cacheConfig.enabled,
1048
+ };
1049
+ }
1050
+ /**
1051
+ * Clear the secret cache
1052
+ */
1053
+ clearCache() {
1054
+ this.cache.clear();
1055
+ this.cacheAccessOrder = [];
1056
+ }
1057
+ // -----------------------------------------------------------------------
1058
+ // Private helpers
1059
+ // -----------------------------------------------------------------------
1060
+ /**
1061
+ * Create a provider instance from its configuration
1062
+ */
1063
+ createProvider(config) {
1064
+ switch (config.type) {
1065
+ case "environment":
1066
+ return new EnvironmentSecretProvider(config.config);
1067
+ case "memory":
1068
+ return new InMemorySecretProvider();
1069
+ case "vault":
1070
+ return new VaultSecretProvider(config.config);
1071
+ case "aws":
1072
+ return new AWSSecretsProvider(config.config);
1073
+ case "gcp":
1074
+ return new GCPSecretProvider(config.config);
1075
+ default: {
1076
+ const exhaustive = config;
1077
+ throw new Error(`Unknown secret provider type: ${exhaustive.type}`);
1078
+ }
1079
+ }
1080
+ }
1081
+ /**
1082
+ * Retrieve a value from the cache, returning undefined if not found or expired
1083
+ */
1084
+ getCached(key) {
1085
+ const entry = this.cache.get(key);
1086
+ if (!entry)
1087
+ return undefined;
1088
+ if (Date.now() > entry.expiresAt) {
1089
+ this.cache.delete(key);
1090
+ this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
1091
+ return undefined;
1092
+ }
1093
+ // Move to end of access order (LRU)
1094
+ this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
1095
+ this.cacheAccessOrder.push(key);
1096
+ return entry.value;
1097
+ }
1098
+ /**
1099
+ * Store a value in the cache with TTL, evicting LRU entries if at capacity
1100
+ */
1101
+ setCache(key, value) {
1102
+ // Evict if at capacity
1103
+ while (this.cache.size >= this.cacheConfig.maxSize && this.cacheAccessOrder.length > 0) {
1104
+ const evict = this.cacheAccessOrder.shift();
1105
+ if (evict) {
1106
+ this.cache.delete(evict);
1107
+ }
1108
+ }
1109
+ this.cache.set(key, {
1110
+ value,
1111
+ expiresAt: Date.now() + this.cacheConfig.ttlMs,
1112
+ });
1113
+ // Update access order
1114
+ this.cacheAccessOrder = this.cacheAccessOrder.filter((k) => k !== key);
1115
+ this.cacheAccessOrder.push(key);
1116
+ }
1117
+ /**
1118
+ * Emit a secret access audit event
1119
+ */
1120
+ emitAccess(operation, key, provider, success, cached, error) {
1121
+ if (!this.auditLog)
1122
+ return;
1123
+ const event = {
1124
+ operation,
1125
+ key,
1126
+ provider,
1127
+ success,
1128
+ cached,
1129
+ timestamp: new Date().toISOString(),
1130
+ ...(error ? { error } : {}),
1131
+ };
1132
+ this.emit("secretAccess", event);
1133
+ }
1134
+ }
1135
+ // ---------------------------------------------------------------------------
1136
+ // Helpers
1137
+ // ---------------------------------------------------------------------------
1138
+ /**
1139
+ * Safely extract an error message from an unknown thrown value
1140
+ */
1141
+ function errorMessage(err) {
1142
+ if (err instanceof Error)
1143
+ return err.message;
1144
+ return String(err);
1145
+ }
1146
+ //# sourceMappingURL=SecretManager.js.map