@effect/cluster 0.38.2 → 0.38.4

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.
@@ -42,6 +42,7 @@ export const make = Effect.gen(function*() {
42
42
  string,
43
43
  Entity.Entity<
44
44
  | Rpc.Rpc<"run", Schema.Struct<{}>, Schema.Schema<Workflow.Result<any, any>>>
45
+ | Rpc.Rpc<"deferred", Schema.Struct<{ name: typeof Schema.String; exit: typeof ExitUnknown }>, typeof ExitUnknown>
45
46
  | Rpc.Rpc<
46
47
  "activity",
47
48
  Schema.Struct<{ name: typeof Schema.String; attempt: typeof Schema.Number }>,
@@ -65,7 +66,6 @@ export const make = Effect.gen(function*() {
65
66
  idleTimeToLive: "5 minutes"
66
67
  })
67
68
  const clockClient = yield* ClockEntity.client
68
- const deferredClient = yield* DeferredEntity.client
69
69
 
70
70
  const requestIdFor = Effect.fnUntraced(function*(options: {
71
71
  readonly workflow: Workflow.Any
@@ -151,6 +151,23 @@ export const make = Effect.gen(function*() {
151
151
  yield* storage.clearAddress(clockAddress)
152
152
  })
153
153
 
154
+ const resume = Effect.fnUntraced(function*(workflow: Workflow.Any, executionId: string) {
155
+ const maybeReply = yield* requestReply({
156
+ workflow,
157
+ entityType: `Workflow/${workflow.name}`,
158
+ executionId,
159
+ tag: "run",
160
+ id: ""
161
+ })
162
+ const maybeSuspended = Option.filter(
163
+ maybeReply,
164
+ (reply) => reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended"
165
+ )
166
+ if (Option.isNone(maybeSuspended)) return
167
+ yield* sharding.reset(Snowflake.Snowflake(maybeSuspended.value.requestId))
168
+ yield* sharding.pollStorage
169
+ })
170
+
154
171
  return WorkflowEngine.of({
155
172
  register(workflow, execute) {
156
173
  // eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -198,6 +215,7 @@ export const make = Effect.gen(function*() {
198
215
  Effect.provideService(WorkflowInstance, instance)
199
216
  ) as any
200
217
  },
218
+
201
219
  activity: Effect.fnUntraced(function*(request: Entity.Request<any>) {
202
220
  const activityId = `${executionId}/${request.payload.name}`
203
221
  let entry = activities.get(activityId)
@@ -224,7 +242,12 @@ export const make = Effect.gen(function*() {
224
242
  activities.delete(activityId)
225
243
  }))
226
244
  )
227
- }, Rpc.fork)
245
+ }, Rpc.fork),
246
+
247
+ deferred: Effect.fnUntraced(function*(request: Entity.Request<any>) {
248
+ yield* ensureSuccess(resume(workflow, executionId))
249
+ return request.payload.exit
250
+ })
228
251
  }
229
252
  })
230
253
  ) as Effect.Effect<void>
