alchemy 2.0.0-beta.2 → 2.0.0-beta.4

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 (77) hide show
  1. package/bin/{alchemy-effect.js → alchemy.js} +171 -171
  2. package/bin/alchemy.js.map +1 -0
  3. package/bin/{alchemy-effect.sh → alchemy.sh} +2 -2
  4. package/lib/cli/index.d.ts.map +1 -1
  5. package/lib/cli/index.js +167 -167
  6. package/lib/cli/index.js.map +1 -1
  7. package/package.json +54 -45
  8. package/src/AWS/AGENTS.md +10 -10
  9. package/src/AWS/Assets.ts +1 -1
  10. package/src/AWS/AuthProvider.ts +392 -0
  11. package/src/AWS/Credentials.ts +0 -1
  12. package/src/AWS/DynamoDB/Table.ts +63 -10
  13. package/src/AWS/EC2/VpcEndpoint.ts +4 -4
  14. package/src/AWS/EC2/hosted.ts +1 -1
  15. package/src/AWS/ECS/Task.ts +1 -1
  16. package/src/AWS/Kinesis/Stream.ts +44 -8
  17. package/src/AWS/Lambda/Function.ts +218 -10
  18. package/src/AWS/S3/Bucket.ts +26 -19
  19. package/src/AWS/S3/BucketNotifications.ts +1 -1
  20. package/src/AWS/SNS/Topic.ts +47 -10
  21. package/src/AWS/SQS/Queue.ts +66 -0
  22. package/src/Auth/AuthProvider.ts +33 -0
  23. package/src/Auth/Credentials.ts +56 -0
  24. package/src/Auth/Env.ts +45 -0
  25. package/src/Auth/Profile.ts +72 -0
  26. package/src/Auth/index.ts +2 -0
  27. package/src/Cloudflare/Auth/AuthProvider.ts +670 -0
  28. package/src/Cloudflare/Auth/OAuthClient.ts +315 -0
  29. package/src/Cloudflare/Container/Container.ts +122 -0
  30. package/src/Cloudflare/Container/ContainerApplication.ts +6 -3
  31. package/src/Cloudflare/Container/ContainerBinding.ts +2 -4
  32. package/src/Cloudflare/Container/StartContainer.ts +1 -1
  33. package/src/Cloudflare/D1/D1Database.ts +23 -4
  34. package/src/Cloudflare/KV/KVNamespace.ts +25 -0
  35. package/src/Cloudflare/Providers.ts +1 -6
  36. package/src/Cloudflare/R2/R2Bucket.ts +45 -2
  37. package/src/Cloudflare/Website/StaticSite.ts +101 -15
  38. package/src/Cloudflare/Website/Vite.ts +86 -20
  39. package/src/Cloudflare/Workers/Assets.ts +170 -207
  40. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +629 -0
  41. package/src/Cloudflare/Workers/DurableObjectState.ts +63 -0
  42. package/src/Cloudflare/Workers/DurableObjectStorage.ts +256 -0
  43. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +66 -7
  44. package/src/Cloudflare/Workers/InferEnv.ts +11 -7
  45. package/src/Cloudflare/Workers/Rpc.ts +13 -2
  46. package/src/Cloudflare/Workers/ScheduledEvents.ts +185 -0
  47. package/src/Cloudflare/Workers/WebSocket.ts +1 -1
  48. package/src/Cloudflare/Workers/Worker.ts +572 -67
  49. package/src/Cloudflare/Workers/Workflow.ts +53 -11
  50. package/src/Cloudflare/Workers/index.ts +4 -1
  51. package/src/Construct.ts +2 -2
  52. package/src/GitHub/Comment.ts +224 -0
  53. package/src/GitHub/Secret.ts +257 -0
  54. package/src/GitHub/Variable.ts +166 -0
  55. package/src/GitHub/index.ts +3 -0
  56. package/src/Kubernetes/client.ts +2 -2
  57. package/src/Output.ts +1 -1
  58. package/src/Platform.ts +10 -5
  59. package/src/Provider.ts +1 -1
  60. package/src/Resource.ts +4 -4
  61. package/src/Test/Vitest.ts +4 -3
  62. package/src/Util/Clank.ts +77 -0
  63. package/src/Util/PlatformServices.ts +21 -0
  64. package/src/Util/dedent.ts +59 -0
  65. package/src/Util/index.ts +1 -0
  66. package/bin/alchemy-effect.js.map +0 -1
  67. package/src/Cloudflare/Workers/DurableObject.ts +0 -527
  68. package/src/Daemon/Client.ts +0 -116
  69. package/src/Daemon/Config.ts +0 -28
  70. package/src/Daemon/Errors.ts +0 -48
  71. package/src/Daemon/Lock.ts +0 -162
  72. package/src/Daemon/ProcessRegistry.ts +0 -284
  73. package/src/Daemon/RpcSchema.ts +0 -43
  74. package/src/Daemon/RpcServer.ts +0 -231
  75. package/src/Daemon/index.ts +0 -27
  76. package/src/Spawn.ts +0 -23
  77. /package/bin/{alchemy-effect.ts → alchemy.ts} +0 -0
