alchemy 2.0.0-beta.40 → 2.0.0-beta.41

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 (179) hide show
  1. package/bin/exec.js +16 -3
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/EC2/hosted.js +1 -1
  4. package/lib/AWS/EC2/hosted.js.map +1 -1
  5. package/lib/AWS/ECS/Task.js +1 -1
  6. package/lib/AWS/ECS/Task.js.map +1 -1
  7. package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
  8. package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
  9. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
  10. package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
  11. package/lib/AWS/Lambda/Function.js +1 -1
  12. package/lib/AWS/Lambda/Function.js.map +1 -1
  13. package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
  14. package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
  15. package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
  16. package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
  17. package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
  18. package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
  19. package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
  20. package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
  21. package/lib/Apply.d.ts.map +1 -1
  22. package/lib/Apply.js +41 -7
  23. package/lib/Apply.js.map +1 -1
  24. package/lib/Cloudflare/Bridge.d.ts +1 -0
  25. package/lib/Cloudflare/Bridge.d.ts.map +1 -1
  26. package/lib/Cloudflare/Bridge.js +1 -0
  27. package/lib/Cloudflare/Bridge.js.map +1 -1
  28. package/lib/Cloudflare/Container/ContainerApplication.js +1 -1
  29. package/lib/Cloudflare/Container/ContainerApplication.js.map +1 -1
  30. package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
  31. package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
  32. package/lib/Cloudflare/Container/ContainerBinding.js +1 -1
  33. package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
  34. package/lib/Cloudflare/Fetcher.d.ts +9 -0
  35. package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
  36. package/lib/Cloudflare/Fetcher.js +20 -1
  37. package/lib/Cloudflare/Fetcher.js.map +1 -1
  38. package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -1
  39. package/lib/Cloudflare/Local/SidecarHandlers.js +1 -1
  40. package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
  41. package/lib/Cloudflare/StateStore/Api.d.ts +2 -2
  42. package/lib/Cloudflare/Website/StaticSite.d.ts +5 -4
  43. package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
  44. package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
  45. package/lib/Cloudflare/Website/Vite.d.ts +4 -4
  46. package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
  47. package/lib/Cloudflare/Website/Vite.js.map +1 -1
  48. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts +17 -0
  49. package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -0
  50. package/lib/Cloudflare/Workers/DurableObjectBridge.js +86 -0
  51. package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -0
  52. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +2 -1
  53. package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
  54. package/lib/Cloudflare/Workers/DurableObjectNamespace.js +10 -39
  55. package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
  56. package/lib/Cloudflare/Workers/HttpServer.d.ts +2 -8
  57. package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
  58. package/lib/Cloudflare/Workers/HttpServer.js +29 -33
  59. package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
  60. package/lib/Cloudflare/Workers/InferEnv.d.ts +5 -24
  61. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  62. package/lib/Cloudflare/Workers/Rpc.d.ts +0 -101
  63. package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
  64. package/lib/Cloudflare/Workers/Rpc.js +9 -286
  65. package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
  66. package/lib/Cloudflare/Workers/RpcAsync.d.ts +8 -0
  67. package/lib/Cloudflare/Workers/RpcAsync.d.ts.map +1 -0
  68. package/lib/Cloudflare/Workers/RpcAsync.js +45 -0
  69. package/lib/Cloudflare/Workers/RpcAsync.js.map +1 -0
  70. package/lib/Cloudflare/Workers/Worker.d.ts +16 -162
  71. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  72. package/lib/Cloudflare/Workers/Worker.js +49 -354
  73. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  74. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +8 -0
  75. package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -0
  76. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +129 -0
  77. package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -0
  78. package/lib/Cloudflare/Workers/WorkerBinding.d.ts +154 -0
  79. package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -0
  80. package/lib/Cloudflare/Workers/WorkerBinding.js +51 -0
  81. package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -0
  82. package/lib/Cloudflare/Workers/WorkerBridge.d.ts +35 -0
  83. package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -0
  84. package/lib/Cloudflare/Workers/WorkerBridge.js +149 -0
  85. package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -0
  86. package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
  87. package/lib/Cloudflare/Workers/WorkerBundle.js +18 -84
  88. package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
  89. package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts +8 -0
  90. package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts.map +1 -0
  91. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js +113 -0
  92. package/lib/Cloudflare/Workers/WorkerRuntimeContext.js.map +1 -0
  93. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts +26 -0
  94. package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -0
  95. package/lib/Cloudflare/Workers/WorkflowBridge.js +41 -0
  96. package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -0
  97. package/lib/Cloudflare/Workers/index.d.ts +4 -0
  98. package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
  99. package/lib/Cloudflare/Workers/index.js +4 -0
  100. package/lib/Cloudflare/Workers/index.js.map +1 -1
  101. package/lib/Cloudflare/index.d.ts +4 -2
  102. package/lib/Cloudflare/index.d.ts.map +1 -1
  103. package/lib/Cloudflare/index.js +4 -2
  104. package/lib/Cloudflare/index.js.map +1 -1
  105. package/lib/Neon/Project.d.ts.map +1 -1
  106. package/lib/Neon/Project.js +2 -1
  107. package/lib/Neon/Project.js.map +1 -1
  108. package/lib/Platform.d.ts +3 -4
  109. package/lib/Platform.d.ts.map +1 -1
  110. package/lib/Platform.js.map +1 -1
  111. package/lib/Rpc.d.ts +17 -0
  112. package/lib/Rpc.d.ts.map +1 -0
  113. package/lib/Rpc.js +1 -0
  114. package/lib/Rpc.js.map +1 -0
  115. package/lib/RuntimeContext.d.ts +1 -0
  116. package/lib/RuntimeContext.d.ts.map +1 -1
  117. package/lib/RuntimeContext.js.map +1 -1
  118. package/lib/Sidecar/RpcHandler.d.ts.map +1 -1
  119. package/lib/Sidecar/RpcHandler.js +13 -9
  120. package/lib/Sidecar/RpcHandler.js.map +1 -1
  121. package/lib/State/HttpStateApi.d.ts +10 -0
  122. package/lib/State/HttpStateApi.d.ts.map +1 -1
  123. package/lib/State/HttpStateApi.js +10 -0
  124. package/lib/State/HttpStateApi.js.map +1 -1
  125. package/lib/State/HttpStateStore.d.ts.map +1 -1
  126. package/lib/State/HttpStateStore.js +2 -1
  127. package/lib/State/HttpStateStore.js.map +1 -1
  128. package/lib/State/InMemoryState.d.ts.map +1 -1
  129. package/lib/State/InMemoryState.js +2 -0
  130. package/lib/State/InMemoryState.js.map +1 -1
  131. package/lib/State/LocalState.d.ts.map +1 -1
  132. package/lib/State/LocalState.js +2 -0
  133. package/lib/State/LocalState.js.map +1 -1
  134. package/lib/State/State.d.ts +8 -0
  135. package/lib/State/State.d.ts.map +1 -1
  136. package/lib/State/StateEncoding.d.ts.map +1 -1
  137. package/lib/State/StateEncoding.js +5 -1
  138. package/lib/State/StateEncoding.js.map +1 -1
  139. package/lib/index.d.ts +2 -1
  140. package/lib/index.d.ts.map +1 -1
  141. package/lib/index.js +1 -1
  142. package/lib/index.js.map +1 -1
  143. package/lib/tsconfig.test.tsbuildinfo +1 -1
  144. package/package.json +1 -1
  145. package/src/Apply.ts +44 -10
  146. package/src/Cli/Event.ts +1 -1
  147. package/src/Cloudflare/Bridge.ts +1 -0
  148. package/src/Cloudflare/Container/ContainerBinding.ts +1 -1
  149. package/src/Cloudflare/Fetcher.ts +38 -1
  150. package/src/Cloudflare/Local/SidecarHandlers.ts +3 -6
  151. package/src/Cloudflare/Website/StaticSite.ts +10 -5
  152. package/src/Cloudflare/Website/Vite.ts +7 -4
  153. package/src/Cloudflare/Workers/DurableObjectBridge.ts +180 -0
  154. package/src/Cloudflare/Workers/DurableObjectNamespace.ts +22 -70
  155. package/src/Cloudflare/Workers/HttpServer.ts +25 -39
  156. package/src/Cloudflare/Workers/InferEnv.ts +42 -78
  157. package/src/Cloudflare/Workers/Rpc.ts +18 -417
  158. package/src/Cloudflare/Workers/RpcAsync.ts +63 -0
  159. package/src/Cloudflare/Workers/Worker.ts +107 -548
  160. package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +149 -0
  161. package/src/Cloudflare/Workers/WorkerBinding.ts +103 -0
  162. package/src/Cloudflare/Workers/WorkerBridge.ts +304 -0
  163. package/src/Cloudflare/Workers/WorkerBundle.ts +17 -83
  164. package/src/Cloudflare/Workers/WorkerRuntimeContext.ts +164 -0
  165. package/src/Cloudflare/Workers/WorkflowBridge.ts +92 -0
  166. package/src/Cloudflare/Workers/index.ts +4 -0
  167. package/src/Cloudflare/index.ts +4 -2
  168. package/src/Neon/Project.ts +3 -1
  169. package/src/Platform.ts +3 -4
  170. package/src/Rpc.ts +25 -0
  171. package/src/RuntimeContext.ts +1 -0
  172. package/src/Sidecar/RpcHandler.ts +18 -9
  173. package/src/State/HttpStateApi.ts +11 -0
  174. package/src/State/HttpStateStore.ts +7 -2
  175. package/src/State/InMemoryState.ts +2 -0
  176. package/src/State/LocalState.ts +2 -0
  177. package/src/State/State.ts +8 -0
  178. package/src/State/StateEncoding.ts +8 -1
  179. package/src/index.ts +7 -6
