effect-cf 0.6.0 → 0.7.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.
@@ -1,10 +1,8 @@
1
- import { Data, Effect, Option, Schema as S } from "effect";
1
+ import { Context, Data, Effect, Option, Schema as S } from "effect";
2
2
 
3
3
  import * as Binding from "./Binding";
4
4
  import type * as RpcDefinition from "./RpcDefinition";
5
5
 
6
- const TypeId = "effect-cf/WorkflowBinding" as const;
7
-
8
6
  export type WorkflowInstanceCreateOptions<Payload> = Omit<
9
7
  globalThis.WorkflowInstanceCreateOptions<Payload>,
10
8
  "params"
@@ -66,20 +64,27 @@ export interface WorkflowBindingDefinition<
66
64
  readonly result: Result;
67
65
  }
68
66
 
69
- declare const WorkflowServiceTypeId: unique symbol;
70
-
71
- export interface WorkflowService<
72
- Id extends string,
67
+ export interface WorkflowBindingClient<
73
68
  Payload extends RpcDefinition.ServiceFreeSchema,
74
69
  Result extends RpcDefinition.ServiceFreeSchema,
75
70
  > {
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
- };
71
+ readonly create: (
72
+ payload: S.Schema.Type<Payload>,
73
+ options?: WorkflowInstanceCreateOptions<S.Codec.Encoded<Payload>>,
74
+ ) => Effect.Effect<
75
+ WorkflowInstance<S.Schema.Type<Result>>,
76
+ WorkflowOperationError | S.SchemaError
77
+ >;
78
+ readonly createBatch: (
79
+ batch: WorkflowInstanceCreateBatchOptions<S.Schema.Type<Payload>, S.Codec.Encoded<Payload>>,
80
+ ) => Effect.Effect<
81
+ ReadonlyArray<WorkflowInstance<S.Schema.Type<Result>>>,
82
+ WorkflowOperationError | S.SchemaError
83
+ >;
84
+ readonly get: (
85
+ instanceId: string,
86
+ ) => Effect.Effect<WorkflowInstance<S.Schema.Type<Result>>, WorkflowOperationError>;
87
+ readonly unsafeRaw: Effect.Effect<globalThis.Workflow<S.Codec.Encoded<Payload>>>;
83
88
  }
84
89
 
85
90
  export class WorkflowOperationError extends Data.TaggedError("WorkflowOperationError")<{
@@ -107,7 +112,7 @@ const tryWorkflowPromise = <A>(
107
112
  catch: (cause) => workflowError(binding, operation, cause),
108
113
  });
109
114
 
110
- const isWorkflow = <Payload>(value: unknown): value is globalThis.Workflow<Payload> => {
115
+ export const isWorkflow = <Payload>(value: unknown): value is globalThis.Workflow<Payload> => {
111
116
  if (typeof value !== "object" || value === null) {
112
117
  return false;
113
118
  }
@@ -121,102 +126,83 @@ const isWorkflow = <Payload>(value: unknown): value is globalThis.Workflow<Paylo
121
126
  );
122
127
  };
123
128
 
124
- export const make = <
125
- Id extends string,
129
+ export const makeClient = <
126
130
  Payload extends RpcDefinition.ServiceFreeSchema,
127
131
  Result extends RpcDefinition.ServiceFreeSchema,
128
132
  >(
129
- id: Id,
130
133
  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
- ),
134
+ ): ((
135
+ workflow: globalThis.Workflow<S.Codec.Encoded<Payload>>,
136
+ ) => WorkflowBindingClient<Payload, Result>) => {
137
+ type PayloadValue = S.Schema.Type<Payload>;
138
+ type EncodedPayload = S.Codec.Encoded<Payload>;
139
+ type ResultValue = S.Schema.Type<Result>;
140
+
141
+ const encodePayload = S.encodeEffect(definition.payload);
142
+ const decodeResult = S.decodeUnknownEffect(definition.result);
143
+
144
+ const wrapInstance = (raw: globalThis.WorkflowInstance): WorkflowInstance<ResultValue> => {
145
+ const operation = <A>(name: string, evaluate: () => Promise<A>) =>
146
+ tryWorkflowPromise(definition.binding, name, evaluate);
147
+
148
+ return {
149
+ raw,
150
+ id: raw.id,
151
+ pause: operation("pause", () => raw.pause()),
152
+ resume: operation("resume", () => raw.resume()),
153
+ terminate: operation("terminate", () => raw.terminate()),
154
+ restart: (options) =>
155
+ operation("restart", () =>
156
+ (raw as { restart(options?: WorkflowInstanceRestartOptions): Promise<void> }).restart(
157
+ options,
171
158
  ),
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
- ),
159
+ ),
160
+ status: operation("status", () => raw.status()).pipe(
161
+ Effect.flatMap((status) =>
162
+ Effect.gen(function* () {
163
+ const output =
164
+ status.output === undefined
165
+ ? Option.none<ResultValue>()
166
+ : Option.some(
167
+ yield* decodeResult(status.output).pipe(
168
+ Effect.mapError(
169
+ (cause) =>
170
+ new WorkflowResultDecodeError({
171
+ binding: definition.binding,
172
+ instanceId: raw.id,
173
+ cause,
174
+ }),
188
175
  ),
189
- );
190
-
191
- return {
192
- status: status.status,
193
- output,
194
- error: status.error === undefined ? Option.none() : Option.some(status.error),
195
- };
196
- }),
197
- ),
176
+ ),
177
+ );
178
+
179
+ return {
180
+ status: status.status,
181
+ output,
182
+ error: status.error === undefined ? Option.none() : Option.some(status.error),
183
+ };
184
+ }),
198
185
  ),
