effect-cf 0.1.0 → 0.2.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 +21 -0
- package/README.md +128 -1
- package/dist/index.d.mts +901 -308
- package/dist/index.mjs +613 -124
- package/package.json +3 -2
- package/src/DurableObjectRpcWebSocket.ts +21 -17
- package/src/DurableObjectState.ts +12 -10
- package/src/DurableObjectWebSocket.ts +215 -6
- package/src/Queue.ts +116 -0
- package/src/QueueBinding.ts +131 -0
- package/src/QueueDefinition.ts +129 -0
- package/src/Worker.ts +57 -68
- package/src/WorkerDefinition.ts +6 -0
- package/src/Workflow.ts +279 -0
- package/src/WorkflowBinding.ts +254 -0
- package/src/WorkflowDefinition.ts +167 -0
- package/src/index.ts +6 -0
- package/src/internal/WorkerContext.ts +83 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Data, Effect, Option, Schema as S } from "effect";
|
|
2
|
+
|
|
3
|
+
import * as Binding from "./Binding";
|
|
4
|
+
import type * as RpcDefinition from "./RpcDefinition";
|
|
5
|
+
|
|
6
|
+
const TypeId = "effect-cf/WorkflowBinding" as const;
|
|
7
|
+
|
|
8
|
+
export type WorkflowInstanceCreateOptions<Payload> = Omit<
|
|
9
|
+
globalThis.WorkflowInstanceCreateOptions<Payload>,
|
|
10
|
+
"params"
|
|
11
|
+
>;
|
|
12
|
+
|
|
13
|
+
export type WorkflowInstanceCreateBatchOptions<Payload, EncodedPayload = unknown> = ReadonlyArray<
|
|
14
|
+
{ readonly payload: Payload } & WorkflowInstanceCreateOptions<EncodedPayload>
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
export interface WorkflowInstanceRestartOptions {
|
|
18
|
+
readonly from?: {
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
readonly count?: number;
|
|
21
|
+
readonly type?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type WorkflowInstanceStatusName = globalThis.InstanceStatus["status"];
|
|
26
|
+
|
|
27
|
+
export interface WorkflowInstanceStatus<Result> {
|
|
28
|
+
readonly status: WorkflowInstanceStatusName;
|
|
29
|
+
readonly output: Option.Option<Result>;
|
|
30
|
+
readonly error: Option.Option<{
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly message: string;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WorkflowInstance<Result> {
|
|
37
|
+
readonly raw: globalThis.WorkflowInstance;
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly pause: Effect.Effect<void, WorkflowOperationError>;
|
|
40
|
+
readonly resume: Effect.Effect<void, WorkflowOperationError>;
|
|
41
|
+
readonly terminate: Effect.Effect<void, WorkflowOperationError>;
|
|
42
|
+
readonly restart: (
|
|
43
|
+
options?: WorkflowInstanceRestartOptions,
|
|
44
|
+
) => Effect.Effect<void, WorkflowOperationError>;
|
|
45
|
+
readonly status: Effect.Effect<
|
|
46
|
+
WorkflowInstanceStatus<Result>,
|
|
47
|
+
WorkflowOperationError | WorkflowResultDecodeError
|
|
48
|
+
>;
|
|
49
|
+
readonly sendEvent: (event: WorkflowInstanceEvent) => Effect.Effect<void, WorkflowOperationError>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface WorkflowInstanceEvent {
|
|
53
|
+
readonly type: string;
|
|
54
|
+
readonly payload: unknown;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface WorkflowBindingDefinition<
|
|
58
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
59
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
60
|
+
> {
|
|
61
|
+
/** Binding name as configured in `wrangler.jsonc`. */
|
|
62
|
+
readonly binding: string;
|
|
63
|
+
/** Codec used to encode payloads passed to `Workflow.create`. */
|
|
64
|
+
readonly payload: Payload;
|
|
65
|
+
/** Codec used to decode completed workflow status output. */
|
|
66
|
+
readonly result: Result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
declare const WorkflowServiceTypeId: unique symbol;
|
|
70
|
+
|
|
71
|
+
export interface WorkflowService<
|
|
72
|
+
Id extends string,
|
|
73
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
74
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
75
|
+
> {
|
|
76
|
+
readonly [WorkflowServiceTypeId]: {
|
|
77
|
+
readonly id: Id;
|
|
78
|
+
readonly payload: S.Schema.Type<Payload>;
|
|
79
|
+
readonly encodedPayload: S.Codec.Encoded<Payload>;
|
|
80
|
+
readonly result: S.Schema.Type<Result>;
|
|
81
|
+
readonly encodedResult: S.Codec.Encoded<Result>;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export class WorkflowOperationError extends Data.TaggedError("WorkflowOperationError")<{
|
|
86
|
+
readonly binding: string;
|
|
87
|
+
readonly operation: string;
|
|
88
|
+
readonly cause: unknown;
|
|
89
|
+
}> {}
|
|
90
|
+
|
|
91
|
+
export class WorkflowResultDecodeError extends Data.TaggedError("WorkflowResultDecodeError")<{
|
|
92
|
+
readonly binding: string;
|
|
93
|
+
readonly instanceId: string;
|
|
94
|
+
readonly cause: unknown;
|
|
95
|
+
}> {}
|
|
96
|
+
|
|
97
|
+
const workflowError = (binding: string, operation: string, cause: unknown) =>
|
|
98
|
+
new WorkflowOperationError({ binding, operation, cause });
|
|
99
|
+
|
|
100
|
+
const tryWorkflowPromise = <A>(
|
|
101
|
+
binding: string,
|
|
102
|
+
operation: string,
|
|
103
|
+
evaluate: () => Promise<A>,
|
|
104
|
+
): Effect.Effect<A, WorkflowOperationError> =>
|
|
105
|
+
Effect.tryPromise({
|
|
106
|
+
try: evaluate,
|
|
107
|
+
catch: (cause) => workflowError(binding, operation, cause),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const isWorkflow = <Payload>(value: unknown): value is globalThis.Workflow<Payload> => {
|
|
111
|
+
if (typeof value !== "object" || value === null) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const resource = value as Record<string, unknown>;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
typeof resource.create === "function" &&
|
|
119
|
+
typeof resource.createBatch === "function" &&
|
|
120
|
+
typeof resource.get === "function"
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const make = <
|
|
125
|
+
Id extends string,
|
|
126
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
127
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
128
|
+
>(
|
|
129
|
+
id: Id,
|
|
130
|
+
definition: WorkflowBindingDefinition<Payload, Result>,
|
|
131
|
+
) => Service<WorkflowService<Id, Payload, Result>>()<Id, Payload, Result>(id, definition);
|
|
132
|
+
|
|
133
|
+
export const Service =
|
|
134
|
+
<Self>() =>
|
|
135
|
+
<
|
|
136
|
+
Id extends string,
|
|
137
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
138
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
139
|
+
>(
|
|
140
|
+
id: Id,
|
|
141
|
+
definition: WorkflowBindingDefinition<Payload, Result>,
|
|
142
|
+
) => {
|
|
143
|
+
type PayloadValue = S.Schema.Type<Payload>;
|
|
144
|
+
type EncodedPayload = S.Codec.Encoded<Payload>;
|
|
145
|
+
type ResultValue = S.Schema.Type<Result>;
|
|
146
|
+
|
|
147
|
+
const tag = Binding.Service<Self>()(
|
|
148
|
+
id,
|
|
149
|
+
definition.binding,
|
|
150
|
+
(value): value is globalThis.Workflow<EncodedPayload> => isWorkflow<EncodedPayload>(value),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const encodePayload = S.encodeEffect(definition.payload);
|
|
154
|
+
const decodeResult = S.decodeUnknownEffect(definition.result);
|
|
155
|
+
|
|
156
|
+
const wrapInstance = (raw: globalThis.WorkflowInstance): WorkflowInstance<ResultValue> => {
|
|
157
|
+
const operation = <A>(name: string, evaluate: () => Promise<A>) =>
|
|
158
|
+
tryWorkflowPromise(definition.binding, name, evaluate);
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
raw,
|
|
162
|
+
id: raw.id,
|
|
163
|
+
pause: operation("pause", () => raw.pause()),
|
|
164
|
+
resume: operation("resume", () => raw.resume()),
|
|
165
|
+
terminate: operation("terminate", () => raw.terminate()),
|
|
166
|
+
restart: (options) =>
|
|
167
|
+
operation("restart", () =>
|
|
168
|
+
(raw as { restart(options?: WorkflowInstanceRestartOptions): Promise<void> }).restart(
|
|
169
|
+
options,
|
|
170
|
+
),
|
|
171
|
+
),
|
|
172
|
+
status: operation("status", () => raw.status()).pipe(
|
|
173
|
+
Effect.flatMap((status) =>
|
|
174
|
+
Effect.gen(function* () {
|
|
175
|
+
const output =
|
|
176
|
+
status.output === undefined
|
|
177
|
+
? Option.none<ResultValue>()
|
|
178
|
+
: Option.some(
|
|
179
|
+
yield* decodeResult(status.output).pipe(
|
|
180
|
+
Effect.mapError(
|
|
181
|
+
(cause) =>
|
|
182
|
+
new WorkflowResultDecodeError({
|
|
183
|
+
binding: definition.binding,
|
|
184
|
+
instanceId: raw.id,
|
|
185
|
+
cause,
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
status: status.status,
|
|
193
|
+
output,
|
|
194
|
+
error: status.error === undefined ? Option.none() : Option.some(status.error),
|
|
195
|
+
};
|
|
196
|
+
}),
|
|
197
|
+
),
|
|
198
|
+
),
|
|
199
|
+
sendEvent: (event) => operation("sendEvent", () => raw.sendEvent(event)),
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const create = Effect.fnUntraced(function* (
|
|
204
|
+
payload: PayloadValue,
|
|
205
|
+
options?: WorkflowInstanceCreateOptions<EncodedPayload>,
|
|
206
|
+
) {
|
|
207
|
+
const workflow = yield* tag;
|
|
208
|
+
const encoded = yield* encodePayload(payload);
|
|
209
|
+
const raw = yield* tryWorkflowPromise(definition.binding, "create", () =>
|
|
210
|
+
workflow.create({ ...options, params: encoded }),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
return wrapInstance(raw);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const createBatch = Effect.fnUntraced(function* (
|
|
217
|
+
batch: WorkflowInstanceCreateBatchOptions<PayloadValue, EncodedPayload>,
|
|
218
|
+
) {
|
|
219
|
+
const workflow = yield* tag;
|
|
220
|
+
const encodedBatch: Array<globalThis.WorkflowInstanceCreateOptions<EncodedPayload>> = [];
|
|
221
|
+
|
|
222
|
+
for (const item of batch) {
|
|
223
|
+
const { payload, ...options } = item;
|
|
224
|
+
|
|
225
|
+
encodedBatch.push({
|
|
226
|
+
...options,
|
|
227
|
+
params: yield* encodePayload(payload),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const rawInstances = yield* tryWorkflowPromise(definition.binding, "createBatch", () =>
|
|
232
|
+
workflow.createBatch(encodedBatch),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return rawInstances.map(wrapInstance);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const get = Effect.fnUntraced(function* (instanceId: string) {
|
|
239
|
+
const workflow = yield* tag;
|
|
240
|
+
const raw = yield* tryWorkflowPromise(definition.binding, "get", () =>
|
|
241
|
+
workflow.get(instanceId),
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return wrapInstance(raw);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return Object.assign(tag, {
|
|
248
|
+
[TypeId]: TypeId,
|
|
249
|
+
binding: definition.binding,
|
|
250
|
+
create,
|
|
251
|
+
createBatch,
|
|
252
|
+
get,
|
|
253
|
+
});
|
|
254
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Effect, Schema as S, type Layer } from "effect";
|
|
2
|
+
|
|
3
|
+
import type { WorkerEnvironment } from "./Environment";
|
|
4
|
+
import type { ExecutionContext, WorkerContext } from "./Worker";
|
|
5
|
+
import type * as RpcDefinition from "./RpcDefinition";
|
|
6
|
+
import * as WorkflowBinding from "./WorkflowBinding";
|
|
7
|
+
import * as WorkflowEntrypoint from "./Workflow";
|
|
8
|
+
|
|
9
|
+
export interface Definition<
|
|
10
|
+
Id extends string = string,
|
|
11
|
+
Payload extends RpcDefinition.ServiceFreeSchema = RpcDefinition.ServiceFreeSchema,
|
|
12
|
+
Result extends RpcDefinition.ServiceFreeSchema = RpcDefinition.ServiceFreeSchema,
|
|
13
|
+
> {
|
|
14
|
+
readonly id: Id;
|
|
15
|
+
readonly payload: Payload;
|
|
16
|
+
readonly result: Result;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export namespace Definition {
|
|
20
|
+
export type Any = Definition<
|
|
21
|
+
string,
|
|
22
|
+
RpcDefinition.ServiceFreeSchema,
|
|
23
|
+
RpcDefinition.ServiceFreeSchema
|
|
24
|
+
>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare const BindingServiceTypeId: unique symbol;
|
|
28
|
+
|
|
29
|
+
export interface BindingService<Id extends string, Self extends Definition.Any> {
|
|
30
|
+
readonly [BindingServiceTypeId]: {
|
|
31
|
+
readonly id: Id;
|
|
32
|
+
readonly definition: Self;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type Handler<ROut, Self extends Definition.Any> = (
|
|
37
|
+
payload: S.Schema.Type<Self["payload"]>,
|
|
38
|
+
) => Effect.Effect<
|
|
39
|
+
S.Schema.Type<Self["result"]>,
|
|
40
|
+
unknown,
|
|
41
|
+
WorkflowEntrypoint.WorkflowRunContext<ROut>
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
export interface Options<ROut, Self extends Definition.Any> {
|
|
45
|
+
readonly run: Handler<ROut, Self>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const makeDefinition = <
|
|
49
|
+
Id extends string,
|
|
50
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
51
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
52
|
+
>(
|
|
53
|
+
id: Id,
|
|
54
|
+
definition: {
|
|
55
|
+
readonly payload: Payload;
|
|
56
|
+
readonly result: Result;
|
|
57
|
+
},
|
|
58
|
+
) => {
|
|
59
|
+
type SelfDefinition = Definition<Id, Payload, Result>;
|
|
60
|
+
const workflowDefinition: SelfDefinition = {
|
|
61
|
+
id,
|
|
62
|
+
payload: definition.payload,
|
|
63
|
+
result: definition.result,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return Object.assign(workflowDefinition, {
|
|
67
|
+
make: <ROut, LayerError>(
|
|
68
|
+
layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
|
|
69
|
+
options: Options<ROut, SelfDefinition>,
|
|
70
|
+
) =>
|
|
71
|
+
WorkflowEntrypoint.make(layer, {
|
|
72
|
+
run: wrapHandler(workflowDefinition, options.run),
|
|
73
|
+
}),
|
|
74
|
+
Binding:
|
|
75
|
+
<Self>() =>
|
|
76
|
+
<BindingId extends string>(
|
|
77
|
+
bindingId: BindingId,
|
|
78
|
+
binding: Omit<
|
|
79
|
+
WorkflowBinding.WorkflowBindingDefinition<Payload, Result>,
|
|
80
|
+
"payload" | "result"
|
|
81
|
+
>,
|
|
82
|
+
) =>
|
|
83
|
+
WorkflowBinding.Service<Self>()<BindingId, Payload, Result>(bindingId, {
|
|
84
|
+
...binding,
|
|
85
|
+
payload: definition.payload,
|
|
86
|
+
result: definition.result,
|
|
87
|
+
}),
|
|
88
|
+
binding: <BindingId extends string>(
|
|
89
|
+
bindingId: BindingId,
|
|
90
|
+
binding: Omit<
|
|
91
|
+
WorkflowBinding.WorkflowBindingDefinition<Payload, Result>,
|
|
92
|
+
"payload" | "result"
|
|
93
|
+
>,
|
|
94
|
+
) =>
|
|
95
|
+
WorkflowBinding.Service<BindingService<BindingId, SelfDefinition>>()<
|
|
96
|
+
BindingId,
|
|
97
|
+
Payload,
|
|
98
|
+
Result
|
|
99
|
+
>(bindingId, {
|
|
100
|
+
...binding,
|
|
101
|
+
payload: definition.payload,
|
|
102
|
+
result: definition.result,
|
|
103
|
+
}),
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const make = makeDefinition;
|
|
108
|
+
|
|
109
|
+
export const Tag =
|
|
110
|
+
<_Self>() =>
|
|
111
|
+
<
|
|
112
|
+
Id extends string,
|
|
113
|
+
Payload extends RpcDefinition.ServiceFreeSchema,
|
|
114
|
+
Result extends RpcDefinition.ServiceFreeSchema,
|
|
115
|
+
>(
|
|
116
|
+
id: Id,
|
|
117
|
+
definition: {
|
|
118
|
+
readonly payload: Payload;
|
|
119
|
+
readonly result: Result;
|
|
120
|
+
},
|
|
121
|
+
) => {
|
|
122
|
+
const workflowDefinition = makeDefinition(id, definition);
|
|
123
|
+
|
|
124
|
+
abstract class WorkflowDefinitionClass {
|
|
125
|
+
static readonly id = workflowDefinition.id;
|
|
126
|
+
static readonly payload = workflowDefinition.payload;
|
|
127
|
+
static readonly result = workflowDefinition.result;
|
|
128
|
+
static readonly make = workflowDefinition.make;
|
|
129
|
+
static readonly Binding = workflowDefinition.Binding;
|
|
130
|
+
static readonly binding = workflowDefinition.binding;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return WorkflowDefinitionClass as (abstract new () => object) & typeof workflowDefinition;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const Workflow = Tag;
|
|
137
|
+
|
|
138
|
+
const wrapHandler = <ROut, const Self extends Definition.Any>(
|
|
139
|
+
definition: Self,
|
|
140
|
+
handler: Handler<ROut, Self>,
|
|
141
|
+
): WorkflowEntrypoint.WorkflowHandler<
|
|
142
|
+
ROut,
|
|
143
|
+
S.Codec.Encoded<Self["payload"]>,
|
|
144
|
+
S.Codec.Encoded<Self["result"]>
|
|
145
|
+
> => {
|
|
146
|
+
const decodePayload = S.decodeUnknownEffect(definition.payload);
|
|
147
|
+
const encodeResult = S.encodeEffect(definition.result);
|
|
148
|
+
|
|
149
|
+
return (payload) =>
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
const decodedPayload = yield* decodePayload(payload);
|
|
152
|
+
const event = yield* WorkflowEntrypoint.WorkflowEvent;
|
|
153
|
+
const decodedEvent = {
|
|
154
|
+
...event,
|
|
155
|
+
payload: decodedPayload,
|
|
156
|
+
} as WorkflowEntrypoint.WorkflowEventService<S.Schema.Type<Self["payload"]>>;
|
|
157
|
+
const result = yield* handler(decodedPayload as S.Schema.Type<Self["payload"]>).pipe(
|
|
158
|
+
Effect.provideService(WorkflowEntrypoint.WorkflowEvent, decodedEvent),
|
|
159
|
+
);
|
|
160
|
+
return yield* encodeResult(result as S.Schema.Type<Self["result"]>);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const implement = <ROut, const Self extends Definition.Any>(
|
|
165
|
+
_definition: Self,
|
|
166
|
+
handler: Handler<ROut, Self>,
|
|
167
|
+
): Handler<ROut, Self> => handler;
|
package/src/index.ts
CHANGED
|
@@ -8,9 +8,15 @@ export * as DurableObjectState from "./DurableObjectState";
|
|
|
8
8
|
export * as DurableObjectWebSocket from "./DurableObjectWebSocket";
|
|
9
9
|
export * as DurableObjectStorage from "./DurableObjectStorage";
|
|
10
10
|
export * as Kv from "./Kv";
|
|
11
|
+
export * as Queue from "./Queue";
|
|
12
|
+
export * as QueueBinding from "./QueueBinding";
|
|
13
|
+
export * as QueueDefinition from "./QueueDefinition";
|
|
11
14
|
export * as Rpc from "./Rpc";
|
|
12
15
|
export * as RpcDefinition from "./RpcDefinition";
|
|
13
16
|
export * as ServiceBinding from "./ServiceBinding";
|
|
14
17
|
export * as Worker from "./Worker";
|
|
15
18
|
export * as WorkerDefinition from "./WorkerDefinition";
|
|
19
|
+
export * as Workflow from "./Workflow";
|
|
20
|
+
export * as WorkflowBinding from "./WorkflowBinding";
|
|
21
|
+
export * as WorkflowDefinition from "./WorkflowDefinition";
|
|
16
22
|
export { WorkerConfig, WorkerEnvironment, type WorkerEnv } from "./Environment";
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Cause, Effect, Exit } from "effect";
|
|
2
|
+
|
|
3
|
+
import type { WorkerContextService, WorkerContextWaitUntilOptions } from "../Worker";
|
|
4
|
+
|
|
5
|
+
export type RunWaitUntilEffect = <A, E>(
|
|
6
|
+
effect: Effect.Effect<A, E, never>,
|
|
7
|
+
) => Promise<Exit.Exit<A, unknown>>;
|
|
8
|
+
|
|
9
|
+
const causeError = <E>(cause: Cause.Cause<E>) => new Error(Cause.pretty(cause));
|
|
10
|
+
|
|
11
|
+
const failureHandler = <E, R>(
|
|
12
|
+
cause: Cause.Cause<E>,
|
|
13
|
+
options: WorkerContextWaitUntilOptions<E, R> | undefined,
|
|
14
|
+
) =>
|
|
15
|
+
(
|
|
16
|
+
options?.onFailure?.(cause) ??
|
|
17
|
+
Effect.logError("WorkerContext.waitUntil failed", Cause.pretty(cause))
|
|
18
|
+
).pipe(
|
|
19
|
+
Effect.catchCause((handlerCause) =>
|
|
20
|
+
Effect.logError(
|
|
21
|
+
"WorkerContext.waitUntil failure handler failed",
|
|
22
|
+
Cause.pretty(cause),
|
|
23
|
+
Cause.pretty(handlerCause),
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const fromExecutionContext = (
|
|
29
|
+
ctx: globalThis.ExecutionContext,
|
|
30
|
+
runPromiseExit: RunWaitUntilEffect,
|
|
31
|
+
): WorkerContextService => {
|
|
32
|
+
const schedule = <A, E, R, R2 = never>(
|
|
33
|
+
effect: Effect.Effect<A, E, R>,
|
|
34
|
+
options: WorkerContextWaitUntilOptions<E, R2> | undefined,
|
|
35
|
+
mode: "observe" | "propagate",
|
|
36
|
+
) =>
|
|
37
|
+
Effect.context<R | R2>().pipe(
|
|
38
|
+
Effect.flatMap((context) =>
|
|
39
|
+
Effect.sync(() => {
|
|
40
|
+
const runHandler = (cause: Cause.Cause<E>) =>
|
|
41
|
+
runPromiseExit(
|
|
42
|
+
Effect.scoped(Effect.provideContext(failureHandler(cause, options), context)),
|
|
43
|
+
).then((exit) => {
|
|
44
|
+
if (Exit.isFailure(exit)) {
|
|
45
|
+
console.error(
|
|
46
|
+
"WorkerContext.waitUntil failure handler failed",
|
|
47
|
+
Cause.pretty(exit.cause),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
ctx.waitUntil(
|
|
53
|
+
runPromiseExit(Effect.scoped(Effect.provideContext(effect, context))).then(
|
|
54
|
+
async (exit) => {
|
|
55
|
+
if (Exit.isSuccess(exit)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await runHandler(exit.cause as Cause.Cause<E>);
|
|
60
|
+
|
|
61
|
+
if (mode === "propagate") {
|
|
62
|
+
throw causeError(exit.cause as Cause.Cause<E>);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}),
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
raw: ctx,
|
|
73
|
+
waitUntil: <A, E, R, R2 = never>(
|
|
74
|
+
effect: Effect.Effect<A, E, R>,
|
|
75
|
+
options?: WorkerContextWaitUntilOptions<E, R2>,
|
|
76
|
+
) => schedule(effect, options, options?.mode ?? "observe"),
|
|
77
|
+
waitUntilPropagating: <A, E, R, R2 = never>(
|
|
78
|
+
effect: Effect.Effect<A, E, R>,
|
|
79
|
+
options?: Omit<WorkerContextWaitUntilOptions<E, R2>, "mode">,
|
|
80
|
+
) => schedule(effect, options, "propagate"),
|
|
81
|
+
passThroughOnException: Effect.sync(() => ctx.passThroughOnException()),
|
|
82
|
+
};
|
|
83
|
+
};
|