@@ -0,0 +1,164 @@
1
+ import type * as cf from "@cloudflare/workers-types";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import * as Redacted from "effect/Redacted";
5
+ import type { HttpEffect } from "../../Http.ts";
6
+ import * as Output from "../../Output.ts";
7
+ import type * as Serverless from "../../Serverless/index.ts";
8
+ import { makeRequestHandler } from "./HttpServer.ts";
9
+ import {
10
+ ExportedHandlerMethods,
11
+ WorkerEnvironment,
12
+ WorkerTypeId,
13
+ type WorkerEvent,
14
+ } from "./Worker.ts";
15
+
16
+ export interface WorkerRuntimeContext extends Serverless.FunctionContext {
17
+ export(name: string, value: any): Effect.Effect<void>;
18
+ shape: () => Record<string, any>;
19
+ }
20
+
21
+ export const makeWorkerRuntimeContext = (id: string): WorkerRuntimeContext => {
22
+ const listeners: Effect.Effect<Serverless.FunctionListener>[] = [];
23
+ const exports: Record<string, any> = {};
24
+ const env: Record<string, any> = {};
25
+ let userShape: Record<string, unknown> | undefined;
26
+
27
+ const ctx = {
28
+ Type: WorkerTypeId,
29
+ id,
30
+ env,
31
+ shape: () => userShape!,
32
+ get: (key: string) =>
33
+ Effect.serviceOption(WorkerEnvironment).pipe(
34
+ Effect.map(Option.getOrUndefined),
35
+ Effect.flatMap((env) =>
36
+ env
37
+ ? Effect.succeed(env[key])
38
+ : Effect.die("WorkerEnvironment not found"),
39
+ ),
40
+ Effect.flatMap((value) =>
41
+ value
42
+ ? Effect.succeed(value)
43
+ : Effect.die(`Environment variable '${key}' not found`),
44
+ ),
45
+ Effect.map((json) => {
46
+ try {
47
+ const value = JSON.parse(json);
48
+ // The `set` path serializes Redacted values as
49
+ // `{_tag: "Redacted", value: ...}`. After JSON.parse the
50
+ // result is a plain object — `Redacted.isRedacted` would
51
+ // always return `false` on it — so detect the marker shape
52
+ // and rebuild the Redacted wrapper. Plain values pass
53
+ // through unchanged.
54
+ if (
55
+ value !== null &&
56
+ typeof value === "object" &&
57
+ (value as { _tag?: unknown })._tag === "Redacted" &&
58
+ "value" in (value as object)
59
+ ) {
60
+ return Redacted.make((value as { value: unknown }).value);
61
+ }
62
+ return value;
63
+ } catch {
64
+ return json;
65
+ }
66
+ }),
67
+ ) as any,
68
+ set: (id: string, output: Output.Output) =>
69
+ Effect.sync(() => {
70
+ const key = id.replaceAll(/[^a-zA-Z0-9]/g, "_");
71
+ // Preserve `Redacted`-ness across the Output → env → Cloudflare
72
+ // binding boundary so the put-worker loop can deploy secrets via
73
+ // `secret_text` instead of leaking them as `plain_text`. The JSON
74
+ // payload still carries the `{_tag: "Redacted", …}` marker so the
75
+ // runtime `get` accessor can rebuild the wrapper after Cloudflare
76
+ // hands the binding back as a plain string.
77
+ env[key] = output.pipe(
78
+ Output.map((value) =>
79
+ Redacted.isRedacted(value)
80
+ ? Redacted.make(
81
+ JSON.stringify({
82
+ _tag: "Redacted",
83
+ value: Redacted.value(value),
84
+ }),
85
+ )
86
+ : JSON.stringify(value),
87
+ ),
88
+ );
89
+ return key;
90
+ }),
91
+ serve: <Req = never>(
92
+ handler: HttpEffect<Req> | Effect.Effect<HttpEffect<Req>>,
93
+ options?: { shape?: Record<string, unknown> },
94
+ ) => {
95
+ // Capture the user's full default-export shape so `exports` can
96
+ // expose any non-handler methods on it as RPC methods on the
97
+ // deployed `WorkerEntrypoint` subclass — see `__rpc__` below.
98
+ if (options?.shape) userShape = options.shape;
99
+ return ctx.listen(makeRequestHandler(handler));
100
+ },
101
+ listen: ((
102
+ handler:
103
+ | Serverless.FunctionListener
104
+ | Effect.Effect<Serverless.FunctionListener>,
105
+ ) =>
106
+ Effect.sync(() =>
107
+ Effect.isEffect(handler)
108
+ ? listeners.push(handler)
109
+ : listeners.push(Effect.succeed(handler)),
110
+ )) as any as Serverless.FunctionContext["listen"],
111
+ export: (name: string, value: any) =>
112
+ Effect.sync(() => {
113
+ exports[name] = value;
114
+ }),
115
+ exports: Effect.gen(function* () {
116
+ const handlers = yield* Effect.all(listeners, {
117
+ concurrency: "unbounded",
118
+ });
119
+ const services = yield* Effect.context();
120
+
121
+ const dispatch =
122
+ (type: WorkerEvent["type"]) =>
123
+ (request: any, env: unknown, context: cf.ExecutionContext) => {
124
+ const event: WorkerEvent = {
125
+ kind: "Cloudflare.Workers.WorkerEvent",
126
+ type,
127
+ input: request,
128
+ env,
129
+ context,
130
+ };
131
+ for (const handler of handlers) {
132
+ const eff = handler(event);
133
+ if (Effect.isEffect(eff)) {
134
+ return [eff, services];
135
+ }
136
+ }
137
+ return [
138
+ Effect.die(
139
+ new Error(`No event handler found for event type '${type}'`),
140
+ ),
141
+ services,
142
+ ];
143
+ };
144
+
145
+ // RPC method dispatchers — one per non-handler method on the user's
146
+ // shape. Each dispatcher is invoked by the WorkerEntrypoint bridge
147
+ // as `dispatcher(args, ctx)`: `args` are the user-facing call args,
148
+ // `ctx` is the `this.ctx` that Cloudflare hands the bridge per RPC
149
+ // request. The dispatcher runs the user effect with the same runtime
150
+ // layer the fetch path uses, then envelope-encodes the result so
151
+ // `Effect.fail` round-trips as `RpcErrorEnvelope` and `Stream` as
152
+ // `RpcStreamEnvelope` (consumers wrap the binding with
153
+ // `toPromiseApi`/`bindWorker` to decode).
154
+
155
+ return {
156
+ ...exports,
157
+ default: Object.fromEntries(
158
+ ExportedHandlerMethods.map((method) => [method, dispatch(method)]),
159
+ ),
160
+ };
161
+ }),
162
+ };
163
+ return ctx;
164
+ };
@@ -0,0 +1,92 @@
1
+ import * as Effect from "effect/Effect";
2
+ import {
3
+ WorkflowEvent as WorkflowEventService,
4
+ type WorkflowExport,
5
+ type WorkflowImpl,
6
+ WorkflowStep,
7
+ } from "./Workflow.ts";
8
+ import { getWorkerExport } from "./WorkerBridge.ts";
9
+
10
+ /**
11
+ * Create a WorkflowBridge class that extends `WorkflowEntrypoint` and
12
+ * delegates the `run(event, step)` call to the Effect-native workflow body
13
+ * registered via `worker.export(...)`.
14
+ *
15
+ * The bridge provides `WorkflowEvent` and `WorkflowStep` as Effect
16
+ * services so the user writes `yield* WorkflowEvent` and `yield* task(...)`
17
+ * instead of receiving callback parameters.
18
+ */
19
+ export const makeWorkflowBridge =
20
+ (
21
+ WorkflowEntrypoint: abstract new (
22
+ ctx: unknown,
23
+ env: unknown,
24
+ ) => { run(event: any, step: any): Promise<unknown> },
25
+ {
26
+ entrypoint,
27
+ stack,
28
+ }: {
29
+ entrypoint: Effect.Effect<Record<string, any>>;
30
+ stack: { name: string; stage: string };
31
+ },
32
+ ) =>
33
+ (className: string) =>
34
+ class WorkflowBridge extends WorkflowEntrypoint {
35
+ readonly fn: Promise<WorkflowImpl<unknown, unknown>>;
36
+
37
+ constructor(ctx: unknown, env: unknown) {
38
+ super(ctx, env);
39
+
40
+ const { globalContext, exported } = getWorkerExport<WorkflowExport>({
41
+ entrypoint,
42
+ stack,
43
+ exportName: className,
44
+ });
45
+
46
+ this.fn = exported.pipe(
47
+ Effect.flatMap((wf) => wf.make(env)),
48
+ Effect.provide(globalContext),
49
+ Effect.runPromise,
50
+ ) as Promise<WorkflowImpl<unknown, unknown>>;
51
+ }
52
+
53
+ async run(event: any, step: any): Promise<unknown> {
54
+ const fn = await this.fn;
55
+ return Effect.runPromise(
56
+ fn(event.payload).pipe(
57
+ Effect.provideService(
58
+ WorkflowEventService,
59
+ wrapWorkflowEvent(event),
60
+ ),
61
+ Effect.provideService(WorkflowStep, wrapWorkflowStep(step)),
62
+ ) as Effect.Effect<unknown>,
63
+ );
64
+ }
65
+ };
66
+
67
+ const wrapWorkflowEvent = (
68
+ event: any,
69
+ ): { payload: unknown; timestamp: Date; instanceId: string } => ({
70
+ payload: event.payload,
71
+ timestamp:
72
+ event.timestamp instanceof Date
73
+ ? event.timestamp
74
+ : new Date(event.timestamp),
75
+ instanceId: event.instanceId ?? "",
76
+ });
77
+
78
+ const wrapWorkflowStep = (step: any): WorkflowStep["Service"] => ({
79
+ do: <T>(name: string, effect: Effect.Effect<T>): Effect.Effect<T> =>
80
+ Effect.tryPromise(
81
+ () => step.do(name, () => Effect.runPromise(effect)) as Promise<T>,
82
+ ),
83
+ sleep: (name: string, duration: string | number): Effect.Effect<void> =>
84
+ Effect.tryPromise(() => step.sleep(name, duration)),
85
+ sleepUntil: (name: string, timestamp: Date | number): Effect.Effect<void> =>
86
+ Effect.tryPromise(() =>
87
+ step.sleepUntil(
88
+ name,
89
+ timestamp instanceof Date ? timestamp.toISOString() : timestamp,
90
+ ),
91
+ ),
92
+ });
@@ -1,6 +1,7 @@
1
1
  export * from "./Assets.ts";
