@griffin-app/griffin-executor 0.1.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 (153) hide show
  1. package/README.md +152 -0
  2. package/dist/adapters/axios.d.ts +5 -0
  3. package/dist/adapters/axios.d.ts.map +1 -0
  4. package/dist/adapters/axios.js +36 -0
  5. package/dist/adapters/axios.js.map +1 -0
  6. package/dist/adapters/index.d.ts +3 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +3 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/adapters/stub.d.ts +22 -0
  11. package/dist/adapters/stub.d.ts.map +1 -0
  12. package/dist/adapters/stub.js +36 -0
  13. package/dist/adapters/stub.js.map +1 -0
  14. package/dist/events/adapters/in-memory.d.ts +52 -0
  15. package/dist/events/adapters/in-memory.d.ts.map +1 -0
  16. package/dist/events/adapters/in-memory.js +70 -0
  17. package/dist/events/adapters/in-memory.js.map +1 -0
  18. package/dist/events/adapters/in-memory.test.d.ts +2 -0
  19. package/dist/events/adapters/in-memory.test.d.ts.map +1 -0
  20. package/dist/events/adapters/in-memory.test.js +109 -0
  21. package/dist/events/adapters/in-memory.test.js.map +1 -0
  22. package/dist/events/adapters/index.d.ts +9 -0
  23. package/dist/events/adapters/index.d.ts.map +1 -0
  24. package/dist/events/adapters/index.js +9 -0
  25. package/dist/events/adapters/index.js.map +1 -0
  26. package/dist/events/adapters/kinesis.d.ts +91 -0
  27. package/dist/events/adapters/kinesis.d.ts.map +1 -0
  28. package/dist/events/adapters/kinesis.js +136 -0
  29. package/dist/events/adapters/kinesis.js.map +1 -0
  30. package/dist/events/adapters/kinesis.test.d.ts +2 -0
  31. package/dist/events/adapters/kinesis.test.d.ts.map +1 -0
  32. package/dist/events/adapters/kinesis.test.js +249 -0
  33. package/dist/events/adapters/kinesis.test.js.map +1 -0
  34. package/dist/events/emitter.d.ts +68 -0
  35. package/dist/events/emitter.d.ts.map +1 -0
  36. package/dist/events/emitter.js +83 -0
  37. package/dist/events/emitter.js.map +1 -0
  38. package/dist/events/emitter.test.d.ts +2 -0
  39. package/dist/events/emitter.test.d.ts.map +1 -0
  40. package/dist/events/emitter.test.js +262 -0
  41. package/dist/events/emitter.test.js.map +1 -0
  42. package/dist/events/index.d.ts +4 -0
  43. package/dist/events/index.d.ts.map +1 -0
  44. package/dist/events/index.js +4 -0
  45. package/dist/events/index.js.map +1 -0
  46. package/dist/events/types.d.ts +112 -0
  47. package/dist/events/types.d.ts.map +1 -0
  48. package/dist/events/types.js +9 -0
  49. package/dist/events/types.js.map +1 -0
  50. package/dist/executor.d.ts +4 -0
  51. package/dist/executor.d.ts.map +1 -0
  52. package/dist/executor.js +799 -0
  53. package/dist/executor.js.map +1 -0
  54. package/dist/executor.test.d.ts +2 -0
  55. package/dist/executor.test.d.ts.map +1 -0
  56. package/dist/executor.test.js +1584 -0
  57. package/dist/executor.test.js.map +1 -0
  58. package/dist/index.d.ts +9 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +15 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/secrets/factory.d.ts +121 -0
  63. package/dist/secrets/factory.d.ts.map +1 -0
  64. package/dist/secrets/factory.js +137 -0
  65. package/dist/secrets/factory.js.map +1 -0
  66. package/dist/secrets/index.d.ts +14 -0
  67. package/dist/secrets/index.d.ts.map +1 -0
  68. package/dist/secrets/index.js +18 -0
  69. package/dist/secrets/index.js.map +1 -0
  70. package/dist/secrets/providers/aws.d.ts +63 -0
  71. package/dist/secrets/providers/aws.d.ts.map +1 -0
  72. package/dist/secrets/providers/aws.js +110 -0
  73. package/dist/secrets/providers/aws.js.map +1 -0
  74. package/dist/secrets/providers/env.d.ts +36 -0
  75. package/dist/secrets/providers/env.d.ts.map +1 -0
  76. package/dist/secrets/providers/env.js +37 -0
  77. package/dist/secrets/providers/env.js.map +1 -0
  78. package/dist/secrets/providers/index.d.ts +7 -0
  79. package/dist/secrets/providers/index.d.ts.map +1 -0
  80. package/dist/secrets/providers/index.js +7 -0
  81. package/dist/secrets/providers/index.js.map +1 -0
  82. package/dist/secrets/providers/vault.d.ts +75 -0
  83. package/dist/secrets/providers/vault.d.ts.map +1 -0
  84. package/dist/secrets/providers/vault.js +143 -0
  85. package/dist/secrets/providers/vault.js.map +1 -0
  86. package/dist/secrets/registry.d.ts +39 -0
  87. package/dist/secrets/registry.d.ts.map +1 -0
  88. package/dist/secrets/registry.js +134 -0
  89. package/dist/secrets/registry.js.map +1 -0
  90. package/dist/secrets/resolver.d.ts +45 -0
  91. package/dist/secrets/resolver.d.ts.map +1 -0
  92. package/dist/secrets/resolver.js +188 -0
  93. package/dist/secrets/resolver.js.map +1 -0
  94. package/dist/secrets/secrets.test.d.ts +2 -0
  95. package/dist/secrets/secrets.test.d.ts.map +1 -0
  96. package/dist/secrets/secrets.test.js +317 -0
  97. package/dist/secrets/secrets.test.js.map +1 -0
  98. package/dist/secrets/types.d.ts +70 -0
  99. package/dist/secrets/types.d.ts.map +1 -0
  100. package/dist/secrets/types.js +42 -0
  101. package/dist/secrets/types.js.map +1 -0
  102. package/dist/shared.d.ts +8 -0
  103. package/dist/shared.d.ts.map +1 -0
  104. package/dist/shared.js +30 -0
  105. package/dist/shared.js.map +1 -0
  106. package/dist/test-monitor-types.d.ts +43 -0
  107. package/dist/test-monitor-types.d.ts.map +1 -0
  108. package/dist/test-monitor-types.js +2 -0
  109. package/dist/test-monitor-types.js.map +1 -0
  110. package/dist/test-plan-types.d.ts +43 -0
  111. package/dist/test-plan-types.d.ts.map +1 -0
  112. package/dist/test-plan-types.js +2 -0
  113. package/dist/test-plan-types.js.map +1 -0
  114. package/dist/types.d.ts +93 -0
  115. package/dist/types.d.ts.map +1 -0
  116. package/dist/types.js +3 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/utils/dates.d.ts +11 -0
  119. package/dist/utils/dates.d.ts.map +1 -0
  120. package/dist/utils/dates.js +13 -0
  121. package/dist/utils/dates.js.map +1 -0
  122. package/package.json +39 -0
  123. package/src/adapters/axios.ts +39 -0
  124. package/src/adapters/index.ts +2 -0
  125. package/src/adapters/stub.ts +47 -0
  126. package/src/events/adapters/README.md +144 -0
  127. package/src/events/adapters/in-memory.test.ts +146 -0
  128. package/src/events/adapters/in-memory.ts +93 -0
  129. package/src/events/adapters/index.ts +9 -0
  130. package/src/events/adapters/kinesis.test.ts +323 -0
  131. package/src/events/adapters/kinesis.ts +211 -0
  132. package/src/events/emitter.test.ts +327 -0
  133. package/src/events/emitter.ts +133 -0
  134. package/src/events/index.ts +3 -0
  135. package/src/events/types.ts +136 -0
  136. package/src/executor.test.ts +1732 -0
  137. package/src/executor.ts +1075 -0
  138. package/src/index.ts +81 -0
  139. package/src/secrets/factory.ts +248 -0
  140. package/src/secrets/index.ts +48 -0
  141. package/src/secrets/providers/aws.ts +178 -0
  142. package/src/secrets/providers/env.ts +66 -0
  143. package/src/secrets/providers/index.ts +15 -0
  144. package/src/secrets/providers/vault.ts +257 -0
  145. package/src/secrets/resolver.ts +269 -0
  146. package/src/secrets/secrets.test.ts +402 -0
  147. package/src/secrets/types.ts +106 -0
  148. package/src/shared.ts +46 -0
  149. package/src/test-monitor-types.ts +49 -0
  150. package/src/types.ts +114 -0
  151. package/src/utils/dates.ts +13 -0
  152. package/tsconfig.json +20 -0
  153. package/vitest.config.ts +14 -0
