alchemy 2.0.0-beta.51 → 2.0.0-beta.52

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 (176) hide show
  1. package/bin/exec.js +16 -22
  2. package/bin/exec.js.map +1 -1
  3. package/lib/Apply.js +15 -3
  4. package/lib/Apply.js.map +1 -1
  5. package/lib/Auth/AuthProvider.js +1 -1
  6. package/lib/Auth/AuthProvider.js.map +1 -1
  7. package/lib/Binding.d.ts.map +1 -1
  8. package/lib/Binding.js +12 -8
  9. package/lib/Binding.js.map +1 -1
  10. package/lib/Build/DevServer.d.ts +113 -0
  11. package/lib/Build/DevServer.d.ts.map +1 -0
  12. package/lib/Build/DevServer.js +212 -0
  13. package/lib/Build/DevServer.js.map +1 -0
  14. package/lib/Build/Local.d.ts +2 -0
  15. package/lib/Build/Local.d.ts.map +1 -0
  16. package/lib/Build/Local.js +4 -0
  17. package/lib/Build/Local.js.map +1 -0
  18. package/lib/Bundle/Bundle.js +1 -1
  19. package/lib/Bundle/Bundle.js.map +1 -1
  20. package/lib/Cloudflare/Access.js +1 -1
  21. package/lib/Cloudflare/Access.js.map +1 -1
  22. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +1 -1
  23. package/lib/Cloudflare/AiGateway/AiGateway.js +1 -1
  24. package/lib/Cloudflare/Browser/Browser.d.ts +84 -12
  25. package/lib/Cloudflare/Browser/Browser.d.ts.map +1 -1
  26. package/lib/Cloudflare/Browser/Browser.js +80 -9
  27. package/lib/Cloudflare/Browser/Browser.js.map +1 -1
  28. package/lib/Cloudflare/Browser/BrowserBinding.d.ts +90 -16
  29. package/lib/Cloudflare/Browser/BrowserBinding.d.ts.map +1 -1
  30. package/lib/Cloudflare/Browser/BrowserBinding.js +41 -6
  31. package/lib/Cloudflare/Browser/BrowserBinding.js.map +1 -1
  32. package/lib/Cloudflare/Browser/index.d.ts +1 -1
  33. package/lib/Cloudflare/Browser/index.d.ts.map +1 -1
  34. package/lib/Cloudflare/Browser/index.js.map +1 -1
  35. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  36. package/lib/Cloudflare/Container/ContainerBinding.js +2 -4
  37. package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
  38. package/lib/Cloudflare/Images/Images.d.ts +4 -3
  39. package/lib/Cloudflare/Images/Images.d.ts.map +1 -1
  40. package/lib/Cloudflare/Images/Images.js.map +1 -1
  41. package/lib/Cloudflare/Providers.d.ts +1 -1
  42. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  43. package/lib/Cloudflare/Providers.js +4 -1
  44. package/lib/Cloudflare/Providers.js.map +1 -1
  45. package/lib/Cloudflare/RateLimit/RateLimit.d.ts +4 -3
  46. package/lib/Cloudflare/RateLimit/RateLimit.d.ts.map +1 -1
  47. package/lib/Cloudflare/RateLimit/RateLimit.js.map +1 -1
  48. package/lib/Cloudflare/Website/StaticSite.d.ts +64 -12
  49. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  50. package/lib/Cloudflare/Website/StaticSite.js +80 -30
  51. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  52. package/lib/Cloudflare/Website/Vite.d.ts +36 -7
  53. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  54. package/lib/Cloudflare/Website/Vite.js +26 -8
  55. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  56. package/lib/Cloudflare/Workers/Assets.d.ts +10 -4
  57. package/lib/Cloudflare/Workers/Assets.d.ts.map +1 -1
  58. package/lib/Cloudflare/Workers/Assets.js +6 -6
  59. package/lib/Cloudflare/Workers/Assets.js.map +1 -1
  60. package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts +4 -4
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  62. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  63. package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/LocalWorkerProvider.js +45 -11
  65. package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
  66. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts +5 -4
  67. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.d.ts.map +1 -1
  68. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js +4 -2
  69. package/lib/Cloudflare/Workers/RpcDurableObjectNamespace.js.map +1 -1
  70. package/lib/Cloudflare/Workers/RpcWorker.d.ts +3 -2
  71. package/lib/Cloudflare/Workers/RpcWorker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/RpcWorker.js +5 -3
  73. package/lib/Cloudflare/Workers/RpcWorker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/VersionMetadata.d.ts +99 -0
  75. package/lib/Cloudflare/Workers/VersionMetadata.d.ts.map +1 -0
  76. package/lib/Cloudflare/Workers/VersionMetadata.js +65 -0
  77. package/lib/Cloudflare/Workers/VersionMetadata.js.map +1 -0
  78. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts +35 -0
  79. package/lib/Cloudflare/Workers/VersionMetadataBinding.d.ts.map +1 -0
  80. package/lib/Cloudflare/Workers/VersionMetadataBinding.js +31 -0
  81. package/lib/Cloudflare/Workers/VersionMetadataBinding.js.map +1 -0
  82. package/lib/Cloudflare/Workers/Worker.d.ts +18 -18
  83. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  84. package/lib/Cloudflare/Workers/Worker.js +23 -37
  85. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  86. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
  87. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +7 -0
  88. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
  89. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +2 -1
  90. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
  91. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
  92. package/lib/Cloudflare/Workers/Workflow.d.ts +8 -1
  93. package/lib/Cloudflare/Workers/Workflow.d.ts.map +1 -1
  94. package/lib/Cloudflare/Workers/Workflow.js +1 -0
  95. package/lib/Cloudflare/Workers/Workflow.js.map +1 -1
  96. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -1
  97. package/lib/Cloudflare/Workers/WorkflowBridge.js +31 -2
  98. package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -1
  99. package/lib/Cloudflare/Workers/index.d.ts +2 -0
  100. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  101. package/lib/Cloudflare/Workers/index.js +2 -0
  102. package/lib/Cloudflare/Workers/index.js.map +1 -1
  103. package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts.map +1 -1
  104. package/lib/Diff.d.ts.map +1 -1
  105. package/lib/Diff.js +7 -0
  106. package/lib/Diff.js.map +1 -1
  107. package/lib/Drizzle/Postgres.d.ts +13 -12
  108. package/lib/Drizzle/Postgres.d.ts.map +1 -1
  109. package/lib/Drizzle/Postgres.js +13 -12
  110. package/lib/Drizzle/Postgres.js.map +1 -1
  111. package/lib/Drizzle/Schema.d.ts +1 -1
  112. package/lib/Drizzle/Schema.d.ts.map +1 -1
  113. package/lib/Drizzle/Schema.js +14 -6
  114. package/lib/Drizzle/Schema.js.map +1 -1
  115. package/lib/Local/RpcSerialization.js +5 -2
  116. package/lib/Local/RpcSerialization.js.map +1 -1
  117. package/lib/Platform.d.ts.map +1 -1
  118. package/lib/Platform.js +27 -25
  119. package/lib/Platform.js.map +1 -1
  120. package/lib/Resource.d.ts +1 -1
  121. package/lib/Resource.d.ts.map +1 -1
  122. package/lib/Resource.js +9 -12
  123. package/lib/Resource.js.map +1 -1
  124. package/lib/Runtime.d.ts +1 -1
  125. package/lib/Runtime.d.ts.map +1 -1
  126. package/lib/Runtime.js +3 -4
  127. package/lib/Runtime.js.map +1 -1
  128. package/lib/Util/effect.d.ts.map +1 -1
  129. package/lib/Util/effect.js +13 -15
  130. package/lib/Util/effect.js.map +1 -1
  131. package/lib/Util/poll.d.ts +0 -1
  132. package/lib/Util/poll.d.ts.map +1 -1
  133. package/lib/Util/poll.js +2 -2
  134. package/lib/Util/poll.js.map +1 -1
  135. package/lib/tsconfig.test.tsbuildinfo +1 -1
  136. package/package.json +20 -20
  137. package/src/Apply.ts +22 -3
  138. package/src/Auth/AuthProvider.ts +1 -1
  139. package/src/Binding.ts +45 -38
  140. package/src/Build/DevServer.ts +330 -0
  141. package/src/Build/Local.ts +4 -0
  142. package/src/Bundle/Bundle.ts +1 -1
  143. package/src/Cloudflare/Access.ts +1 -1
  144. package/src/Cloudflare/AiGateway/AiGateway.ts +1 -1
  145. package/src/Cloudflare/Browser/Browser.ts +84 -12
  146. package/src/Cloudflare/Browser/BrowserBinding.ts +205 -33
  147. package/src/Cloudflare/Browser/index.ts +15 -2
  148. package/src/Cloudflare/Container/ContainerBinding.ts +5 -10
  149. package/src/Cloudflare/Images/Images.ts +4 -3
  150. package/src/Cloudflare/Providers.ts +5 -0
  151. package/src/Cloudflare/RateLimit/RateLimit.ts +4 -3
  152. package/src/Cloudflare/Website/StaticSite.ts +154 -41
  153. package/src/Cloudflare/Website/Vite.ts +73 -25
  154. package/src/Cloudflare/Workers/Assets.ts +10 -8
  155. package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +4 -4
  156. package/src/Cloudflare/Workers/InferEnv.ts +14 -12
  157. package/src/Cloudflare/Workers/LocalWorkerProvider.ts +54 -13
  158. package/src/Cloudflare/Workers/RpcDurableObjectNamespace.ts +11 -8
  159. package/src/Cloudflare/Workers/RpcWorker.ts +8 -7
  160. package/src/Cloudflare/Workers/VersionMetadata.ts +129 -0
  161. package/src/Cloudflare/Workers/VersionMetadataBinding.ts +82 -0
  162. package/src/Cloudflare/Workers/Worker.ts +60 -81
  163. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +6 -0
  164. package/src/Cloudflare/Workers/WorkerBinding.ts +2 -0
  165. package/src/Cloudflare/Workers/Workflow.ts +12 -1
  166. package/src/Cloudflare/Workers/WorkflowBridge.ts +47 -6
  167. package/src/Cloudflare/Workers/index.ts +2 -0
  168. package/src/Diff.ts +7 -0
  169. package/src/Drizzle/Postgres.ts +13 -12
  170. package/src/Drizzle/Schema.ts +16 -6
  171. package/src/Local/RpcSerialization.ts +5 -2
  172. package/src/Platform.ts +36 -32
  173. package/src/Resource.ts +12 -13
  174. package/src/Runtime.ts +3 -7
  175. package/src/Util/effect.ts +13 -17
  176. package/src/Util/poll.ts +3 -3