199
- sendEvent: (event) => operation("sendEvent", () => raw.sendEvent(event)),
200
- };
186
+ ),
187
+ sendEvent: (event) => operation("sendEvent", () => raw.sendEvent(event)),
201
188
  };
189
+ };
202
190
 
203
- const create = Effect.fnUntraced(function* (
191
+ return (workflow) => ({
192
+ create: Effect.fnUntraced(function* (
204
193
  payload: PayloadValue,
205
194
  options?: WorkflowInstanceCreateOptions<EncodedPayload>,
206
195
  ) {
207
- const workflow = yield* tag;
208
196
  const encoded = yield* encodePayload(payload);
209
197
  const raw = yield* tryWorkflowPromise(definition.binding, "create", () =>
210
198
  workflow.create({ ...options, params: encoded }),
211
199
  );
212
200
 
213
201
  return wrapInstance(raw);
214
- });
215
-
216
- const createBatch = Effect.fnUntraced(function* (
202
+ }),
203
+ createBatch: Effect.fnUntraced(function* (
217
204
  batch: WorkflowInstanceCreateBatchOptions<PayloadValue, EncodedPayload>,
218
205
  ) {
219
- const workflow = yield* tag;
220
206
  const encodedBatch: Array<globalThis.WorkflowInstanceCreateOptions<EncodedPayload>> = [];
221
207
 
222
208
  for (const item of batch) {
@@ -233,22 +219,27 @@ export const Service =
233
219
  );
234
220
 
235
221
  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
- );
222
+ }),
223
+ get: (instanceId) =>
224
+ tryWorkflowPromise(definition.binding, "get", () => workflow.get(instanceId)).pipe(
225
+ Effect.map(wrapInstance),
226
+ ),
227
+ unsafeRaw: Effect.succeed(workflow),
228
+ });
229
+ };
243
230
 
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
- };
231
+ export const layer = <
232
+ Self,
233
+ Payload extends RpcDefinition.ServiceFreeSchema,
234
+ Result extends RpcDefinition.ServiceFreeSchema,
235
+ >(
236
+ tag: Context.Service<Self, WorkflowBindingClient<Payload, Result>>,
237
+ definition: WorkflowBindingDefinition<Payload, Result>,
238
+ ) =>
239
+ Binding.layer(
240
+ tag,
241
+ definition.binding,
242
+ (value): value is globalThis.Workflow<S.Codec.Encoded<Payload>> =>
243
+ isWorkflow<S.Codec.Encoded<Payload>>(value),
244
+ makeClient(definition),
245
+ );
@@ -1,5 +1,6 @@
1
- import { Effect, Schema as S, type Layer } from "effect";
1
+ import { Context, Effect, Schema as S, type Layer } from "effect";
2
2
 
3
+ import * as Binding from "./Binding";
3
4
  import type { WorkerEnvironment } from "./Environment";
4
5
  import type { ExecutionContext, WorkerContext } from "./Worker";
5
6
  import type * as RpcDefinition from "./RpcDefinition";
@@ -24,15 +25,6 @@ export namespace Definition {
24
25
  >;
25
26
  }
26
27
 
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
28
  export type Handler<ROut, Self extends Definition.Any> = (
37
29
  payload: S.Schema.Type<Self["payload"]>,
38
30
  ) => Effect.Effect<
@@ -45,6 +37,62 @@ export interface Options<ROut, Self extends Definition.Any> {
45
37
  readonly run: Handler<ROut, Self>;
46
38
  }
47
39
 
