@geekmidas/envkit 9.0.2 → 10.0.0-alpha.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 (50) hide show
  1. package/README.md +2 -2
  2. package/dist/{EnvironmentBuilder-DDgLJAAo.cjs → EnvironmentBuilder-15SdFJdK.cjs} +2 -2
  3. package/dist/EnvironmentBuilder-15SdFJdK.cjs.map +1 -0
  4. package/dist/{EnvironmentBuilder-CFen3oIg.mjs → EnvironmentBuilder-C-2fViCT.mjs} +2 -2
  5. package/dist/EnvironmentBuilder-C-2fViCT.mjs.map +1 -0
  6. package/dist/{EnvironmentBuilder-DHfDXJUm.d.mts → EnvironmentBuilder-CoBQ9Xp2.d.mts} +2 -2
  7. package/dist/{EnvironmentBuilder-CNgcdzSR.d.cts.map → EnvironmentBuilder-CoBQ9Xp2.d.mts.map} +1 -1
  8. package/dist/{EnvironmentBuilder-CNgcdzSR.d.cts → EnvironmentBuilder-DeIle4QN.d.cts} +2 -2
  9. package/dist/{EnvironmentBuilder-DHfDXJUm.d.mts.map → EnvironmentBuilder-DeIle4QN.d.cts.map} +1 -1
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.mjs +1 -1
  14. package/dist/sst.cjs +13 -6
  15. package/dist/sst.cjs.map +1 -1
  16. package/dist/sst.d.cts +15 -4
  17. package/dist/sst.d.cts.map +1 -1
  18. package/dist/sst.d.mts +15 -4
  19. package/dist/sst.d.mts.map +1 -1
  20. package/dist/sst.mjs +13 -6
  21. package/dist/sst.mjs.map +1 -1
  22. package/package.json +6 -2
  23. package/CHANGELOG.md +0 -115
  24. package/dist/EnvironmentBuilder-CFen3oIg.mjs.map +0 -1
  25. package/dist/EnvironmentBuilder-DDgLJAAo.cjs.map +0 -1
  26. package/docs/api-reference.md +0 -302
  27. package/docs/async-secrets-design.md +0 -355
  28. package/examples/basic-usage.ts +0 -386
  29. package/src/EnvironmentBuilder.ts +0 -192
  30. package/src/EnvironmentParser.ts +0 -330
  31. package/src/SnifferEnvironmentParser.ts +0 -334
  32. package/src/SstEnvValidator.ts +0 -369
  33. package/src/SstEnvironmentBuilder.ts +0 -343
  34. package/src/__tests__/ConfigParser.spec.ts +0 -394
  35. package/src/__tests__/EnvironmentBuilder.spec.ts +0 -254
  36. package/src/__tests__/EnvironmentParser.spec.ts +0 -839
  37. package/src/__tests__/SnifferEnvironmentParser.spec.ts +0 -644
  38. package/src/__tests__/SstEnvValidator.spec.ts +0 -236
  39. package/src/__tests__/SstEnvironmentBuilder.spec.ts +0 -397
  40. package/src/__tests__/credentials.integration.spec.ts +0 -239
  41. package/src/__tests__/credentials.spec.ts +0 -136
  42. package/src/__tests__/formatter.spec.ts +0 -268
  43. package/src/__tests__/sst.spec.ts +0 -437
  44. package/src/credentials.ts +0 -112
  45. package/src/formatter.ts +0 -146
  46. package/src/index.ts +0 -24
  47. package/src/sst.ts +0 -76
  48. package/sst-env.d.ts +0 -8
  49. package/tsconfig.json +0 -9
  50. package/tsdown.config.ts +0 -18