@@ -1,11 +1,17 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Scope from "effect/Scope";
6
+ import { ExecutionContext } from "../../ExecutionContext.ts";
7
+ import { isScopeEjected } from "./HttpServer.ts";
8
+ import { getWorkerExport } from "./WorkerBridge.ts";
2
9
  import {
3
10
  WorkflowEvent as WorkflowEventService,
4
11
  type WorkflowExport,
5
12
  type WorkflowImpl,
6
13
  WorkflowStep,
7
14
  } from "./Workflow.ts";
8
- import { getWorkerExport } from "./WorkerBridge.ts";
9
15
 
10
16
  /**
11
17
  * Create a WorkflowBridge class that extends `WorkflowEntrypoint` and
@@ -52,15 +58,50 @@ export const makeWorkflowBridge =
52
58
 
53
59
  async run(event: any, step: any): Promise<unknown> {
54
60
  const fn = await this.fn;
55
- return Effect.runPromise(
61
+ // Each run-invocation gets a fresh ExecutionContext (scope + cache),
62
+ // following the same per-invocation-scope pattern as
63
+ // `WorkerBridge.processEvent`. `task` threads this into every step via
64
+ // the surrounding body context, so `@binding` helpers that need it
65
+ // (e.g. `Drizzle.postgres`) resolve their per-run resources inside
66
+ // workflow steps. The same scope is also provided as the ambient
67
+ // `Scope` service, matching the Worker and Durable Object bridges.
68
+ const scope = Scope.makeUnsafe();
69
+ const exit = await Effect.runPromiseExit(
56
70
  fn(event.payload).pipe(
57
- Effect.provideService(
58
- WorkflowEventService,
59
- wrapWorkflowEvent(event),
71
+ Effect.provide(
72
+ Layer.succeed(
73
+ WorkflowEventService,
74
+ wrapWorkflowEvent(event),
75
+ ).pipe(
76
+ Layer.provideMerge(
77
+ Layer.succeed(WorkflowStep, wrapWorkflowStep(step)),
78
+ ),
79
+ Layer.provideMerge(
80
+ Layer.succeed(ExecutionContext, { scope, cache: {} }),
81
+ ),
82
+ Layer.provideMerge(Layer.succeed(Scope.Scope, scope)),
83
+ ),
60
84
  ),
61
- Effect.provideService(WorkflowStep, wrapWorkflowStep(step)),
62
85
  ) as Effect.Effect<unknown>,
63
86
  );
87
+ // Settle the run's resources with its real exit, unless a binding
88
+ // ejected the scope to outlive the invocation. The workflow runtime has
89
+ // no `waitUntil` to detach cleanup to, so close inline — a failing
90
+ // finalizer (e.g. a pg pool `end()` on a dropped connection) is logged
91
+ // and ignored so it can't mask the run's outcome.
92
+ if (!isScopeEjected(scope)) {
93
+ await Scope.close(scope, exit).pipe(
94
+ Effect.ignoreCause({
95
+ log: "Warn",
96
+ message: "Workflow run scope close failed",
97
+ }),
98
+ Effect.runPromise,
99
+ );
100
+ }
101
+ if (Exit.isSuccess(exit)) {
102
+ return exit.value;
103
+ }
104
+ throw Cause.squash(exit.cause);
64
105
  }
65
106
  };
66
107
 
@@ -16,6 +16,8 @@ export * from "./Rpc.ts";
16
16
  export * from "./RpcDurableObjectNamespace.ts";
17
17
  export * from "./RpcWorker.ts";
18
18
  export * from "./ScheduledEvents.ts";
19
+ export * from "./VersionMetadata.ts";
20
+ export * from "./VersionMetadataBinding.ts";
19
21
  export * from "./WebSocket.ts";
20
22
  export * from "./Worker.ts";
21
23
  export * from "./WorkerBinding.ts";
package/src/Diff.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as Redacted from "effect/Redacted";
2
3
  import type { Input } from "./Input.ts";
3
4
  import * as Output from "./Output.ts";
4
5
  import type { BindingNode } from "./Plan.ts";
@@ -119,6 +120,12 @@ export const deepEqual = (
119
120
 
120
121
  const canonicalize = (value: unknown, stripNullish: boolean): unknown => {
121
122
  if (stripNullish && value == null) return undefined;
123
+ if (Redacted.isRedacted(value)) {
124
+ return {
125
+ _tag: "Redacted",
126
+ value: Redacted.value(value),
127
+ };
128
+ }
122
129
  if (Array.isArray(value)) {
123
130
  return value.map((v) => canonicalize(v, stripNullish));
124
131
  }
@@ -25,19 +25,20 @@ import { proxyChain } from "../Util/proxy-chain.ts";
25
25
  * });
26
26
  * ```
27
27
  *
28
- * Behind the scenes the actual connect work is wrapped in `Effect.cached`,
29
- * so the pool is built at most once per JS realm. Yielding the
30
- * connection string is also deferred until first query, so deploy /
31
- * plan-time invocations (where `WorkerEnvironment` isn't provided)
32
- * never trigger a real connection attempt.
28
+ * The connect work is deferred until the first query and memoized on the
29
+ * current `ExecutionContext` (`ctx.cache`), so the pool is built at most
30
+ * once per execution — a Worker `fetch`/`queue`/`scheduled` event or a
31
+ * Workflow run — and reused across every query and `task` step in that
32
+ * execution. Yielding the connection string is likewise deferred, so
33
+ * deploy / plan-time invocations (where `WorkerEnvironment` isn't
34
+ * provided) never trigger a real connection attempt.
33
35
  *
34
- * The PgClient pool is built against an isolated, never-closing `Scope`
35
- * so it outlives whatever scope this helper is yielded under. In a
36
- * Cloudflare Worker the surrounding `Cloudflare.Worker` runs init
37
- * inside `Effect.scoped`, which closes after returning the exports
38
- * object — without an isolated scope, the pool's `end` finalizer
39
- * would fire there and every subsequent request would see "Cannot
40
- * use a pool after end".
36
+ * The pool is built against the execution's `Scope` (`ctx.scope`), so its
37
+ * `end` finalizer fires when that scope closes — when the request / run
38
+ * settles, not when the `Cloudflare.Worker` init scope closes. Init runs
39
+ * inside `Effect.scoped` and closes after returning the exports object;
40
+ * building lazily against `ctx.scope` (rather than that init scope) is
41
+ * what keeps later requests from seeing "Cannot use a pool after end".
41
42
  *
42
43
  * @binding
43
44
  */
