alchemy 2.0.0-beta.36 → 2.0.0-beta.37

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 (198) hide show
  1. package/bin/exec.js +166 -43
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/Lambda/Function.d.ts.map +1 -1
  4. package/lib/AWS/Lambda/Function.js +22 -2
  5. package/lib/AWS/Lambda/Function.js.map +1 -1
  6. package/lib/Apply.d.ts.map +1 -1
  7. package/lib/Apply.js +6 -1
  8. package/lib/Apply.js.map +1 -1
  9. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts +54 -0
  10. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.d.ts.map +1 -0
  11. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js +17 -0
  12. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.js.map +1 -0
  13. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts +37 -0
  14. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.d.ts.map +1 -0
  15. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js +45 -0
  16. package/lib/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.js.map +1 -0
  17. package/lib/Cloudflare/AnalyticsEngine/index.d.ts +3 -0
  18. package/lib/Cloudflare/AnalyticsEngine/index.d.ts.map +1 -0
  19. package/lib/Cloudflare/AnalyticsEngine/index.js +3 -0
  20. package/lib/Cloudflare/AnalyticsEngine/index.js.map +1 -0
  21. package/lib/Cloudflare/D1/D1Connection.d.ts +47 -3
  22. package/lib/Cloudflare/D1/D1Connection.d.ts.map +1 -1
  23. package/lib/Cloudflare/D1/D1Connection.js +62 -5
  24. package/lib/Cloudflare/D1/D1Connection.js.map +1 -1
  25. package/lib/Cloudflare/Local/SidecarHandlers.js +9 -1
  26. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  27. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  28. package/lib/Cloudflare/Providers.js +4 -1
  29. package/lib/Cloudflare/Providers.js.map +1 -1
  30. package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
  31. package/lib/Cloudflare/R2/R2Bucket.js +16 -0
  32. package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
  33. package/lib/Cloudflare/StateStore/Api.d.ts +6 -1
  34. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  35. package/lib/Cloudflare/StateStore/Api.js +23 -1
  36. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  37. package/lib/Cloudflare/StateStore/Store.d.ts +16 -1
  38. package/lib/Cloudflare/StateStore/Store.d.ts.map +1 -1
  39. package/lib/Cloudflare/StateStore/Store.js +22 -5
  40. package/lib/Cloudflare/StateStore/Store.js.map +1 -1
  41. package/lib/Cloudflare/Workers/CronEventSource.d.ts +35 -0
  42. package/lib/Cloudflare/Workers/CronEventSource.d.ts.map +1 -0
  43. package/lib/Cloudflare/Workers/CronEventSource.js +55 -0
  44. package/lib/Cloudflare/Workers/CronEventSource.js.map +1 -0
  45. package/lib/Cloudflare/Workers/InferEnv.d.ts +37 -2
  46. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  47. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  48. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -1
  49. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  50. package/lib/Cloudflare/Workers/Rpc.d.ts +76 -3
  51. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  52. package/lib/Cloudflare/Workers/Rpc.js +197 -13
  53. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  54. package/lib/Cloudflare/Workers/Worker.d.ts +22 -2
  55. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  56. package/lib/Cloudflare/Workers/Worker.js +174 -43
  57. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  58. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  59. package/lib/Cloudflare/Workers/WorkerBundle.js +15 -5
  60. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  61. package/lib/Cloudflare/Workers/Workflow.d.ts +50 -47
  62. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  63. package/lib/Cloudflare/Workers/Workflow.js +41 -35
  64. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  65. package/lib/Cloudflare/Workers/index.d.ts +1 -0
  66. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  67. package/lib/Cloudflare/Workers/index.js +1 -0
  68. package/lib/Cloudflare/Workers/index.js.map +1 -1
  69. package/lib/Cloudflare/index.d.ts +1 -0
  70. package/lib/Cloudflare/index.d.ts.map +1 -1
  71. package/lib/Cloudflare/index.js +1 -0
  72. package/lib/Cloudflare/index.js.map +1 -1
  73. package/lib/Deploy.d.ts +4 -1
  74. package/lib/Deploy.d.ts.map +1 -1
  75. package/lib/Deploy.js +2 -2
  76. package/lib/Deploy.js.map +1 -1
  77. package/lib/Destroy.d.ts +4 -1
  78. package/lib/Destroy.d.ts.map +1 -1
  79. package/lib/Destroy.js +2 -2
  80. package/lib/Destroy.js.map +1 -1
  81. package/lib/Output.d.ts +45 -5
  82. package/lib/Output.d.ts.map +1 -1
  83. package/lib/Output.js +100 -4
  84. package/lib/Output.js.map +1 -1
  85. package/lib/Plan.d.ts.map +1 -1
  86. package/lib/Plan.js +93 -12
  87. package/lib/Plan.js.map +1 -1
  88. package/lib/Platform.d.ts.map +1 -1
  89. package/lib/Platform.js +16 -3
  90. package/lib/Platform.js.map +1 -1
  91. package/lib/Ref.d.ts +4 -1
  92. package/lib/Ref.d.ts.map +1 -1
  93. package/lib/Ref.js +1 -1
  94. package/lib/Ref.js.map +1 -1
  95. package/lib/Resource.d.ts +8 -0
  96. package/lib/Resource.d.ts.map +1 -1
  97. package/lib/Resource.js +10 -0
  98. package/lib/Resource.js.map +1 -1
  99. package/lib/RuntimeContext.d.ts +3 -1
  100. package/lib/RuntimeContext.d.ts.map +1 -1
  101. package/lib/RuntimeContext.js.map +1 -1
  102. package/lib/Secret.d.ts +50 -0
  103. package/lib/Secret.d.ts.map +1 -0
  104. package/lib/Secret.js +23 -0
  105. package/lib/Secret.js.map +1 -0
  106. package/lib/Serverless/Function.d.ts +9 -1
  107. package/lib/Serverless/Function.d.ts.map +1 -1
  108. package/lib/Stack.d.ts +14 -5
  109. package/lib/Stack.d.ts.map +1 -1
  110. package/lib/Stack.js +31 -19
  111. package/lib/Stack.js.map +1 -1
  112. package/lib/State/HttpStateApi.d.ts +15 -1
  113. package/lib/State/HttpStateApi.d.ts.map +1 -1
  114. package/lib/State/HttpStateApi.js +11 -0
  115. package/lib/State/HttpStateApi.js.map +1 -1
  116. package/lib/State/HttpStateStore.d.ts.map +1 -1
  117. package/lib/State/HttpStateStore.js +11 -0
  118. package/lib/State/HttpStateStore.js.map +1 -1
  119. package/lib/State/InMemoryState.d.ts +2 -2
  120. package/lib/State/InMemoryState.d.ts.map +1 -1
  121. package/lib/State/InMemoryState.js +12 -6
  122. package/lib/State/InMemoryState.js.map +1 -1
  123. package/lib/State/LocalState.d.ts.map +1 -1
  124. package/lib/State/LocalState.js +11 -2
  125. package/lib/State/LocalState.js.map +1 -1
  126. package/lib/State/State.d.ts +21 -0
  127. package/lib/State/State.d.ts.map +1 -1
  128. package/lib/Test/Bun.d.ts.map +1 -1
  129. package/lib/Test/Bun.js +30 -7
  130. package/lib/Test/Bun.js.map +1 -1
  131. package/lib/Test/Core.d.ts +17 -2
  132. package/lib/Test/Core.d.ts.map +1 -1
  133. package/lib/Test/Core.js +29 -11
  134. package/lib/Test/Core.js.map +1 -1
  135. package/lib/Test/Vitest.d.ts.map +1 -1
  136. package/lib/Test/Vitest.js +24 -5
  137. package/lib/Test/Vitest.js.map +1 -1
  138. package/lib/Util/LocalhostDns.d.ts +14 -10
  139. package/lib/Util/LocalhostDns.d.ts.map +1 -1
  140. package/lib/Util/LocalhostDns.js +27 -36
  141. package/lib/Util/LocalhostDns.js.map +1 -1
  142. package/lib/Util/effect.d.ts +7 -0
  143. package/lib/Util/effect.d.ts.map +1 -1
  144. package/lib/Util/effect.js +6 -0
  145. package/lib/Util/effect.js.map +1 -1
  146. package/lib/Variable.d.ts +29 -0
  147. package/lib/Variable.d.ts.map +1 -0
  148. package/lib/Variable.js +9 -0
  149. package/lib/Variable.js.map +1 -0
  150. package/lib/index.d.ts +3 -0
  151. package/lib/index.d.ts.map +1 -1
  152. package/lib/index.js +3 -0
  153. package/lib/index.js.map +1 -1
  154. package/lib/tsconfig.test.tsbuildinfo +1 -1
  155. package/package.json +6 -7
  156. package/src/AWS/Lambda/Function.ts +30 -2
  157. package/src/Apply.ts +8 -1
  158. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDataset.ts +82 -0
  159. package/src/Cloudflare/AnalyticsEngine/AnalyticsEngineDatasetBinding.ts +106 -0
  160. package/src/Cloudflare/AnalyticsEngine/index.ts +2 -0
  161. package/src/Cloudflare/D1/D1Connection.ts +95 -12
  162. package/src/Cloudflare/Local/SidecarHandlers.ts +9 -1
  163. package/src/Cloudflare/Providers.ts +5 -0
  164. package/src/Cloudflare/R2/R2Bucket.ts +31 -0
  165. package/src/Cloudflare/StateStore/Api.ts +34 -1
  166. package/src/Cloudflare/StateStore/Store.ts +40 -10
  167. package/src/Cloudflare/Workers/CronEventSource.ts +89 -0
  168. package/src/Cloudflare/Workers/InferEnv.ts +94 -20
  169. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +9 -1
  170. package/src/Cloudflare/Workers/Rpc.ts +227 -18
  171. package/src/Cloudflare/Workers/Worker.ts +277 -63
  172. package/src/Cloudflare/Workers/WorkerBundle.ts +15 -5
  173. package/src/Cloudflare/Workers/Workflow.ts +74 -70
  174. package/src/Cloudflare/Workers/index.ts +1 -0
  175. package/src/Cloudflare/index.ts +1 -0
  176. package/src/Deploy.ts +5 -1
  177. package/src/Destroy.ts +5 -1
  178. package/src/Output.ts +121 -12
  179. package/src/Plan.ts +101 -12
  180. package/src/Platform.ts +19 -4
  181. package/src/Ref.ts +10 -5
  182. package/src/Resource.ts +22 -0
  183. package/src/RuntimeContext.ts +1 -0
  184. package/src/Secret.ts +87 -0
  185. package/src/Serverless/Function.ts +10 -1
  186. package/src/Stack.ts +52 -29
  187. package/src/State/HttpStateApi.ts +21 -0
  188. package/src/State/HttpStateStore.ts +21 -0
  189. package/src/State/InMemoryState.ts +29 -8
  190. package/src/State/LocalState.ts +28 -4
  191. package/src/State/State.ts +21 -0
  192. package/src/Test/Bun.ts +43 -7
  193. package/src/Test/Core.ts +29 -8
  194. package/src/Test/Vitest.ts +33 -4
  195. package/src/Util/LocalhostDns.ts +30 -36
  196. package/src/Util/effect.ts +25 -0
  197. package/src/Variable.ts +44 -0
  198. package/src/index.ts +3 -0