@@ -240,17 +263,13 @@ export const make = Effect.gen(function*() {
240
263
 
241
264
  interrupt: Effect.fnUntraced(
242
265
  function*(this: WorkflowEngine["Type"], workflow, executionId) {
243
- const requestId = yield* requestIdFor({
266
+ const reply = yield* requestReply({
244
267
  workflow,
245
268
  entityType: `Workflow/${workflow.name}`,
246
269
  executionId,
247
270
  tag: "run",
248
271
  id: ""
249
272
  })
250
- if (Option.isNone(requestId)) {
251
- return
252
- }
253
- const reply = yield* replyForRequestId(requestId.value)
254
273
  const nonSuspendedReply = reply.pipe(
255
274
  Option.filter((reply) => reply.exit._tag !== "Success" || reply.exit.value._tag !== "Suspended")
256
275
  )
@@ -273,34 +292,6 @@ export const make = Effect.gen(function*() {
273
292
  Effect.orDie
274
293
  ),
275
294
 
276
- resume: Effect.fnUntraced(
277
- function*(workflowName: string, executionId: string) {
278
- const workflow = workflows.get(workflowName)
279
- if (!workflow) {
280
- return yield* Effect.dieMessage(`WorkflowEngine.resume: ${workflowName} not registered`)
281
- }
282
- const maybeReply = yield* requestReply({
283
- workflow,
284
- entityType: `Workflow/${workflowName}`,
285
- executionId,
286
- tag: "run",
287
- id: ""
288
- })
289
- const maybeSuspended = Option.filter(
290
- maybeReply,
291
- (reply) => reply.exit._tag === "Success" && reply.exit.value._tag === "Suspended"
292
- )
293
- if (Option.isNone(maybeSuspended)) return
294
- yield* sharding.reset(Snowflake.Snowflake(maybeSuspended.value.requestId))
295
- },
296
- Effect.retry({
297
- while: (e) => e._tag === "PersistenceError",
298
- times: 3,
299
- schedule: Schedule.exponential(250)
300
- }),
301
- Effect.orDie
302
- ),
303
-
304
295
  activityExecute: Effect.fnUntraced(function*({ activity, attempt }) {
305
296
  const context = yield* Effect.context<WorkflowInstance>()
306
297
  const instance = Context.get(context, WorkflowInstance)
@@ -335,9 +326,9 @@ export const make = Effect.gen(function*() {
335
326
  Effect.flatMap((instance) =>
336
327
  requestReply({
337
328
  workflow: instance.workflow,
338
- entityType: DeferredEntity.type,
329
+ entityType: `Workflow/${instance.workflow.name}`,
339
330
  executionId: instance.executionId,
340
- tag: "set",
331
+ tag: "deferred",
341
332
  id: deferred.name
342
333
  })
343
334
  ),
@@ -350,14 +341,15 @@ export const make = Effect.gen(function*() {
350
341
  Effect.orDie
351
342
  ),
352
343
 
353
- deferredDone({ deferred, executionId, exit, workflowName }) {
354
- const client = deferredClient(executionId)
355
- return Effect.orDie(client.set({
356
- workflowName,
357
- name: deferred.name,
358
- exit
359
- }))
360
- },
344
+ deferredDone: Effect.fnUntraced(function*({ deferred, executionId, exit, workflowName }) {
345
+ const client = yield* RcMap.get(clients, workflowName)
346
+ return yield* Effect.orDie(
347
+ client(executionId).deferred({
348
+ name: deferred.name,
349
+ exit
350
+ })
351
+ )
352
+ }, Effect.scoped),
361
353
 
362
354
  scheduleClock(options) {
363
355
  const client = clockClient(options.executionId)
@@ -396,7 +388,7 @@ const ActivityRpc = Rpc.make("activity", {
396
388
  success: Schema.Unknown,
397
389
  error: Schema.Unknown
398
390
  })
399
- })
391
+ }).annotate(ClusterSchema.Persisted, true)
400
392
 
401
393
  const makeWorkflowEntity = (workflow: Workflow.Any) =>
402
394
  Entity.make(`Workflow/${workflow.name}`, [
@@ -407,12 +399,23 @@ const makeWorkflowEntity = (workflow: Workflow.Any) =>
407
399
  success: workflow.successSchema,
408
400
  error: workflow.errorSchema
409
401
  })
410
- }),
402
+ })
403
+ .annotate(ClusterSchema.Persisted, true)
404
+ .annotate(ClusterSchema.Uninterruptible, true),
405
+
406
+ Rpc.make("deferred", {
407
+ payload: {
408
+ name: Schema.String,
409
+ exit: ExitUnknown
410
+ },
411
+ primaryKey: ({ name }) => name,
412
+ success: ExitUnknown
413
+ })
414
+ .annotate(ClusterSchema.Persisted, true)
415
+ .annotate(ClusterSchema.Uninterruptible, true),
416
+
411
417
  ActivityRpc
412
- ])
413
- .annotateContext(workflow.annotations)
414
- .annotateRpcs(ClusterSchema.Persisted, true)
415
- .annotateRpcs(ClusterSchema.Uninterruptible, true)
418
+ ]).annotateContext(workflow.annotations)
416
419
 
417
420
  const activityPrimaryKey = (activity: string, attempt: number) => `${activity}/${attempt}`
418
421
 
@@ -422,42 +425,6 @@ const ExitUnknown = Schema.encodedSchema(Schema.Exit({
422
425
  defect: Schema.Defect
423
426
  }))
424
427
 
425
- const DeferredEntity = Entity.make("Workflow/-/DurableDeferred", [
426
- Rpc.make("set", {
427
- payload: {
428
- workflowName: Schema.String,
429
- name: Schema.String,
430
- exit: ExitUnknown
431
- },
432
- primaryKey: ({ name }) => name,
433
- success: ExitUnknown
434
- }),
435
- Rpc.make("resume", {
436
- payload: {
437
- workflowName: Schema.String,
438
- name: Schema.String
439
- },
440
- primaryKey: ({ name }) => name
441
- })
442
- ])
443
- .annotateRpcs(ClusterSchema.Persisted, true)
444
- .annotateRpcs(ClusterSchema.Uninterruptible, true)
445
-
446
- const DeferredEntityLayer = DeferredEntity.toLayer(Effect.gen(function*() {
447
- const engine = yield* WorkflowEngine
448
- const address = yield* Entity.CurrentAddress
449
- const executionId = address.entityId
450
- const client = (yield* DeferredEntity.client)(executionId)
451
- return {
452
- set: (request) =>
453
- Effect.as(
454
- ensureSuccess(client.resume(request.payload, { discard: true })),
455
- request.payload.exit
456
- ),
457
- resume: (request) => engine.resume(request.payload.workflowName, executionId)
458
- }
459
- }))
460
-
461
428
  class ClockPayload extends Schema.Class<ClockPayload>(`Workflow/DurableClock/Run`)({
462
429
  name: Schema.String,
463
430
  workflowName: Schema.String,
@@ -504,7 +471,6 @@ export const layer: Layer.Layer<
504
471
  WorkflowEngine,
505
472
  never,
506
473
  Sharding.Sharding | MessageStorage
507
- > = DeferredEntityLayer.pipe(
508
- Layer.merge(ClockEntityLayer),
474
+ > = ClockEntityLayer.pipe(
509
475
  Layer.provideMerge(Layer.scoped(WorkflowEngine, make))
510
476
  )
package/src/Sharding.ts CHANGED
@@ -154,6 +154,11 @@ export class Sharding extends Context.Tag("@effect/cluster/Sharding")<Sharding,
154
154
  */
155
155
  readonly reset: (requestId: Snowflake.Snowflake) => Effect.Effect<boolean>
156
156
 
157
+ /**
158
+ * Trigger a storage read, which will read all unprocessed messages.
159
+ */
160
+ readonly pollStorage: Effect.Effect<void>
161
+
157
162
  /**
158
163
  * Retrieves the active entity count for the current runner.
159
164
  */
@@ -1191,6 +1196,7 @@ const make = Effect.gen(function*() {
1191
1196
  sendOutgoing: (message, discard) => sendOutgoing(message, discard),
1192
1197
  notify: (message) => notifyLocal(message, false),
1193
1198
  activeEntityCount,
1199
+ pollStorage: storageReadLatch.open,
1194
1200
  reset
1195
1201
  })
1196
1202