@@ -1,7 +1,7 @@
1
- import * as crypto from "node:crypto";
2
1
  import * as Effect from "effect/Effect";
3
2
  import * as FileSystem from "effect/FileSystem";
4
3
  import * as Path from "effect/Path";
4
+ import * as crypto from "node:crypto";
5
5
  import { isResolved } from "../Diff.ts";
6
6
  import * as Provider from "../Provider.ts";
7
7
  import { Resource } from "../Resource.ts";
@@ -45,7 +45,7 @@ export type Schema = Resource<
45
45
  "Drizzle.Schema",
46
46
  SchemaProps,
47
47
  {
48
- /** Absolute path to the migrations directory. */
48
+ /** Path to the migrations directory, relative to the current working directory. */
49
49
  out: string;
50
50
  /**
51
51
  * sha256 of the latest snapshot.json. Stable across deploys when the
@@ -117,6 +117,12 @@ export const SchemaProvider = () =>
117
117
  const resolveOut = (p: SchemaProps) =>
118
118
  path.resolve(process.cwd(), p.out ?? "./migrations");
119
119
 
120
+ // The `out` attribute is exposed as a path relative to the current
121
+ // working directory so that persisted state stays portable across
122
+ // machines/checkouts. Internal filesystem ops always use the absolute
123
+ // `resolveOut` form.
124
+ const relativeOut = (abs: string) => path.relative(process.cwd(), abs);
125
+
120
126
  const resolveSchema = (p: SchemaProps) =>
121
127
  path.resolve(process.cwd(), p.schema);
122
128
 
@@ -241,7 +247,7 @@ export const SchemaProvider = () =>
241
247
  const migrations = yield* listMigrationDirs(out);
242
248
  const latest = yield* readLatestSnapshot(out);
243
249
  return {
244
- out,
250
+ out: relativeOut(out),
245
251
  snapshotHash: latest?.hash ?? sha(JSON.stringify(cur)),
246
252
  migrations,
247
253
  };
@@ -256,8 +262,12 @@ export const SchemaProvider = () =>
256
262
  // `schema.out` as an unresolved Output during plan and cascade
257
263
  // into spurious updates of their own.
258
264
  const { sqlStatements } = yield* detectDrift(news);
259
- if (sqlStatements.length === 0) return undefined;
260
- return { action: "update" } as const;
265
+ // Originally `output.out` was an absolute path, which is not portable.
266
+ // So, we trigger an update to migrate existing resources.
267
+ // This is safe because `regenerate` is idempotent.
268
+ return sqlStatements.length > 0 || path.isAbsolute(output.out)
269
+ ? { action: "update" }
270
+ : undefined;
261
271
  }),
262
272
  read: Effect.fn(function* ({ olds, output }) {
263
273
  if (!output) return undefined;
@@ -267,7 +277,7 @@ export const SchemaProvider = () =>
267
277
  const latest = yield* readLatestSnapshot(out);
268
278
  const migrations = yield* listMigrationDirs(out);
269
279
  return {
270
- out,
280
+ out: relativeOut(out),
271
281
  snapshotHash: latest?.hash ?? output.snapshotHash,
272
282
  migrations,
273
283
  };
@@ -89,7 +89,7 @@ export const unwrapRpcHandlers = <T extends Record<string, any>>(
89
89
  ) as RpcUnwrapped<T>;
90
90
  };
91
91
 
92
- const serializeError = Schema.encodeSync(Schema.Defect);
92
+ const serializeError = Schema.encodeSync(Schema.Defect());
93
93
 
94
94
  const wrapRpcEffectHandler = <Args extends Array<any>, Success, Error>(
95
95
  handler: RpcEffectHandler<Args, Success, Error>,
@@ -107,7 +107,10 @@ const wrapRpcEffectHandler = <Args extends Array<any>, Success, Error>(
107
107
  cause: exit.cause.reasons.map((reason): RpcSerializedCause<Error> => {
108
108
  switch (reason._tag) {
109
109
  case "Fail":
110
- return { _tag: "Fail", error: serializeError(reason.error) };
110
+ return {
111
+ _tag: "Fail",
112
+ error: serializeError(reason.error) as Error,
113
+ };
111
114
  case "Die":
112
115
  return { _tag: "Die", defect: serializeError(reason.defect) };
113
116
  case "Interrupt":
package/src/Platform.ts CHANGED
@@ -2,13 +2,12 @@ import * as ConfigError from "effect/Config";
2
2
  import * as ConfigProvider from "effect/ConfigProvider";
3
3
  import * as Context from "effect/Context";
4
4
  import * as Effect from "effect/Effect";
5
- import { pipe } from "effect/Function";
5
+ import * as Effectable from "effect/Effectable";
6
6
  import * as Layer from "effect/Layer";
7
7
  import * as Option from "effect/Option";
8
8
  import * as Redacted from "effect/Redacted";
9
9
  import type { Scope } from "effect/Scope";
10
10
  import type { HttpClient } from "effect/unstable/http/HttpClient";
11
- import { SingleShotGen } from "effect/Utils";
12
11
  import type { PolicyLike } from "./Binding.ts";
13
12
  import type { Dependencies } from "./Dependencies.ts";
14
13
  import type { ExecutionContext } from "./ExecutionContext.ts";
@@ -225,7 +224,7 @@ export const Platform = <
225
224
  constructor(id, props, impl, true);
226
225
  } else if (!impl) {
227
226
  const cls = makeClass(id, props);
228
- const asEffect = () =>
227
+ const evaluate = () =>
229
228
  (!isTag
230
229
  ? // this is a non-tagged resource yielded without providing an implementation
231
230
  // e.g.
@@ -272,7 +271,7 @@ export const Platform = <
272
271
  );
273
272
  return Object.assign(
274
273
  function (impl: Impl) {
275
- return cls.asEffect().pipe(Effect.provide(cls.make(impl)));
274
+ return cls.Self.pipe(Effect.provide(cls.make(impl)));
276
275
  },
277
276
  // we splice in the Effect so this can be yielded to indicate a non-Effect native instance
278
277
  // e.g. here, we yield it - in this case we don't want to provide an implementation
@@ -280,42 +279,29 @@ export const Platform = <
280
279
  // main: "./src/worker.ts"
281
280
  // });
282
281
  cls,
283
- {
284
- asEffect,
285
- // @ts-expect-error
286
- pipe: (...args: any[]) => asEffect().pipe(...args),
287
- [Symbol.iterator]: () => new SingleShotGen(asEffect()),
288
- },
282
+ // Spread the Effect prototype LAST so it overrides the evaluate copied
283
+ // from `cls`: yielding this no-impl form bridges to the (possibly
284
+ // non-Effect-native) resource rather than resolving the Self tag.
285
+ Effectable.Prototype({
286
+ label: `${type}<${id}>`,
287
+ evaluate,
288
+ }),
289
289
  );
290
290
  } else {
291
291
  // impl was provided inline, this is a non-tagged eager instance
292
292
  // e.g.
293
293
  // export default Cloudflare.Worker("id", { main: "./src/worker.ts" }, Effect.gen(function* () { .. })
294
294
  const cls = makeClass(id, props);
295
- return cls.asEffect().pipe(Effect.provide(cls.make(impl)), effectClass);
295
+ return cls.Self.pipe(Effect.provide(cls.make(impl)), effectClass);
296
296
  }
297
297
  };
298
298
 
299
299
  const makeClass = (id: string, props: Props) => {
300
- return class Platform {
300
+ class Platform {
301
301
  static readonly Self = Self(`${type}<${id}>`);
302
302
  static readonly Platform = Context.Service<Platform, Platform>(
303
303
  `Platform<${type}<${id}>>`,
304
304
  );
305
- static [Symbol.iterator](): Iterator<
306
- Effect.Effect<void, never, Self>,
307
- Resource,
308
- void
309
- > {
310
- return new SingleShotGen(this.asEffect()) as any;
311
- }
312
- static asEffect() {
313
- return this.Self;
314
- }
315
- static pipe(...args: any[]) {
316
- // @ts-expect-error
317
- return pipe(this, ...args);
318
- }
319
305
  static of = (shape: any) => shape;
320
306
  static make = (impl: Impl) => {
321
307
  // build the Layer once for the root Self
@@ -460,13 +446,31 @@ export const Platform = <
460
446
  SelfLayer,
461
447
  );
462
448
  };
463
- };
449
+ }
450
+ // Make the platform class itself a real Effect: `yield* MyWorker` resolves
451
+ // the Self tag. Replaces the hand-rolled asEffect/pipe/[Symbol.iterator].
452
+ return Object.assign(
453
+ Platform,
454
+ Effectable.Prototype({
455
+ label: `${type}<${id}>`,
456
+ evaluate: () => Platform.Self,
457
+ }),
458
+ );
464
459
  };
465
460
 
466
- const instance = Object.assign(constructor, resource, {
467
- Platform: Platform,
468
- asEffect: () => resource.Self,
469
- ...methods,
470
- }) as any;
461
+ const instance = Object.assign(
462
+ constructor,
463
+ resource,
464
+ // Spread the Effect prototype LAST so it overrides any evaluate inherited
465
+ // from `resource`; `yield* Cloudflare.Worker` resolves the resource Self.
466
+ Effectable.Prototype({
467
+ label: `${type}`,
468
+ evaluate: () => resource.Self,
469
+ }),
470
+ {
471
+ Platform: Platform,
472
+ ...methods,
473
+ },
474
+ ) as any;
471
475
  return instance;
472
476
  };
package/src/Resource.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as Effect from "effect/Effect";
2
+ import * as Effectable from "effect/Effectable";
2
3
  import * as Option from "effect/Option";
3
- import { pipeArguments, type Pipeable } from "effect/Pipeable";
4
- import { SingleShotGen } from "effect/Utils";
4
+ import type { Pipeable } from "effect/Pipeable";
5
5
  import { AdoptPolicy } from "./AdoptPolicy.ts";
6
6
  import { toFqn } from "./FQN.ts";
7
7
  import type { Input, InputProps } from "./Input.ts";
@@ -287,17 +287,6 @@ export function Resource<R extends ResourceLike>(
287
287
  const ProviderTag = Provider(type);
288
288
 
289
289
  const Service = {
290
- [Symbol.iterator]() {
291
- return new SingleShotGen(this.asEffect());
292
- },
293
- pipe() {
294
- return pipeArguments(this, arguments);
295
- },
296
- asEffect() {
297
- return Effect.succeed((id: string, props: R["Props"]) =>
298
- constructor(id, props),
299
- );
300
- },
301
290
  /**
302
291
  * Build a typed reference to a deployed instance of this resource
303
292
  * — in the current stack/stage by default, or in another via
@@ -323,6 +312,16 @@ export function Resource<R extends ResourceLike>(
323
312
  ? Object.assign(ResourceClass, args[0])
324
313
  : constructor(...(args as [string, R["Props"]])),
325
314
  Service,
315
+ // Make the constructor itself a real Effect: `yield* MyResource` resolves
316
+ // to the constructor function (same as the old `asEffect()`), and
317
+ // `Effect.isEffect(MyResource)` is now true so `Effect.all`/`forEach` work.
318
+ Effectable.Prototype({
319
+ label: `Resource<${type}>`,
320
+ evaluate: () =>
321
+ Effect.succeed((id: string, props: R["Props"]) =>
322
+ constructor(id, props),
323
+ ),
324
+ }),
326
325
  ) as any;
327
326
 
328
327
  return ResourceClass;
package/src/Runtime.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import * as Layer from "effect/Layer";
10
+ import { asEffect } from "./Util/types.ts";
10
11
 
11
12
  /**
12
13
  * Resolve the user's default-export entrypoint into a `Layer` for the
@@ -14,7 +15,7 @@ import * as Layer from "effect/Layer";
14
15
  *
15
16
  * `entrypoint` may be any of:
16
17
  * - a `Layer` factory (`{ build: (...) => ... }`) — used as-is
17
- * - an effect-class with `asEffect()` (Alchemy `Platform`/`Worker` shape)
18
+ * - an Alchemy `Platform`/`Worker` construct (now a real `Effect`)
18
19
  * - a plain `Effect`
19
20
  *
20
21
  * Centralized so the inline ternary doesn't have to be re-emitted into
@@ -29,10 +30,5 @@ export const makeEntrypointLayer = (
29
30
  if (typeof entrypoint?.build === "function") {
30
31
  return entrypoint;
31
32
  }
32
- return Layer.effect(
33
- tag,
34
- typeof entrypoint?.asEffect === "function"
35
- ? entrypoint.asEffect()
36
- : entrypoint,
37
- );
33
+ return Layer.effect(tag, asEffect(entrypoint));
38
34
  };
@@ -1,7 +1,6 @@
1
1
  import * as Context from "effect/Context";
2
2
  import * as Effect from "effect/Effect";
3
- import { pipeArguments } from "effect/Pipeable";
4
- import { SingleShotGen } from "effect/Utils";
3
+ import * as Effectable from "effect/Effectable";
5
4
 
6
5
  export type EffectClass<Shape, A, Err = never, Req = never> = Effect.Effect<
7
6
  A,
@@ -22,18 +21,15 @@ export const effectClass: {
22
21
  impl === undefined
23
22
  ? (innerImpl: any) => effectClass(innerImpl)
24
23
  : (Object.assign(
25
- class {
26
- static asEffect() {
27
- return impl;
28
- }
29
- static [Symbol.iterator]() {
30
- return new SingleShotGen(this.asEffect());
31
- }
32
- static pipe(...fns: any) {
33
- return pipeArguments(this, fns);
34
- }
35
- },
36
- impl,
24
+ class {},
25
+ // Spreading the Effect prototype onto the class (static side) makes the
26
+ // class itself a real Effect — `Effect.isEffect(X)` is true, so
27
+ // `Effect.all([X])` / `Effect.forEach` work — and subclasses
28
+ // (`class Y extends effectClass(impl)`) inherit the protocol statically.
29
+ Effectable.Prototype({
30
+ label: "alchemy/EffectClass",
31
+ evaluate: () => impl,
32
+ }),
37
33
  ) as unknown as EffectClass<any, any, any, any>)) as any;
38
34
 
39
35
  export const taggedFunction = <
@@ -43,10 +39,10 @@ export const taggedFunction = <
43
39
  tag: Tag,
44
40
  fn: Fn,
45
41
  ): Tag & Fn => {
42
+ // The Proxy below forwards every Effect-protocol key to `tag` (already an
43
+ // Effect), so `asEffect`/`[Symbol.iterator]`/`pipe` need no explicit
44
+ // override — only `toString` diverges.
46
45
  const overrides = {
47
- asEffect: () => tag,
48
- [Symbol.iterator]: () => tag[Symbol.iterator](),
49
- pipe: (...fns: any[]) => pipeArguments(tag, fns as any),
50
46
  toString: () => `${tag.toString()}.${fn.name}`,
51
47
  };
52
48
 
package/src/Util/poll.ts CHANGED
@@ -27,7 +27,6 @@ export const poll = Effect.fn("poll")(
27
27
  effect: Effect.Effect<A, E, R>;
28
28
  predicate: (value: A) => boolean;
29
29
  schedule?: Schedule.Schedule<unknown, unknown, never>;
30
- times?: number;
31
30
  }) =>
32
31
  input.effect.pipe(
33
32
  Effect.filterOrFail(
@@ -40,8 +39,9 @@ export const poll = Effect.fn("poll")(
40
39
  ),
41
40
  Effect.retry({
42
41
  while: isPredicateFailed,
43
- schedule: input.schedule ?? Schedule.spaced("3 seconds"),
44
- times: input.times ?? 50,
42
+ schedule:
43
+ input.schedule ??
44
+ Schedule.both(Schedule.spaced("5 seconds"), Schedule.recurs(50)),
45
45
  }),
46
46
  ),
47
47
  );