2
2
  export * from "./ConfigProvider.ts";
3
3
  export * from "./CronEventSource.ts";
4
+ export * from "./DurableObjectBridge.ts";
4
5
  export * from "./DurableObjectNamespace.ts";
5
6
  export * from "./DurableObjectState.ts";
6
7
  export * from "./DurableObjectStorage.ts";
@@ -14,4 +15,7 @@ export * from "./Rpc.ts";
14
15
  export * from "./ScheduledEvents.ts";
15
16
  export * from "./WebSocket.ts";
16
17
  export * from "./Worker.ts";
18
+ export * from "./WorkerBinding.ts";
19
+ export * from "./WorkerBridge.ts";
17
20
  export * from "./Workflow.ts";
21
+ export * from "./WorkflowBridge.ts";
@@ -1,11 +1,12 @@
1
- export * from "./ApiToken/index.ts";
2
- export * from "./Artifacts/index.ts";
3
1
  export * from "./AiGateway/index.ts";
4
2
  export * from "./AnalyticsEngine/index.ts";
3
+ export * from "./ApiToken/index.ts";
4
+ export * from "./Artifacts/index.ts";
5
5
  export * from "./CloudflareEnvironment.ts";
6
6
  export * from "./Container/index.ts";
7
7
  export * from "./D1/index.ts";