@@ -1,369 +0,0 @@
1
- import { EnvironmentBuilder, type InputValue } from './EnvironmentBuilder';
2
- import { type SstResource, sstResolvers } from './SstEnvironmentBuilder';
3
-
4
- /**
5
- * Variables AWS Lambda injects into the runtime. Always valid on AWS, but never
6
- * assumed — opt in via `platform: 'aws'` or by spreading {@link AWS_RUNTIME_ENV_VARS}
7
- * into `whitelist`.
8
- */
9
- export const AWS_RUNTIME_ENV_VARS = [
10
- 'AWS_REGION',
11
- 'AWS_DEFAULT_REGION',
12
- 'AWS_ENDPOINT_URL',
13
- 'AWS_ENDPOINT',
14
- 'AWS_ACCESS_KEY_ID',
15
- 'AWS_SECRET_ACCESS_KEY',
16
- 'AWS_SESSION_TOKEN',
17
- 'AWS_LAMBDA_FUNCTION_NAME',
18
- 'AWS_LAMBDA_FUNCTION_VERSION',
19
- ] as const;
20
-
21
- /**
22
- * Variables GCP injects into Cloud Functions / Cloud Run.
23
- */
24
- export const GCP_RUNTIME_ENV_VARS = [
25
- 'GOOGLE_CLOUD_PROJECT',
26
- 'GCP_PROJECT',
27
- 'FUNCTION_TARGET',
28
- 'FUNCTION_SIGNATURE_TYPE',
29
- 'K_SERVICE',
30
- 'K_REVISION',
31
- 'K_CONFIGURATION',
32
- 'PORT',
33
- ] as const;
34
-
35
- /**
36
- * Variables Cloudflare exposes to Workers. Workers inject almost nothing into
37
- * the global env, so this is intentionally minimal.
38
- */
39
- export const CLOUDFLARE_RUNTIME_ENV_VARS = [
40
- 'CF_PAGES',
41
- 'CF_PAGES_URL',
42
- ] as const;
43
-
44
- /**
45
- * Registry of platform → always-valid runtime variables. Export, don't assume:
46
- * a caller states the platform it deploys to and the matching set is trusted.
47
- */
48
- export const PLATFORM_ENV_VARS = {
49
- aws: AWS_RUNTIME_ENV_VARS,
50
- gcp: GCP_RUNTIME_ENV_VARS,
51
- cloudflare: CLOUDFLARE_RUNTIME_ENV_VARS,
52
- } as const;
53
-
54
- export type Platform = keyof typeof PLATFORM_ENV_VARS;
55
-
56
- /**
57
- * The always-valid runtime variables for a platform. The "whitelist construct":
58
- * resolve a platform to its set, to spread into `whitelist` or compare against.
59
- */
60
- export function platformEnvVars(platform: Platform): readonly string[] {
61
- return PLATFORM_ENV_VARS[platform];
62
- }
63
-
64
- /** How many link-provided vars to list in an error before truncating. */
65
- const MAX_LISTED_VARS = 8;
66
-
67
- /** Minimum similarity for a variable to be offered as a "did you mean". */
68
- const SUGGESTION_THRESHOLD = 0.3;
69
-
70
- /**
71
- * Input shape for the validator: the same record `SstEnvironmentBuilder`
72
- * accepts — keyed by resource name, valued by an SST resource (or a plain
73
- * string for pass-through env vars).
74
- */
75
- export type LinkRecord = Record<string, SstResource | InputValue | string>;
76
-
77
- export interface ValidationResult {
78
- valid: boolean;
79
- /** Requested variables that are not available (excluding optional `?` vars). */
80
- invalidVars: string[];
81
- /** Every variable the links + whitelist make available. */
82
- availableVars: string[];
83
- /** Nearest available matches for each invalid variable (best first). */
84
- suggestions: Record<string, string[]>;
85
- }
86
-
87
- export interface EnvValidatorOptions {
88
- /**
89
- * Platform whose runtime-injected variables are always valid. Omit to assume
90
- * none — the validator never bakes in a platform's variables.
91
- */
92
- platform?: Platform;
93
- /**
94
- * Extra always-valid variables in addition to the platform set — typically
95
- * the keys of a function's explicit `environment`.
96
- */
97
- whitelist?: readonly string[];
98
- /**
99
- * Label for the deployable unit (e.g. a function name), used in error
100
- * messages so a failure points at exactly one unit.
101
- */
102
- context?: string;
103
- }
104
-
105
- /**
106
- * Levenshtein edit distance between two strings.
107
- */
108
- function levenshtein(a: string, b: string): number {
109
- const m = a.length;
110
- const n = b.length;
111
- if (m === 0) return n;
112
- if (n === 0) return m;
113
-
114
- let prev = Array.from({ length: n + 1 }, (_, i) => i);
115
-
116
- for (let i = 1; i <= m; i++) {
117
- const curr: number[] = [i];
118
- for (let j = 1; j <= n; j++) {
119
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
120
- const del = (prev[j] ?? 0) + 1;
121
- const ins = (curr[j - 1] ?? 0) + 1;
122
- const sub = (prev[j - 1] ?? 0) + cost;
123
- curr[j] = Math.min(del, ins, sub);
124
- }
125
- prev = curr;
126
- }
127
-
128
- return prev[n] ?? 0;
129
- }
130
-
131
- /**
132
- * Similarity in `[0, 1]` combining edit distance with `_`-token overlap, so both
133
- * near-typos (a missing underscore) and renames (`DATABASE_URL` ~ `DB_URL`,
134
- * which share the `URL` token) are caught.
135
- */
136
- function similarity(a: string, b: string): number {
137
- const editSim = 1 - levenshtein(a, b) / Math.max(a.length, b.length, 1);
138
-
139
- const ta = new Set(a.split('_').filter(Boolean));
140
- const tb = new Set(b.split('_').filter(Boolean));
141
- const shared = [...ta].filter((t) => tb.has(t)).length;
142
- const union = new Set([...ta, ...tb]).size || 1;
143
- const tokenSim = shared / union;
144
-
145
- // Nudge token-overlap matches ahead of coincidental edit-distance ones, so a
146
- // rename like DATABASE_URL prefers DB_URL (shares `URL`) over DB_PASSWORD.
147
- return Math.max(editSim, tokenSim * 1.15);
148
- }
149
-
150
- /**
151
- * Returns the nearest available variables to `missing`, best first, limited to
152
- * those above {@link SUGGESTION_THRESHOLD}.
153
- */
154
- function suggestionsFor(
155
- missing: string,
156
- available: readonly string[],
157
- max = 1,
158
- ): string[] {
159
- return available
160
- .map((candidate) => ({ candidate, score: similarity(missing, candidate) }))
161
- .filter((s) => s.score >= SUGGESTION_THRESHOLD)
162
- .sort((a, b) => b.score - a.score)
163
- .slice(0, max)
164
- .map((s) => s.candidate);
165
- }
166
-
167
- /**
168
- * Thrown when a deployable unit requires environment variables its links do not
169
- * provide. Carries the structured `missing`/`available`/`suggestions` so callers
170
- * can inspect the failure programmatically, not just read the message.
171
- */
172
- export class EnvValidationError extends Error {
173
- readonly isEnvValidationError = true;
174
- readonly missing: string[];
175
- readonly available: string[];
176
- readonly suggestions: Record<string, string[]>;
177
- readonly context?: string;
178
-
179
- constructor(args: {
180
- missing: string[];
181
- available: string[];
182
- linkVars: string[];
183
- suggestions: Record<string, string[]>;
184
- context?: string;
185
- }) {
186
- super(EnvValidationError.format(args));
187
- this.name = 'EnvValidationError';
188
- this.missing = args.missing;
189
- this.available = args.available;
190
- this.suggestions = args.suggestions;
191
- this.context = args.context;
192
- // V8-only; keeps the constructor out of the stack trace.
193
- (
194
- Error as unknown as {
195
- captureStackTrace?(target: object, ctor?: unknown): void;
196
- }
197
- ).captureStackTrace?.(this, EnvValidationError);
198
- }
199
-
200
- private static format(args: {
201
- missing: string[];
202
- linkVars: string[];
203
- suggestions: Record<string, string[]>;
204
- context?: string;
205
- }): string {
206
- const { missing, linkVars, suggestions, context } = args;
207
- const who = context ? `'${context}'` : 'a deployable unit';
208
-
209
- const width = Math.max(...missing.map((v) => v.length));
210
- const lines = missing.map((v) => {
211
- const hints = suggestions[v] ?? [];
212
- const hint = hints.length ? `(did you mean ${hints.join(' or ')}?)` : '';
213
- return ` - ${v.padEnd(width)} ${hint}`.trimEnd();
214
- });
215
-
216
- let provided: string;
217
- if (linkVars.length === 0) {
218
- provided = '(no links provide environment variables)';
219
- } else {
220
- const shown = linkVars.slice(0, MAX_LISTED_VARS).join(', ');
221
- const extra =
222
- linkVars.length > MAX_LISTED_VARS
223
- ? ` ...(+${linkVars.length - MAX_LISTED_VARS})`
224
- : '';
225
- provided = `Provided by links: ${shown}${extra}`;
226
- }
227
-
228
- return `${who} is missing required env vars:\n${lines.join('\n')}\n\n${provided}`;
229
- }
230
- }
231
-
232
- /**
233
- * Derives the set of environment-variable **keys** a group of linked resources
234
- * will produce at runtime — without resolving any value.
235
- *
236
- * This reuses the runtime resolvers (`sstResolvers`) via {@link EnvironmentBuilder},
237
- * so the keys computed here are exactly the keys a deployed function receives:
238
- * the validator and the runtime resolution share a single source of truth and
239
- * cannot drift. Because every resolver's output keys depend only on the record
240
- * key and the resource `type` (never the value), each resource is reduced to
241
- * `{ type }` before building, so no SST `Output` is ever read.
242
- *
243
- * @example
244
- * resolveEnvKeys({ db: { type: ResourceType.Postgres } });
245
- * // ['DB_NAME', 'DB_HOST', 'DB_PASSWORD', 'DB_PORT', 'DB_USERNAME', 'DB_URL']
246
- */
247
- export function resolveEnvKeys(record: LinkRecord): string[] {
248
- const typeOnly: Record<string, InputValue> = {};
249
- for (const [key, value] of Object.entries(record)) {
250
- typeOnly[key] = typeof value === 'string' ? '' : { type: value.type };
251
- }
252
- return Object.keys(new EnvironmentBuilder(typeOnly, sstResolvers).build());
253
- }
254
-
255
- /**
256
- * Validates that the environment variables a deployable unit requires are
257
- * actually provided by its linked resources — at infra time, before deploy.
258
- *
259
- * Available variables are the union of the keys derived from `links`
260
- * ({@link resolveEnvKeys}), the chosen platform's runtime variables (only when
261
- * `options.platform` is set — nothing is assumed), and any `options.whitelist`
262
- * (e.g. the keys of a function's explicit `environment`). A requested variable
263
- * suffixed with `?` is treated as optional and never causes a failure.
264
- *
265
- * @example
266
- * const validator = new EnvValidator(
267
- * { db: { type: ResourceType.Postgres } },
268
- * { platform: 'aws', whitelist: ['APP_NAME'], context: 'orders-fn' },
269
- * );
270
- * validator.assert(['DB_HOST', 'AWS_REGION', 'APP_NAME', 'SENTRY_DSN?']); // ok
271
- * validator.assert(['DATABASE_URL']); // throws EnvValidationError (did you mean DB_URL?)
272
- */
273
- export class EnvValidator {
274
- /** Every variable available — link-derived, platform, and extra whitelist. */
275
- readonly availableVars: string[];
276
- /** Just the variables the links provide (used for error context). */
277
- readonly linkVars: string[];
278
- readonly context?: string;
279
- private readonly availableSet: Set<string>;
280
- /** Per-link (record key → its env-var keys), for least-privilege filtering. */
281
- private readonly linkVarsByName: Map<string, string[]>;
282
-
283
- constructor(links: LinkRecord, options: EnvValidatorOptions = {}) {
284
- const { platform, whitelist = [], context } = options;
285
- this.context = context;
286
- this.linkVarsByName = new Map(
287
- Object.entries(links).map(([name, value]) => [
288
- name,
289
- resolveEnvKeys({ [name]: value }),
290
- ]),
291
- );
292
- this.linkVars = [...this.linkVarsByName.values()].flat();
293
- this.availableVars = [
294
- ...this.linkVars,
295
- ...(platform ? PLATFORM_ENV_VARS[platform] : []),
296
- ...whitelist,
297
- ];
298
- this.availableSet = new Set(this.availableVars);
299
- }
300
-
301
- /**
302
- * Returns the names of the links that provide at least one of the requested
303
- * variables — so a construct can attach only the links a unit actually needs
304
- * (least privilege) rather than the whole pool. Trailing `?` is ignored.
305
- */
306
- getProvidersForEnvVars(requestedEnvVars: readonly string[]): string[] {
307
- const requested = new Set(
308
- requestedEnvVars.map((v) => (v.endsWith('?') ? v.slice(0, -1) : v)),
309
- );
310
- const providers: string[] = [];
311
- for (const [name, vars] of this.linkVarsByName) {
312
- if (vars.some((v) => requested.has(v))) providers.push(name);
313
- }
314
- return providers;
315
- }
316
-
317
- /**
318
- * Checks every requested variable against the available set. Variables
319
- * ending in `?` are optional and never reported as invalid. Invalid
320
- * variables come back with their nearest available matches.
321
- */
322
- validate(requestedEnvVars: readonly string[]): ValidationResult {
323
- const invalidVars = requestedEnvVars.filter((envVar) => {
324
- if (envVar.endsWith('?')) return false;
325
- return !this.availableSet.has(envVar);
326
- });
327
-
328
- const suggestions: Record<string, string[]> = {};
329
- for (const envVar of invalidVars) {
330
- const matches = suggestionsFor(envVar, this.availableVars);
331
- if (matches.length) suggestions[envVar] = matches;
332
- }
333
-
334
- return {
335
- valid: invalidVars.length === 0,
336
- invalidVars,
337
- availableVars: this.availableVars,
338
- suggestions,
339
- };
340
- }
341
-
342
- /**
343
- * Asserts that every requested variable is available, throwing an
344
- * {@link EnvValidationError} otherwise. Intended to run in a construct's
345
- * constructor so a misconfigured `sst.config.ts` fails at synth time, before
346
- * any AWS call.
347
- */
348
- assert(requestedEnvVars: readonly string[]): void {
349
- const { valid, invalidVars, suggestions } = this.validate(requestedEnvVars);
350
-
351
- if (!valid) {
352
- throw new EnvValidationError({
353
- missing: invalidVars,
354
- available: this.availableVars,
355
- linkVars: this.linkVars,
356
- suggestions,
357
- context: this.context,
358
- });
359
- }
360
- }
361
-
362
- /**
363
- * Whether a single variable is available. Does not treat a trailing `?` as
364
- * optional — pass the bare name.
365
- */
366
- has(envVar: string): boolean {
367
- return this.availableSet.has(envVar);
368
- }
369
- }
@@ -1,343 +0,0 @@
1
- import {
2
- EnvironmentBuilder,
3
- type EnvironmentBuilderOptions,
4
- type EnvironmentResolver,
5
- type EnvRecord,
6
- type InputValue,
7
- type Resolvers,
8
- } from './EnvironmentBuilder';
9
-
10
- /**
11
- * Enumeration of supported SST (Serverless Stack Toolkit) resource types.
12
- * Used to identify and process different AWS and SST resources.
13
- */
14
- export enum ResourceType {
15
- // Legacy format (dot notation)
16
- ApiGatewayV2 = 'sst.aws.ApiGatewayV2',
17
- Postgres = 'sst.aws.Postgres',
18
- Function = 'sst.aws.Function',
19
- Bucket = 'sst.aws.Bucket',
20
- Vpc = 'sst.aws.Vpc',
21
- Secret = 'sst.sst.Secret',
22
- Dynamo = 'sst.aws.Dynamo',
23
- Queue = 'sst.aws.Queue',
24
-
25
- // Modern format (colon notation)
26
- SSTSecret = 'sst:sst:Secret',
27
- SSTFunction = 'sst:sst:Function',
28
- SSTApiGatewayV2 = 'sst:aws:ApiGatewayV2',
29
- SSTPostgres = 'sst:aws:Postgres',
30
- SSTBucket = 'sst:aws:Bucket',
31
- SnsTopic = 'sst:aws:SnsTopic',
32
- SSTDynamo = 'sst:aws:Dynamo',
33
- SSTQueue = 'sst:aws:Queue',
34
- }
35
-
36
- /**
37
- * AWS API Gateway V2 resource type.
38
- * Represents an HTTP/WebSocket API.
39
- */
40
- export type ApiGatewayV2 = {
41
- type: ResourceType.ApiGatewayV2 | ResourceType.SSTApiGatewayV2;
42
- url: string;
43
- };
44
-
45
- /**
46
- * PostgreSQL database resource type.
47
- * Contains all connection details needed to connect to the database.
48
- */
49
- export type Postgres = {
50
- type: ResourceType.Postgres | ResourceType.SSTPostgres;
51
- database: string;
52
- host: string;
53
- password: string;
54
- port: number;
55
- username: string;
56
- };
57
-
58
- /**
59
- * AWS Lambda Function resource type.
60
- */
61
- export type Function = {
62
- type: ResourceType.Function | ResourceType.SSTFunction;
63
- name: string;
64
- };
65
-
66
- /**
67
- * AWS S3 Bucket resource type.
68
- */
69
- export type Bucket = {
70
- type: ResourceType.Bucket | ResourceType.SSTBucket;
71
- name: string;
72
- };
73
-
74
- /**
75
- * AWS VPC (Virtual Private Cloud) resource type.
76
- */
77
- export type Vpc = {
78
- type: ResourceType.Vpc;
79
- bastion: string;
80
- };
81
-
82
- /**
83
- * Secret resource type for storing sensitive values.
84
- */
85
- export type Secret = {
86
- type: ResourceType.Secret | ResourceType.SSTSecret;
87
- value: string;
88
- };
89
-
90
- /**
91
- * AWS SNS Topic resource type.
92
- */
93
- export type SnsTopic = {
94
- type: ResourceType.SnsTopic;
95
- arn: string;
96
- };
97
-
98
- /**
99
- * AWS SQS Queue resource type.
100
- */
101
- export type Queue = {
102
- type: ResourceType.Queue | ResourceType.SSTQueue;
103
- url: string;
104
- arn: string;
105
- };
106
-
107
- /**
108
- * AWS DynamoDB Table resource type.
109
- */
110
- export type Dynamo = {
111
- type: ResourceType.Dynamo | ResourceType.SSTDynamo;
112
- name: string;
113
- };
114
-
115
- /**
116
- * Union type of all supported SST resource types.
117
- */
118
- export type SstResource =
119
- | ApiGatewayV2
120
- | Postgres
121
- | Function
122
- | Bucket
123
- | Vpc
124
- | Secret
125
- | SnsTopic
126
- | Queue
127
- | Dynamo;
128
-
129
- // Value types without the `type` key (for resolver parameters)
130
- type SecretValue = Omit<Secret, 'type'>;
131
- type PostgresValue = Omit<Postgres, 'type'>;
132
- type BucketValue = Omit<Bucket, 'type'>;
133
- type SnsTopicValue = Omit<SnsTopic, 'type'>;
134
- type QueueValue = Omit<Queue, 'type'>;
135
- type DynamoValue = Omit<Dynamo, 'type'>;
136
-
137
- /**
138
- * Function type for processing a specific resource type into environment variables.
139
- *
140
- * @template K - The specific resource type (without `type` key)
141
- * @param name - The resource name
142
- * @param value - The resource value (without `type` key)
143
- * @returns Object mapping environment variable names to values
144
- */
145
- export type ResourceProcessor<K> = (name: string, value: K) => EnvRecord;
146
-
147
- // SST Resource Resolvers (receive values without `type` key)
148
-
149
- const secretResolver = (name: string, value: SecretValue) => ({
150
- [name]: value.value,
151
- });
152
-
153
- const postgresResolver = (key: string, value: PostgresValue) => ({
154
- [`${key}Name`]: value.database,
155
- [`${key}Host`]: value.host,
156
- [`${key}Password`]: value.password,
157
- [`${key}Port`]: value.port,
158
- [`${key}Username`]: value.username,
159
- [`${key}Url`]: `postgresql://${encodeURIComponent(value.username)}:${encodeURIComponent(value.password)}@${value.host}:${value.port}/${value.database}`,
160
- });
161
-
162
- const bucketResolver = (name: string, value: BucketValue) => ({
163
- [`${name}Name`]: value.name,
164
- });
165
-
166
- const topicResolver = (name: string, value: SnsTopicValue) => ({
167
- [`${name}Arn`]: value.arn,
168
- // A publisher connection string for `@geekmidas/events` (SNS); region is
169
- // resolved from the ARN / AWS_REGION at runtime.
170
- [`${name}PublisherConnectionString`]: `sns://?topicArn=${encodeURIComponent(value.arn)}`,
171
- });
172
-
173
- const queueResolver = (name: string, value: QueueValue) => ({
174
- [`${name}Url`]: value.url,
175
- [`${name}Arn`]: value.arn,
176
- // A publisher connection string for `@geekmidas/events` (SQS); region is
177
- // resolved from the URL / AWS_REGION at runtime.
178
- [`${name}PublisherConnectionString`]: `sqs://?queueUrl=${encodeURIComponent(value.url)}`,
179
- });
180
-
181
- const dynamoResolver = (name: string, value: DynamoValue) => ({
182
- [`${name}Name`]: value.name,
183
- });
184
-
185
- const noopResolver = () => ({});
186
-
187
- /**
188
- * Pre-configured resolvers for all SST resource types.
189
- */
190
- export const sstResolvers: Resolvers = {
191
- // Legacy format
192
- [ResourceType.ApiGatewayV2]: noopResolver,
193
- [ResourceType.Function]: noopResolver,
194
- [ResourceType.Vpc]: noopResolver,
195
- [ResourceType.Secret]: secretResolver,
196
- [ResourceType.Postgres]: postgresResolver,
197
- [ResourceType.Bucket]: bucketResolver,
198
- [ResourceType.Dynamo]: dynamoResolver,
199
- [ResourceType.Queue]: queueResolver,
200
-
201
- // Modern format
202
- [ResourceType.SSTSecret]: secretResolver,
203
- [ResourceType.SSTBucket]: bucketResolver,
204
- [ResourceType.SSTFunction]: noopResolver,
205
- [ResourceType.SSTPostgres]: postgresResolver,
206
- [ResourceType.SSTApiGatewayV2]: noopResolver,
207
- [ResourceType.SnsTopic]: topicResolver,
208
- [ResourceType.SSTDynamo]: dynamoResolver,
209
- [ResourceType.SSTQueue]: queueResolver,
210
- };
211
-
212
- /**
213
- * All known SST resource type strings.
214
- */
215
- type SstResourceTypeString = `${ResourceType}`;
216
-
217
- /**
218
- * Extracts the `type` string value from an input value.
219
- */
220
- type ExtractType<T> = T extends { type: infer U extends string } ? U : never;
221
-
222
- /**
223
- * Removes the `type` key from an object type.
224
- */
225
- type OmitType<T> = T extends { type: string } ? Omit<T, 'type'> : never;
226
-
227
- /**
228
- * Extracts all unique `type` values from a record (excluding plain strings).
229
- */
230
- type AllTypeValues<TRecord extends Record<string, InputValue>> = {
231
- [K in keyof TRecord]: ExtractType<TRecord[K]>;
232
- }[keyof TRecord];
233
-
234
- /**
235
- * Extracts only the custom (non-SST) type values from a record.
236
- */
237
- type CustomTypeValues<TRecord extends Record<string, InputValue>> = Exclude<
238
- AllTypeValues<TRecord>,
239
- SstResourceTypeString
240
- >;
241
-
242
- /**
243
- * For a given type value, finds the corresponding value type (without `type` key).
244
- */
245
- type ValueForType<
246
- TRecord extends Record<string, InputValue>,
247
- TType extends string,
248
- > = {
249
- [K in keyof TRecord]: TRecord[K] extends { type: TType }
250
- ? OmitType<TRecord[K]>
251
- : never;
252
- }[keyof TRecord];
253
-
254
- /**
255
- * Generates typed resolvers for custom (non-SST) types in the input record.
256
- */
257
- type CustomResolvers<TRecord extends Record<string, InputValue>> =
258
- CustomTypeValues<TRecord> extends never
259
- ? Resolvers | undefined
260
- : {
261
- [TType in CustomTypeValues<TRecord>]: EnvironmentResolver<
262
- ValueForType<TRecord, TType>
263
- >;
264
- };
265
-
266
- /**
267
- * SST-specific environment builder with built-in resolvers for all known
268
- * SST resource types.
269
- *
270
- * Wraps the generic EnvironmentBuilder with pre-configured SST resolvers.
271
- *
272
- * @template TRecord - The input record type for type inference
273
- *
274
- * @example
275
- * ```typescript
276
- * const env = new SstEnvironmentBuilder({
277
- * database: { type: 'sst:aws:Postgres', host: '...', ... },
278
- * apiKey: { type: 'sst:sst:Secret', value: 'secret' },
279
- * appName: 'my-app',
280
- * }).build();
281
- *
282
- * // With custom resolvers (typed based on input)
283
- * const env = new SstEnvironmentBuilder(
284
- * {
285
- * database: postgresResource,
286
- * custom: { type: 'my-custom' as const, data: 'foo' },
287
- * },
288
- * {
289
- * // TypeScript requires 'my-custom' resolver with typed value
290
- * 'my-custom': (key, value) => ({ [`${key}Data`]: value.data }),
291
- * }
292
- * ).build();
293
- * ```
294
- */
295
- export class SstEnvironmentBuilder<
296
- TRecord extends Record<string, SstResource | InputValue | string>,
297
- > {
298
- private readonly builder: EnvironmentBuilder<
299
- Record<string, InputValue>,
300
- Resolvers
301
- >;
302
-
303
- /**
304
- * Create a new SST environment builder.
305
- *
306
- * @param record - Object containing SST resources, custom resources, and/or string values
307
- * @param additionalResolvers - Optional custom resolvers (typed based on custom types in record)
308
- * @param options - Optional configuration options
309
- */
310
- constructor(
311
- record: TRecord,
312
- additionalResolvers?: CustomResolvers<TRecord>,
313
- options?: EnvironmentBuilderOptions,
314
- ) {
315
- // Merge resolvers with custom ones taking precedence
316
- const mergedResolvers: Resolvers = additionalResolvers
317
- ? { ...sstResolvers, ...additionalResolvers }
318
- : sstResolvers;
319
-
320
- this.builder = new EnvironmentBuilder(
321
- record as Record<string, InputValue>,
322
- mergedResolvers,
323
- options,
324
- );
325
- }
326
-
327
- /**
328
- * Build environment variables from the input record.
329
- *
330
- * @returns A record of environment variables
331
- */
332
- build(): EnvRecord {
333
- return this.builder.build();
334
- }
335
- }
336
-
337
- export type {
338
- EnvironmentBuilderOptions,
339
- EnvRecord,
340
- EnvValue,
341
- } from './EnvironmentBuilder';
342
- // Re-export useful types
343
- export { environmentCase } from './EnvironmentBuilder';