package/src/index.ts ADDED
@@ -0,0 +1,81 @@
1
+ export { executeMonitorV1 } from "./executor.js";
2
+ export type {
3
+ ExecutionOptions,
4
+ ExecutionResult,
5
+ NodeResult,
6
+ HttpClientAdapter,
7
+ HttpRequest,
8
+ HttpResponse,
9
+ RunStatusUpdate,
10
+ StatusCallbacks,
11
+ } from "./types.js";
12
+ export type {
13
+ TestMonitor,
14
+ HttpRequest as HttpRequestNode,
15
+ WaitNode,
16
+ AssertionNode,
17
+ Edge,
18
+ } from "./test-monitor-types.js";
19
+ export {
20
+ AxiosAdapter,
21
+ StubAdapter,
22
+ type StubResponse,
23
+ } from "./adapters/index.js";
24
+
25
+ export {
26
+ LocalEventEmitter,
27
+ DurableEventEmitter,
28
+ type ExecutionEventEmitter,
29
+ type DurableEventBusAdapter,
30
+ } from "./events/emitter.js";
31
+ export type {
32
+ ExecutionEvent,
33
+ BaseEvent,
34
+ MonitorStartEvent,
35
+ MonitorEndEvent,
36
+ NodeStartEvent,
37
+ NodeEndEvent,
38
+ HttpRequestEvent,
39
+ HttpResponseEvent,
40
+ HttpRetryEvent,
41
+ AssertionResultEvent,
42
+ WaitStartEvent,
43
+ NodeStreamEvent,
44
+ ErrorEvent,
45
+ } from "./events/types.js";
46
+ export {
47
+ // Event bus adapters
48
+ KinesisAdapter,
49
+ type KinesisAdapterOptions,
50
+ InMemoryAdapter,
51
+ } from "./events/adapters/index.js";
52
+
53
+ // Export secrets system
54
+ export {
55
+ // Core types and utilities
56
+ type SecretProvider,
57
+ type SecretRef,
58
+ type SecretRefData,
59
+ type SecretResolveOptions,
60
+ SecretResolutionError,
61
+ isSecretRef,
62
+ // Resolution utilities
63
+ resolveSecretsInMonitor,
64
+ collectSecretsFromMonitor,
65
+ planHasSecrets,
66
+ // Providers
67
+ EnvSecretProvider,
68
+ type EnvSecretProviderOptions,
69
+ AwsSecretsManagerProvider,
70
+ type AwsSecretsManagerProviderOptions,
71
+ type AwsSecretsManagerClient,
72
+ VaultProvider,
73
+ type VaultProviderOptions,
74
+ type VaultHttpClient,
75
+ // Factory functions
76
+ createSecretProvider,
77
+ type SecretProviderConfig,
78
+ type EnvProviderConfig,
79
+ type AwsProviderConfig,
80
+ type VaultProviderConfig,
81
+ } from "./secrets/index.js";
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Factory for creating SecretProvider instances from configuration.
3
+ *
4
+ * This module provides pure factory functions that construct providers
5
+ * from config objects. Callers (hub, CLI, agents) are responsible for
6
+ * sourcing the configuration from their environment.
7
+ */
8
+
9
+ import type { SecretProvider } from "./types.js";
10
+ import { EnvSecretProvider } from "./providers/env.js";
11
+ import { AwsSecretsManagerProvider, type AwsSecretsManagerClient } from "./providers/aws.js";
12
+ import { VaultProvider, type VaultHttpClient } from "./providers/vault.js";
13
+ import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
14
+ import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
15
+
16
+
17
+ /**
18
+ * Configuration for environment variable provider.
19
+ */
20
+ export interface EnvProviderConfig {
21
+ provider: "env";
22
+ /**
23
+ * Optional prefix to prepend to secret refs.
24
+ * For example, prefix="APP_" means secret("env:API_KEY") looks for "APP_API_KEY".
25
+ */
26
+ prefix?: string;
27
+ /**
28
+ * Custom environment object to read from.
29
+ * If not provided, caller should pass process.env.
30
+ */
31
+ env?: Record<string, string | undefined>;
32
+ }
33
+
34
+ /**
35
+ * Configuration for AWS Secrets Manager provider.
36
+ */
37
+ export interface AwsProviderConfig {
38
+ provider: "aws";
39
+ /**
40
+ * AWS region for Secrets Manager.
41
+ */
42
+ region: string;
43
+ /**
44
+ * Optional prefix for secret names.
45
+ * For example, prefix="myapp/" means secret("aws:api-key") looks for "myapp/api-key".
46
+ */
47
+ prefix?: string;
48
+ /**
49
+ * Optional IAM role to assume.
50
+ */
51
+ roleArn?: string;
52
+ /**
53
+ * Optional external ID for role assumption.
54
+ */
55
+ externalId?: string;
56
+ /**
57
+ * AWS credentials. If not provided, uses ambient credentials (IAM role, environment, etc).
58
+ */
59
+ credentials?: {
60
+ accessKeyId: string;
61
+ secretAccessKey: string;
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Configuration for HashiCorp Vault provider.
67
+ */
68
+ export interface VaultProviderConfig {
69
+ provider: "vault";
70
+ /**
71
+ * Vault server address (e.g., "https://vault.example.com").
72
+ */
73
+ address: string;
74
+ /**
75
+ * Vault authentication token.
76
+ */
77
+ token: string;
78
+ /**
79
+ * Optional Vault namespace.
80
+ */
81
+ namespace?: string;
82
+ /**
83
+ * KV secrets engine version (1 or 2). Defaults to 2.
84
+ */
85
+ kvVersion?: 1 | 2;
86
+ /**
87
+ * Optional prefix for secret paths.
88
+ */
89
+ prefix?: string;
90
+ }
91
+
92
+ /**
93
+ * Union type for all provider configurations.
94
+ */
95
+ export type SecretProviderConfig =
96
+ | EnvProviderConfig
97
+ | AwsProviderConfig
98
+ | VaultProviderConfig;
99
+
100
+ /**
101
+ * Create a SecretProvider from configuration.
102
+ *
103
+ * This is a pure factory function - it does not access environment variables
104
+ * or external state. All configuration must be provided by the caller.
105
+ *
106
+ * @example
107
+ * // Environment provider
108
+ * const envProvider = await createSecretProvider({
109
+ * provider: "env",
110
+ * prefix: "APP_",
111
+ * env: process.env,
112
+ * });
113
+ *
114
+ * @example
115
+ * // AWS provider with credentials
116
+ * const awsProvider = await createSecretProvider({
117
+ * provider: "aws",
118
+ * region: "us-east-1",
119
+ * prefix: "myapp/",
120
+ * credentials: {
121
+ * accessKeyId: "...",
122
+ * secretAccessKey: "...",
123
+ * },
124
+ * });
125
+ *
126
+ * @example
127
+ * // Vault provider
128
+ * const vaultProvider = await createSecretProvider({
129
+ * provider: "vault",
130
+ * address: "https://vault.example.com",
131
+ * token: "...",
132
+ * kvVersion: 2,
133
+ * });
134
+ */
135
+ export async function createSecretProvider(
136
+ config: SecretProviderConfig,
137
+ ): Promise<SecretProvider> {
138
+ switch (config.provider) {
139
+ case "env":
140
+ return new EnvSecretProvider({
141
+ prefix: config.prefix,
142
+ env: config.env,
143
+ });
144
+
145
+ case "aws":
146
+ return createAwsProvider(config);
147
+
148
+ case "vault":
149
+ return createVaultProvider(config);
150
+
151
+ default:
152
+ const exhaustive: never = config;
153
+ throw new Error(`Unknown provider type: ${(exhaustive as any).provider}`);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Create AWS Secrets Manager provider with SDK client.
159
+ */
160
+ async function createAwsProvider(
161
+ config: AwsProviderConfig,
162
+ ): Promise<AwsSecretsManagerProvider> {
163
+
164
+
165
+ const clientConfig: {
166
+ region: string;
167
+ credentials?: {
168
+ accessKeyId: string;
169
+ secretAccessKey: string;
170
+ sessionToken?: string;
171
+ };
172
+ } = { region: config.region };
173
+
174
+ // Handle role assumption if roleArn is provided
175
+ if (config.roleArn) {
176
+ const stsClient = new STSClient({ region: config.region });
177
+ const assumeRoleResponse = await stsClient.send(
178
+ new AssumeRoleCommand({
179
+ RoleArn: config.roleArn,
180
+ RoleSessionName: "griffin-executor",
181
+ ExternalId: config.externalId,
182
+ }),
183
+ );
184
+
185
+ if (assumeRoleResponse.Credentials) {
186
+ clientConfig.credentials = {
187
+ accessKeyId: assumeRoleResponse.Credentials.AccessKeyId ?? "",
188
+ secretAccessKey: assumeRoleResponse.Credentials.SecretAccessKey ?? "",
189
+ sessionToken: assumeRoleResponse.Credentials.SessionToken,
190
+ };
191
+ }
192
+ } else if (config.credentials) {
193
+ clientConfig.credentials = config.credentials;
194
+ }
195
+ // Otherwise uses ambient AWS credentials (IAM role, environment variables, etc)
196
+
197
+ const smClient = new SecretsManagerClient(clientConfig);
198
+
199
+ // Wrap SDK client with the interface expected by AwsSecretsManagerProvider
200
+ const client: AwsSecretsManagerClient = {
201
+ async getSecretValue(params: { SecretId: string; VersionStage?: string }) {
202
+ const command = new GetSecretValueCommand({
203
+ SecretId: params.SecretId,
204
+ VersionStage: params.VersionStage,
205
+ });
206
+ const response = await smClient.send(command);
207
+ return {
208
+ SecretString: response.SecretString,
209
+ SecretBinary: response.SecretBinary,
210
+ };
211
+ },
212
+ };
213
+
214
+ return new AwsSecretsManagerProvider({
215
+ client,
216
+ prefix: config.prefix,
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Create Vault provider with HTTP client.
222
+ */
223
+ function createVaultProvider(config: VaultProviderConfig): VaultProvider {
224
+ // Create HTTP client for Vault API calls
225
+ const httpClient: VaultHttpClient = {
226
+ async get(url: string, options: { headers: Record<string, string> }) {
227
+ // Import axios dynamically to avoid bundling it when not needed
228
+ const axios = await import("axios");
229
+ const response = await axios.default.get(url, {
230
+ headers: options.headers,
231
+ validateStatus: () => true, // Don't throw on non-2xx
232
+ });
233
+ return {
234
+ status: response.status,
235
+ data: response.data,
236
+ };
237
+ },
238
+ };
239
+
240
+ return new VaultProvider({
241
+ address: config.address,
242
+ token: config.token,
243
+ httpClient,
244
+ namespace: config.namespace,
245
+ kvVersion: config.kvVersion ?? 2,
246
+ prefix: config.prefix,
247
+ });
248
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Secret management for griffin monitor executor.
3
+ *
4
+ * This module provides:
5
+ * - SecretProvider interface for implementing custom providers
6
+ * - Secret resolution utilities for test monitors
7
+ * - Built-in providers: env, aws, vault
8
+ * - Factory functions for creating providers from configuration
9
+ */
10
+
11
+ // Core types
12
+ export {
13
+ type SecretProvider,
14
+ type SecretRef,
15
+ type SecretRefData,
16
+ type SecretResolveOptions,
17
+ SecretResolutionError,
18
+ isSecretRef,
19
+ isStringLiteral,
20
+ } from "./types.js";
21
+
22
+ // Resolution utilities
23
+ export {
24
+ resolveSecretsInMonitor,
25
+ collectSecretsFromMonitor,
26
+ planHasSecrets,
27
+ } from "./resolver.js";
28
+
29
+ // Providers
30
+ export {
31
+ EnvSecretProvider,
32
+ type EnvSecretProviderOptions,
33
+ AwsSecretsManagerProvider,
34
+ type AwsSecretsManagerProviderOptions,
35
+ type AwsSecretsManagerClient,
36
+ VaultProvider,
37
+ type VaultProviderOptions,
38
+ type VaultHttpClient,
39
+ } from "./providers/index.js";
40
+
41
+ // Factory functions
42
+ export {
43
+ createSecretProvider,
44
+ type SecretProviderConfig,
45
+ type EnvProviderConfig,
46
+ type AwsProviderConfig,
47
+ type VaultProviderConfig,
48
+ } from "./factory.js";
@@ -0,0 +1,178 @@
1
+ /**
2
+ * AWS Secrets Manager secret provider.
3
+ *
4
+ * Reads secrets from AWS Secrets Manager. Supports JSON secrets
5
+ * with field extraction and version staging.
6
+ *
7
+ * Usage in DSL:
8
+ * secret("aws:my-secret")
9
+ * secret("aws:prod/api-keys", { field: "stripe" })
10
+ * secret("aws:my-secret", { version: "AWSPREVIOUS" })
11
+ */
12
+
13
+ import type { SecretProvider, SecretResolveOptions } from "../types.js";
14
+ import { SecretResolutionError } from "../types.js";
15
+
16
+ /**
17
+ * Interface for AWS Secrets Manager client.
18
+ * This allows dependency injection of the actual AWS SDK client.
19
+ */
20
+ export interface AwsSecretsManagerClient {
21
+ getSecretValue(params: { SecretId: string; VersionStage?: string }): Promise<{
22
+ SecretString?: string;
23
+ SecretBinary?: Uint8Array;
24
+ }>;
25
+ }
26
+
27
+ export interface AwsSecretsManagerProviderOptions {
28
+ /**
29
+ * AWS Secrets Manager client instance.
30
+ * Should be pre-configured with region and credentials.
31
+ */
32
+ client: AwsSecretsManagerClient;
33
+
34
+ /**
35
+ * Optional prefix for secret names.
36
+ * For example, if prefix is "myapp/", then secret("aws:api-key")
37
+ * will look for "myapp/api-key" in Secrets Manager.
38
+ */
39
+ prefix?: string;
40
+
41
+ /**
42
+ * Default version stage to use if not specified.
43
+ * Defaults to "AWSCURRENT".
44
+ */
45
+ defaultVersionStage?: string;
46
+ }
47
+
48
+ export class AwsSecretsManagerProvider implements SecretProvider {
49
+ readonly name = "aws";
50
+ private readonly client: AwsSecretsManagerClient;
51
+ private readonly prefix: string;
52
+ private readonly defaultVersionStage: string;
53
+
54
+ // Simple in-memory cache with TTL
55
+ private cache = new Map<string, { value: string; expires: number }>();
56
+ private readonly cacheTtlMs = 5 * 60 * 1000; // 5 minutes
57
+
58
+ constructor(options: AwsSecretsManagerProviderOptions) {
59
+ this.client = options.client;
60
+ this.prefix = options.prefix ?? "";
61
+ this.defaultVersionStage = options.defaultVersionStage ?? "AWSCURRENT";
62
+ }
63
+
64
+ async resolve(ref: string, options?: SecretResolveOptions): Promise<string> {
65
+ const secretId = this.prefix + ref;
66
+ const versionStage = options?.version ?? this.defaultVersionStage;
67
+ const cacheKey = `${secretId}:${versionStage}`;
68
+
69
+ // Check cache
70
+ const cached = this.cache.get(cacheKey);
71
+ if (cached && cached.expires > Date.now()) {
72
+ return this.extractField(cached.value, options?.field, ref);
73
+ }
74
+
75
+ try {
76
+ const response = await this.client.getSecretValue({
77
+ SecretId: secretId,
78
+ VersionStage: versionStage,
79
+ });
80
+
81
+ if (!response.SecretString) {
82
+ throw new SecretResolutionError(
83
+ `Secret "${secretId}" does not contain a string value (binary secrets are not supported)`,
84
+ { ref },
85
+ );
86
+ }
87
+
88
+ // Cache the raw value
89
+ this.cache.set(cacheKey, {
90
+ value: response.SecretString,
91
+ expires: Date.now() + this.cacheTtlMs,
92
+ });
93
+
94
+ return this.extractField(response.SecretString, options?.field, ref);
95
+ } catch (error) {
96
+ if (error instanceof SecretResolutionError) {
97
+ throw error;
98
+ }
99
+
100
+ // Handle common AWS errors
101
+ const awsError = error as { name?: string; message?: string };
102
+ let message = `Failed to retrieve secret "${secretId}"`;
103
+
104
+ if (awsError.name === "ResourceNotFoundException") {
105
+ message = `Secret "${secretId}" not found in AWS Secrets Manager`;
106
+ } else if (awsError.name === "AccessDeniedException") {
107
+ message = `Access denied to secret "${secretId}". Check IAM permissions.`;
108
+ } else if (awsError.message) {
109
+ message = `${message}: ${awsError.message}`;
110
+ }
111
+
112
+ throw new SecretResolutionError(message, {
113
+ ref,
114
+ cause: error,
115
+ });
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Extract a field from a JSON secret string.
121
+ */
122
+ private extractField(
123
+ secretValue: string,
124
+ field: string | undefined,
125
+ ref: string,
126
+ ): string {
127
+ if (!field) {
128
+ return secretValue;
129
+ }
130
+
131
+ try {
132
+ const parsed = JSON.parse(secretValue);
133
+
134
+ if (typeof parsed !== "object" || parsed === null) {
135
+ throw new SecretResolutionError(
136
+ `Secret "${ref}" is not a JSON object, cannot extract field "${field}"`,
137
+ { ref },
138
+ );
139
+ }
140
+
141
+ const value = parsed[field];
142
+
143
+ if (value === undefined) {
144
+ throw new SecretResolutionError(
145
+ `Field "${field}" not found in secret "${ref}"`,
146
+ { ref },
147
+ );
148
+ }
149
+
150
+ // Convert to string if not already
151
+ return typeof value === "string" ? value : JSON.stringify(value);
152
+ } catch (error) {
153
+ if (error instanceof SecretResolutionError) {
154
+ throw error;
155
+ }
156
+
157
+ throw new SecretResolutionError(
158
+ `Failed to parse secret "${ref}" as JSON for field extraction: ${
159
+ error instanceof Error ? error.message : String(error)
160
+ }`,
161
+ { ref, cause: error },
162
+ );
163
+ }
164
+ }
165
+
166
+ async validate(): Promise<void> {
167
+ // Try a simple operation to verify credentials
168
+ // This is a no-op if the client is properly configured
169
+ // The actual validation happens on first secret access
170
+ }
171
+
172
+ /**
173
+ * Clear the cache. Useful for testing or forced refresh.
174
+ */
175
+ clearCache(): void {
176
+ this.cache.clear();
177
+ }
178
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Environment variable secret provider.
3
+ *
4
+ * Reads secrets from process.env. Useful for local development
5
+ * and simple deployments where secrets are injected as environment variables.
6
+ *
7
+ * Usage in DSL:
8
+ * secret("env:MY_API_KEY")
9
+ * secret("env:DATABASE_URL")
10
+ */
11
+
12
+ import type { SecretProvider, SecretResolveOptions } from "../types.js";
13
+ import { SecretResolutionError } from "../types.js";
14
+
15
+ export interface EnvSecretProviderOptions {
16
+ /**
17
+ * Custom environment object to read from.
18
+ * Defaults to process.env.
19
+ */
20
+ env?: Record<string, string | undefined>;
21
+
22
+ /**
23
+ * Prefix to strip from secret refs.
24
+ * For example, if prefix is "APP_", then secret("env:API_KEY")
25
+ * will look for "APP_API_KEY" in the environment.
26
+ */
27
+ prefix?: string;
28
+ }
29
+
30
+ export class EnvSecretProvider implements SecretProvider {
31
+ readonly name = "env";
32
+ private readonly env: Record<string, string | undefined>;
33
+ private readonly prefix: string;
34
+
35
+ constructor(options: EnvSecretProviderOptions = {}) {
36
+ this.env = options.env ?? process.env;
37
+ this.prefix = options.prefix ?? "";
38
+ }
39
+
40
+ async resolve(ref: string, _options?: SecretResolveOptions): Promise<string> {
41
+ const envKey = this.prefix + ref;
42
+ const value = this.env[envKey];
43
+
44
+ if (value === undefined) {
45
+ throw new SecretResolutionError(
46
+ `Environment variable "${envKey}" is not set`,
47
+ { ref },
48
+ );
49
+ }
50
+
51
+ return value;
52
+ }
53
+
54
+ async resolveMany(
55
+ refs: Array<{ ref: string; options?: SecretResolveOptions }>,
56
+ ): Promise<Map<string, string>> {
57
+ const results = new Map<string, string>();
58
+
59
+ for (const { ref } of refs) {
60
+ const value = await this.resolve(ref);
61
+ results.set(ref, value);
62
+ }
63
+
64
+ return results;
65
+ }
66
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Secret provider implementations.
3
+ */
4
+
5
+ export { EnvSecretProvider, type EnvSecretProviderOptions } from "./env.js";
6
+ export {
7
+ AwsSecretsManagerProvider,
8
+ type AwsSecretsManagerProviderOptions,
9
+ type AwsSecretsManagerClient,
10
+ } from "./aws.js";
11
+ export {
12
+ VaultProvider,
13
+ type VaultProviderOptions,
14
+ type VaultHttpClient,
15
+ } from "./vault.js";