8
8
  export * from "./EdgeSession.ts";
9
+ export * from "./Fetcher.ts";
9
10
  export * from "./Email/index.ts";
10
11
  export * from "./Hyperdrive/index.ts";
11
12
  export * from "./Images/index.ts";
@@ -19,3 +20,4 @@ export * from "./Tunnel/index.ts";
19
20
  export * from "./VpcService/index.ts";
20
21
  export * from "./Website/index.ts";
21
22
  export * from "./Workers/index.ts";
23
+ export * from "./Workers/RpcAsync.ts";
@@ -6,11 +6,12 @@ import {
6
6
  getProjectOperation,
7
7
  listProjectBranchDatabases,
8
8
  listProjectBranches,
9
- type ListProjectsOutput,
10
9
  listProjects,
10
+ type ListProjectsOutput,
11
11
  createProject as sdkCreateProject,
12
12
  updateProject,
13
13
  } from "@distilled.cloud/neon";
14
+ import * as Console from "effect/Console";
14
15
  import * as Data from "effect/Data";
15
16
  import * as Duration from "effect/Duration";
16
17
  import * as Effect from "effect/Effect";
@@ -586,6 +587,7 @@ export const ProjectProvider = () =>
586
587
  }),
587
588
  delete: Effect.fn(function* ({ output }) {
588
589
  yield* deleteProject({ project_id: output.projectId }).pipe(
590
+ Effect.tapError(Console.log),
589
591
  Effect.catchTag("NotFound", () => Effect.void),
590
592
  );
591
593
  }),