package/src/Test/Core.ts CHANGED
@@ -2,6 +2,7 @@ import { ConfigProvider } from "effect/ConfigProvider";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Layer from "effect/Layer";
4
4
  import * as Option from "effect/Option";
5
+ import * as Scope from "effect/Scope";
5
6
  import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
6
7
 
7
8
  import { AdoptPolicy } from "../AdoptPolicy.ts";
@@ -90,14 +91,22 @@ const alchemyLayer = Layer.mergeAll(LoggingCli, AlchemyContextLive);
90
91
  * registry that the user's `providers` layer populates, the platform layers,
91
92
  * and the configured state store. Adapters wrap this into runner-specific
92
93
  * thunks (`bun.test` -> `runPromise`, `it.live` -> as-is).
94
+ *
95
+ * When `scope` is provided, scoped resources (like the Cloudflare dev
96
+ * sidecar) survive past this effect and are tied to the lifetime of the
97
+ * provided scope instead. The runner is responsible for closing it.
98
+ *
99
+ * When `scope` is omitted, the effect runs with `Effect.scoped` and any
100
+ * scoped resources are torn down as soon as it resolves.
93
101
  */
94
102
  export const toEffect = <A>(
95
103
  effect: TestEffect<A>,
96
104
  options: MakeOptions,
97
- ): Effect.Effect<A, any, never> =>
98
- Effect.gen(function* () {
99
- const base = yield* loadConfigProvider(Option.none());
100
- const configProvider = withProfileOverride(base, options.profile);
105
+ scope?: Scope.Scope,
106
+ ): Effect.Effect<A, any, never> => {
107
+ const base = Effect.gen(function* () {
108
+ const cfg = yield* loadConfigProvider(Option.none());
109
+ const configProvider = withProfileOverride(cfg, options.profile);
101
110
  return yield* effect.pipe(
102
111
  provideFreshArtifactStore,
103
112
  Effect.provide(Layer.succeed(ConfigProvider, configProvider)),
@@ -112,14 +121,19 @@ export const toEffect = <A>(
112
121
  Effect.provide(options.state ?? State.localState()),
113
122
  Effect.provideService(AuthProviders, {}),
114
123
  Effect.provide(Layer.provideMerge(alchemyLayer, platformLayer)),
115
- Effect.scoped,
124
+ );
125
+
126
+ return (
127
+ scope === undefined ? Effect.scoped(base) : Scope.provide(base, scope)
116
128
  ) as Effect.Effect<A, any, never>;
129
+ };
117
130
 
118
131
  /** Promise wrapper around {@link toEffect} for `bun.test`-style runners. */
119
132
  export const run = <A>(
120
133
  effect: TestEffect<A>,
121
134
  options: MakeOptions,
122
- ): Promise<A> => Effect.runPromise(toEffect(effect, options));
135
+ scope?: Scope.Scope,
136
+ ): Promise<A> => Effect.runPromise(toEffect(effect, options, scope));
123
137
 
124
138
  /**
125
139
  * Wrap an effect so it runs with `options.providers` + a placeholder Stack +
@@ -148,27 +162,34 @@ export const withProviders = <A, E, R, ROut>(
148
162
  * Curried `deploy` for the test factory: bakes in the configured stage and
149
163
  * adds the telemetry layer the CLI uses, so `beforeAll(deploy(Stack))` works
150
164
  * the same way as `alchemy deploy`.
165
+ *
166
+ * `scope`, when supplied, is forwarded down so the dev sidecar (and other
167
+ * scoped resources) lives until the caller closes it instead of dying as
168
+ * soon as `deploy` resolves. The test harness uses this to keep workerd
169
+ * alive across `beforeAll` → tests → `afterAll`.
151
170
  */
152
171
  export const deploy = <A>(
153
172
  options: MakeOptions,
154
173
  stack: TestEffect<CompiledStack<A>, Stage | AlchemyContext>,
155
- callOptions?: { stage?: string },
174
+ callOptions?: { stage?: string; scope?: Scope.Scope },
156
175
  ) =>
157
176
  _deploy({
158
177
  stack: stack as Effect.Effect<CompiledStack<A>, never, any>,
159
178
  stage: callOptions?.stage ?? options.stage ?? "test",
160
179
  dev: resolveDev(options),
180
+ scope: callOptions?.scope,
161
181
  }).pipe(Effect.provide(TelemetryLive));
162
182
 
163
183
  export const destroy = (
164
184
  options: MakeOptions,
165
185
  stack: TestEffect<CompiledStack, Stage | AlchemyContext>,
166
- callOptions?: { stage?: string },
186
+ callOptions?: { stage?: string; scope?: Scope.Scope },
167
187
  ) =>
168
188
  _destroy({
169
189
  stack: stack as Effect.Effect<CompiledStack, never, any>,
170
190
  stage: callOptions?.stage ?? options.stage ?? "test",
171
191
  dev: resolveDev(options),
192
+ scope: callOptions?.scope,
172
193
  }).pipe(Effect.provide(TelemetryLive));
173
194
 
174
195
  /**
@@ -1,5 +1,7 @@
1
1
  import { it } from "@effect/vitest";
2
2
  import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Scope from "effect/Scope";
3
5
  import {
4
6
  afterAll as vitestAfterAll,
5
7
  afterEach as vitestAfterEach,
@@ -98,8 +100,13 @@ const DEFAULT_TIMEOUT = 120_000;
98
100
  * Effect-aware tests stay first-class.
99
101
  */
100
102
  export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
101
- const wrap = <A>(eff: TestEffect<A>) => Core.toEffect(eff, options);
102
- const runEff = <A>(eff: TestEffect<A>) => Core.run(eff, options);
103
+ // See `Test/Bun.ts` for the rationale: the dev sidecar must outlive a
104
+ // single `runPromise` boundary, so all hooks share one scope that's only
105
+ // closed after `destroy(...)` runs.
106
+ const sharedScope = Scope.makeUnsafe("sequential");
107
+ const wrap = <A>(eff: TestEffect<A>) =>
108
+ Core.toEffect(eff, options, sharedScope);
109
+ const runEff = <A>(eff: TestEffect<A>) => Core.run(eff, options, sharedScope);
103
110
 
104
111
  const test = ((name, eff, opts) => {
105
112
  it.live(name, () => wrap(eff), timeoutOf(opts));
@@ -130,6 +137,7 @@ export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
130
137
  return Core.toEffect(
131
138
  Core.withProviders(fn(scratch), options, scratch.name),
132
139
  { ...options, state: scratch.state },
140
+ sharedScope,
133
141
  );
134
142
  };
135
143
 
@@ -182,13 +190,34 @@ export const make = <ROut = any>(options: MakeOptions<ROut>): TestApi => {
182
190
  vitestAfterEach(() => runEff(eff), timeoutOf(hookOptions));
183
191
  };
184
192
 
193
+ // `destroy(Stack)` needs the dev sidecar alive so it can call `sidecar.stop`.
194
+ // `Scope.close` on an already-closed scope is a no-op, so the destroy
195
+ // wrapper AND the fallback cleanup hook below can both call it safely.
196
+ const closeScope = Effect.suspend(() =>
197
+ Scope.close(sharedScope, Exit.void),
198
+ ).pipe(Effect.ignore);
199
+
200
+ // Fallback cleanup: if the user never calls `destroy(Stack)` (e.g.
201
+ // `NO_DESTROY=1`), nothing else closes the shared scope and the sidecar
202
+ // child process leaks past the test process. Register an `afterAll` that
203
+ // closes it. We defer registration to a microtask so it runs AFTER any
204
+ // user-registered `afterAll` (including `destroy(Stack)`); vitest runs
205
+ // afterAll hooks in registration order.
206
+ queueMicrotask(() => {
207
+ vitestAfterAll(() => Effect.runPromise(closeScope), DEFAULT_TIMEOUT);
208
+ });
209
+
185
210
  return {
186
211
  test,
187
212
  beforeAll,
188
213
  beforeEach,
189
214
  afterAll,
190
215
  afterEach,
191
- deploy: (stack, callOpts) => Core.deploy(options, stack, callOpts),
192
- destroy: (stack, callOpts) => Core.destroy(options, stack, callOpts),
216
+ deploy: (stack, callOpts) =>
217
+ Core.deploy(options, stack, { ...callOpts, scope: sharedScope }),
218
+ destroy: (stack, callOpts) =>
219
+ Core.destroy(options, stack, { ...callOpts, scope: sharedScope }).pipe(
220
+ Effect.ensuring(closeScope),
221
+ ),
193
222
  };
194
223
  };
@@ -1,22 +1,27 @@
1
1
  /**
2
- * Process-level DNS shim that makes `*.localhost` resolve to 127.0.0.1.
2
+ * Process-level DNS shim that makes `*.localhost` resolve to 127.0.0.1
3
+ * for any HTTP client that goes through undici (Node's global `fetch`,
4
+ * undici-based clients, etc.).
3
5
  *
4
6
  * Background: macOS's `getaddrinfo` does not honor RFC 6761 for arbitrary
5
- * `*.localhost` names; only the bare `localhost` is mapped to loopback.
6
- * workerd and browsers handle it themselves, but Node's undici-based fetch
7
- * (and Bun's libcurl-based fetch on some platforms) fails with `ENOTFOUND`
8
- * when calling URLs returned by the local Cloudflare worker proxy
9
- * (e.g. `http://my-worker.localhost:1337`).
7
+ * `*.localhost` names — only the bare `localhost` is mapped to loopback.
8
+ * The Cloudflare local proxy returns URLs like
9
+ * `http://my-worker.localhost:1337` and dispatches by the URL hostname,
10
+ * so we cannot rewrite the URL client-side to `127.0.0.1` (and `Host` is
11
+ * a forbidden fetch header). The fix is to make the host resolvable at
12
+ * the network layer.
10
13
  *
11
- * The proxy in `@distilled.cloud/cloudflare-runtime` dispatches by the
12
- * URL hostname, so we cannot rewrite the URL client-side to `127.0.0.1`
13
- * (and `Host` is a forbidden fetch header). The fix is to make the host
14
- * resolvable at the network layer.
14
+ * On Bun, `globalThis.fetch` is the native (Zig) implementation and does
15
+ * not consult undici's global dispatcher; Bun resolves `*.localhost`
16
+ * itself on Darwin/Linux, so this shim is effectively a no-op there for
17
+ * the global `fetch` and only matters if user code reaches for `undici`
18
+ * directly.
15
19
  *
16
20
  * Calling `installLocalhostDns()` is idempotent.
17
21
  */
18
22
 
19
- import { createRequire } from "node:module";
23
+ import * as dns from "node:dns";
24
+ import * as undici from "undici";
20
25
 
21
26
  let installed = false;
22
27
 
@@ -28,31 +33,20 @@ const isLocalhost = (hostname: string): boolean =>
28
33
  export const installLocalhostDns = (): void => {
29
34
  if (installed) return;
30
35
  installed = true;
31
- // Bun resolves `*.localhost` natively via its libcurl-based fetch on
32
- // Darwin and Linux, so the shim is a no-op there. If a future Bun
33
- // release changes this, install via Bun's undici-compatible global
34
- // dispatcher here.
35
- if (typeof (globalThis as { Bun?: unknown }).Bun !== "undefined") return;
36
- try {
37
- const require = createRequire(import.meta.url);
38
- const undici: typeof import("undici") = require("undici");
39
- const dns: typeof import("node:dns") = require("node:dns");
40
- undici.setGlobalDispatcher(
41
- new undici.Agent({
42
- connect: {
43
- lookup: (hostname, options, cb: any) => {
44
- if (isLocalhost(hostname)) {
45
- if (options && (options as { all?: boolean }).all) {
46
- return cb(null, [{ address: "127.0.0.1", family: 4 }]);
47
- }
48
- return cb(null, "127.0.0.1", 4);
36
+
37
+ undici.setGlobalDispatcher(
38
+ new undici.Agent({
39
+ connect: {
40
+ lookup: (hostname, options, cb) => {
41
+ if (isLocalhost(hostname)) {
42
+ if (options && (options as { all?: boolean }).all) {
43
+ return cb(null, [{ address: "127.0.0.1", family: 4 }]);
49
44
  }
50
- return dns.lookup(hostname, options, cb);
51
- },
45
+ return cb(null, "127.0.0.1", 4);
46
+ }
47
+ return dns.lookup(hostname, options, cb);
52
48
  },
53
- }),
54
- );
55
- } catch {
56
- // undici not available; nothing to do.
57
- }
49
+ },
50
+ }),
51
+ );
58
52
  };
@@ -64,6 +64,31 @@ export const taggedFunction = <
64
64
  }) as Tag & Fn;
65
65
  };
66
66
 
67
+ export const isYieldableEffect = (
68
+ value: unknown,
69
+ ): value is Effect.Effect<unknown, unknown, unknown> =>
70
+ Effect.isEffect(value) &&
71
+ typeof (value as { [Symbol.iterator]?: unknown })[Symbol.iterator] ===
72
+ "function";
73
+
74
+ export type YieldableEffectLike<A = unknown, E = unknown, R = unknown> =
75
+ | Effect.Effect<A, E, R>
76
+ | {
77
+ asEffect: () => Effect.Effect<A, E, R>;
78
+ [Symbol.iterator]: () => Iterator<unknown>;
79
+ };
80
+
81
+ export const isEffectClassLike = (
82
+ value: unknown,
83
+ ): value is YieldableEffectLike =>
84
+ typeof value === "function" &&
85
+ typeof (value as { asEffect?: unknown }).asEffect === "function";
86
+
87
+ export const isYieldableEffectLike = (
88
+ value: unknown,
89
+ ): value is YieldableEffectLike =>
90
+ isYieldableEffect(value) || isEffectClassLike(value);
91
+
67
92
  export type UnwrapEffect<T> =
68
93
  T extends Effect.Effect<infer A, any, any> ? A : T;
69
94
 
@@ -0,0 +1,44 @@
1
+ import * as Config from "effect/Config";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Output from "./Output.ts";
4
+ import { toOutput } from "./Secret.ts";
5
+
6
+ /**
7
+ * The shapes a {@link variable} can be derived from.
8
+ */
9
+ export type VariableInput<T, R = never> =
10
+ | T
11
+ | Effect.Effect<T, any, R>
12
+ | Config.Config<T>;
13
+
14
+ /**
15
+ * Bind a non-secret value into the active {@link RuntimeContext} (e.g. a
16
+ * Cloudflare Worker's `env`) under the given `name`. Counterpart to
17
+ * {@link Secret} for plain (non-redacted) values — the resource provider
18
+ * deploys it as `plain_text` / `json` rather than `secret_text`.
19
+ *
20
+ * The result is an `Output<T>` — `yield*`-ing it returns an `Accessor<T>`
21
+ * that resolves the value at runtime.
22
+ *
23
+ * @example One-arg shortcut — reads `Config.string("DATABASE_URL")`
24
+ * ```ts
25
+ * const url = yield* Alchemy.Variable("DATABASE_URL");
26
+ * ```
27
+ *
28
+ * @example From a literal
29
+ * ```ts
30
+ * const port = yield* Alchemy.Variable("PORT", 3000);
31
+ * ```
32
+ */
33
+ export function Variable(name: string): Output.Output<string, never>;
34
+ export function Variable<T, R = never>(
35
+ name: string,
36
+ value: VariableInput<T, R>,
37
+ ): Output.Output<T, R>;
38
+ export function Variable<T = string, R = never>(
39
+ name: string,
40
+ value?: VariableInput<T, R>,
41
+ ): Output.Output<T, R> {
42
+ const source: any = value ?? Config.string(name);
43
+ return Output.named(toOutput<T, R>(source), name);
44
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./Diff.ts";
14
14
  export * from "./Input.ts";
15
15
  export * from "./InstanceId.ts";
16
16
  export * from "./Namespace.ts";
17
+ export { stackRef } from "./Output.ts";
17
18
  export type { Output } from "./Output.ts";
18
19
  export * from "./PhysicalName.ts";
19
20
  export * as Plan from "./Plan.ts";
@@ -23,12 +24,14 @@ export * from "./Ref.ts";
23
24
  export * as RemovalPolicy from "./RemovalPolicy.ts";
24
25
  export * from "./Resource.ts";
25
26
  export * as Schema from "./Schema.ts";
27
+ export { Secret } from "./Secret.ts";
26
28
  export * as Server from "./Server/index.ts";
27
29
  export * as Serverless from "./Serverless/index.ts";
28
30
  export { Stack } from "./Stack.ts";
29
31
  export * from "./Stage.ts";
30
32
  export { inMemoryState, localState } from "./State/index.ts";
31
33
  export * from "./Trait.ts";
34
+ export { Variable } from "./Variable.ts";
32
35
 
33
36
  export * as Construct from "./Construct.ts";
34
37