@danielsoren/secrets-loader 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,578 @@
1
+ # @danielsoren/secrets-loader
2
+
3
+ Load one JSON secret from a provider, merge local overrides, validate with Zod, and return typed backend config.
4
+
5
+ > **Provider support:** AWS Secrets Manager today. The API is shaped so other providers (e.g. GCP, Vault) can plug in later without breaking changes.
6
+
7
+ ```ts
8
+ import { loadSecrets } from "@danielsoren/secrets-loader";
9
+ import { z } from "zod";
10
+
11
+ const schema = z.object({
12
+ NODE_ENV: z.enum(["development", "test", "production"]),
13
+ DATABASE_URL: z.url(),
14
+ JWT_SECRET: z.string().min(32),
15
+ PORT: z.coerce.number().int().positive().default(3000),
16
+ });
17
+
18
+ const result = await loadSecrets({
19
+ schema,
20
+ providers: {
21
+ aws: { secretId: "prod/my-api" },
22
+ },
23
+ });
24
+
25
+ if (!result.success) {
26
+ console.error(result.error.message);
27
+ process.exit(1);
28
+ }
29
+
30
+ const app = createApp({ env: result.data });
31
+ await app.listen(result.data.PORT);
32
+ ```
33
+
34
+ ## Why this package exists
35
+
36
+ A managed secret store is a good place to keep backend secrets, but loading them at runtime creates one awkward problem: secrets are async, while app configuration often wants to be available before anything else starts.
37
+
38
+ This package makes that startup step explicit:
39
+
40
+ 1. fetch one secret from a configured provider (AWS today).
41
+ 2. parse it as a JSON object.
42
+ 3. merge it with local `process.env` if configured.
43
+ 4. validate it with Zod.
44
+ 5. return a typed result.
45
+ 6. optionally write validated values to `process.env`.
46
+
47
+ No import-time magic. No hidden globals. No surprise mutation.
48
+
49
+ ## Requirements
50
+
51
+ ```txt
52
+ Node.js >= 22
53
+ ESM project
54
+ Backend/server runtime
55
+ ```
56
+
57
+ This package is not intended for browser/frontend usage.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ npm install @danielsoren/secrets-loader zod
63
+ ```
64
+
65
+ or:
66
+
67
+ ```bash
68
+ pnpm add @danielsoren/secrets-loader zod
69
+ ```
70
+
71
+ The AWS provider uses AWS SDK v3 internally. `zod` is a peer dependency.
72
+
73
+ ## Secret format
74
+
75
+ Store one JSON object as the secret value (`SecretString` in AWS Secrets Manager):
76
+
77
+ ```json
78
+ {
79
+ "NODE_ENV": "production",
80
+ "DATABASE_URL": "postgres://user:pass@host:5432/app",
81
+ "JWT_SECRET": "replace-with-a-long-secret",
82
+ "PORT": "3000"
83
+ }
84
+ ```
85
+
86
+ Top-level arrays, strings, numbers, booleans, and `null` are invalid.
87
+
88
+ For AWS, `SecretBinary` is not supported in v1.
89
+
90
+ ## Recommended usage: load first, then compose your app
91
+
92
+ ```ts
93
+ import { loadSecrets } from "@danielsoren/secrets-loader";
94
+ import { z } from "zod";
95
+ import { createApp } from "./app.js";
96
+
97
+ const schema = z.object({
98
+ NODE_ENV: z.enum(["development", "test", "production"]),
99
+ DATABASE_URL: z.url(),
100
+ JWT_SECRET: z.string().min(32),
101
+ PORT: z.coerce.number().int().positive().default(3000),
102
+ });
103
+
104
+ const result = await loadSecrets({
105
+ schema,
106
+ providers: {
107
+ aws: { secretId: "prod/my-api" },
108
+ },
109
+ });
110
+
111
+ if (!result.success) {
112
+ console.error(result.error.message);
113
+ process.exit(1);
114
+ }
115
+
116
+ const app = createApp({
117
+ env: result.data,
118
+ });
119
+
120
+ await app.listen(result.data.PORT);
121
+ ```
122
+
123
+ Then your application can receive config explicitly:
124
+
125
+ ```ts
126
+ import type { z } from "zod";
127
+
128
+ const schema = z.object({
129
+ DATABASE_URL: z.url(),
130
+ JWT_SECRET: z.string().min(32),
131
+ PORT: z.coerce.number(),
132
+ });
133
+
134
+ type AppEnv = z.output<typeof schema>;
135
+
136
+ export function createApp(ctx: { env: AppEnv }) {
137
+ const db = createDb(ctx.env.DATABASE_URL);
138
+ // create routes/services/etc.
139
+ }
140
+ ```
141
+
142
+ Dependency-injection style is the safest approach: app startup is deterministic and typed.
143
+
144
+ ## Source modes
145
+
146
+ `loadSecrets` can combine provider values and local `process.env` values.
147
+
148
+ Default mode:
149
+
150
+ ```ts
151
+ "provider-then-process-env"
152
+ ```
153
+
154
+ Provider values are loaded first, then local environment variables override them.
155
+
156
+ | Mode | Provider fetch? | Uses process.env? | Priority |
157
+ |---|---:|---:|---|
158
+ | `provider-only` | yes | no | provider only |
159
+ | `process-env-only` | no | yes | local only |
160
+ | `provider-then-process-env` | yes | yes | `process.env` overrides provider |
161
+ | `process-env-then-provider` | yes | yes | provider overrides `process.env` |
162
+
163
+ Example:
164
+
165
+ ```ts
166
+ const result = await loadSecrets({
167
+ schema,
168
+ source: "process-env-then-provider",
169
+ providers: {
170
+ aws: { secretId: "prod/my-api" },
171
+ },
172
+ });
173
+ ```
174
+
175
+ ## Local-only mode
176
+
177
+ Useful for tests, local scripts, or environments where the provider is not available:
178
+
179
+ ```ts
180
+ const result = await loadSecrets({
181
+ schema,
182
+ source: "process-env-only",
183
+ });
184
+ ```
185
+
186
+ In this mode, no provider config is required and no provider is called.
187
+
188
+ ## AWS provider
189
+
190
+ The AWS provider reads from AWS Secrets Manager. Configure it under `providers.aws`.
191
+
192
+ ### Region and credentials
193
+
194
+ You can pass a region explicitly:
195
+
196
+ ```ts
197
+ const result = await loadSecrets({
198
+ schema,
199
+ providers: {
200
+ aws: {
201
+ secretId: "prod/my-api",
202
+ region: "eu-central-1",
203
+ },
204
+ },
205
+ });
206
+ ```
207
+
208
+ If `region` is omitted, AWS SDK default region resolution is used.
209
+
210
+ Credentials are also resolved by AWS SDK by default. You may either omit credentials and use normal AWS SDK mechanisms, or pass explicit credentials through `providers.aws.credentials`.
211
+
212
+ Normal AWS SDK mechanisms include:
213
+
214
+ ```txt
215
+ AWS_ACCESS_KEY_ID
216
+ AWS_SECRET_ACCESS_KEY
217
+ AWS_SESSION_TOKEN
218
+ AWS_REGION
219
+ AWS_PROFILE
220
+ IAM role
221
+ ECS task role
222
+ EC2 instance role
223
+ Lambda execution role
224
+ ```
225
+
226
+ You can also pass explicit credentials when needed:
227
+
228
+ ```ts
229
+ const result = await loadSecrets({
230
+ schema,
231
+ providers: {
232
+ aws: {
233
+ secretId: "prod/my-api",
234
+ region: "eu-central-1",
235
+ credentials: {
236
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
237
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
238
+ sessionToken: process.env.AWS_SESSION_TOKEN,
239
+ },
240
+ },
241
+ },
242
+ });
243
+ ```
244
+
245
+ If `credentials` is omitted, the package does not pass any credential override to AWS SDK. `accessKeyId` and `secretAccessKey` are both required when `credentials` is provided; `sessionToken` is optional.
246
+
247
+ ## Validation and typing
248
+
249
+ The returned `data` type is inferred from the Zod schema output (`z.output<TSchema>`), so coercions and transforms are reflected in the final type.
250
+
251
+ ```ts
252
+ const schema = z.object({
253
+ PORT: z.coerce.number().int().positive(),
254
+ });
255
+
256
+ const result = await loadSecrets({
257
+ schema,
258
+ source: "process-env-only",
259
+ });
260
+
261
+ if (result.success) {
262
+ // number, not string
263
+ result.data.PORT;
264
+ }
265
+ ```
266
+
267
+ ## Result object
268
+
269
+ `loadSecrets` does not intentionally throw for expected failures. It returns a discriminated union:
270
+
271
+ ```ts
272
+ const result = await loadSecrets({
273
+ schema,
274
+ providers: { aws: { secretId } },
275
+ });
276
+
277
+ if (result.success) {
278
+ result.data;
279
+ result.meta;
280
+ } else {
281
+ result.error.code;
282
+ result.error.message;
283
+ result.error.issues;
284
+ }
285
+ ```
286
+
287
+ Success:
288
+
289
+ ```ts
290
+ {
291
+ success: true,
292
+ data: { DATABASE_URL: "...", PORT: 3000 },
293
+ error: null,
294
+ meta: {
295
+ source: "provider-then-process-env",
296
+ secretId: "prod/my-api",
297
+ loadedAt: new Date(),
298
+ cache: { enabled: false, hit: false },
299
+ usedSources: { aws: true, processEnv: true },
300
+ processEnvMutation: {
301
+ requested: false,
302
+ performed: false,
303
+ overwrite: false,
304
+ writtenKeys: [],
305
+ skippedKeys: [],
306
+ },
307
+ },
308
+ }
309
+ ```
310
+
311
+ Failure:
312
+
313
+ ```ts
314
+ {
315
+ success: false,
316
+ data: null,
317
+ error: {
318
+ code: "SCHEMA_VALIDATION_FAILED",
319
+ message: "Secret validation failed.",
320
+ issues: [{ path: "DATABASE_URL", message: "Invalid URL" }],
321
+ },
322
+ meta: { /* ... */ },
323
+ }
324
+ ```
325
+
326
+ ## Error codes
327
+
328
+ ```ts
329
+ type LoadSecretsErrorCode =
330
+ | "AWS_SECRET_ID_MISSING"
331
+ | "AWS_FETCH_FAILED"
332
+ | "AWS_SECRET_EMPTY"
333
+ | "AWS_SECRET_BINARY_UNSUPPORTED"
334
+ | "SECRET_JSON_INVALID"
335
+ | "SECRET_JSON_NOT_OBJECT"
336
+ | "SCHEMA_VALIDATION_FAILED"
337
+ | "PROCESS_ENV_WRITE_FAILED"
338
+ | "TIMEOUT"
339
+ | "INVALID_OPTIONS"
340
+ | "UNKNOWN";
341
+ ```
342
+
343
+ `AWS_*` codes are emitted when the AWS provider is in use. Future providers will introduce their own prefixed codes alongside these.
344
+
345
+ Package-generated `message` fields are sanitized and never contain secret values. The optional `cause` field may contain the raw SDK or system error — do not blindly `JSON.stringify(result.error)` into public logs if you cannot trust upstream error contents.
346
+
347
+ ## Optional process.env mutation
348
+
349
+ By default, the package does not write to `process.env`.
350
+
351
+ You can enable mutation:
352
+
353
+ ```ts
354
+ const result = await loadSecrets({
355
+ schema,
356
+ providers: {
357
+ aws: { secretId: "prod/my-api" },
358
+ },
359
+ processEnv: {
360
+ mutate: true,
361
+ overwrite: false,
362
+ },
363
+ });
364
+ ```
365
+
366
+ Rules:
367
+
368
+ - mutation happens only after successful validation.
369
+ - failed validation writes nothing.
370
+ - `overwrite: false` preserves existing environment variables.
371
+ - `overwrite: true` replaces existing values.
372
+ - `null` and `undefined` values are skipped.
373
+
374
+ Stringification rules used when writing to `process.env`:
375
+
376
+ ```txt
377
+ string -> same value
378
+ number -> String(value)
379
+ boolean -> "true" | "false"
380
+ bigint -> String(value)
381
+ Date -> toISOString()
382
+ object -> JSON.stringify(value)
383
+ array -> JSON.stringify(value)
384
+ null/undefined -> skip
385
+ ```
386
+
387
+ Useful for legacy libraries that read directly from `process.env`. The recommended default is still explicit app context / DI.
388
+
389
+ ## Cache
390
+
391
+ Cache is disabled by default.
392
+
393
+ ```ts
394
+ const result = await loadSecrets({
395
+ schema,
396
+ providers: {
397
+ aws: { secretId: "prod/my-api" },
398
+ },
399
+ cache: {
400
+ enabled: true,
401
+ ttlMs: 60_000,
402
+ },
403
+ });
404
+ ```
405
+
406
+ The cache stores only the fetched secret string, not the final validated config. That means:
407
+
408
+ - repeated provider calls can be avoided.
409
+ - `process.env` is still re-read on every call.
410
+ - validation still runs on every call.
411
+
412
+ Security note: enabling the cache keeps the secret string in process memory until TTL expires.
413
+
414
+ ## Timeout
415
+
416
+ Default provider fetch timeout:
417
+
418
+ ```ts
419
+ 5000 // milliseconds
420
+ ```
421
+
422
+ Override:
423
+
424
+ ```ts
425
+ const result = await loadSecrets({
426
+ schema,
427
+ providers: { aws: { secretId: "prod/my-api" } },
428
+ timeoutMs: 10_000,
429
+ });
430
+ ```
431
+
432
+ If the timeout is reached, the error code is `"TIMEOUT"`.
433
+
434
+ ## Framework examples
435
+
436
+ ### Hono
437
+
438
+ ```ts
439
+ const result = await loadSecrets({
440
+ schema,
441
+ providers: { aws: { secretId: "prod/api" } },
442
+ });
443
+ if (!result.success) {
444
+ console.error(result.error.message);
445
+ process.exit(1);
446
+ }
447
+
448
+ const app = new Hono<{ Variables: { env: typeof result.data } }>();
449
+ app.use("*", async (c, next) => {
450
+ c.set("env", result.data);
451
+ await next();
452
+ });
453
+ ```
454
+
455
+ ### Express
456
+
457
+ ```ts
458
+ const result = await loadSecrets({
459
+ schema,
460
+ providers: { aws: { secretId: "prod/api" } },
461
+ });
462
+ if (!result.success) {
463
+ console.error(result.error.message);
464
+ process.exit(1);
465
+ }
466
+
467
+ const app = express();
468
+ const env = result.data;
469
+
470
+ app.get("/health", (_req, res) => {
471
+ res.json({ env: env.NODE_ENV });
472
+ });
473
+
474
+ app.listen(env.PORT);
475
+ ```
476
+
477
+ ### Generic service composition
478
+
479
+ ```ts
480
+ const result = await loadSecrets({
481
+ schema,
482
+ providers: { aws: { secretId: "prod/worker" } },
483
+ });
484
+ if (!result.success) {
485
+ console.error(result.error.message);
486
+ process.exit(1);
487
+ }
488
+
489
+ const worker = createWorker({
490
+ env: result.data,
491
+ db: createDb(result.data.DATABASE_URL),
492
+ });
493
+
494
+ await worker.start();
495
+ ```
496
+
497
+ ## Recommended IAM policy (AWS)
498
+
499
+ Prefer least privilege:
500
+
501
+ ```json
502
+ {
503
+ "Effect": "Allow",
504
+ "Action": "secretsmanager:GetSecretValue",
505
+ "Resource": "arn:aws:secretsmanager:REGION:ACCOUNT_ID:secret:prod/my-api-*"
506
+ }
507
+ ```
508
+
509
+ ## Security notes
510
+
511
+ - Do not use this package in frontend code.
512
+ - Do not embed loaded secrets into frontend bundles.
513
+ - Do not log `result.data`.
514
+ - Do not log raw provider errors if your logging pipeline is public or shared.
515
+ - Package-generated error messages are sanitized and do not include secret values.
516
+ - `process.env` mutation is off by default.
517
+ - Cache is off by default.
518
+ - The package has no import-time side effects.
519
+
520
+ ## Limitations
521
+
522
+ v1 intentionally does not support:
523
+
524
+ - multiple secrets in one call.
525
+ - providers other than AWS Secrets Manager.
526
+ - AWS `SecretBinary`.
527
+ - custom AWS endpoints.
528
+ - LocalStack-specific configuration.
529
+ - browser usage.
530
+ - CommonJS.
531
+ - throwing mode.
532
+ - automatic import-time loading.
533
+
534
+ ## Roadmap
535
+
536
+ The `providers.aws` shape is designed so additional providers can be added without breaking the existing API. Likely future additions:
537
+
538
+ - additional cloud providers (e.g. GCP Secret Manager, HashiCorp Vault).
539
+ - custom provider injection.
540
+
541
+ These are not implemented yet.
542
+
543
+ ## Bad patterns to avoid
544
+
545
+ Avoid async global initialization soup:
546
+
547
+ ```ts
548
+ // avoid
549
+ export let env;
550
+
551
+ loadSecrets({
552
+ schema,
553
+ providers: { aws: { secretId: "prod/api" } },
554
+ }).then((result) => {
555
+ if (result.success) {
556
+ env = result.data;
557
+ }
558
+ });
559
+ ```
560
+
561
+ Prefer explicit startup:
562
+
563
+ ```ts
564
+ const result = await loadSecrets({
565
+ schema,
566
+ providers: { aws: { secretId: "prod/api" } },
567
+ });
568
+
569
+ if (!result.success) {
570
+ process.exit(1);
571
+ }
572
+
573
+ await startApp(result.data);
574
+ ```
575
+
576
+ ## License
577
+
578
+ MIT
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+
3
+ type SecretSourceMode = "provider-only" | "process-env-only" | "provider-then-process-env" | "process-env-then-provider";
4
+ type AwsCredentialsOption = {
5
+ accessKeyId: string;
6
+ secretAccessKey: string;
7
+ sessionToken?: string;
8
+ };
9
+ type AwsOption = {
10
+ secretId?: string;
11
+ region?: string;
12
+ credentials?: AwsCredentialsOption;
13
+ };
14
+ type ProvidersOption = {
15
+ aws?: AwsOption;
16
+ };
17
+ type CacheOption = {
18
+ enabled?: boolean;
19
+ ttlMs?: number;
20
+ };
21
+ type ProcessEnvOption = {
22
+ mutate?: boolean;
23
+ overwrite?: boolean;
24
+ };
25
+ type LoadSecretsOptions<TSchema extends z.ZodTypeAny> = {
26
+ schema: TSchema;
27
+ providers?: ProvidersOption;
28
+ source?: SecretSourceMode;
29
+ timeoutMs?: number;
30
+ cache?: CacheOption;
31
+ processEnv?: ProcessEnvOption;
32
+ };
33
+ type LoadSecretsErrorCode = "AWS_SECRET_ID_MISSING" | "AWS_FETCH_FAILED" | "AWS_SECRET_EMPTY" | "AWS_SECRET_BINARY_UNSUPPORTED" | "SECRET_JSON_INVALID" | "SECRET_JSON_NOT_OBJECT" | "SCHEMA_VALIDATION_FAILED" | "PROCESS_ENV_WRITE_FAILED" | "TIMEOUT" | "INVALID_OPTIONS" | "UNKNOWN";
34
+ type LoadSecretsIssue = {
35
+ path: string;
36
+ message: string;
37
+ };
38
+ type LoadSecretsError = {
39
+ code: LoadSecretsErrorCode;
40
+ message: string;
41
+ issues?: LoadSecretsIssue[];
42
+ cause?: unknown;
43
+ };
44
+ type LoadSecretsMeta = {
45
+ source: SecretSourceMode;
46
+ secretId?: string;
47
+ region?: string;
48
+ loadedAt: Date;
49
+ cache: {
50
+ enabled: boolean;
51
+ hit: boolean;
52
+ ttlMs?: number;
53
+ };
54
+ usedSources: {
55
+ aws: boolean;
56
+ processEnv: boolean;
57
+ };
58
+ processEnvMutation: {
59
+ requested: boolean;
60
+ performed: boolean;
61
+ overwrite: boolean;
62
+ writtenKeys: string[];
63
+ skippedKeys: string[];
64
+ };
65
+ };
66
+ type LoadSecretsSuccess<TData> = {
67
+ success: true;
68
+ data: TData;
69
+ error: null;
70
+ meta: LoadSecretsMeta;
71
+ };
72
+ type LoadSecretsFailure = {
73
+ success: false;
74
+ data: null;
75
+ error: LoadSecretsError;
76
+ meta: LoadSecretsMeta;
77
+ };
78
+ type LoadSecretsResult<TData> = LoadSecretsSuccess<TData> | LoadSecretsFailure;
79
+
80
+ declare function loadSecrets<TSchema extends z.ZodTypeAny>(options: LoadSecretsOptions<TSchema>): Promise<LoadSecretsResult<z.output<TSchema>>>;
81
+
82
+ export { type AwsCredentialsOption, type AwsOption, type CacheOption, type LoadSecretsError, type LoadSecretsErrorCode, type LoadSecretsFailure, type LoadSecretsIssue, type LoadSecretsMeta, type LoadSecretsOptions, type LoadSecretsResult, type LoadSecretsSuccess, type ProcessEnvOption, type ProvidersOption, type SecretSourceMode, loadSecrets };
package/dist/index.js ADDED
@@ -0,0 +1,472 @@
1
+ // src/aws/fetch-secret-string.ts
2
+ import { GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
3
+
4
+ // src/core/errors.ts
5
+ var MESSAGES = {
6
+ AWS_SECRET_ID_MISSING: "AWS secretId is required for the selected source mode.",
7
+ AWS_FETCH_FAILED: "Failed to fetch secret from AWS Secrets Manager.",
8
+ AWS_SECRET_EMPTY: "AWS secret value is empty.",
9
+ AWS_SECRET_BINARY_UNSUPPORTED: "SecretBinary is not supported. Store the secret as a JSON object in SecretString.",
10
+ SECRET_JSON_INVALID: "AWS SecretString must be valid JSON.",
11
+ SECRET_JSON_NOT_OBJECT: "AWS SecretString must contain a JSON object.",
12
+ SCHEMA_VALIDATION_FAILED: "Secret validation failed.",
13
+ PROCESS_ENV_WRITE_FAILED: "Failed to write validated secrets to process.env.",
14
+ TIMEOUT: "Timed out while fetching secret from AWS Secrets Manager.",
15
+ INVALID_OPTIONS: "Invalid loadSecrets options.",
16
+ UNKNOWN: "Unexpected error while loading secrets."
17
+ };
18
+ function createError(code, options) {
19
+ const error = {
20
+ code,
21
+ message: MESSAGES[code]
22
+ };
23
+ if (options?.issues !== void 0) {
24
+ error.issues = options.issues;
25
+ }
26
+ if (options?.cause !== void 0) {
27
+ error.cause = options.cause;
28
+ }
29
+ return error;
30
+ }
31
+
32
+ // src/utils/timeout.ts
33
+ var TimeoutError = class extends Error {
34
+ constructor(message = "Operation timed out") {
35
+ super(message);
36
+ this.name = "TimeoutError";
37
+ }
38
+ };
39
+ function withTimeout(promise, timeoutMs) {
40
+ return new Promise((resolve, reject) => {
41
+ const timer = setTimeout(() => {
42
+ reject(new TimeoutError());
43
+ }, timeoutMs);
44
+ promise.then(
45
+ (value) => {
46
+ clearTimeout(timer);
47
+ resolve(value);
48
+ },
49
+ (err) => {
50
+ clearTimeout(timer);
51
+ reject(err);
52
+ }
53
+ );
54
+ });
55
+ }
56
+
57
+ // src/aws/create-secrets-manager-client.ts
58
+ import {
59
+ SecretsManagerClient
60
+ } from "@aws-sdk/client-secrets-manager";
61
+ function createSecretsManagerClient(input) {
62
+ const config = {};
63
+ if (input.region !== void 0) {
64
+ config.region = input.region;
65
+ }
66
+ if (input.credentials !== void 0) {
67
+ const { accessKeyId, secretAccessKey, sessionToken } = input.credentials;
68
+ config.credentials = sessionToken !== void 0 ? { accessKeyId, secretAccessKey, sessionToken } : { accessKeyId, secretAccessKey };
69
+ }
70
+ return new SecretsManagerClient(config);
71
+ }
72
+
73
+ // src/aws/fetch-secret-string.ts
74
+ async function fetchSecretString(input) {
75
+ const client = createSecretsManagerClient({
76
+ ...input.region !== void 0 ? { region: input.region } : {},
77
+ ...input.credentials !== void 0 ? { credentials: input.credentials } : {}
78
+ });
79
+ try {
80
+ const response = await withTimeout(
81
+ client.send(new GetSecretValueCommand({ SecretId: input.secretId })),
82
+ input.timeoutMs
83
+ );
84
+ const secretString = response.SecretString;
85
+ if (typeof secretString === "string" && secretString.length > 0) {
86
+ return { success: true, secretString };
87
+ }
88
+ if (response.SecretBinary !== void 0) {
89
+ return {
90
+ success: false,
91
+ error: createError("AWS_SECRET_BINARY_UNSUPPORTED")
92
+ };
93
+ }
94
+ return { success: false, error: createError("AWS_SECRET_EMPTY") };
95
+ } catch (cause) {
96
+ if (cause instanceof TimeoutError) {
97
+ return { success: false, error: createError("TIMEOUT", { cause }) };
98
+ }
99
+ return {
100
+ success: false,
101
+ error: createError("AWS_FETCH_FAILED", { cause })
102
+ };
103
+ } finally {
104
+ try {
105
+ client.destroy();
106
+ } catch {
107
+ }
108
+ }
109
+ }
110
+
111
+ // src/core/cache.ts
112
+ var cache = /* @__PURE__ */ new Map();
113
+ function buildCacheKey(secretId, region) {
114
+ return `${region ?? "default"}:${secretId}`;
115
+ }
116
+ function getCachedSecretString(key, now = Date.now()) {
117
+ const entry = cache.get(key);
118
+ if (!entry) return null;
119
+ if (now >= entry.expiresAt) {
120
+ cache.delete(key);
121
+ return null;
122
+ }
123
+ return entry.value;
124
+ }
125
+ function setCachedSecretString(key, value, ttlMs, now = Date.now()) {
126
+ cache.set(key, { value, expiresAt: now + ttlMs });
127
+ }
128
+
129
+ // src/core/merge-sources.ts
130
+ function mergeSources(input) {
131
+ const { source, providerValues, processEnvValues } = input;
132
+ switch (source) {
133
+ case "provider-only":
134
+ return { ...providerValues ?? {} };
135
+ case "process-env-only":
136
+ return { ...processEnvValues ?? {} };
137
+ case "provider-then-process-env":
138
+ return { ...providerValues ?? {}, ...processEnvValues ?? {} };
139
+ case "process-env-then-provider":
140
+ return { ...processEnvValues ?? {}, ...providerValues ?? {} };
141
+ }
142
+ }
143
+
144
+ // src/core/constants.ts
145
+ var DEFAULT_SOURCE = "provider-then-process-env";
146
+ var DEFAULT_TIMEOUT_MS = 5e3;
147
+ var DEFAULT_CACHE_ENABLED = false;
148
+ var DEFAULT_CACHE_TTL_MS = 6e4;
149
+ var DEFAULT_PROCESS_ENV_MUTATE = false;
150
+ var DEFAULT_PROCESS_ENV_OVERWRITE = false;
151
+
152
+ // src/core/normalize-options.ts
153
+ var VALID_SOURCES = /* @__PURE__ */ new Set([
154
+ "provider-only",
155
+ "process-env-only",
156
+ "provider-then-process-env",
157
+ "process-env-then-provider"
158
+ ]);
159
+ function normalizeOptions(options) {
160
+ const source = options.source ?? DEFAULT_SOURCE;
161
+ if (!VALID_SOURCES.has(source)) {
162
+ return { success: false, error: createError("INVALID_OPTIONS") };
163
+ }
164
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
165
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
166
+ return { success: false, error: createError("INVALID_OPTIONS") };
167
+ }
168
+ const cacheEnabled = options.cache?.enabled ?? DEFAULT_CACHE_ENABLED;
169
+ const cacheTtlMs = options.cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS;
170
+ if (cacheEnabled && (!Number.isFinite(cacheTtlMs) || cacheTtlMs <= 0)) {
171
+ return { success: false, error: createError("INVALID_OPTIONS") };
172
+ }
173
+ const mutate = options.processEnv?.mutate ?? DEFAULT_PROCESS_ENV_MUTATE;
174
+ const overwrite = options.processEnv?.overwrite ?? DEFAULT_PROCESS_ENV_OVERWRITE;
175
+ const awsInput = options.providers?.aws;
176
+ const aws = {};
177
+ if (awsInput?.secretId !== void 0 && awsInput.secretId.length > 0) {
178
+ aws.secretId = awsInput.secretId;
179
+ }
180
+ if (awsInput?.region !== void 0 && awsInput.region.length > 0) {
181
+ aws.region = awsInput.region;
182
+ }
183
+ if (awsInput?.credentials !== void 0) {
184
+ const c = awsInput.credentials;
185
+ if (typeof c.accessKeyId !== "string" || c.accessKeyId.length === 0 || typeof c.secretAccessKey !== "string" || c.secretAccessKey.length === 0) {
186
+ return { success: false, error: createError("INVALID_OPTIONS") };
187
+ }
188
+ aws.credentials = {
189
+ accessKeyId: c.accessKeyId,
190
+ secretAccessKey: c.secretAccessKey,
191
+ ...c.sessionToken !== void 0 ? { sessionToken: c.sessionToken } : {}
192
+ };
193
+ }
194
+ return {
195
+ success: true,
196
+ data: {
197
+ source,
198
+ timeoutMs,
199
+ providers: { aws },
200
+ cache: {
201
+ enabled: cacheEnabled,
202
+ ttlMs: cacheTtlMs
203
+ },
204
+ processEnv: {
205
+ mutate,
206
+ overwrite
207
+ }
208
+ }
209
+ };
210
+ }
211
+
212
+ // src/core/process-env.ts
213
+ function snapshotProcessEnv() {
214
+ const snapshot = {};
215
+ for (const key of Object.keys(process.env)) {
216
+ snapshot[key] = process.env[key];
217
+ }
218
+ return snapshot;
219
+ }
220
+ function stringifyForEnv(value) {
221
+ if (value === null || value === void 0) return null;
222
+ if (typeof value === "string") return value;
223
+ if (typeof value === "number") return String(value);
224
+ if (typeof value === "boolean") return value ? "true" : "false";
225
+ if (typeof value === "bigint") return value.toString();
226
+ if (value instanceof Date) return value.toISOString();
227
+ if (typeof value === "object") return JSON.stringify(value);
228
+ return String(value);
229
+ }
230
+ function mutateProcessEnv(validated, overwrite) {
231
+ const candidates = [];
232
+ const skippedKeys = [];
233
+ for (const key of Object.keys(validated)) {
234
+ const stringified = stringifyForEnv(validated[key]);
235
+ if (stringified === null) {
236
+ skippedKeys.push(key);
237
+ continue;
238
+ }
239
+ if (!overwrite && Object.hasOwn(process.env, key) && process.env[key] !== void 0) {
240
+ skippedKeys.push(key);
241
+ continue;
242
+ }
243
+ candidates.push([key, stringified]);
244
+ }
245
+ const previous = /* @__PURE__ */ new Map();
246
+ const writtenKeys = [];
247
+ try {
248
+ for (const [key, value] of candidates) {
249
+ previous.set(key, process.env[key]);
250
+ process.env[key] = value;
251
+ writtenKeys.push(key);
252
+ }
253
+ return { success: true, writtenKeys, skippedKeys };
254
+ } catch (cause) {
255
+ for (const [key, prev] of previous) {
256
+ if (prev === void 0) {
257
+ delete process.env[key];
258
+ } else {
259
+ process.env[key] = prev;
260
+ }
261
+ }
262
+ return {
263
+ success: false,
264
+ error: createError("PROCESS_ENV_WRITE_FAILED", { cause }),
265
+ writtenKeys: [],
266
+ skippedKeys
267
+ };
268
+ }
269
+ }
270
+
271
+ // src/core/resolve-source-mode.ts
272
+ function sourceUsesProvider(source) {
273
+ return source !== "process-env-only";
274
+ }
275
+ function sourceUsesProcessEnv(source) {
276
+ return source !== "provider-only";
277
+ }
278
+
279
+ // src/core/result.ts
280
+ function success(meta, data) {
281
+ return {
282
+ success: true,
283
+ data,
284
+ error: null,
285
+ meta
286
+ };
287
+ }
288
+ function failure(meta, error) {
289
+ return {
290
+ success: false,
291
+ data: null,
292
+ error,
293
+ meta
294
+ };
295
+ }
296
+
297
+ // src/core/validate-schema.ts
298
+ function mapIssues(error) {
299
+ return error.issues.map((issue) => {
300
+ const path = issue.path.map((p) => String(p)).join(".");
301
+ return {
302
+ path: path.length === 0 ? "<root>" : path,
303
+ message: issue.message
304
+ };
305
+ });
306
+ }
307
+ async function validateSchema(schema, value) {
308
+ const parsed = await schema.safeParseAsync(value);
309
+ if (parsed.success) {
310
+ return { success: true, data: parsed.data };
311
+ }
312
+ return { success: false, issues: mapIssues(parsed.error) };
313
+ }
314
+
315
+ // src/utils/is-plain-record.ts
316
+ function isPlainRecord(value) {
317
+ if (value === null || typeof value !== "object") return false;
318
+ if (Array.isArray(value)) return false;
319
+ const proto = Object.getPrototypeOf(value);
320
+ return proto === Object.prototype || proto === null;
321
+ }
322
+
323
+ // src/utils/parse-json-secret.ts
324
+ function parseJsonSecret(input) {
325
+ let parsed;
326
+ try {
327
+ parsed = JSON.parse(input);
328
+ } catch {
329
+ return {
330
+ success: false,
331
+ error: {
332
+ code: "SECRET_JSON_INVALID",
333
+ message: "AWS SecretString must be valid JSON."
334
+ }
335
+ };
336
+ }
337
+ if (!isPlainRecord(parsed)) {
338
+ return {
339
+ success: false,
340
+ error: {
341
+ code: "SECRET_JSON_NOT_OBJECT",
342
+ message: "AWS SecretString must contain a JSON object."
343
+ }
344
+ };
345
+ }
346
+ return { success: true, data: parsed };
347
+ }
348
+
349
+ // src/load-secrets.ts
350
+ function createInitialMeta(normalized) {
351
+ const meta = {
352
+ source: normalized.source,
353
+ loadedAt: /* @__PURE__ */ new Date(),
354
+ cache: {
355
+ enabled: normalized.cache.enabled,
356
+ hit: false
357
+ },
358
+ usedSources: {
359
+ aws: sourceUsesProvider(normalized.source),
360
+ processEnv: sourceUsesProcessEnv(normalized.source)
361
+ },
362
+ processEnvMutation: {
363
+ requested: normalized.processEnv.mutate,
364
+ performed: false,
365
+ overwrite: normalized.processEnv.overwrite,
366
+ writtenKeys: [],
367
+ skippedKeys: []
368
+ }
369
+ };
370
+ if (normalized.providers.aws.secretId !== void 0) {
371
+ meta.secretId = normalized.providers.aws.secretId;
372
+ }
373
+ if (normalized.providers.aws.region !== void 0) {
374
+ meta.region = normalized.providers.aws.region;
375
+ }
376
+ if (normalized.cache.enabled) {
377
+ meta.cache.ttlMs = normalized.cache.ttlMs;
378
+ }
379
+ return meta;
380
+ }
381
+ async function loadSecrets(options) {
382
+ const normalizedResult = normalizeOptions(options);
383
+ if (!normalizedResult.success) {
384
+ const reportedSource = options.source ?? "provider-then-process-env";
385
+ const baseMeta = {
386
+ source: reportedSource,
387
+ loadedAt: /* @__PURE__ */ new Date(),
388
+ cache: { enabled: false, hit: false },
389
+ usedSources: {
390
+ aws: sourceUsesProvider(reportedSource),
391
+ processEnv: sourceUsesProcessEnv(reportedSource)
392
+ },
393
+ processEnvMutation: {
394
+ requested: options.processEnv?.mutate ?? false,
395
+ performed: false,
396
+ overwrite: options.processEnv?.overwrite ?? false,
397
+ writtenKeys: [],
398
+ skippedKeys: []
399
+ }
400
+ };
401
+ return failure(baseMeta, normalizedResult.error);
402
+ }
403
+ const normalized = normalizedResult.data;
404
+ const meta = createInitialMeta(normalized);
405
+ try {
406
+ let providerValues;
407
+ let processEnvValues;
408
+ if (sourceUsesProvider(normalized.source)) {
409
+ const aws = normalized.providers.aws;
410
+ if (aws.secretId === void 0 || aws.secretId.length === 0) {
411
+ return failure(meta, createError("AWS_SECRET_ID_MISSING"));
412
+ }
413
+ let secretString = null;
414
+ const cacheKey = buildCacheKey(aws.secretId, aws.region);
415
+ if (normalized.cache.enabled) {
416
+ secretString = getCachedSecretString(cacheKey);
417
+ if (secretString !== null) {
418
+ meta.cache.hit = true;
419
+ }
420
+ }
421
+ if (secretString === null) {
422
+ const fetchResult = await fetchSecretString({
423
+ secretId: aws.secretId,
424
+ timeoutMs: normalized.timeoutMs,
425
+ ...aws.region !== void 0 ? { region: aws.region } : {},
426
+ ...aws.credentials !== void 0 ? { credentials: aws.credentials } : {}
427
+ });
428
+ if (!fetchResult.success) {
429
+ return failure(meta, fetchResult.error);
430
+ }
431
+ secretString = fetchResult.secretString;
432
+ if (normalized.cache.enabled) {
433
+ setCachedSecretString(cacheKey, secretString, normalized.cache.ttlMs);
434
+ }
435
+ }
436
+ const parsed = parseJsonSecret(secretString);
437
+ if (!parsed.success) {
438
+ return failure(meta, parsed.error);
439
+ }
440
+ providerValues = parsed.data;
441
+ }
442
+ if (sourceUsesProcessEnv(normalized.source)) {
443
+ processEnvValues = snapshotProcessEnv();
444
+ }
445
+ const merged = mergeSources({
446
+ source: normalized.source,
447
+ ...providerValues !== void 0 ? { providerValues } : {},
448
+ ...processEnvValues !== void 0 ? { processEnvValues } : {}
449
+ });
450
+ const validation = await validateSchema(options.schema, merged);
451
+ if (!validation.success) {
452
+ return failure(meta, createError("SCHEMA_VALIDATION_FAILED", { issues: validation.issues }));
453
+ }
454
+ if (normalized.processEnv.mutate) {
455
+ const validatedRecord = validation.data && typeof validation.data === "object" && !Array.isArray(validation.data) ? validation.data : {};
456
+ const mutation = mutateProcessEnv(validatedRecord, normalized.processEnv.overwrite);
457
+ meta.processEnvMutation.writtenKeys = mutation.writtenKeys;
458
+ meta.processEnvMutation.skippedKeys = mutation.skippedKeys;
459
+ if (!mutation.success) {
460
+ return failure(meta, mutation.error);
461
+ }
462
+ meta.processEnvMutation.performed = true;
463
+ }
464
+ return success(meta, validation.data);
465
+ } catch (cause) {
466
+ return failure(meta, createError("UNKNOWN", { cause }));
467
+ }
468
+ }
469
+ export {
470
+ loadSecrets
471
+ };
472
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/aws/fetch-secret-string.ts","../src/core/errors.ts","../src/utils/timeout.ts","../src/aws/create-secrets-manager-client.ts","../src/core/cache.ts","../src/core/merge-sources.ts","../src/core/constants.ts","../src/core/normalize-options.ts","../src/core/process-env.ts","../src/core/resolve-source-mode.ts","../src/core/result.ts","../src/core/validate-schema.ts","../src/utils/is-plain-record.ts","../src/utils/parse-json-secret.ts","../src/load-secrets.ts"],"sourcesContent":["import { GetSecretValueCommand } from \"@aws-sdk/client-secrets-manager\";\nimport { createError } from \"../core/errors.js\";\nimport type { AwsCredentialsOption, LoadSecretsError } from \"../core/types.js\";\nimport { TimeoutError, withTimeout } from \"../utils/timeout.js\";\nimport { createSecretsManagerClient } from \"./create-secrets-manager-client.js\";\n\nexport type FetchSecretStringInput = {\n secretId: string;\n region?: string;\n credentials?: AwsCredentialsOption;\n timeoutMs: number;\n};\n\nexport type FetchSecretStringResult =\n | { success: true; secretString: string }\n | { success: false; error: LoadSecretsError };\n\nexport async function fetchSecretString(\n input: FetchSecretStringInput,\n): Promise<FetchSecretStringResult> {\n const client = createSecretsManagerClient({\n ...(input.region !== undefined ? { region: input.region } : {}),\n ...(input.credentials !== undefined ? { credentials: input.credentials } : {}),\n });\n\n try {\n const response = await withTimeout(\n client.send(new GetSecretValueCommand({ SecretId: input.secretId })),\n input.timeoutMs,\n );\n\n const secretString = response.SecretString;\n if (typeof secretString === \"string\" && secretString.length > 0) {\n return { success: true, secretString };\n }\n\n if (response.SecretBinary !== undefined) {\n return {\n success: false,\n error: createError(\"AWS_SECRET_BINARY_UNSUPPORTED\"),\n };\n }\n\n return { success: false, error: createError(\"AWS_SECRET_EMPTY\") };\n } catch (cause) {\n if (cause instanceof TimeoutError) {\n return { success: false, error: createError(\"TIMEOUT\", { cause }) };\n }\n return {\n success: false,\n error: createError(\"AWS_FETCH_FAILED\", { cause }),\n };\n } finally {\n try {\n client.destroy();\n } catch {\n // ignore client destroy errors\n }\n }\n}\n","import type { LoadSecretsError, LoadSecretsErrorCode, LoadSecretsIssue } from \"./types.js\";\n\nconst MESSAGES: Record<LoadSecretsErrorCode, string> = {\n AWS_SECRET_ID_MISSING: \"AWS secretId is required for the selected source mode.\",\n AWS_FETCH_FAILED: \"Failed to fetch secret from AWS Secrets Manager.\",\n AWS_SECRET_EMPTY: \"AWS secret value is empty.\",\n AWS_SECRET_BINARY_UNSUPPORTED:\n \"SecretBinary is not supported. Store the secret as a JSON object in SecretString.\",\n SECRET_JSON_INVALID: \"AWS SecretString must be valid JSON.\",\n SECRET_JSON_NOT_OBJECT: \"AWS SecretString must contain a JSON object.\",\n SCHEMA_VALIDATION_FAILED: \"Secret validation failed.\",\n PROCESS_ENV_WRITE_FAILED: \"Failed to write validated secrets to process.env.\",\n TIMEOUT: \"Timed out while fetching secret from AWS Secrets Manager.\",\n INVALID_OPTIONS: \"Invalid loadSecrets options.\",\n UNKNOWN: \"Unexpected error while loading secrets.\",\n};\n\nexport function createError(\n code: LoadSecretsErrorCode,\n options?: { issues?: LoadSecretsIssue[]; cause?: unknown },\n): LoadSecretsError {\n const error: LoadSecretsError = {\n code,\n message: MESSAGES[code],\n };\n if (options?.issues !== undefined) {\n error.issues = options.issues;\n }\n if (options?.cause !== undefined) {\n error.cause = options.cause;\n }\n return error;\n}\n","export class TimeoutError extends Error {\n constructor(message = \"Operation timed out\") {\n super(message);\n this.name = \"TimeoutError\";\n }\n}\n\nexport function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new TimeoutError());\n }, timeoutMs);\n\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err: unknown) => {\n clearTimeout(timer);\n reject(err as Error);\n },\n );\n });\n}\n","import {\n SecretsManagerClient,\n type SecretsManagerClientConfig,\n} from \"@aws-sdk/client-secrets-manager\";\nimport type { AwsCredentialsOption } from \"../core/types.js\";\n\nexport type CreateSecretsManagerClientInput = {\n region?: string;\n credentials?: AwsCredentialsOption;\n};\n\nexport function createSecretsManagerClient(\n input: CreateSecretsManagerClientInput,\n): SecretsManagerClient {\n const config: SecretsManagerClientConfig = {};\n if (input.region !== undefined) {\n config.region = input.region;\n }\n if (input.credentials !== undefined) {\n const { accessKeyId, secretAccessKey, sessionToken } = input.credentials;\n config.credentials =\n sessionToken !== undefined\n ? { accessKeyId, secretAccessKey, sessionToken }\n : { accessKeyId, secretAccessKey };\n }\n return new SecretsManagerClient(config);\n}\n","type CacheEntry = {\n value: string;\n expiresAt: number;\n};\n\nconst cache = new Map<string, CacheEntry>();\n\nexport function buildCacheKey(secretId: string, region?: string): string {\n return `${region ?? \"default\"}:${secretId}`;\n}\n\nexport function getCachedSecretString(key: string, now: number = Date.now()): string | null {\n const entry = cache.get(key);\n if (!entry) return null;\n if (now >= entry.expiresAt) {\n cache.delete(key);\n return null;\n }\n return entry.value;\n}\n\nexport function setCachedSecretString(\n key: string,\n value: string,\n ttlMs: number,\n now: number = Date.now(),\n): void {\n cache.set(key, { value, expiresAt: now + ttlMs });\n}\n\nexport function clearCache(): void {\n cache.clear();\n}\n","import type { SecretSourceMode } from \"./types.js\";\n\nexport type MergeSourcesInput = {\n source: SecretSourceMode;\n providerValues?: Record<string, unknown>;\n processEnvValues?: Record<string, string | undefined>;\n};\n\nexport function mergeSources(input: MergeSourcesInput): Record<string, unknown> {\n const { source, providerValues, processEnvValues } = input;\n\n switch (source) {\n case \"provider-only\":\n return { ...(providerValues ?? {}) };\n case \"process-env-only\":\n return { ...(processEnvValues ?? {}) };\n case \"provider-then-process-env\":\n return { ...(providerValues ?? {}), ...(processEnvValues ?? {}) };\n case \"process-env-then-provider\":\n return { ...(processEnvValues ?? {}), ...(providerValues ?? {}) };\n }\n}\n","import type { SecretSourceMode } from \"./types.js\";\n\nexport const DEFAULT_SOURCE: SecretSourceMode = \"provider-then-process-env\";\nexport const DEFAULT_TIMEOUT_MS = 5000;\nexport const DEFAULT_CACHE_ENABLED = false;\nexport const DEFAULT_CACHE_TTL_MS = 60_000;\nexport const DEFAULT_PROCESS_ENV_MUTATE = false;\nexport const DEFAULT_PROCESS_ENV_OVERWRITE = false;\n","import {\n DEFAULT_CACHE_ENABLED,\n DEFAULT_CACHE_TTL_MS,\n DEFAULT_PROCESS_ENV_MUTATE,\n DEFAULT_PROCESS_ENV_OVERWRITE,\n DEFAULT_SOURCE,\n DEFAULT_TIMEOUT_MS,\n} from \"./constants.js\";\nimport { createError } from \"./errors.js\";\nimport type { LoadSecretsError, LoadSecretsOptions, NormalizedOptions } from \"./types.js\";\n\nexport type NormalizeResult =\n | { success: true; data: NormalizedOptions }\n | { success: false; error: LoadSecretsError };\n\nconst VALID_SOURCES = new Set([\n \"provider-only\",\n \"process-env-only\",\n \"provider-then-process-env\",\n \"process-env-then-provider\",\n]);\n\nexport function normalizeOptions<TSchema extends import(\"zod\").z.ZodTypeAny>(\n options: LoadSecretsOptions<TSchema>,\n): NormalizeResult {\n const source = options.source ?? DEFAULT_SOURCE;\n if (!VALID_SOURCES.has(source)) {\n return { success: false, error: createError(\"INVALID_OPTIONS\") };\n }\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n return { success: false, error: createError(\"INVALID_OPTIONS\") };\n }\n\n const cacheEnabled = options.cache?.enabled ?? DEFAULT_CACHE_ENABLED;\n const cacheTtlMs = options.cache?.ttlMs ?? DEFAULT_CACHE_TTL_MS;\n if (cacheEnabled && (!Number.isFinite(cacheTtlMs) || cacheTtlMs <= 0)) {\n return { success: false, error: createError(\"INVALID_OPTIONS\") };\n }\n\n const mutate = options.processEnv?.mutate ?? DEFAULT_PROCESS_ENV_MUTATE;\n const overwrite = options.processEnv?.overwrite ?? DEFAULT_PROCESS_ENV_OVERWRITE;\n\n const awsInput = options.providers?.aws;\n const aws: NormalizedOptions[\"providers\"][\"aws\"] = {};\n if (awsInput?.secretId !== undefined && awsInput.secretId.length > 0) {\n aws.secretId = awsInput.secretId;\n }\n if (awsInput?.region !== undefined && awsInput.region.length > 0) {\n aws.region = awsInput.region;\n }\n if (awsInput?.credentials !== undefined) {\n const c = awsInput.credentials;\n if (\n typeof c.accessKeyId !== \"string\" ||\n c.accessKeyId.length === 0 ||\n typeof c.secretAccessKey !== \"string\" ||\n c.secretAccessKey.length === 0\n ) {\n return { success: false, error: createError(\"INVALID_OPTIONS\") };\n }\n aws.credentials = {\n accessKeyId: c.accessKeyId,\n secretAccessKey: c.secretAccessKey,\n ...(c.sessionToken !== undefined ? { sessionToken: c.sessionToken } : {}),\n };\n }\n\n return {\n success: true,\n data: {\n source,\n timeoutMs,\n providers: { aws },\n cache: {\n enabled: cacheEnabled,\n ttlMs: cacheTtlMs,\n },\n processEnv: {\n mutate,\n overwrite,\n },\n },\n };\n}\n","import { createError } from \"./errors.js\";\nimport type { LoadSecretsError } from \"./types.js\";\n\nexport function snapshotProcessEnv(): Record<string, string | undefined> {\n const snapshot: Record<string, string | undefined> = {};\n for (const key of Object.keys(process.env)) {\n snapshot[key] = process.env[key];\n }\n return snapshot;\n}\n\nexport function stringifyForEnv(value: unknown): string | null {\n if (value === null || value === undefined) return null;\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\") return String(value);\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return value.toISOString();\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n}\n\nexport type MutationOutcome = {\n success: true;\n writtenKeys: string[];\n skippedKeys: string[];\n};\n\nexport type MutationResult =\n | MutationOutcome\n | { success: false; error: LoadSecretsError; writtenKeys: string[]; skippedKeys: string[] };\n\nexport function mutateProcessEnv(\n validated: Record<string, unknown>,\n overwrite: boolean,\n): MutationResult {\n const candidates: Array<[string, string]> = [];\n const skippedKeys: string[] = [];\n\n for (const key of Object.keys(validated)) {\n const stringified = stringifyForEnv(validated[key]);\n if (stringified === null) {\n skippedKeys.push(key);\n continue;\n }\n if (!overwrite && Object.hasOwn(process.env, key) && process.env[key] !== undefined) {\n skippedKeys.push(key);\n continue;\n }\n candidates.push([key, stringified]);\n }\n\n const previous = new Map<string, string | undefined>();\n const writtenKeys: string[] = [];\n\n try {\n for (const [key, value] of candidates) {\n previous.set(key, process.env[key]);\n process.env[key] = value;\n writtenKeys.push(key);\n }\n return { success: true, writtenKeys, skippedKeys };\n } catch (cause) {\n for (const [key, prev] of previous) {\n if (prev === undefined) {\n delete process.env[key];\n } else {\n process.env[key] = prev;\n }\n }\n return {\n success: false,\n error: createError(\"PROCESS_ENV_WRITE_FAILED\", { cause }),\n writtenKeys: [],\n skippedKeys,\n };\n }\n}\n","import type { SecretSourceMode } from \"./types.js\";\n\nexport function sourceUsesProvider(source: SecretSourceMode): boolean {\n return source !== \"process-env-only\";\n}\n\nexport function sourceUsesProcessEnv(source: SecretSourceMode): boolean {\n return source !== \"provider-only\";\n}\n","import type {\n LoadSecretsError,\n LoadSecretsFailure,\n LoadSecretsMeta,\n LoadSecretsSuccess,\n} from \"./types.js\";\n\nexport function success<TData>(meta: LoadSecretsMeta, data: TData): LoadSecretsSuccess<TData> {\n return {\n success: true,\n data,\n error: null,\n meta,\n };\n}\n\nexport function failure(meta: LoadSecretsMeta, error: LoadSecretsError): LoadSecretsFailure {\n return {\n success: false,\n data: null,\n error,\n meta,\n };\n}\n","import type { z } from \"zod\";\nimport type { LoadSecretsIssue } from \"./types.js\";\n\nexport type ValidateSchemaResult<TData> =\n | { success: true; data: TData }\n | { success: false; issues: LoadSecretsIssue[] };\n\ntype ZodLikeIssue = {\n path: ReadonlyArray<PropertyKey>;\n message: string;\n};\n\ntype ZodLikeError = {\n issues: ReadonlyArray<ZodLikeIssue>;\n};\n\nfunction mapIssues(error: ZodLikeError): LoadSecretsIssue[] {\n return error.issues.map((issue) => {\n const path = issue.path.map((p) => String(p)).join(\".\");\n return {\n path: path.length === 0 ? \"<root>\" : path,\n message: issue.message,\n };\n });\n}\n\nexport async function validateSchema<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n value: unknown,\n): Promise<ValidateSchemaResult<z.output<TSchema>>> {\n const parsed = await schema.safeParseAsync(value);\n if (parsed.success) {\n return { success: true, data: parsed.data as z.output<TSchema> };\n }\n return { success: false, issues: mapIssues(parsed.error as ZodLikeError) };\n}\n","export function isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== \"object\") return false;\n if (Array.isArray(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n","import type { LoadSecretsError } from \"../core/types.js\";\nimport { isPlainRecord } from \"./is-plain-record.js\";\n\nexport type ParseJsonSecretResult =\n | { success: true; data: Record<string, unknown> }\n | { success: false; error: LoadSecretsError };\n\nexport function parseJsonSecret(input: string): ParseJsonSecretResult {\n let parsed: unknown;\n try {\n parsed = JSON.parse(input);\n } catch {\n return {\n success: false,\n error: {\n code: \"SECRET_JSON_INVALID\",\n message: \"AWS SecretString must be valid JSON.\",\n },\n };\n }\n\n if (!isPlainRecord(parsed)) {\n return {\n success: false,\n error: {\n code: \"SECRET_JSON_NOT_OBJECT\",\n message: \"AWS SecretString must contain a JSON object.\",\n },\n };\n }\n\n return { success: true, data: parsed };\n}\n","import type { z } from \"zod\";\nimport { fetchSecretString } from \"./aws/fetch-secret-string.js\";\nimport { buildCacheKey, getCachedSecretString, setCachedSecretString } from \"./core/cache.js\";\nimport { createError } from \"./core/errors.js\";\nimport { mergeSources } from \"./core/merge-sources.js\";\nimport { normalizeOptions } from \"./core/normalize-options.js\";\nimport { mutateProcessEnv, snapshotProcessEnv } from \"./core/process-env.js\";\nimport { sourceUsesProcessEnv, sourceUsesProvider } from \"./core/resolve-source-mode.js\";\nimport { failure, success } from \"./core/result.js\";\nimport type {\n LoadSecretsMeta,\n LoadSecretsOptions,\n LoadSecretsResult,\n NormalizedOptions,\n} from \"./core/types.js\";\nimport { validateSchema } from \"./core/validate-schema.js\";\nimport { parseJsonSecret } from \"./utils/parse-json-secret.js\";\n\nfunction createInitialMeta(normalized: NormalizedOptions): LoadSecretsMeta {\n const meta: LoadSecretsMeta = {\n source: normalized.source,\n loadedAt: new Date(),\n cache: {\n enabled: normalized.cache.enabled,\n hit: false,\n },\n usedSources: {\n aws: sourceUsesProvider(normalized.source),\n processEnv: sourceUsesProcessEnv(normalized.source),\n },\n processEnvMutation: {\n requested: normalized.processEnv.mutate,\n performed: false,\n overwrite: normalized.processEnv.overwrite,\n writtenKeys: [],\n skippedKeys: [],\n },\n };\n if (normalized.providers.aws.secretId !== undefined) {\n meta.secretId = normalized.providers.aws.secretId;\n }\n if (normalized.providers.aws.region !== undefined) {\n meta.region = normalized.providers.aws.region;\n }\n if (normalized.cache.enabled) {\n meta.cache.ttlMs = normalized.cache.ttlMs;\n }\n return meta;\n}\n\nexport async function loadSecrets<TSchema extends z.ZodTypeAny>(\n options: LoadSecretsOptions<TSchema>,\n): Promise<LoadSecretsResult<z.output<TSchema>>> {\n const normalizedResult = normalizeOptions(options);\n\n if (!normalizedResult.success) {\n const reportedSource = options.source ?? \"provider-then-process-env\";\n const baseMeta: LoadSecretsMeta = {\n source: reportedSource,\n loadedAt: new Date(),\n cache: { enabled: false, hit: false },\n usedSources: {\n aws: sourceUsesProvider(reportedSource),\n processEnv: sourceUsesProcessEnv(reportedSource),\n },\n processEnvMutation: {\n requested: options.processEnv?.mutate ?? false,\n performed: false,\n overwrite: options.processEnv?.overwrite ?? false,\n writtenKeys: [],\n skippedKeys: [],\n },\n };\n return failure(baseMeta, normalizedResult.error);\n }\n\n const normalized = normalizedResult.data;\n const meta = createInitialMeta(normalized);\n\n try {\n let providerValues: Record<string, unknown> | undefined;\n let processEnvValues: Record<string, string | undefined> | undefined;\n\n if (sourceUsesProvider(normalized.source)) {\n const aws = normalized.providers.aws;\n if (aws.secretId === undefined || aws.secretId.length === 0) {\n return failure(meta, createError(\"AWS_SECRET_ID_MISSING\"));\n }\n\n let secretString: string | null = null;\n const cacheKey = buildCacheKey(aws.secretId, aws.region);\n\n if (normalized.cache.enabled) {\n secretString = getCachedSecretString(cacheKey);\n if (secretString !== null) {\n meta.cache.hit = true;\n }\n }\n\n if (secretString === null) {\n const fetchResult = await fetchSecretString({\n secretId: aws.secretId,\n timeoutMs: normalized.timeoutMs,\n ...(aws.region !== undefined ? { region: aws.region } : {}),\n ...(aws.credentials !== undefined ? { credentials: aws.credentials } : {}),\n });\n\n if (!fetchResult.success) {\n return failure(meta, fetchResult.error);\n }\n\n secretString = fetchResult.secretString;\n\n if (normalized.cache.enabled) {\n setCachedSecretString(cacheKey, secretString, normalized.cache.ttlMs);\n }\n }\n\n const parsed = parseJsonSecret(secretString);\n if (!parsed.success) {\n return failure(meta, parsed.error);\n }\n providerValues = parsed.data;\n }\n\n if (sourceUsesProcessEnv(normalized.source)) {\n processEnvValues = snapshotProcessEnv();\n }\n\n const merged = mergeSources({\n source: normalized.source,\n ...(providerValues !== undefined ? { providerValues } : {}),\n ...(processEnvValues !== undefined ? { processEnvValues } : {}),\n });\n\n const validation = await validateSchema(options.schema, merged);\n\n if (!validation.success) {\n return failure(meta, createError(\"SCHEMA_VALIDATION_FAILED\", { issues: validation.issues }));\n }\n\n if (normalized.processEnv.mutate) {\n const validatedRecord =\n validation.data && typeof validation.data === \"object\" && !Array.isArray(validation.data)\n ? (validation.data as Record<string, unknown>)\n : {};\n const mutation = mutateProcessEnv(validatedRecord, normalized.processEnv.overwrite);\n meta.processEnvMutation.writtenKeys = mutation.writtenKeys;\n meta.processEnvMutation.skippedKeys = mutation.skippedKeys;\n if (!mutation.success) {\n return failure(meta, mutation.error);\n }\n meta.processEnvMutation.performed = true;\n }\n\n return success(meta, validation.data);\n } catch (cause) {\n return failure(meta, createError(\"UNKNOWN\", { cause }));\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@danielsoren/secrets-loader",
3
+ "version": "0.1.0",
4
+ "description": "Load one JSON secret from a provider (AWS today; pluggable in the future), merge local overrides, validate with Zod, and return typed config.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
10
+ "files": ["dist", "README.md", "LICENSE"],
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "types": "./dist/index.d.ts",
18
+ "main": "./dist/index.js",
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "biome check .",
23
+ "format": "biome format --write .",
24
+ "test": "vitest run",
25
+ "test:watch": "vitest",
26
+ "prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
27
+ },
28
+ "keywords": ["secrets", "secrets-manager", "aws", "config", "env", "zod", "typescript", "esm"],
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "@aws-sdk/client-secrets-manager": "^3.0.0"
32
+ },
33
+ "peerDependencies": {
34
+ "zod": "^4.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "^1.9.4",
38
+ "@changesets/cli": "^2.27.0",
39
+ "@types/node": "^22.0.0",
40
+ "tsup": "^8.0.0",
41
+ "typescript": "^5.7.0",
42
+ "vitest": "^3.0.0",
43
+ "zod": "^4.0.0"
44
+ }
45
+ }