package/src/Platform.ts CHANGED
@@ -7,10 +7,12 @@ import type { Scope } from "effect/Scope";
7
7
  import type { HttpClient } from "effect/unstable/http/HttpClient";
8
8
  import { SingleShotGen } from "effect/Utils";
9
9
  import type { PolicyLike } from "./Binding.ts";
10
+ import type { ExecutionContext } from "./ExecutionContext.ts";
10
11
  import type { HttpEffect } from "./Http.ts";
11
12
  import type { InputProps } from "./Input.ts";
12
13
  import type { Provider, ProviderCollectionLike } from "./Provider.ts";
13
14
  import { Resource, type ResourceLike } from "./Resource.ts";
15
+ import type { Rpc } from "./Rpc.ts";
14
16
  import { RuntimeContext, type BaseRuntimeContext } from "./RuntimeContext.ts";
15
17
  import { Self } from "./Self.ts";
16
18
  import type { Stack, StackServices } from "./Stack.ts";
@@ -35,13 +37,10 @@ export type Main<InitServices = never> = void | {
35
37
  >;
36
38
  };
37
39
 
38
- export type Rpc<Shape> = {
39
- "~alchemy/rpc": Shape;
40
- };
41
-
42
40
  // services provided to the Resource
43
41
  export type PlatformServices =
