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.
- package/bin/exec.js +16 -3
- package/bin/exec.js.map +1 -1
- package/lib/AWS/EC2/hosted.js +1 -1
- package/lib/AWS/EC2/hosted.js.map +1 -1
- package/lib/AWS/ECS/Task.js +1 -1
- package/lib/AWS/ECS/Task.js.map +1 -1
- package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/Function.js +1 -1
- package/lib/AWS/Lambda/Function.js.map +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
- package/lib/Apply.d.ts.map +1 -1
- package/lib/Apply.js +41 -7
- package/lib/Apply.js.map +1 -1
- package/lib/Cloudflare/Bridge.d.ts +1 -0
- package/lib/Cloudflare/Bridge.d.ts.map +1 -1
- package/lib/Cloudflare/Bridge.js +1 -0
- package/lib/Cloudflare/Bridge.js.map +1 -1
- package/lib/Cloudflare/Container/ContainerApplication.js +1 -1
- package/lib/Cloudflare/Container/ContainerApplication.js.map +1 -1
- package/lib/Cloudflare/Container/ContainerBinding.d.ts +1 -1
- package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Container/ContainerBinding.js +1 -1
- package/lib/Cloudflare/Container/ContainerBinding.js.map +1 -1
- package/lib/Cloudflare/Fetcher.d.ts +9 -0
- package/lib/Cloudflare/Fetcher.d.ts.map +1 -1
- package/lib/Cloudflare/Fetcher.js +20 -1
- package/lib/Cloudflare/Fetcher.js.map +1 -1
- package/lib/Cloudflare/Local/SidecarHandlers.d.ts.map +1 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js +1 -1
- package/lib/Cloudflare/Local/SidecarHandlers.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +2 -2
- package/lib/Cloudflare/Website/StaticSite.d.ts +5 -4
- package/lib/Cloudflare/Website/StaticSite.d.ts.map +1 -1
- package/lib/Cloudflare/Website/StaticSite.js.map +1 -1
- package/lib/Cloudflare/Website/Vite.d.ts +4 -4
- package/lib/Cloudflare/Website/Vite.d.ts.map +1 -1
- package/lib/Cloudflare/Website/Vite.js.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts +17 -0
- package/lib/Cloudflare/Workers/DurableObjectBridge.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/DurableObjectBridge.js +86 -0
- package/lib/Cloudflare/Workers/DurableObjectBridge.js.map +1 -0
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts +2 -1
- package/lib/Cloudflare/Workers/DurableObjectNamespace.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js +10 -39
- package/lib/Cloudflare/Workers/DurableObjectNamespace.js.map +1 -1
- package/lib/Cloudflare/Workers/HttpServer.d.ts +2 -8
- package/lib/Cloudflare/Workers/HttpServer.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/HttpServer.js +29 -33
- package/lib/Cloudflare/Workers/HttpServer.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +5 -24
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.d.ts +0 -101
- package/lib/Cloudflare/Workers/Rpc.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Rpc.js +9 -286
- package/lib/Cloudflare/Workers/Rpc.js.map +1 -1
- package/lib/Cloudflare/Workers/RpcAsync.d.ts +8 -0
- package/lib/Cloudflare/Workers/RpcAsync.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/RpcAsync.js +45 -0
- package/lib/Cloudflare/Workers/RpcAsync.js.map +1 -0
- package/lib/Cloudflare/Workers/Worker.d.ts +16 -162
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js +49 -354
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts +8 -0
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +129 -0
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +154 -0
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBinding.js +51 -0
- package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBridge.d.ts +35 -0
- package/lib/Cloudflare/Workers/WorkerBridge.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBridge.js +149 -0
- package/lib/Cloudflare/Workers/WorkerBridge.js.map +1 -0
- package/lib/Cloudflare/Workers/WorkerBundle.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBundle.js +18 -84
- package/lib/Cloudflare/Workers/WorkerBundle.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts +8 -0
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.js +113 -0
- package/lib/Cloudflare/Workers/WorkerRuntimeContext.js.map +1 -0
- package/lib/Cloudflare/Workers/WorkflowBridge.d.ts +26 -0
- package/lib/Cloudflare/Workers/WorkflowBridge.d.ts.map +1 -0
- package/lib/Cloudflare/Workers/WorkflowBridge.js +41 -0
- package/lib/Cloudflare/Workers/WorkflowBridge.js.map +1 -0
- package/lib/Cloudflare/Workers/index.d.ts +4 -0
- package/lib/Cloudflare/Workers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/index.js +4 -0
- package/lib/Cloudflare/Workers/index.js.map +1 -1
- package/lib/Cloudflare/index.d.ts +4 -2
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +4 -2
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Neon/Project.d.ts.map +1 -1
- package/lib/Neon/Project.js +2 -1
- package/lib/Neon/Project.js.map +1 -1
- package/lib/Platform.d.ts +3 -4
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js.map +1 -1
- package/lib/Rpc.d.ts +17 -0
- package/lib/Rpc.d.ts.map +1 -0
- package/lib/Rpc.js +1 -0
- package/lib/Rpc.js.map +1 -0
- package/lib/RuntimeContext.d.ts +1 -0
- package/lib/RuntimeContext.d.ts.map +1 -1
- package/lib/RuntimeContext.js.map +1 -1
- package/lib/Sidecar/RpcHandler.d.ts.map +1 -1
- package/lib/Sidecar/RpcHandler.js +13 -9
- package/lib/Sidecar/RpcHandler.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +10 -0
- package/lib/State/HttpStateApi.d.ts.map +1 -1
- package/lib/State/HttpStateApi.js +10 -0
- package/lib/State/HttpStateApi.js.map +1 -1
- package/lib/State/HttpStateStore.d.ts.map +1 -1
- package/lib/State/HttpStateStore.js +2 -1
- package/lib/State/HttpStateStore.js.map +1 -1
- package/lib/State/InMemoryState.d.ts.map +1 -1
- package/lib/State/InMemoryState.js +2 -0
- package/lib/State/InMemoryState.js.map +1 -1
- package/lib/State/LocalState.d.ts.map +1 -1
- package/lib/State/LocalState.js +2 -0
- package/lib/State/LocalState.js.map +1 -1
- package/lib/State/State.d.ts +8 -0
- package/lib/State/State.d.ts.map +1 -1
- package/lib/State/StateEncoding.d.ts.map +1 -1
- package/lib/State/StateEncoding.js +5 -1
- package/lib/State/StateEncoding.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/Apply.ts +44 -10
- package/src/Cli/Event.ts +1 -1
- package/src/Cloudflare/Bridge.ts +1 -0
- package/src/Cloudflare/Container/ContainerBinding.ts +1 -1
- package/src/Cloudflare/Fetcher.ts +38 -1
- package/src/Cloudflare/Local/SidecarHandlers.ts +3 -6
- package/src/Cloudflare/Website/StaticSite.ts +10 -5
- package/src/Cloudflare/Website/Vite.ts +7 -4
- package/src/Cloudflare/Workers/DurableObjectBridge.ts +180 -0
- package/src/Cloudflare/Workers/DurableObjectNamespace.ts +22 -70
- package/src/Cloudflare/Workers/HttpServer.ts +25 -39
- package/src/Cloudflare/Workers/InferEnv.ts +42 -78
- package/src/Cloudflare/Workers/Rpc.ts +18 -417
- package/src/Cloudflare/Workers/RpcAsync.ts +63 -0
- package/src/Cloudflare/Workers/Worker.ts +107 -548
- package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +149 -0
- package/src/Cloudflare/Workers/WorkerBinding.ts +103 -0
- package/src/Cloudflare/Workers/WorkerBridge.ts +304 -0
- package/src/Cloudflare/Workers/WorkerBundle.ts +17 -83
- package/src/Cloudflare/Workers/WorkerRuntimeContext.ts +164 -0
- package/src/Cloudflare/Workers/WorkflowBridge.ts +92 -0
- package/src/Cloudflare/Workers/index.ts +4 -0
- package/src/Cloudflare/index.ts +4 -2
- package/src/Neon/Project.ts +3 -1
- package/src/Platform.ts +3 -4
- package/src/Rpc.ts +25 -0
- package/src/RuntimeContext.ts +1 -0
- package/src/Sidecar/RpcHandler.ts +18 -9
- package/src/State/HttpStateApi.ts +11 -0
- package/src/State/HttpStateStore.ts +7 -2
- package/src/State/InMemoryState.ts +2 -0
- package/src/State/LocalState.ts +2 -0
- package/src/State/State.ts +8 -0
- package/src/State/StateEncoding.ts +8 -1
- 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";
|
package/src/Cloudflare/index.ts
CHANGED
|
@@ -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";
|
package/src/Neon/Project.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/RuntimeContext.ts
CHANGED
|
@@ -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(
|
|
141
|
-
|
|
142
|
-
|
|
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(
|
package/src/State/LocalState.ts
CHANGED
|
@@ -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,
|
package/src/State/State.ts
CHANGED
|
@@ -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 (
|
|
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,
|