40
+ export type LayerOptions = {
41
+ readonly binding: string;
42
+ };
43
+
44
+ export interface TagClass<
45
+ Self,
46
+ Id extends string,
47
+ Payload extends RpcDefinition.ServiceFreeSchema,
48
+ Result extends RpcDefinition.ServiceFreeSchema,
49
+ > extends Context.ServiceClass<Self, Id, WorkflowBinding.WorkflowBindingClient<Payload, Result>> {
50
+ readonly id: Id;
51
+ readonly payload: Payload;
52
+ readonly result: Result;
53
+ readonly make: <ROut, LayerError>(
54
+ layer: Layer.Layer<ROut, LayerError, ExecutionContext | WorkerContext | WorkerEnvironment>,
55
+ options: Options<ROut, Definition<Id, Payload, Result>>,
56
+ ) => WorkflowEntrypoint.WorkflowClass<S.Codec.Encoded<Payload>, S.Codec.Encoded<Result>, ROut>;
57
+ readonly layer: (
58
+ options: LayerOptions,
59
+ ) => Layer.Layer<
60
+ Self,
61
+ Binding.BindingNotFoundError | Binding.BindingValidationError,
62
+ WorkerEnvironment
63
+ >;
64
+ readonly create: (
65
+ payload: S.Schema.Type<Payload>,
66
+ options?: WorkflowBinding.WorkflowInstanceCreateOptions<S.Codec.Encoded<Payload>>,
67
+ ) => Effect.Effect<
68
+ WorkflowBinding.WorkflowInstance<S.Schema.Type<Result>>,
69
+ WorkflowBinding.WorkflowOperationError | S.SchemaError,
70
+ Self
71
+ >;
72
+ readonly createBatch: (
73
+ batch: WorkflowBinding.WorkflowInstanceCreateBatchOptions<
74
+ S.Schema.Type<Payload>,
75
+ S.Codec.Encoded<Payload>
76
+ >,
77
+ ) => Effect.Effect<
78
+ ReadonlyArray<WorkflowBinding.WorkflowInstance<S.Schema.Type<Result>>>,
79
+ WorkflowBinding.WorkflowOperationError | S.SchemaError,
80
+ Self
81
+ >;
82
+ readonly get: (
83
+ instanceId: string,
84
+ ) => Effect.Effect<
85
+ WorkflowBinding.WorkflowInstance<S.Schema.Type<Result>>,
86
+ WorkflowBinding.WorkflowOperationError,
87
+ Self
88
+ >;
89
+ readonly unsafeRaw: () => Effect.Effect<
90
+ globalThis.Workflow<S.Codec.Encoded<Payload>>,
91
+ never,
92
+ Self
93
+ >;
94
+ }
95
+
48
96
  const makeDefinition = <
49
97
  Id extends string,
50
98
  Payload extends RpcDefinition.ServiceFreeSchema,
@@ -71,43 +119,23 @@ const makeDefinition = <
71
119
  WorkflowEntrypoint.make(layer, {
72
120
  run: wrapHandler(workflowDefinition, options.run),
73
121
  }),
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
122
  });
105
123
  };
106
124
 
107
- export const make = makeDefinition;
125
+ export const make = <
126
+ Id extends string,
127
+ Payload extends RpcDefinition.ServiceFreeSchema,
128
+ Result extends RpcDefinition.ServiceFreeSchema,
129
+ >(
130
+ id: Id,
131
+ definition: {
132
+ readonly payload: Payload;
133
+ readonly result: Result;
134
+ },
135
+ ) => Tag<Definition<Id, Payload, Result>>()(id, definition);
108
136
 
109
137
  export const Tag =
110
- <_Self>() =>
138
+ <Self>() =>
111
139
  <
112
140
  Id extends string,
113
141
  Payload extends RpcDefinition.ServiceFreeSchema,
@@ -120,17 +148,54 @@ export const Tag =
120
148
  },
121
149
  ) => {
122
150
  const workflowDefinition = makeDefinition(id, definition);
151
+ const tag = Context.Service<Self, WorkflowBinding.WorkflowBindingClient<Payload, Result>>()(id);
123
152
 
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
- }
153
+ const layer = (binding: LayerOptions) =>
154
+ WorkflowBinding.layer(tag, {
155
+ ...binding,
156
+ payload: definition.payload,
157
+ result: definition.result,
158
+ });
159
+
160
+ const create = Effect.fnUntraced(function* (
161
+ payload: S.Schema.Type<Payload>,
162
+ options?: WorkflowBinding.WorkflowInstanceCreateOptions<S.Codec.Encoded<Payload>>,
163
+ ) {
164
+ const workflow = yield* tag;
165
+ return yield* workflow.create(payload, options);
166
+ });
167
+
168
+ const createBatch = Effect.fnUntraced(function* (
169
+ batch: WorkflowBinding.WorkflowInstanceCreateBatchOptions<
170
+ S.Schema.Type<Payload>,
171
+ S.Codec.Encoded<Payload>
172
+ >,
173
+ ) {
174
+ const workflow = yield* tag;
175
+ return yield* workflow.createBatch(batch);
176
+ });
177
+
178
+ const get = Effect.fnUntraced(function* (instanceId: string) {
179
+ const workflow = yield* tag;
180
+ return yield* workflow.get(instanceId);
181
+ });
182
+
183
+ const unsafeRaw = Effect.fnUntraced(function* () {
184
+ const workflow = yield* tag;
185
+ return yield* workflow.unsafeRaw;
186
+ });
132
187
 
133
- return WorkflowDefinitionClass as (abstract new () => object) & typeof workflowDefinition;
188
+ return Object.assign(tag, {
189
+ id: workflowDefinition.id,
190
+ payload: workflowDefinition.payload,
191
+ result: workflowDefinition.result,
192
+ make: workflowDefinition.make,
193
+ layer,
194
+ create,
195
+ createBatch,
196
+ get,
197
+ unsafeRaw,
198
+ }) as TagClass<Self, Id, Payload, Result>;
134
199
  };
135
200
 
136
201
  export const Workflow = Tag;