44
42
  | RuntimeContext
43
+ | ExecutionContext
45
44
  | HttpClient
46
45
  | PolicyLike
47
46
  | Provider<any>
package/src/Rpc.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type * as Effect from "effect/Effect";
2
+
3
+ export type Rpc<Shape> = {
4
+ "~alchemy/rpc": Shape;
5
+ };
6
+
7
+ /**
8
+ * Recover the user's RPC `Shape` from any of the forms a caller might pass
9
+ * to {@link toPromiseApi}:
10
+ *
11
+ * - the Worker class value's type, e.g. `typeof Backend`, which extends
12
+ * `Effect.Effect<Worker & Rpc<Shape>, …>`
13
+ * - the unwrapped `Worker & Rpc<Shape>` type
14
+ * - a bare `Shape` (when the caller types it explicitly)
15
+ */
16
+ export declare namespace Rpc {
17
+ export type Shape<W> =
18
+ W extends Effect.Effect<infer R, any, any>
19
+ ? R extends Rpc<infer Shape>
20
+ ? Shape
21
+ : R
22
+ : W extends Rpc<infer Shape>
23
+ ? Shape
24
+ : W;
25
+ }
@@ -16,6 +16,7 @@ export interface BaseRuntimeContext {
16
16
  handler: HttpEffect<Req>,
17
17
  options?: { shape?: Record<string, unknown> },
18
18
  ): Effect.Effect<void, never, Req>;