@@ -0,0 +1,392 @@
1
+ import * as DistilledAuth from "@distilled.cloud/aws/Auth";
2
+ import * as Console from "effect/Console";
3
+ import * as Effect from "effect/Effect";
4
+ import * as FileSystem from "effect/FileSystem";
5
+ import * as Match from "effect/Match";
6
+ import * as Path from "effect/Path";
7
+ import * as Redacted from "effect/Redacted";
8
+ import * as HttpClient from "effect/unstable/http/HttpClient";
9
+ import {
10
+ ChildProcess,
11
+ type ChildProcessSpawner,
12
+ } from "effect/unstable/process";
13
+ import * as NodeCrypto from "node:crypto";
14
+ import * as NodeOs from "node:os";
15
+ import { AuthError, type AuthProvider } from "../Auth/AuthProvider.ts";
16
+ import {
17
+ deleteCredentials,
18
+ displayRedacted,
19
+ readCredentials,
20
+ writeCredentials,
21
+ } from "../Auth/Credentials.ts";
22
+ import {
23
+ getEnv,
24
+ getEnvRedacted,
25
+ getEnvRedactedRequired,
26
+ retryOnce,
27
+ } from "../Auth/Env.ts";
28
+ import * as Clank from "../Util/Clank.ts";
29
+
30
+ export type AwsAuthConfig =
31
+ | { method: "sso"; ssoProfile: string }
32
+ | { method: "stored" }
33
+ | { method: "env" };
34
+
35
+ const options: Array<{
36
+ value: AwsAuthConfig["method"];
37
+ label: string;
38
+ hint?: string;
39
+ }> = [
40
+ {
41
+ value: "sso",
42
+ label: "SSO",
43
+ hint: "aws sso login — credentials loaded from AWS SSO cache",
44
+ },
45
+ {
46
+ value: "env",
47
+ label: "Environment Variables",
48
+ hint: "AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY",
49
+ },
50
+ {
51
+ value: "stored",
52
+ label: "Stored",
53
+ hint: "stored in ~/.alchemy/credentials",
54
+ },
55
+ ];
56
+
57
+ export interface AwsStoredCredentials {
58
+ accessKeyId: string;
59
+ secretAccessKey: string;
60
+ sessionToken?: string;
61
+ region?: string;
62
+ }
63
+
64
+ export interface AwsResolvedCredentials {
65
+ accessKeyId: Redacted.Redacted<string>;
66
+ secretAccessKey: Redacted.Redacted<string>;
67
+ sessionToken?: Redacted.Redacted<string>;
68
+ region?: string;
69
+ source: { type: AwsAuthConfig["method"]; details?: string };
70
+ }
71
+
72
+ export const AwsAuth = Effect.gen(function* () {
73
+ const context = yield* Effect.context<
74
+ | FileSystem.FileSystem
75
+ | Path.Path
76
+ | HttpClient.HttpClient
77
+ | ChildProcessSpawner.ChildProcessSpawner
78
+ >();
79
+
80
+ return {
81
+ name: "AWS",
82
+ configure: (profileName: string) =>
83
+ configureCredentials(profileName).pipe(Effect.provide(context)),
84
+
85
+ logout: (profileName, config) =>
86
+ logout(profileName, config).pipe(Effect.provide(context)),
87
+
88
+ login: (profileName, config) =>
89
+ login(profileName, config).pipe(Effect.provide(context)),
90
+
91
+ prettyPrint: (profileName, config) =>
92
+ prettyPrint(profileName, config).pipe(Effect.provide(context)),
93
+
94
+ read: (profileName, config) =>
95
+ resolveCredentials(profileName, config).pipe(Effect.provide(context)),
96
+ } satisfies AuthProvider<AwsAuthConfig, AwsResolvedCredentials>;
97
+ });
98
+
99
+ const runSsoCommand = (command: "login" | "logout", ssoProfile: string) =>
100
+ Effect.gen(function* () {
101
+ const handle = yield* ChildProcess.make(
102
+ "aws",
103
+ ["sso", command, "--profile", ssoProfile],
104
+ {
105
+ shell: false,
106
+ stdin: "inherit",
107
+ stdout: "inherit",
108
+ stderr: "inherit",
109
+ },
110
+ );
111
+ const exit = yield* handle.exitCode;
112
+ if (exit !== 0) {
113
+ yield* Effect.fail(
114
+ new AuthError({
115
+ message: `aws sso ${command} exited with code ${exit}`,
116
+ }),
117
+ );
118
+ }
119
+ }).pipe(Effect.scoped);
120
+
121
+ const resolveCredentials = (profileName: string, config: AwsAuthConfig) =>
122
+ Match.value(config)
123
+ .pipe(
124
+ Match.when(
125
+ { method: "env" },
126
+ Effect.fnUntraced(function* () {
127
+ const accessKeyId =
128
+ yield* getEnvRedactedRequired("AWS_ACCESS_KEY_ID");
129
+ const secretAccessKey = yield* getEnvRedactedRequired(
130
+ "AWS_SECRET_ACCESS_KEY",
131
+ );
132
+ const sessionToken = yield* getEnvRedacted("AWS_SESSION_TOKEN");
133
+ const region = yield* (
134
+ getEnv("AWS_REGION") ?? getEnv("AWS_DEFAULT_REGION") ?? undefined
135
+ );
136
+ return {
137
+ accessKeyId,
138
+ secretAccessKey,
139
+ sessionToken,
140
+ region,
141
+ source: { type: "env" as const },
142
+ };
143
+ }),
144
+ ),
145
+ Match.when({ method: "stored" }, () =>
146
+ readCredentials<AwsStoredCredentials>(profileName, "aws").pipe(
147
+ Effect.flatMap((creds) =>
148
+ creds == null
149
+ ? Effect.fail(
150
+ new AuthError({
151
+ message:
152
+ "AWS stored credentials not found. Run: alchemy-effect login --configure",
153
+ }),
154
+ )
155
+ : Effect.succeed({
156
+ accessKeyId: Redacted.make(creds.accessKeyId),
157
+ secretAccessKey: Redacted.make(creds.secretAccessKey),
158
+ sessionToken: creds.sessionToken
159
+ ? Redacted.make(creds.sessionToken)
160
+ : undefined,
161
+ region: creds.region,
162
+ source: { type: "stored" as const },
163
+ }),
164
+ ),
165
+ ),
166
+ ),
167
+ Match.when({ method: "sso" }, (config) =>
168
+ Effect.gen(function* () {
169
+ const auth = yield* DistilledAuth.Default;
170
+ const creds = yield* auth
171
+ .loadProfileCredentials(config.ssoProfile)
172
+ .pipe(
173
+ Effect.mapError(
174
+ (e) =>
175
+ new AuthError({
176
+ message: "failed to load credentials",
177
+ cause: e,
178
+ }),
179
+ ),
180
+ );
181
+ const profile = yield* auth
182
+ .loadProfile(config.ssoProfile)
183
+ .pipe(Effect.catch(() => Effect.succeed(undefined)));
184
+ return {
185
+ accessKeyId: creds.accessKeyId,
186
+ secretAccessKey: creds.secretAccessKey,
187
+ sessionToken: creds.sessionToken,
188
+ region: profile?.region,
189
+ source: { type: "sso" as const, details: config.ssoProfile },
190
+ };
191
+ }),
192
+ ),
193
+ Match.exhaustive,
194
+ )
195
+ .pipe(
196
+ Effect.mapError(
197
+ (e) => new AuthError({ message: "login failed", cause: e }),
198
+ ),
199
+ );
200
+
201
+ const prettyPrint = (profileName: string, config: AwsAuthConfig) =>
202
+ resolveCredentials(profileName, config).pipe(
203
+ Effect.tap((creds) =>
204
+ Effect.all([
205
+ Console.log(` accessKeyId: ${displayRedacted(creds.accessKeyId)}`),
206
+ Console.log(
207
+ ` secretAccessKey: ${displayRedacted(creds.secretAccessKey)}`,
208
+ ),
209
+ creds.sessionToken
210
+ ? Console.log(
211
+ ` sessionToken: ${displayRedacted(creds.sessionToken)}`,
212
+ )
213
+ : Effect.void,
214
+ creds.region
215
+ ? Console.log(` region: ${creds.region}`)
216
+ : Effect.void,
217
+ Console.log(
218
+ //@ts-expect-error
219
+ ` source: ${creds.source.details ? `${creds.source.type} - ${creds.source.details}` : creds.source.type}`,
220
+ ),
221
+ ]),
222
+ ),
223
+ Effect.catch((e) =>
224
+ Console.error(` Failed to retrieve credentials: ${e}`),
225
+ ),
226
+ );
227
+
228
+ /**
229
+ * `aws sso logout` only clears AWS CLI's own caches — it does not know about the
230
+ * `<sha1(sso_session)>.credentials.json` file that `@distilled.cloud/aws`
231
+ * writes alongside the SSO token. Without this cleanup, `loadProfileCredentials`
232
+ * short-circuits on the stale distilled cache file after logout and appears to
233
+ * stay logged in until the role creds hit their TTL.
234
+ */
235
+ const clearDistilledSsoCache = (ssoProfile: string) =>
236
+ Effect.gen(function* () {
237
+ const auth = yield* DistilledAuth.Default;
238
+ const profile = yield* auth.loadProfile(ssoProfile);
239
+ const ssoSession = (profile as { sso_session?: string }).sso_session;
240
+ if (!ssoSession) return;
241
+ const fs = yield* FileSystem.FileSystem;
242
+ const path = yield* Path.Path;
243
+ const hash = NodeCrypto.createHash("sha1").update(ssoSession).digest("hex");
244
+ const cacheFile = path.join(
245
+ NodeOs.homedir(),
246
+ ".aws",
247
+ "sso",
248
+ "cache",
249
+ `${hash}.credentials.json`,
250
+ );
251
+ yield* fs.remove(cacheFile).pipe(Effect.catch(() => Effect.void));
252
+ }).pipe(Effect.catch(() => Effect.void));
253
+
254
+ const logout = (profileName: string, config: AwsAuthConfig) =>
255
+ Match.value(config).pipe(
256
+ Match.when({ method: "env" }, () => Effect.void),
257
+ Match.when({ method: "sso" }, (config) =>
258
+ Clank.info(
259
+ `AWS: running 'aws sso logout --profile ${config.ssoProfile}'...`,
260
+ ).pipe(
261
+ Effect.zip(runSsoCommand("logout", config.ssoProfile)),
262
+ Effect.zip(clearDistilledSsoCache(config.ssoProfile)),
263
+ Effect.match({
264
+ onSuccess: () => Clank.success("AWS: SSO logout complete"),
265
+ onFailure: (e) =>
266
+ Clank.warn(`AWS: SSO logout failed: \`${e.message}\``),
267
+ }),
268
+ ),
269
+ ),
270
+ Match.when({ method: "stored" }, () =>
271
+ deleteCredentials(profileName, "aws").pipe(
272
+ Effect.andThen(Clank.success("AWS: stored credentials removed")),
273
+ ),
274
+ ),
275
+ Match.exhaustive,
276
+ );
277
+
278
+ const login = (profileName: string, config: AwsAuthConfig) =>
279
+ Match.value(config)
280
+ .pipe(
281
+ Match.when({ method: "env" }, () => Effect.void),
282
+ Match.when({ method: "sso" }, (config) =>
283
+ DistilledAuth.loadProfileCredentials(config.ssoProfile).pipe(
284
+ Effect.matchEffect({
285
+ onSuccess: () =>
286
+ Clank.info(
287
+ `AWS: SSO profile '${config.ssoProfile}' already has valid credentials`,
288
+ ),
289
+ onFailure: () => loginSSO(config),
290
+ }),
291
+ ),
292
+ ),
293
+ Match.when({ method: "stored" }, () =>
294
+ readCredentials<AwsStoredCredentials>(profileName, "aws").pipe(
295
+ Effect.flatMap((creds) =>
296
+ creds == null ? loginStored(profileName) : Effect.void,
297
+ ),
298
+ ),
299
+ ),
300
+ Match.exhaustive,
301
+ )
302
+ .pipe(
303
+ Effect.mapError(
304
+ (e) => new AuthError({ message: "login failed", cause: e }),
305
+ ),
306
+ );
307
+
308
+ const configureCredentials = (profileName: string) =>
309
+ Clank.select({
310
+ message: "AWS authentication method",
311
+ options,
312
+ })
313
+ .pipe(
314
+ Effect.flatMap((method) =>
315
+ Match.value(method).pipe(
316
+ Match.when("env", () => Effect.succeed({ method: "env" as const })),
317
+ Match.when("sso", () =>
318
+ Effect.gen(function* () {
319
+ const ssoProfile = yield* Clank.text({
320
+ message: "AWS profile name (from ~/.aws/config)",
321
+ placeholder: "default",
322
+ defaultValue: "default",
323
+ });
324
+
325
+ const config = {
326
+ method: "sso" as const,
327
+ ssoProfile: ssoProfile ?? "default",
328
+ };
329
+
330
+ yield* loginSSO(config);
331
+
332
+ return config;
333
+ }),
334
+ ),
335
+ Match.when("stored", () => loginStored(profileName)),
336
+ Match.exhaustive,
337
+ ),
338
+ ),
339
+ )
340
+ .pipe(
341
+ Effect.mapError(
342
+ (e) =>
343
+ new AuthError({
344
+ message: "failed to configure credentials",
345
+ cause: e,
346
+ }),
347
+ ),
348
+ );
349
+
350
+ const loginSSO = (config: Extract<AwsAuthConfig, { method: "sso" }>) =>
351
+ Clank.info(
352
+ `AWS SSO: running 'aws sso login --profile ${config.ssoProfile}'...`,
353
+ ).pipe(
354
+ Effect.andThen(runSsoCommand("login", config.ssoProfile)),
355
+ Effect.matchEffect({
356
+ onSuccess: () => Clank.success("AWS SSO: login complete"),
357
+ onFailure: (e) => Clank.warn(`AWS SSO: login faield: \`${e.message}\``),
358
+ }),
359
+ );
360
+
361
+ const loginStored = Effect.fnUntraced(function* (profileName: string) {
362
+ const accessKeyId = yield* Clank.text({
363
+ message: "AWS Access Key ID",
364
+ validate: (v) => (v.length === 0 ? "Required" : undefined),
365
+ }).pipe(retryOnce);
366
+
367
+ const secretAccessKey = yield* Clank.password({
368
+ message: "AWS Secret Access Key",
369
+ validate: (v) => (v.length === 0 ? "Required" : undefined),
370
+ }).pipe(retryOnce);
371
+
372
+ const sessionToken = yield* Clank.text({
373
+ message: "AWS Session Token (optional — press Enter or Esc to skip)",
374
+ placeholder: "(none)",
375
+ }).pipe(Effect.catch(() => Effect.succeed("")));
376
+
377
+ const region = yield* Clank.text({
378
+ message: "AWS Region",
379
+ placeholder: "us-east-1",
380
+ defaultValue: "us-east-1",
381
+ }).pipe(retryOnce);
382
+
383
+ yield* writeCredentials<AwsStoredCredentials>(profileName, "aws", {
384
+ accessKeyId,
385
+ secretAccessKey,
386
+ sessionToken,
387
+ region,
388
+ });
389
+ yield* Clank.success("AWS credentials saved.");
390
+
391
+ return { method: "stored" as const };
392
+ });
@@ -7,7 +7,6 @@ import * as Effect from "effect/Effect";
7
7
  import * as Layer from "effect/Layer";
