effect-cf 0.26.0 → 0.28.0
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/CHANGELOG.md +16 -0
- package/README.md +8 -6
- package/dist/{Artifacts-DcoXcRP5.d.mts → Artifacts-DnHQA-Ax.d.mts} +42 -25
- package/dist/Artifacts-rq3hUKQE.mjs +257 -0
- package/dist/{Binding-COCtoJXa.d.mts → Binding-xJJJPmiD.d.mts} +6 -5
- package/dist/ComputerArtifacts.d.mts +55 -0
- package/dist/ComputerArtifacts.mjs +95 -0
- package/dist/ComputerWorkspace.d.mts +279 -0
- package/dist/ComputerWorkspace.mjs +577 -0
- package/dist/ComputerWorkspaceHost.d.mts +1 -1
- package/dist/ComputerWorkspaceHost.mjs +18 -16
- package/dist/{WorkflowBinding-D5iWzCUa.d.mts → DurableObjectState-B3uFY1eM.d.mts} +90 -141
- package/dist/{DurableObjectState-CEh0DVw7.mjs → DurableObjectState-BjkK2kwH.mjs} +4 -49
- package/dist/ErrorMessage-BM0K-Nik.mjs +46 -0
- package/dist/{Hyperdrive-6p59yyVk.d.mts → Hyperdrive-z-9fFz15.d.mts} +2 -2
- package/dist/HyperdrivePg.d.mts +2 -2
- package/dist/Vitest.d.mts +6 -4
- package/dist/Vitest.mjs +2 -1
- package/dist/WorkflowBinding-wZ-yGHow.d.mts +74 -0
- package/dist/index.d.mts +92 -398
- package/dist/index.mjs +323 -1105
- package/package.json +12 -1
- package/src/AiGateway.ts +62 -7
- package/src/AnalyticsEngine.ts +17 -13
- package/src/Artifacts.ts +111 -47
- package/src/Binding.ts +34 -23
- package/src/BrowserRendering.ts +72 -33
- package/src/CloudflareOtlp.ts +8 -5
- package/src/ComputerArtifacts.ts +17 -9
- package/src/ComputerWorkspace.ts +202 -98
- package/src/ComputerWorkspaceHost.ts +55 -27
- package/src/ContainerNamespace.ts +8 -9
- package/src/D1.ts +8 -14
- package/src/DurableObject.ts +27 -11
- package/src/DurableObjectAlarm.ts +17 -5
- package/src/DurableObjectDefinition.ts +89 -72
- package/src/DurableObjectNamespace.ts +26 -12
- package/src/DurableObjectRpcWebSocket.ts +9 -10
- package/src/DurableObjectState.ts +1 -0
- package/src/DurableObjectStorage.ts +5 -2
- package/src/DurableObjectWebSocket.ts +4 -1
- package/src/Email.ts +26 -25
- package/src/Hyperdrive.ts +4 -10
- package/src/Images.ts +12 -14
- package/src/Kv.ts +15 -17
- package/src/Queue.ts +2 -1
- package/src/QueueBinding.ts +7 -12
- package/src/QueueDefinition.ts +7 -2
- package/src/R2.ts +33 -24
- package/src/Rpc.ts +15 -21
- package/src/RpcDefinition.ts +33 -38
- package/src/ServiceBinding.ts +26 -8
- package/src/Vectorize.ts +45 -18
- package/src/Vitest.ts +9 -3
- package/src/WebTransport.ts +11 -5
- package/src/Worker.ts +91 -21
- package/src/WorkerDefinition.ts +143 -63
- package/src/WorkersAi.ts +50 -28
- package/src/Workflow.ts +66 -13
- package/src/WorkflowBinding.ts +18 -25
- package/src/WorkflowDefinition.ts +4 -2
- package/src/index.ts +0 -2
- package/src/internal/Entrypoint.ts +29 -13
- package/src/internal/ErrorMessage.ts +7 -6
- package/src/internal/RpcInvocation.ts +34 -11
- package/src/internal/Runtime.ts +33 -22
- package/src/internal/WorkerContext.ts +3 -3
package/src/WorkersAi.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Data, Effect, type Layer } from "effect";
|
|
1
|
+
import { Context, Data, Effect, Option, Predicate, Schema as S, type Layer } from "effect";
|
|
2
2
|
import type {
|
|
3
3
|
Ai as CloudflareAi,
|
|
4
4
|
AiAsyncBatchResponse,
|
|
@@ -44,11 +44,13 @@ export type WorkersAiAsyncBatchResponse = AiAsyncBatchResponse;
|
|
|
44
44
|
export type WorkersAiEmbeddingInput =
|
|
45
45
|
| AiTextEmbeddingsInput
|
|
46
46
|
| Ai_Cf_Qwen_Qwen3_Embedding_0_6B_Input
|
|
47
|
-
|
|
|
47
|
+
| Parameters<WorkersAiBinding["run"]>[1];
|
|
48
|
+
|
|
49
|
+
const embeddingDimensionsKey = "shape";
|
|
48
50
|
|
|
49
51
|
export interface WorkersAiEmbeddingResponse {
|
|
50
52
|
readonly data: ReadonlyArray<ReadonlyArray<number>>;
|
|
51
|
-
readonly
|
|
53
|
+
readonly [embeddingDimensionsKey]: ReadonlyArray<number>;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
export interface WorkersAiClient<ModelList extends AiModelListType = AiModels> {
|
|
@@ -89,7 +91,10 @@ export interface WorkersAiClient<ModelList extends AiModelListType = AiModels> {
|
|
|
89
91
|
input: ModelList[Name]["inputs"],
|
|
90
92
|
options?: WorkersAiOptions,
|
|
91
93
|
): Effect.Effect<ModelList[Name]["postProcessedOutputs"], WorkersAiOperationError>;
|
|
92
|
-
<
|
|
94
|
+
<
|
|
95
|
+
Input extends Parameters<WorkersAiBinding["run"]>[1],
|
|
96
|
+
Output = Awaited<ReturnType<WorkersAiBinding["run"]>>,
|
|
97
|
+
>(
|
|
93
98
|
model: string,
|
|
94
99
|
input: Input,
|
|
95
100
|
options?: WorkersAiOptions,
|
|
@@ -161,33 +166,47 @@ const tryWorkersAiSync = <A>(
|
|
|
161
166
|
catch: (cause) => workersAiError(binding, operation, cause),
|
|
162
167
|
});
|
|
163
168
|
|
|
164
|
-
|
|
165
|
-
|
|
169
|
+
type WorkersAiBindingValue = S.Schema.Type<typeof S.Unknown>;
|
|
170
|
+
|
|
171
|
+
const WorkersAiBindingSchema = S.declare(
|
|
172
|
+
(value: WorkersAiBindingValue): value is WorkersAiBinding =>
|
|
173
|
+
Predicate.hasProperty(value, "run") &&
|
|
174
|
+
Predicate.isFunction(value.run) &&
|
|
175
|
+
Predicate.hasProperty(value, "gateway") &&
|
|
176
|
+
Predicate.isFunction(value.gateway) &&
|
|
177
|
+
Predicate.hasProperty(value, "models") &&
|
|
178
|
+
Predicate.isFunction(value.models),
|
|
179
|
+
);
|
|
180
|
+
const decodeWorkersAiBinding = S.decodeUnknownOption(WorkersAiBindingSchema);
|
|
166
181
|
|
|
167
|
-
export const isWorkersAiBinding = (value:
|
|
168
|
-
|
|
169
|
-
value !== null &&
|
|
170
|
-
hasFunction(value, "run") &&
|
|
171
|
-
hasFunction(value, "gateway") &&
|
|
172
|
-
hasFunction(value, "models");
|
|
182
|
+
export const isWorkersAiBinding = (value: WorkersAiBindingValue): value is WorkersAiBinding =>
|
|
183
|
+
Option.isSome(decodeWorkersAiBinding(value));
|
|
173
184
|
|
|
174
185
|
export const embeddingResponse = (value: {
|
|
175
186
|
readonly data?: ReadonlyArray<ReadonlyArray<number>>;
|
|
176
|
-
readonly
|
|
187
|
+
readonly [embeddingDimensionsKey]?: ReadonlyArray<number>;
|
|
177
188
|
}): WorkersAiEmbeddingResponse => ({
|
|
178
189
|
data: value.data ?? [],
|
|
179
|
-
|
|
190
|
+
[embeddingDimensionsKey]: value[embeddingDimensionsKey] ?? [],
|
|
180
191
|
});
|
|
181
192
|
|
|
193
|
+
const decodeEmbeddingResponse = S.decodeUnknownOption(
|
|
194
|
+
S.Struct({
|
|
195
|
+
data: S.optional(S.Array(S.Array(S.Number))),
|
|
196
|
+
[embeddingDimensionsKey]: S.optional(S.Array(S.Number)),
|
|
197
|
+
}),
|
|
198
|
+
);
|
|
199
|
+
|
|
182
200
|
export const makeClient =
|
|
183
201
|
<ModelList extends AiModelListType = AiModels>(definition: WorkersAiDefinition) =>
|
|
184
202
|
(ai: WorkersAiBinding<ModelList>): WorkersAiClient<ModelList> => {
|
|
185
203
|
const run = Effect.fn("WorkersAi.run")(
|
|
186
|
-
(model: string, input:
|
|
187
|
-
tryWorkersAiPromise(definition.binding, "run", () =>
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
(model: string, input: Parameters<WorkersAiBinding["run"]>[1], options?: WorkersAiOptions) =>
|
|
205
|
+
tryWorkersAiPromise(definition.binding, "run", () => {
|
|
206
|
+
// SAFETY: a non-literal string intentionally selects Cloudflare's unknown-model fallback overload.
|
|
207
|
+
return ai.run(model as string & {}, input, options);
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
191
210
|
|
|
192
211
|
return {
|
|
193
212
|
definition,
|
|
@@ -198,17 +217,19 @@ export const makeClient =
|
|
|
198
217
|
),
|
|
199
218
|
run,
|
|
200
219
|
runEmbedding: Effect.fn("WorkersAi.runEmbedding")(
|
|
201
|
-
(model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) =>
|
|
202
|
-
|
|
220
|
+
(model: string, input: WorkersAiEmbeddingInput, options?: WorkersAiOptions) => {
|
|
221
|
+
// SAFETY: every WorkersAiEmbeddingInput variant is a string-keyed Workers AI payload.
|
|
222
|
+
const modelInput = input as Parameters<WorkersAiBinding["run"]>[1];
|
|
223
|
+
|
|
224
|
+
return run(model, modelInput, options).pipe(
|
|
203
225
|
Effect.map((response) =>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
},
|
|
209
|
-
),
|
|
226
|
+
Option.match(decodeEmbeddingResponse(response), {
|
|
227
|
+
onNone: () => embeddingResponse({}),
|
|
228
|
+
onSome: embeddingResponse,
|
|
229
|
+
}),
|
|
210
230
|
),
|
|
211
|
-
)
|
|
231
|
+
);
|
|
232
|
+
},
|
|
212
233
|
),
|
|
213
234
|
models: Effect.fn("WorkersAi.models")((params?: WorkersAiModelsSearchParams) =>
|
|
214
235
|
tryWorkersAiPromise(definition.binding, "models", () => ai.models(params)),
|
|
@@ -242,6 +263,7 @@ export const Tag =
|
|
|
242
263
|
|
|
243
264
|
const makeLayer = (definition: LayerOptions) => layer(tag, definition);
|
|
244
265
|
|
|
266
|
+
// SAFETY: Object.assign attaches exactly the static id/layer members declared by TagClass.
|
|
245
267
|
return Object.assign(tag, {
|
|
246
268
|
id,
|
|
247
269
|
layer: makeLayer,
|
package/src/Workflow.ts
CHANGED
|
@@ -12,7 +12,17 @@ import {
|
|
|
12
12
|
type WorkflowStepEvent,
|
|
13
13
|
type WorkflowTimeoutDuration,
|
|
14
14
|
} from "cloudflare:workers";
|
|
15
|
-
import {
|
|
15
|
+
import { NonRetryableError as CloudflareNonRetryableError } from "cloudflare:workflows";
|
|
16
|
+
import {
|
|
17
|
+
Cause,
|
|
18
|
+
Context,
|
|
19
|
+
Data,
|
|
20
|
+
Effect,
|
|
21
|
+
Layer,
|
|
22
|
+
type ManagedRuntime,
|
|
23
|
+
Option,
|
|
24
|
+
type Scope,
|
|
25
|
+
} from "effect";
|
|
16
26
|
|
|
17
27
|
import { WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
18
28
|
import { ExecutionContext, WorkerContext } from "./Worker";
|
|
@@ -49,6 +59,22 @@ export class WorkflowStepError extends Data.TaggedError("WorkflowStepError")<{
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Typed step failure that Cloudflare must treat as terminal instead of retrying.
|
|
64
|
+
*
|
|
65
|
+
* The workflow boundary converts this value into Cloudflare's native
|
|
66
|
+
* `NonRetryableError` while preserving the original cause and its message.
|
|
67
|
+
*/
|
|
68
|
+
export class WorkflowStepNonRetryableError extends Data.TaggedError(
|
|
69
|
+
"WorkflowStepNonRetryableError",
|
|
70
|
+
)<{
|
|
71
|
+
readonly cause: unknown;
|
|
72
|
+
}> {
|
|
73
|
+
override get message(): string {
|
|
74
|
+
return `Workflow step failed without retry: ${ErrorMessage.causeMessage(this.cause)}`;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
export interface WorkflowStepService {
|
|
53
79
|
readonly raw: CloudflareWorkflowStep;
|
|
54
80
|
do<A, E, R>(
|
|
@@ -85,6 +111,7 @@ export class WorkflowStepContext extends Context.Service<
|
|
|
85
111
|
const fromWorkflowEvent = <Payload>(
|
|
86
112
|
event: CloudflareWorkflowEvent<Payload>,
|
|
87
113
|
): WorkflowEventService<Payload> => ({
|
|
114
|
+
// SAFETY: raw intentionally erases only the payload type while retaining the same event object.
|
|
88
115
|
raw: event as CloudflareWorkflowEvent<unknown>,
|
|
89
116
|
payload: event.payload,
|
|
90
117
|
timestamp: event.timestamp,
|
|
@@ -92,6 +119,27 @@ const fromWorkflowEvent = <Payload>(
|
|
|
92
119
|
workflowName: event.workflowName,
|
|
93
120
|
});
|
|
94
121
|
|
|
122
|
+
const toCloudflareNonRetryableError = (
|
|
123
|
+
error: WorkflowStepNonRetryableError,
|
|
124
|
+
): CloudflareNonRetryableError => {
|
|
125
|
+
const cloudflareError = new CloudflareNonRetryableError(error.message);
|
|
126
|
+
|
|
127
|
+
cloudflareError.cause = error.cause;
|
|
128
|
+
|
|
129
|
+
return cloudflareError;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const exposeCloudflareNonRetryableError = <A, E, R>(
|
|
133
|
+
effect: Effect.Effect<A, E, R>,
|
|
134
|
+
): Effect.Effect<A, E, R> =>
|
|
135
|
+
Effect.catchCause(effect, (cause) => {
|
|
136
|
+
const error = Cause.findErrorOption(cause);
|
|
137
|
+
|
|
138
|
+
return Option.isSome(error) && error.value instanceof WorkflowStepNonRetryableError
|
|
139
|
+
? Effect.die(toCloudflareNonRetryableError(error.value))
|
|
140
|
+
: Effect.failCause(cause);
|
|
141
|
+
});
|
|
142
|
+
|
|
95
143
|
const fromWorkflowStep = (
|
|
96
144
|
step: CloudflareWorkflowStep,
|
|
97
145
|
runPromise: RunWorkflowStepEffect,
|
|
@@ -104,17 +152,21 @@ const fromWorkflowStep = (
|
|
|
104
152
|
try: () => {
|
|
105
153
|
const run = (stepContext: CloudflareWorkflowStepContext) =>
|
|
106
154
|
runPromise(
|
|
107
|
-
|
|
108
|
-
Effect.
|
|
109
|
-
Effect.
|
|
110
|
-
|
|
111
|
-
|
|
155
|
+
exposeCloudflareNonRetryableError(
|
|
156
|
+
Effect.scoped(
|
|
157
|
+
Effect.provideService(
|
|
158
|
+
Effect.provideContext(
|
|
159
|
+
// SAFETY: WorkflowStepContext is provided immediately below; context supplies every other R service.
|
|
160
|
+
effect as Effect.Effect<A, E, Exclude<R, WorkflowStepContext>>,
|
|
161
|
+
context,
|
|
162
|
+
),
|
|
163
|
+
WorkflowStepContext,
|
|
164
|
+
stepContext,
|
|
112
165
|
),
|
|
113
|
-
WorkflowStepContext,
|
|
114
|
-
stepContext,
|
|
115
166
|
),
|
|
116
167
|
),
|
|
117
168
|
);
|
|
169
|
+
// SAFETY: these overloads reproduce the Cloudflare WorkflowStep.do runtime signatures used here.
|
|
118
170
|
const rawStep = step as {
|
|
119
171
|
do(
|
|
120
172
|
name: string,
|
|
@@ -132,7 +184,7 @@ const fromWorkflowStep = (
|
|
|
132
184
|
catch: (cause) => new WorkflowStepError({ step: name, operation: "do", cause }),
|
|
133
185
|
}),
|
|
134
186
|
),
|
|
135
|
-
)
|
|
187
|
+
),
|
|
136
188
|
sleep: (name, duration) =>
|
|
137
189
|
Effect.tryPromise({
|
|
138
190
|
try: () => step.sleep(name, duration),
|
|
@@ -151,7 +203,10 @@ const fromWorkflowStep = (
|
|
|
151
203
|
},
|
|
152
204
|
) =>
|
|
153
205
|
Effect.tryPromise({
|
|
154
|
-
try: () =>
|
|
206
|
+
try: () => {
|
|
207
|
+
// SAFETY: Cloudflare returns an event whose payload is determined by the caller's event contract.
|
|
208
|
+
return step.waitForEvent(name, options) as Promise<WorkflowStepEvent<Payload>>;
|
|
209
|
+
},
|
|
155
210
|
catch: (cause) => new WorkflowStepError({ step: name, operation: "waitForEvent", cause }),
|
|
156
211
|
}),
|
|
157
212
|
});
|
|
@@ -230,9 +285,7 @@ export const step = <A, E, R>(
|
|
|
230
285
|
effect: Effect.Effect<A, E, R>,
|
|
231
286
|
config?: WorkflowStepConfig,
|
|
232
287
|
): Effect.Effect<A, WorkflowStepError, WorkflowStep | Exclude<R, WorkflowStepContext>> =>
|
|
233
|
-
Effect.flatMap(WorkflowStep, (workflowStep) =>
|
|
234
|
-
workflowStep.do(name, effect, config),
|
|
235
|
-
) as Effect.Effect<A, WorkflowStepError, WorkflowStep | Exclude<R, WorkflowStepContext>>;
|
|
288
|
+
Effect.flatMap(WorkflowStep, (workflowStep) => workflowStep.do(name, effect, config));
|
|
236
289
|
|
|
237
290
|
export const sleep = (
|
|
238
291
|
name: string,
|
package/src/WorkflowBinding.ts
CHANGED
|
@@ -3,8 +3,9 @@ import type {
|
|
|
3
3
|
Workflow as CloudflareWorkflow,
|
|
4
4
|
WorkflowInstance as CloudflareWorkflowInstance,
|
|
5
5
|
WorkflowInstanceCreateOptions as CloudflareWorkflowInstanceCreateOptions,
|
|
6
|
+
WorkflowInstanceRestartOptions as CloudflareWorkflowInstanceRestartOptions,
|
|
6
7
|
} from "@cloudflare/workers-types";
|
|
7
|
-
import { type Context, Data, Effect, Option, Schema as S } from "effect";
|
|
8
|
+
import { type Context, Data, Effect, Option, Predicate, Schema as S } from "effect";
|
|
8
9
|
|
|
9
10
|
import * as Binding from "./Binding";
|
|
10
11
|
import type * as RpcDefinition from "./RpcDefinition";
|
|
@@ -21,13 +22,7 @@ export type WorkflowInstanceCreateBatchOptions<Payload, EncodedPayload = unknown
|
|
|
21
22
|
{ readonly payload: Payload } & WorkflowInstanceCreateOptions<EncodedPayload>
|
|
22
23
|
>;
|
|
23
24
|
|
|
24
|
-
export
|
|
25
|
-
readonly from?: {
|
|
26
|
-
readonly name?: string;
|
|
27
|
-
readonly count?: number;
|
|
28
|
-
readonly type?: string;
|
|
29
|
-
};
|
|
30
|
-
}
|
|
25
|
+
export type WorkflowInstanceRestartOptions = CloudflareWorkflowInstanceRestartOptions;
|
|
31
26
|
|
|
32
27
|
export type WorkflowInstanceStatusName = CloudflareInstanceStatus["status"];
|
|
33
28
|
|
|
@@ -129,19 +124,22 @@ const tryWorkflowPromise = <A>(
|
|
|
129
124
|
catch: (cause) => workflowError(binding, operation, cause),
|
|
130
125
|
});
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
if (typeof value !== "object" || value === null) {
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
127
|
+
type WorkflowBindingValue = S.Schema.Type<typeof S.Unknown>;
|
|
136
128
|
|
|
137
|
-
|
|
129
|
+
const WorkflowBindingSchema = S.declare(
|
|
130
|
+
(value: WorkflowBindingValue): value is CloudflareWorkflow<WorkflowBindingValue> =>
|
|
131
|
+
Predicate.hasProperty(value, "create") &&
|
|
132
|
+
Predicate.isFunction(value.create) &&
|
|
133
|
+
Predicate.hasProperty(value, "createBatch") &&
|
|
134
|
+
Predicate.isFunction(value.createBatch) &&
|
|
135
|
+
Predicate.hasProperty(value, "get") &&
|
|
136
|
+
Predicate.isFunction(value.get),
|
|
137
|
+
);
|
|
138
|
+
const decodeWorkflowBinding = S.decodeUnknownOption(WorkflowBindingSchema);
|
|
138
139
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
typeof resource.get === "function"
|
|
143
|
-
);
|
|
144
|
-
};
|
|
140
|
+
export const isWorkflow = <Payload>(
|
|
141
|
+
value: WorkflowBindingValue,
|
|
142
|
+
): value is CloudflareWorkflow<Payload> => Option.isSome(decodeWorkflowBinding(value));
|
|
145
143
|
|
|
146
144
|
export const makeClient = <
|
|
147
145
|
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
@@ -168,12 +166,7 @@ export const makeClient = <
|
|
|
168
166
|
pause: operation("pause", () => raw.pause()),
|
|
169
167
|
resume: operation("resume", () => raw.resume()),
|
|
170
168
|
terminate: operation("terminate", () => raw.terminate()),
|
|
171
|
-
restart: (options) =>
|
|
172
|
-
operation("restart", () =>
|
|
173
|
-
(raw as { restart(options?: WorkflowInstanceRestartOptions): Promise<void> }).restart(
|
|
174
|
-
options,
|
|
175
|
-
),
|
|
176
|
-
),
|
|
169
|
+
restart: (options) => operation("restart", () => raw.restart(options)),
|
|
177
170
|
status: operation("status", () => raw.status()).pipe(
|
|
178
171
|
Effect.flatMap((status) =>
|
|
179
172
|
Effect.gen(function* () {
|
|
@@ -187,6 +187,7 @@ export const Tag =
|
|
|
187
187
|
|
|
188
188
|
const rawUnsafe = Effect.flatMap(tag, (workflow) => workflow.rawUnsafe);
|
|
189
189
|
|
|
190
|
+
// SAFETY: Object.assign attaches the definition-derived static helpers declared by TagClass.
|
|
190
191
|
return Object.assign(tag, {
|
|
191
192
|
id: workflowDefinition.id,
|
|
192
193
|
payload: workflowDefinition.payload,
|
|
@@ -216,15 +217,16 @@ const wrapHandler = <ROut, const Self extends Definition.Any>(
|
|
|
216
217
|
return Effect.fnUntraced(function* (payload: S.Codec.Encoded<Self["payload"]>) {
|
|
217
218
|
const decodedPayload = yield* decodePayload(payload);
|
|
218
219
|
const event = yield* WorkflowEntrypoint.WorkflowEvent;
|
|
220
|
+
// SAFETY: replacing the encoded event payload with its schema-decoded value establishes this service type.
|
|
219
221
|
const decodedEvent = {
|
|
220
222
|
...event,
|
|
221
223
|
payload: decodedPayload,
|
|
222
224
|
} as WorkflowEntrypoint.WorkflowEventService<S.Schema.Type<Self["payload"]>>;
|
|
223
|
-
const result = yield* handler(decodedPayload
|
|
225
|
+
const result = yield* handler(decodedPayload).pipe(
|
|
224
226
|
Effect.provideService(WorkflowEntrypoint.WorkflowEvent, decodedEvent),
|
|
225
227
|
);
|
|
226
228
|
|
|
227
|
-
return yield* encodeResult(result
|
|
229
|
+
return yield* encodeResult(result);
|
|
228
230
|
});
|
|
229
231
|
};
|
|
230
232
|
|
package/src/index.ts
CHANGED
|
@@ -5,8 +5,6 @@ export * as Binding from "./Binding";
|
|
|
5
5
|
export * as BrowserRendering from "./BrowserRendering";
|
|
6
6
|
export * as Cache from "./Cache";
|
|
7
7
|
export * as CloudflareOtlp from "./CloudflareOtlp";
|
|
8
|
-
export * as ComputerArtifacts from "./ComputerArtifacts";
|
|
9
|
-
export * as ComputerWorkspace from "./ComputerWorkspace";
|
|
10
8
|
export * as ContainerNamespace from "./ContainerNamespace";
|
|
11
9
|
export * as D1 from "./D1";
|
|
12
10
|
export * as DurableObject from "./DurableObject";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect, Layer } from "effect";
|
|
1
|
+
import { Effect, Layer, Schema } from "effect";
|
|
2
2
|
|
|
3
3
|
import * as RpcDefinition from "../RpcDefinition";
|
|
4
4
|
|
|
@@ -7,18 +7,36 @@ type EntrypointRpcMethod = (...args: AnyArgs) => Effect.Effect<any, any, any>;
|
|
|
7
7
|
|
|
8
8
|
export type EntrypointRpc = Record<string, EntrypointRpcMethod>;
|
|
9
9
|
|
|
10
|
+
const invokeEntrypointRpcMethod = <Self>(
|
|
11
|
+
self: Self,
|
|
12
|
+
method: EntrypointRpcMethod,
|
|
13
|
+
args: AnyArgs,
|
|
14
|
+
run: (self: Self, effect: Effect.Effect<any, any, any>) => Promise<any>,
|
|
15
|
+
onExit: ((self: Self) => Effect.Effect<void, never, any>) | undefined,
|
|
16
|
+
): Promise<any> => {
|
|
17
|
+
const handler = Effect.suspend(() => method(...args)).pipe(
|
|
18
|
+
Effect.mapError(RpcDefinition.encodeWireError),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return run(
|
|
22
|
+
self,
|
|
23
|
+
onExit === undefined ? handler : handler.pipe(Effect.onExit(() => onExit(self))),
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
10
27
|
export const provideEntrypointServices = <ROut, LayerError, RIn>(
|
|
11
28
|
layer: Layer.Layer<ROut, LayerError, RIn>,
|
|
12
29
|
services: Layer.Layer<RIn, never, never>,
|
|
13
30
|
): Layer.Layer<ROut | RIn, LayerError, never> =>
|
|
31
|
+
// SAFETY: provideMerge supplies RIn while retaining it in the output context for entrypoint consumers.
|
|
14
32
|
layer.pipe(Layer.provideMerge(services)) as Layer.Layer<ROut | RIn, LayerError, never>;
|
|
15
33
|
|
|
16
|
-
export const defineEntrypointRpcMethods = <Self>(
|
|
34
|
+
export const defineEntrypointRpcMethods = <Self, Prototype extends object = object>(
|
|
17
35
|
target: string,
|
|
18
|
-
prototype:
|
|
36
|
+
prototype: Prototype,
|
|
19
37
|
rpc: EntrypointRpc | undefined,
|
|
20
38
|
reservedMethodNames: ReadonlySet<string>,
|
|
21
|
-
run: (self: Self, effect: Effect.Effect<any, any, any>) => Promise<
|
|
39
|
+
run: (self: Self, effect: Effect.Effect<any, any, any>) => Promise<any>,
|
|
22
40
|
onExit?: (self: Self) => Effect.Effect<void, never, any>,
|
|
23
41
|
): void => {
|
|
24
42
|
const methods = rpc ?? {};
|
|
@@ -29,17 +47,15 @@ export const defineEntrypointRpcMethods = <Self>(
|
|
|
29
47
|
Object.defineProperty(prototype, key, {
|
|
30
48
|
enumerable: true,
|
|
31
49
|
value(this: Self, ...args: AnyArgs) {
|
|
32
|
-
|
|
33
|
-
Effect.mapError(RpcDefinition.encodeWireError),
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return run(
|
|
37
|
-
this,
|
|
38
|
-
onExit === undefined ? handler : handler.pipe(Effect.onExit(() => onExit(this))),
|
|
39
|
-
);
|
|
50
|
+
return invokeEntrypointRpcMethod(this, method, args, run, onExit);
|
|
40
51
|
},
|
|
41
52
|
});
|
|
42
53
|
}
|
|
43
54
|
};
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
type EntrypointClassValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
57
|
+
|
|
58
|
+
export const assumeEntrypointClass = <Class>(entrypoint: EntrypointClassValue): Class => {
|
|
59
|
+
// SAFETY: callers pass a freshly declared Cloudflare entrypoint subclass matching Class's constructor contract.
|
|
60
|
+
return entrypoint as Class;
|
|
61
|
+
};
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* for use in tagged error `message` getters.
|
|
4
4
|
*/
|
|
5
5
|
export const causeMessage = (cause: unknown): string => {
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
if (
|
|
7
|
+
Predicate.hasProperty(cause, "message") &&
|
|
8
|
+
Predicate.isString(cause.message) &&
|
|
9
|
+
cause.message.length > 0
|
|
10
|
+
) {
|
|
11
|
+
return cause.message;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
try {
|
|
@@ -22,3 +22,4 @@ export const causeMessage = (cause: unknown): string => {
|
|
|
22
22
|
export const violationsMessage = (
|
|
23
23
|
violations: ReadonlyArray<{ readonly path: string; readonly message: string }>,
|
|
24
24
|
): string => violations.map((violation) => `${violation.path}: ${violation.message}`).join("; ");
|
|
25
|
+
import { Predicate } from "effect";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Predicate, Schema } from "effect";
|
|
2
2
|
|
|
3
3
|
import type * as CloudflareRpc from "../Rpc";
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ type AnyArgs = Array<any>;
|
|
|
6
6
|
|
|
7
7
|
export type AsyncMethodKey<Api> = {
|
|
8
8
|
[Key in keyof Api]-?: Key extends string
|
|
9
|
-
? Api[Key] extends (...args: AnyArgs) => Promise<
|
|
9
|
+
? Api[Key] extends (...args: AnyArgs) => Promise<any>
|
|
10
10
|
? Key
|
|
11
11
|
: never
|
|
12
12
|
: never;
|
|
@@ -14,7 +14,7 @@ export type AsyncMethodKey<Api> = {
|
|
|
14
14
|
|
|
15
15
|
export type AsyncMethodArgs<Api, Method extends keyof Api> = Api[Method] extends (
|
|
16
16
|
...args: infer Args
|
|
17
|
-
) => Promise<
|
|
17
|
+
) => Promise<any>
|
|
18
18
|
? Args
|
|
19
19
|
: never;
|
|
20
20
|
|
|
@@ -28,11 +28,24 @@ export type AsyncMethodCloudflareReturn<Api, Method extends keyof Api> = Cloudfl
|
|
|
28
28
|
AsyncMethodSuccess<Api, Method>
|
|
29
29
|
>;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
type RpcTargetValue = Schema.Schema.Type<typeof Schema.Unknown>;
|
|
32
|
+
type RpcMethodOwner<Method extends PropertyKey> = {
|
|
33
|
+
readonly [Key in Method]?: RpcTargetValue;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const invokeWithReceiver = <Method extends PropertyKey, Args extends AnyArgs, Return>(
|
|
37
|
+
value: (...args: Args) => Return,
|
|
38
|
+
receiver: RpcMethodOwner<Method>,
|
|
39
|
+
args: Args,
|
|
40
|
+
): Return => {
|
|
41
|
+
const result: RpcTargetValue = Function.prototype.apply.call(value, receiver, args);
|
|
42
|
+
|
|
43
|
+
// SAFETY: the native apply intrinsic returns value's declared Return while avoiding proxy property reads.
|
|
44
|
+
return result as Return;
|
|
45
|
+
};
|
|
33
46
|
|
|
34
47
|
export const lookupRpcMethod = <Api, Method extends AsyncMethodKey<Api>, Error>(
|
|
35
|
-
target:
|
|
48
|
+
target: RpcTargetValue,
|
|
36
49
|
method: Method,
|
|
37
50
|
makeError: (cause: unknown) => Error,
|
|
38
51
|
): Effect.Effect<
|
|
@@ -41,19 +54,29 @@ export const lookupRpcMethod = <Api, Method extends AsyncMethodKey<Api>, Error>(
|
|
|
41
54
|
> =>
|
|
42
55
|
Effect.try({
|
|
43
56
|
try: () => {
|
|
44
|
-
if (!
|
|
57
|
+
if (!Predicate.isObjectKeyword(target)) {
|
|
45
58
|
throw new TypeError(`RPC target is not object-like`);
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
// SAFETY: this assertion enables one proxy property read; the result is validated before invocation.
|
|
62
|
+
const receiver = target as RpcMethodOwner<Method>;
|
|
63
|
+
const value = receiver[method];
|
|
49
64
|
|
|
50
|
-
if (
|
|
65
|
+
if (!Predicate.isFunction(value)) {
|
|
51
66
|
throw new TypeError(`RPC method "${String(method)}" is not callable`);
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
// SAFETY: the runtime check proves callability; Api and Method supply this binding's call contract.
|
|
70
|
+
const callable = value as (
|
|
55
71
|
...args: AsyncMethodArgs<Api, Method>
|
|
56
72
|
) => AsyncMethodCloudflareReturn<Api, Method>;
|
|
73
|
+
|
|
74
|
+
return (...args: AsyncMethodArgs<Api, Method>): AsyncMethodCloudflareReturn<Api, Method> =>
|
|
75
|
+
invokeWithReceiver<
|
|
76
|
+
Method,
|
|
77
|
+
AsyncMethodArgs<Api, Method>,
|
|
78
|
+
AsyncMethodCloudflareReturn<Api, Method>
|
|
79
|
+
>(callable, receiver, args);
|
|
57
80
|
},
|
|
58
81
|
catch: makeError,
|
|
59
82
|
});
|
|
@@ -63,7 +86,7 @@ export const invokeRpcMethod = Effect.fnUntraced(function* <
|
|
|
63
86
|
Method extends AsyncMethodKey<Api>,
|
|
64
87
|
Error,
|
|
65
88
|
>(
|
|
66
|
-
target:
|
|
89
|
+
target: RpcTargetValue,
|
|
67
90
|
method: Method,
|
|
68
91
|
args: AsyncMethodArgs<Api, Method>,
|
|
69
92
|
makeError: (cause: unknown) => Error,
|
package/src/internal/Runtime.ts
CHANGED
|
@@ -26,27 +26,38 @@ export const makeEntrypointRuntime = <ROut, LayerError, Services>(
|
|
|
26
26
|
return ManagedRuntime.make(provideEntrypointServices(layer, entrypointServices));
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export const runEventPromise = <A, E, R, REvent, EventLayerError, LayerError>(
|
|
29
|
+
/** Runs an event effect whose requirements are already available in the runtime. */
|
|
30
|
+
export function runEventPromise<A, E, R, LayerError>(
|
|
31
|
+
runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
|
|
32
|
+
effect: Effect.Effect<A, E, NoInfer<R> | Scope.Scope>,
|
|
33
|
+
eventLayer?: undefined,
|
|
34
|
+
): Promise<A>;
|
|
35
|
+
/** Builds and provides an event layer inside the event effect's scope. */
|
|
36
|
+
export function runEventPromise<A, E, R, REvent, EventLayerError, LayerError>(
|
|
38
37
|
runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
|
|
39
|
-
effect: Effect.Effect<A, E, R | REvent | Scope.Scope>,
|
|
40
|
-
eventLayer: Layer.Layer<REvent, EventLayerError, R
|
|
41
|
-
): Promise<A
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
effect: Effect.Effect<A, E, NoInfer<R> | REvent | Scope.Scope>,
|
|
39
|
+
eventLayer: Layer.Layer<REvent, EventLayerError, NoInfer<R>>,
|
|
40
|
+
): Promise<A>;
|
|
41
|
+
export function runEventPromise<A, E, R, REvent, EventLayerError, LayerError>(
|
|
42
|
+
...args:
|
|
43
|
+
| readonly [
|
|
44
|
+
runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
|
|
45
|
+
effect: Effect.Effect<A, E, R | Scope.Scope>,
|
|
46
|
+
eventLayer?: undefined,
|
|
47
|
+
]
|
|
48
|
+
| readonly [
|
|
49
|
+
runtime: ManagedRuntime.ManagedRuntime<R, LayerError>,
|
|
50
|
+
effect: Effect.Effect<A, E, R | REvent | Scope.Scope>,
|
|
51
|
+
eventLayer: Layer.Layer<REvent, EventLayerError, R>,
|
|
52
|
+
]
|
|
53
|
+
): Promise<A> {
|
|
54
|
+
const [runtime, effect, eventLayer] = args;
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
if (eventLayer === undefined) {
|
|
57
|
+
return runtime.runPromise(Effect.scoped(effect));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return runtime.runPromise(
|
|
61
|
+
Effect.scoped(effect.pipe(Effect.provide(eventLayer, { local: true }))),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { WorkerContextService, WorkerContextWaitUntilOptions } from "../Wor
|
|
|
4
4
|
|
|
5
5
|
export type RunWaitUntilEffect = <A, E>(
|
|
6
6
|
effect: Effect.Effect<A, E, never>,
|
|
7
|
-
) => Promise<Exit.Exit<A,
|
|
7
|
+
) => Promise<Exit.Exit<A, E>>;
|
|
8
8
|
|
|
9
9
|
const causeError = <E>(cause: Cause.Cause<E>) => {
|
|
10
10
|
const squashed = Cause.squash(cause);
|
|
@@ -64,10 +64,10 @@ export const makeWaitUntilScheduler = (
|
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
await runHandler(exit.cause
|
|
67
|
+
await runHandler(exit.cause);
|
|
68
68
|
|
|
69
69
|
if (mode === "propagate") {
|
|
70
|
-
throw causeError(exit.cause
|
|
70
|
+
throw causeError(exit.cause);
|
|
71
71
|
}
|
|
72
72
|
},
|
|
73
73
|
),
|