19
+ shape?: () => Record<string, unknown>;
19
20
  }
20
21
 
21
22
  export interface RuntimeContext<
@@ -129,6 +129,21 @@ export const serializeRpcHandlers = <T extends RpcHandlers>(
129
129
  ]),
130
130
  ) as SerializedRpcHandlers<T>;
131
131
 
132
+ const encodeRedacted = (value: unknown): unknown => {
133
+ if (Redacted.isRedacted(value)) {
134
+ return { _tag: "Redacted", value: Redacted.value(value) };
135
+ }
136
+ if (Array.isArray(value)) {
137
+ return value.map(encodeRedacted);
138
+ }
139
+ if (value && typeof value === "object" && !("toJSON" in value)) {
140
+ return Object.fromEntries(
141
+ Object.entries(value).map(([key, child]) => [key, encodeRedacted(child)]),
142
+ );
143
+ }
144
+ return value;
145
+ };
146
+
132
147
  const deserializeRpcHandler =
133
148
  <Args extends Array<any>, Success, Error>(
134
149
  handler: SerializedRpcHandler<Args, Success, Error>,
@@ -137,15 +152,9 @@ const deserializeRpcHandler =
137
152
  (...args) =>
138
153
  Effect.promise(async () => {
139
154
  try {
140
- const serializedArgs = JSON.stringify(args, (_, value) => {
141
- if (Redacted.isRedacted(value)) {
142
- return {
143
- _tag: "Redacted",
144
- value: Redacted.value(value),
145
- };
146
- }
147
- return value;
148
- }) as SerializedArgs<Args>;
155
+ const serializedArgs = JSON.stringify(
156
+ encodeRedacted(args),
157
+ ) as SerializedArgs<Args>;
149
158
  return await handler(serializedArgs);
150
159
  } catch (error) {
151
160
  console.error("Error calling handler", error);
@@ -174,6 +174,17 @@ export const SetStackOutput = HttpApiEndpoint.put(
174
174
  },
175
175
  );
176
176
 
177
+ /**
178
+ * Version of the State Store wire / behavioural contract.
179
+ *
180
+ * Bump this whenever the wire format or runtime behaviour of an HTTP
181
+ * state-store changes in a way that an older deployed copy can no
182
+ * longer satisfy. Clients query `/version` on the deployed worker and
183
+ * compare against this constant; a mismatch (or 404) triggers a
184
+ * forced redeploy via the bootstrap flow.
185
+ */
186
+ export const STATE_STORE_VERSION = 5 as const;
187
+
177
188
  /** Response shape for the unauthenticated `/version` probe. */
178
189
  export const VersionResponse = Schema.Struct({
179
190
  version: Schema.Number,
@@ -9,8 +9,8 @@ import { StateApi } from "./HttpStateApi.ts";
9
9
  import type { ReplacedResourceState, ResourceState } from "./ResourceState.ts";
10
10
  import {
11
11
  StateStoreError,
12
- type StateService,
13
12
  type PersistedState,
13
+ type StateService,
14
14
  } from "./State.ts";
15
15
  import { encodeState, reviveStateRecursive } from "./StateEncoding.ts";
16
16
 
@@ -59,6 +59,11 @@ export const makeHttpStateStore = ({
59
59
 
60
60
  const service: StateService = {
61
61
  id,
62
+ getVersion: () =>
63
+ apiClient.version.getVersion().pipe(
64
+ Effect.map((v) => v.version),
65
+ mapStateStoreError,
66
+ ),
62
67
  listStacks: () =>
63
68
  state.listStacks().pipe(
64
69
  Effect.map((stacks) => [...stacks]),
@@ -196,8 +201,8 @@ const retryTransient = <A, Err, Req>(eff: Effect.Effect<A, Err, Req>) =>
196
201
  /** Collapse any client failure into a {@link StateStoreError}. */
197
202
  const mapStateStoreError = <A, E, R>(eff: Effect.Effect<A, E, R>) =>
198
203
  eff.pipe(
199
- Effect.tapError(Effect.log),
200
204
  retryTransient,
205
+ Effect.tapError(Effect.log),
201
206
  Effect.catch((e: E) =>
202
207
  Effect.fail(
203
208
  new StateStoreError({
@@ -1,6 +1,7 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Layer from "effect/Layer";
3
3
  import { recordStateStoreInit } from "../Telemetry/Metrics.ts";
4
+ import { STATE_STORE_VERSION } from "./HttpStateApi.ts";
4
5
  import type { ResourceState } from "./ResourceState.ts";
5
6
  import { State, type PersistedState } from "./State.ts";
6
7
 
@@ -28,6 +29,7 @@ export const InMemoryService = (
28
29
  ) =>
29
30
  State.of({
30
31
  id: "inmemory",
32
+ getVersion: () => Effect.succeed(STATE_STORE_VERSION),
31
33
  listStacks: () => Effect.succeed(Array.from(Object.keys(state))),
32
34
  listStages: (stack: string) =>
33
35
  Effect.succeed(
@@ -5,6 +5,7 @@ import * as Path from "effect/Path";
5
5
  import type { PlatformError } from "effect/PlatformError";
6
6
  import { decodeFqn, encodeFqn } from "../FQN.ts";
7
7
  import { recordStateStoreInit } from "../Telemetry/Metrics.ts";
8
+ import { STATE_STORE_VERSION } from "./HttpStateApi.ts";
8
9
  import { State, StateStoreError, type StateService } from "./State.ts";
9
10
  import { encodeState, reviveState } from "./StateEncoding.ts";
10
11
 
@@ -60,6 +61,7 @@ export const makeLocalState = () =>
60
61
 
61
62
  const state: StateService = {
62
63
  id: "local",
64
+ getVersion: () => Effect.succeed(STATE_STORE_VERSION),
63
65
  listStacks: () =>
64
66
  fs.readDirectory(stateDir).pipe(
65
67
  recover,
@@ -45,6 +45,14 @@ export interface StateService {
45
45
  * stable, kebab-case slug.
46
46
  */
47
47
  readonly id: string;
48
+ /**
49
+ * Wire / behavioural contract version of this state-store
50
+ * implementation. For local / in-process stores this is the
51
+ * `STATE_STORE_VERSION` the CLI was built against; for HTTP-backed
52
+ * stores it is the version reported by the deployed `/version`
53
+ * probe.
54
+ */
55
+ getVersion(): Effect.Effect<number, StateStoreError, never>;
48
56
  listStacks(): Effect.Effect<readonly string[], StateStoreError, never>;
49
57
  listStages(
50
58
  stack: string,
@@ -19,7 +19,14 @@ export const REDACTED_MARKER = "__redacted__";
19
19
  * - Plain objects and arrays are walked structurally.
20
20
  */
21
21
  export const encodeState = (value: unknown): unknown => {
22
- if (value === null || value === undefined) return value;
22
+ if (
23
+ value === null ||
24
+ value === undefined ||
25
+ typeof value === "string" ||
26
+ typeof value === "number" ||
27
+ typeof value === "boolean"
28
+ )
29
+ return value;
23
30
  if (Redacted.isRedacted(value)) {
24
31
  return {
25
32
  [REDACTED_MARKER]: encodeState(Redacted.value(value)),
package/src/index.ts CHANGED
@@ -1,3 +1,9 @@
1
+ export {
2
+ Action,
3
+ isAction,
4
+ type Action as ActionInstance,
5
+ type ActionLike,
6
+ } from "./Action.ts";
1
7
  export * as AdoptPolicy from "./AdoptPolicy.ts";
2
8
  export * from "./AlchemyContext.ts";
3
9
  export * from "./Apply.ts";
@@ -23,12 +29,6 @@ export * from "./Random.ts";
23
29
  export * from "./Ref.ts";
24
30
  export * as RemovalPolicy from "./RemovalPolicy.ts";
25
31
  export * from "./Resource.ts";
26
- export {
27
- Action,
28
- isAction,
29
- type ActionLike,
30
- type Action as ActionInstance,
31
- } from "./Action.ts";
32
32
  export * as Schema from "./Schema.ts";
33
33
  export { Secret } from "./Secret.ts";
34
34
  export * as Server from "./Server/index.ts";
@@ -48,6 +48,7 @@ export { Cli } from "./Cli/Cli.ts";
48
48
  export type * from "./Platform.ts";
49
49
  export { Platform } from "./Platform.ts";
50
50
  export type { ProviderCollectionLike } from "./Provider.ts";
51
+ export type * from "./Rpc.ts";
51
52
  export {
52
53
  RuntimeContext,
53
54
  type BaseRuntimeContext,