8
8
  import * as Option from "effect/Option";
9
9
  import { Profile } from "./Profile.ts";
10
-
11
10
  import { StageConfig } from "./StageConfig.ts";
12
11
 
13
12
  export { Credentials } from "@distilled.cloud/aws/Credentials";
@@ -115,7 +115,9 @@ export interface Table extends Resource<
115
115
  * @section Creating Tables
116
116
  * @example Basic Table
117
117
  * ```typescript
118
- * const table = yield* Table("UsersTable", {
118
+ * import * as DynamoDB from "alchemy/AWS/DynamoDB";
119
+ *
120
+ * const table = yield* DynamoDB.Table("UsersTable", {
119
121
  * partitionKey: "pk",
120
122
  * attributes: {
121
123
  * pk: "S",
@@ -125,7 +127,7 @@ export interface Table extends Resource<
125
127
  *
126
128
  * @example Table with Sort Key and TTL
127
129
  * ```typescript
128
- * const table = yield* Table("SessionsTable", {
130
+ * const table = yield* DynamoDB.Table("SessionsTable", {
129
131
  * partitionKey: "userId",
130
132
  * sortKey: "sessionId",
131
133
  * attributes: {
@@ -140,18 +142,69 @@ export interface Table extends Resource<
140
142
  * });
141
143
  * ```
142
144
  *
143
- * @section Runtime Operations
144
- * @example Read an Item
145
+ * @example Table with Global Secondary Index
145
146
  * ```typescript
146
- * const getItem = yield* GetItem.bind(table);
147
- *
148
- * const response = yield* getItem({
149
- * Key: {
150
- * pk: { S: "user#123" },
147
+ * const table = yield* DynamoDB.Table("OrdersTable", {
148
+ * partitionKey: "pk",
149
+ * sortKey: "sk",
150
+ * attributes: {
151
+ * pk: "S",
152
+ * sk: "S",
153
+ * gsi1pk: "S",
154
+ * gsi1sk: "S",
151
155
  * },
152
- * ConsistentRead: true,
156
+ * globalSecondaryIndexes: [{
157
+ * IndexName: "GSI1",
158
+ * KeySchema: [
159
+ * { AttributeName: "gsi1pk", KeyType: "HASH" },
160
+ * { AttributeName: "gsi1sk", KeyType: "RANGE" },
161
+ * ],
162
+ * Projection: { ProjectionType: "ALL" },
163
+ * }],
153
164
  * });
154
165
  * ```
166
+ *
167
+ * @section Runtime Operations
168
+ * Bind DynamoDB operations in the init phase and use them in runtime
169
+ * handlers. Bindings inject the table name and grant scoped IAM
170
+ * permissions automatically.
171
+ *
172
+ * @example Read and write items
173
+ * ```typescript
174
+ * // init
175
+ * const getItem = yield* DynamoDB.GetItem.bind(table);
176
+ * const putItem = yield* DynamoDB.PutItem.bind(table);
177
+ *
178
+ * return {
179
+ * fetch: Effect.gen(function* () {
180
+ * // runtime
181
+ * yield* putItem({
182
+ * Item: { pk: { S: "user#123" }, name: { S: "Alice" } },
183
+ * });
184
+ * const result = yield* getItem({
185
+ * Key: { pk: { S: "user#123" } },
186
+ * });
187
+ * return yield* HttpServerResponse.json(result.Item);
188
+ * }),
189
+ * };
190
+ * ```
191
+ *
192
+ * @section DynamoDB Streams
193
+ * Process change data capture events from a DynamoDB table using a
194
+ * Lambda event source mapping. The stream is enabled automatically
195
+ * through the binding contract.
196
+ *
197
+ * @example Process table changes
198
+ * ```typescript
199
+ * // init
200
+ * yield* DynamoDB.streams(table, {
201
+ * StreamViewType: "NEW_AND_OLD_IMAGES",
202
+ * }).process(
203
+ * Effect.fn(function* (record) {
204
+ * yield* Effect.log(`${record.eventName}: ${JSON.stringify(record.dynamodb)}`);
205
+ * }),
206
+ * );
207
+ * ```
155
208
  */
156
209
  export const Table = Resource<Table>("AWS.DynamoDB.Table");
157
210
 
@@ -9,10 +9,10 @@ import type { ScopedPlanStatusSession } from "../../Cli/Cli.ts";
9
9
  import { isResolved } from "../../Diff.ts";
10
10
  import * as Provider from "../../Provider.ts";
11
11
  import { Resource } from "../../Resource.ts";
12
- import type { Providers } from "../Providers.ts";
13
12
  import { createInternalTags, createTagsList, diffTags } from "../../Tags.ts";
14
13
  import type { AccountID } from "../Account.ts";
15
14
  import { Account } from "../Account.ts";
15
+ import type { Providers } from "../Providers.ts";
16
16
  import type { RegionID } from "../Region.ts";
17
17
  import type { RouteTableId } from "./RouteTable.ts";
18
18
  import type { SecurityGroupId } from "./SecurityGroup.ts";
@@ -595,11 +595,11 @@ const waitForVpcEndpointAvailable = (
595
595
  return yield* new VpcEndpointNotFound({ vpcEndpointId });
596
596
  }
597
597
 
598
- if (ep.State === "Available") {
598
+ if (ep.State === "available") {
599
599
  return ep;
600
600
  }
601
601
 
602
- if (ep.State === "Failed" || ep.State === "Rejected") {
602
+ if (ep.State === "failed" || ep.State === "rejected") {
603
603
  return yield* new VpcEndpointFailed({
604
604
  vpcEndpointId,
605
605
  errorCode: ep.LastError?.Code,
@@ -641,7 +641,7 @@ const waitForVpcEndpointDeleted = (
641
641
 
642
642
  const ep = result.VpcEndpoints?.[0];
643
643
 
644
- if (!ep || ep.State === "Deleted") {
644
+ if (!ep || ep.State === "deleted") {
645
645
  return; // Successfully deleted
646
646
  }
647
647
 
@@ -223,7 +223,7 @@ export const createEc2HostedSupport = ({
223
223
  virtualEntryPlugin(
224
224
  (importPath) => `
225
225
  import { NodeServices } from "@effect/platform-node";
226
- import { Stack } from "alchemy-effect/Stack";
226
+ import { Stack } from "alchemy/Stack";
227
227
  import * as Config from "effect/Config";
228
228
  import * as ConfigProvider from "effect/ConfigProvider";
229
229
  import * as Credentials from "@distilled.cloud/aws/Credentials";
@@ -486,7 +486,7 @@ export const TaskProvider = () =>
486
486
  virtualEntryPlugin(
487
487
  (importPath) => `
488
488
  import { NodeServices } from "@effect/platform-node";
489
- import { Stack } from "alchemy-effect/Stack";
489
+ import { Stack } from "alchemy/Stack";
490
490
  import * as Config from "effect/Config";
491
491
  import * as ConfigProvider from "effect/ConfigProvider";
492
492
  import * as Credentials from "@distilled.cloud/aws/Credentials";
@@ -170,32 +170,68 @@ export interface Stream extends Resource<
170
170
  *
171
171
  * `Stream` owns the stream's lifecycle and mutable control-plane configuration,
172
172
  * including retention, encryption, monitoring, warm throughput, record size, tags,
173
- * and stream resource policy.
173
+ * and stream resource policy. A stream name is auto-generated from the app,
174
+ * stage, and logical ID unless you provide one explicitly.
174
175
  *
175
176
  * @section Creating Streams
176
177
  * @example On-Demand Stream
177
178
  * ```typescript
178
- * const stream = yield* Stream("OrdersStream");
179
+ * import * as Kinesis from "alchemy/AWS/Kinesis";
180
+ *
181
+ * const stream = yield* Kinesis.Stream("OrdersStream");
179
182
  * ```
180
183
  *
181
184
  * @example Provisioned Stream
182
185
  * ```typescript
183
- * const stream = yield* Stream("AnalyticsStream", {
186
+ * const stream = yield* Kinesis.Stream("AnalyticsStream", {
184
187
  * streamMode: "PROVISIONED",
185
188
  * shardCount: 2,
186
189
  * retentionPeriodHours: 48,
187
190
  * });
188
191
  * ```
189
192
  *
193
+ * @example Encrypted Stream
194
+ * ```typescript
195
+ * const stream = yield* Kinesis.Stream("SecureStream", {
196
+ * encryption: true,
197
+ * kmsKeyId: "alias/my-key",
198
+ * });
199
+ * ```
200
+ *
190
201
  * @section Runtime Producers
191
- * @example Put a Record
202
+ * Bind producer operations in the init phase and use them in runtime
203
+ * handlers.
204
+ *
205
+ * @example Put a record from a handler
192
206
  * ```typescript
207
+ * // init
193
208
  * const putRecord = yield* Kinesis.PutRecord.bind(stream);
194
209
  *
195
- * yield* putRecord({
196
- * PartitionKey: "order-123",
197
- * Data: new TextEncoder().encode(JSON.stringify({ orderId: "123" })),
198
- * });
210
+ * return {
211
+ * fetch: Effect.gen(function* () {
212
+ * // runtime
213
+ * yield* putRecord({
214
+ * PartitionKey: "order-123",
215
+ * Data: new TextEncoder().encode(JSON.stringify({ orderId: "123" })),
216
+ * });
217
+ * return HttpServerResponse.text("Sent");
218
+ * }),
219
+ * };
220
+ * ```
221
+ *
222
+ * @section Event Sources
223
+ * Process records from a Kinesis stream using a Lambda event source
224
+ * mapping.
225
+ *
226
+ * @example Process stream records
227
+ * ```typescript
228
+ * // init
229
+ * yield* Kinesis.records(stream).process(
230
+ * Effect.fn(function* (record) {
231
+ * const data = new TextDecoder().decode(record.data);
232
+ * yield* Effect.log(`Received: ${data}`);
233
+ * }),
234
+ * );
199
235
  * ```
200
236
  */
201
237
  export const Stream = Resource<Stream>("AWS.Kinesis.Stream");