effect-inngest 0.1.3 → 0.3.0-beta.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.
Files changed (141) hide show
  1. package/README.md +89 -67
  2. package/dist/Client.d.ts +76 -28
  3. package/dist/Client.js +92 -30
  4. package/dist/Event.d.ts +43 -0
  5. package/dist/Event.js +46 -0
  6. package/dist/Events.d.ts +48 -76
  7. package/dist/Events.js +18 -23
  8. package/dist/Function.d.ts +46 -19
  9. package/dist/Function.js +32 -22
  10. package/dist/Group.d.ts +19 -8
  11. package/dist/Group.js +30 -72
  12. package/dist/HttpApi.d.ts +51 -56
  13. package/dist/HttpApi.js +38 -21
  14. package/dist/_virtual/_rolldown/runtime.js +13 -0
  15. package/dist/index.d.ts +2 -1
  16. package/dist/index.js +3 -3
  17. package/dist/internal/checkpoint/Config.d.ts +10 -0
  18. package/dist/internal/checkpoint/Config.js +29 -0
  19. package/dist/internal/checkpoint/Error.d.ts +11 -0
  20. package/dist/internal/checkpoint/Error.js +8 -0
  21. package/dist/internal/checkpoint/State.d.ts +1 -0
  22. package/dist/internal/checkpoint/State.js +54 -0
  23. package/dist/internal/checkpoint.d.ts +2 -0
  24. package/dist/internal/codec/EventPayload.d.ts +6 -0
  25. package/dist/internal/codec/EventPayload.js +60 -0
  26. package/dist/internal/codec/StepResult.js +55 -0
  27. package/dist/internal/domain/ExecutionInput.d.ts +40 -0
  28. package/dist/internal/domain/ExecutionInput.js +57 -0
  29. package/dist/internal/domain/ExecutionSuspension.d.ts +34 -0
  30. package/dist/internal/domain/ExecutionSuspension.js +64 -0
  31. package/dist/internal/domain/FunctionDefinition.js +13 -0
  32. package/dist/internal/domain/Memo.d.ts +22 -0
  33. package/dist/internal/domain/Memo.js +18 -0
  34. package/dist/internal/domain/StepCommand.d.ts +79 -0
  35. package/dist/internal/domain/StepCommand.js +124 -0
  36. package/dist/internal/domain/StepInfo.d.ts +12 -0
  37. package/dist/internal/domain/StepInfo.js +10 -0
  38. package/dist/internal/domain/StepInput.d.ts +10 -0
  39. package/dist/internal/driver.js +15 -114
  40. package/dist/internal/errors.d.ts +21 -48
  41. package/dist/internal/errors.js +9 -44
  42. package/dist/internal/execution/CheckpointRun.js +25 -0
  43. package/dist/internal/execution/ExecutionFailure.js +19 -0
  44. package/dist/internal/execution/ExecutionHeaders.js +58 -0
  45. package/dist/internal/execution/ExecutionResponse.js +68 -0
  46. package/dist/internal/execution/ExecutionResult.js +56 -0
  47. package/dist/internal/execution/ExecutionScope.js +30 -0
  48. package/dist/internal/execution/HandlerRun.js +16 -0
  49. package/dist/internal/handler.d.ts +5 -16
  50. package/dist/internal/handler.js +128 -96
  51. package/dist/internal/protocol.d.ts +143 -1
  52. package/dist/internal/protocol.js +333 -138
  53. package/dist/internal/runtime/CheckpointContext.js +5 -0
  54. package/dist/internal/runtime/HandlerContext.d.ts +13 -0
  55. package/dist/internal/runtime/HandlerContext.js +19 -0
  56. package/dist/internal/runtime/HandlerFiberScope.d.ts +10 -0
  57. package/dist/internal/runtime/HandlerFiberScope.js +5 -0
  58. package/dist/internal/runtime/StepCommandBus.d.ts +27 -0
  59. package/dist/internal/runtime/StepCommandBus.js +76 -0
  60. package/dist/internal/runtime/StepIdentity.d.ts +27 -0
  61. package/dist/internal/runtime/StepIdentity.js +46 -0
  62. package/dist/internal/runtime/StepTools.d.ts +83 -0
  63. package/dist/internal/runtime/StepTools.js +76 -0
  64. package/dist/internal/runtime/steps/InvokeStep.js +43 -0
  65. package/dist/internal/runtime/steps/SendEventStep.js +46 -0
  66. package/dist/internal/runtime/steps/SleepStep.js +22 -0
  67. package/dist/internal/runtime/steps/SleepUntilStep.js +22 -0
  68. package/dist/internal/runtime/steps/StepRun.js +48 -0
  69. package/dist/internal/runtime/steps/WaitForEventStep.js +27 -0
  70. package/dist/internal/serve/HttpApp.js +71 -0
  71. package/dist/internal/serve/Request.js +23 -0
  72. package/dist/internal/serve/Signature.d.ts +11 -0
  73. package/dist/internal/serve/Signature.js +123 -0
  74. package/dist/internal/wire/Duration.js +19 -0
  75. package/dist/internal/wire/Timestamp.js +14 -0
  76. package/package.json +34 -22
  77. package/src/Client.ts +269 -91
  78. package/src/Event.ts +107 -0
  79. package/src/Events.ts +50 -30
  80. package/src/Function.ts +102 -46
  81. package/src/Group.ts +56 -108
  82. package/src/HttpApi.ts +40 -30
  83. package/src/index.ts +21 -11
  84. package/src/internal/checkpoint/Config.ts +74 -0
  85. package/src/internal/checkpoint/Error.ts +6 -0
  86. package/src/internal/checkpoint/State.ts +107 -0
  87. package/src/internal/checkpoint.ts +3 -0
  88. package/src/internal/codec/EventPayload.ts +98 -0
  89. package/src/internal/codec/StepResult.ts +95 -0
  90. package/src/internal/domain/ExecutionInput.ts +66 -0
  91. package/src/internal/domain/ExecutionSuspension.ts +79 -0
  92. package/src/internal/domain/FunctionDefinition.ts +30 -0
  93. package/src/internal/domain/Memo.ts +28 -0
  94. package/src/internal/domain/StepCommand.ts +166 -0
  95. package/src/internal/domain/StepInfo.ts +8 -0
  96. package/src/internal/domain/StepInput.ts +10 -0
  97. package/src/internal/driver.ts +27 -185
  98. package/src/internal/errors.ts +14 -108
  99. package/src/internal/execution/CheckpointRun.ts +33 -0
  100. package/src/internal/execution/ExecutionFailure.ts +19 -0
  101. package/src/internal/execution/ExecutionHeaders.ts +86 -0
  102. package/src/internal/execution/ExecutionResponse.ts +79 -0
  103. package/src/internal/execution/ExecutionResult.ts +57 -0
  104. package/src/internal/execution/ExecutionScope.ts +41 -0
  105. package/src/internal/execution/HandlerRun.ts +38 -0
  106. package/src/internal/handler.ts +222 -172
  107. package/src/internal/protocol.ts +289 -78
  108. package/src/internal/runtime/CheckpointContext.ts +7 -0
  109. package/src/internal/runtime/HandlerContext.ts +21 -0
  110. package/src/internal/runtime/HandlerFiberScope.ts +9 -0
  111. package/src/internal/runtime/StepCommandBus.ts +129 -0
  112. package/src/internal/runtime/StepIdentity.ts +67 -0
  113. package/src/internal/runtime/StepTools.ts +161 -0
  114. package/src/internal/runtime/steps/InvokeStep.ts +71 -0
  115. package/src/internal/runtime/steps/SendEventStep.ts +67 -0
  116. package/src/internal/runtime/steps/SleepStep.ts +34 -0
  117. package/src/internal/runtime/steps/SleepUntilStep.ts +34 -0
  118. package/src/internal/runtime/steps/StepRun.ts +95 -0
  119. package/src/internal/runtime/steps/WaitForEventStep.ts +55 -0
  120. package/src/internal/serve/HttpApp.ts +123 -0
  121. package/src/internal/serve/Request.ts +27 -0
  122. package/src/internal/serve/Signature.ts +170 -0
  123. package/src/internal/wire/Duration.ts +31 -0
  124. package/src/internal/wire/Timestamp.ts +11 -0
  125. package/dist/_virtual/rolldown_runtime.js +0 -18
  126. package/dist/internal/constants.js +0 -15
  127. package/dist/internal/driver.d.ts +0 -5
  128. package/dist/internal/helpers.js +0 -44
  129. package/dist/internal/interrupts.d.ts +0 -2
  130. package/dist/internal/interrupts.js +0 -45
  131. package/dist/internal/memo.js +0 -56
  132. package/dist/internal/signature.d.ts +0 -18
  133. package/dist/internal/signature.js +0 -97
  134. package/dist/internal/step.d.ts +0 -59
  135. package/dist/internal/step.js +0 -192
  136. package/src/internal/constants.ts +0 -11
  137. package/src/internal/helpers.ts +0 -58
  138. package/src/internal/interrupts.ts +0 -62
  139. package/src/internal/memo.ts +0 -73
  140. package/src/internal/signature.ts +0 -158
  141. package/src/internal/step.ts +0 -394
package/README.md CHANGED
@@ -25,14 +25,16 @@
25
25
  Effect Inngest brings the power of [Effect](https://effect.website) to [Inngest's](https://inngest.com) durable execution platform. Define event schemas once, and types flow automatically through triggers, handlers, and step operations — no manual annotations needed.
26
26
 
27
27
  ```typescript
28
- import { InngestFunction, InngestGroup, InngestClient } from "effect-inngest";
28
+ import { InngestClient, InngestEvent, InngestFunction, InngestGroup } from "effect-inngest";
29
29
  import { Effect, Schema, Layer } from "effect";
30
30
 
31
- // Define events as TaggedClass
32
- class UserSignup extends Schema.TaggedClass<UserSignup>()("user/signup", {
33
- userId: Schema.String,
34
- email: Schema.String,
35
- }) {}
31
+ const UserSignup = InngestEvent.make(
32
+ "user/signup",
33
+ Schema.Struct({
34
+ userId: Schema.String,
35
+ email: Schema.String,
36
+ }),
37
+ );
36
38
 
37
39
  // Create a function — event type is inferred from the trigger
38
40
  const ProcessSignup = InngestFunction.make("process-signup", {
@@ -43,17 +45,15 @@ const ProcessSignup = InngestFunction.make("process-signup", {
43
45
  // Create group and implement handler
44
46
  const Group = InngestGroup.make(ProcessSignup);
45
47
 
46
- class UserOnboarded extends Schema.TaggedClass<UserOnboarded>()("user/onboarded", {
47
- userId: Schema.String,
48
- }) {}
48
+ const UserOnboarded = InngestEvent.make("user/onboarded", Schema.Struct({ userId: Schema.String }));
49
49
 
50
50
  const Handlers = Group.toLayer({
51
51
  "process-signup": ({ event, step }) =>
52
52
  Effect.gen(function* () {
53
- // event is typed as UserSignup
54
- yield* step.run("send-welcome", sendWelcomeEmail(event.email));
53
+ // event is typed as { name: "user/signup", data: { userId, email } }
54
+ yield* step.run("send-welcome", sendWelcomeEmail(event.data.email));
55
55
  yield* step.sleep("delay", "1 hour");
56
- yield* step.sendEvent("notify", new UserOnboarded({ userId: event.userId }));
56
+ yield* step.sendEvent("notify", UserOnboarded.make({ userId: event.data.userId }));
57
57
  }),
58
58
  });
59
59
  ```
@@ -109,17 +109,18 @@ import { BunHttpServer, BunRuntime } from "@effect/platform-bun";
109
109
  import * as HttpMiddleware from "@effect/platform/HttpMiddleware";
110
110
  import * as HttpServer from "@effect/platform/HttpServer";
111
111
  import { Duration, Effect, Layer, Schema } from "effect";
112
- import { InngestFunction, InngestGroup, InngestClient } from "effect-inngest";
113
-
114
- // 1. Define your events as TaggedClass
115
- class UserSignup extends Schema.TaggedClass<UserSignup>()("user/signup", {
116
- userId: Schema.String,
117
- email: Schema.String,
118
- }) {}
112
+ import { InngestClient, InngestEvent, InngestFunction, InngestGroup } from "effect-inngest";
113
+
114
+ // 1. Define your Inngest event definitions
115
+ const UserSignup = InngestEvent.make(
116
+ "user/signup",
117
+ Schema.Struct({
118
+ userId: Schema.String,
119
+ email: Schema.String,
120
+ }),
121
+ );
119
122
 
120
- class UserWelcomeSent extends Schema.TaggedClass<UserWelcomeSent>()("user/welcome-sent", {
121
- userId: Schema.String,
122
- }) {}
123
+ const UserWelcomeSent = InngestEvent.make("user/welcome-sent", Schema.Struct({ userId: Schema.String }));
123
124
 
124
125
  // 2. Define your functions
125
126
  const ProcessSignup = InngestFunction.make("process-signup", {
@@ -138,17 +139,19 @@ const App = InngestGroup.make(ProcessSignup, DailyDigest);
138
139
  const Handlers = App.toLayer({
139
140
  "process-signup": ({ event, step }) =>
140
141
  Effect.gen(function* () {
141
- // event is typed as UserSignup
142
- yield* Effect.log(`Processing signup for ${event.email}`);
142
+ // event is typed as { name: "user/signup", data: { userId, email } }
143
+ yield* Effect.log(`Processing signup for ${event.data.email}`);
143
144
 
144
- // Durable step - memoized across retries
145
- const user = yield* step.run("create-user", Effect.succeed({ id: event.userId, email: event.email }));
145
+ // Durable value-returning steps need a replay schema
146
+ const user = yield* step.run("create-user", Effect.succeed({ id: event.data.userId, email: event.data.email }), {
147
+ schema: Schema.Struct({ id: Schema.String, email: Schema.String }),
148
+ });
146
149
 
147
150
  // Sleep durably
148
151
  yield* step.sleep("welcome-delay", Duration.seconds(5));
149
152
 
150
153
  // Send follow-up event
151
- yield* step.sendEvent("notify", new UserWelcomeSent({ userId: user.id }));
154
+ yield* step.sendEvent("notify", UserWelcomeSent.make({ userId: user.id }));
152
155
 
153
156
  return { welcomed: true };
154
157
  }),
@@ -197,17 +200,18 @@ import { FetchHttpClient } from "@effect/platform";
197
200
  import { BunHttpServer, BunRuntime } from "@effect/platform-bun";
198
201
  import { Duration, Effect, Layer, Schema } from "effect";
199
202
  import { InngestApiGroup, layerGroup } from "effect-inngest/HttpApi";
200
- import { InngestClient, InngestFunction, InngestGroup } from "effect-inngest";
203
+ import { InngestClient, InngestEvent, InngestFunction, InngestGroup } from "effect-inngest";
201
204
 
202
205
  // 1. Define your events
203
- class UserSignup extends Schema.TaggedClass<UserSignup>()("user/signup", {
204
- userId: Schema.String,
205
- email: Schema.String,
206
- }) {}
206
+ const UserSignup = InngestEvent.make(
207
+ "user/signup",
208
+ Schema.Struct({
209
+ userId: Schema.String,
210
+ email: Schema.String,
211
+ }),
212
+ );
207
213
 
208
- class UserWelcomeSent extends Schema.TaggedClass<UserWelcomeSent>()("user/welcome-sent", {
209
- userId: Schema.String,
210
- }) {}
214
+ const UserWelcomeSent = InngestEvent.make("user/welcome-sent", Schema.Struct({ userId: Schema.String }));
211
215
 
212
216
  // 2. Define functions
213
217
  const ProcessSignup = InngestFunction.make("process-signup", {
@@ -226,10 +230,12 @@ const App = InngestGroup.make(ProcessSignup, DailyDigest);
226
230
  const Handlers = App.toLayer({
227
231
  "process-signup": ({ event, step }) =>
228
232
  Effect.gen(function* () {
229
- yield* Effect.log(`Processing signup for ${event.email}`);
230
- const user = yield* step.run("create-user", Effect.succeed({ id: event.userId, email: event.email }));
233
+ yield* Effect.log(`Processing signup for ${event.data.email}`);
234
+ const user = yield* step.run("create-user", Effect.succeed({ id: event.data.userId, email: event.data.email }), {
235
+ schema: Schema.Struct({ id: Schema.String, email: Schema.String }),
236
+ });
231
237
  yield* step.sleep("welcome-delay", Duration.seconds(5));
232
- yield* step.sendEvent("notify", new UserWelcomeSent({ userId: user.id }));
238
+ yield* step.sendEvent("notify", UserWelcomeSent.make({ userId: user.id }));
233
239
  return { welcomed: true };
234
240
  }),
235
241
  "daily-digest": ({ step }) => step.run("send-digest", Effect.log("Sending daily digest...")),
@@ -262,22 +268,31 @@ BunRuntime.runMain(Layer.launch(Server));
262
268
 
263
269
  ## Getting Started
264
270
 
265
- ### 1. Define your events as TaggedClass
271
+ ### 1. Define your events
266
272
 
267
273
  ```typescript
268
274
  import { Schema } from "effect";
275
+ import { InngestEvent } from "effect-inngest";
276
+
277
+ const UserSignup = InngestEvent.make(
278
+ "user/signup",
279
+ Schema.Struct({
280
+ userId: Schema.String,
281
+ email: Schema.String,
282
+ plan: Schema.Literal("free", "pro", "enterprise"),
283
+ }),
284
+ );
285
+
286
+ const OrderPlaced = InngestEvent.make(
287
+ "order/placed",
288
+ Schema.Struct({
289
+ orderId: Schema.String,
290
+ items: Schema.Array(Schema.Struct({ sku: Schema.String, qty: Schema.Number })),
291
+ total: Schema.Number,
292
+ }),
293
+ );
269
294
 
270
- class UserSignup extends Schema.TaggedClass<UserSignup>()("user/signup", {
271
- userId: Schema.String,
272
- email: Schema.String,
273
- plan: Schema.Literal("free", "pro", "enterprise"),
274
- }) {}
275
-
276
- class OrderPlaced extends Schema.TaggedClass<OrderPlaced>()("order/placed", {
277
- orderId: Schema.String,
278
- items: Schema.Array(Schema.Struct({ sku: Schema.String, qty: Schema.Number })),
279
- total: Schema.Number,
280
- }) {}
295
+ const UserWelcomeSent = InngestEvent.make("user/welcome-sent", Schema.Struct({ userId: Schema.String }));
281
296
  ```
282
297
 
283
298
  ### 2. Define your functions
@@ -311,7 +326,7 @@ const HandlersLive = AppFunctions.toLayer({
311
326
  Effect.gen(function* () {
312
327
  yield* step.run("create-user", createUser(event));
313
328
  yield* step.sleep("delay", Duration.minutes(5));
314
- yield* step.sendEvent("welcome", new UserWelcomeSent({ userId: event.userId }));
329
+ yield* step.sendEvent("welcome", UserWelcomeSent.make({ userId: event.data.userId }));
315
330
  return { welcomeEmailSent: true };
316
331
  }),
317
332
 
@@ -350,8 +365,8 @@ All step operations are durable — they're memoized and survive retries:
350
365
  ```typescript
351
366
  ({ step }) =>
352
367
  Effect.gen(function* () {
353
- // Run any Effect with memoization
354
- const user = yield* step.run("fetch-user", fetchUser(userId));
368
+ // Run an Effect with memoization. Value-returning steps require a replay schema.
369
+ const user = yield* step.run("fetch-user", fetchUser(userId), { schema: User });
355
370
 
356
371
  // Sleep for a duration
357
372
  yield* step.sleep("wait", Duration.hours(24));
@@ -372,7 +387,7 @@ All step operations are durable — they're memoized and survive retries:
372
387
  });
373
388
 
374
389
  // Send events
375
- yield* step.sendEvent("notify", new OrderShipped({ orderId }));
390
+ yield* step.sendEvent("notify", OrderShipped.make({ orderId }));
376
391
  });
377
392
  ```
378
393
 
@@ -384,7 +399,11 @@ Run steps in parallel with Effect's concurrency:
384
399
  const [user, orders, prefs] =
385
400
  yield *
386
401
  Effect.all(
387
- [step.run("user", fetchUser(id)), step.run("orders", fetchOrders(id)), step.run("prefs", fetchPreferences(id))],
402
+ [
403
+ step.run("user", fetchUser(id), { schema: User }),
404
+ step.run("orders", fetchOrders(id), { schema: Schema.Array(Order) }),
405
+ step.run("prefs", fetchPreferences(id), { schema: Preferences }),
406
+ ],
388
407
  { concurrency: "unbounded" },
389
408
  );
390
409
  ```
@@ -409,7 +428,7 @@ const HandlersLive = AppFunctions.toLayer({
409
428
  "process-signup": ({ event, step }) =>
410
429
  Effect.gen(function* () {
411
430
  const email = yield* EmailService;
412
- yield* step.run("send", email.send(event.email, "Welcome!"));
431
+ yield* step.run("send", email.send(event.data.email, "Welcome!"));
413
432
  return { welcomeEmailSent: true };
414
433
  }),
415
434
  });
@@ -452,13 +471,14 @@ const ProcessOrder = InngestFunction.make("process-order", {
452
471
  Control retry behavior with typed errors:
453
472
 
454
473
  ```typescript
474
+ import { Duration, Effect } from "effect";
455
475
  import { NonRetriableError, RetryAfterError } from "effect-inngest";
456
476
 
457
477
  // Don't retry this error
458
- yield * new NonRetriableError({ message: "Card permanently declined" });
478
+ yield * Effect.fail(NonRetriableError.make({ message: "Card permanently declined" }));
459
479
 
460
- // Retry after a specific duration (retryAfter in milliseconds)
461
- yield * new RetryAfterError({ message: "Rate limited", retryAfter: 60000 });
480
+ // Retry after a specific duration
481
+ yield * Effect.fail(RetryAfterError.make({ message: "Rate limited", retryAfter: Duration.seconds(30) }));
462
482
  ```
463
483
 
464
484
  ---
@@ -472,19 +492,21 @@ yield * new RetryAfterError({ message: "Rate limited", retryAfter: 60000 });
472
492
  | `InngestFunction` | Function definition with trigger-based event inference |
473
493
  | `InngestGroup` | Group functions, create handlers, and serve HTTP |
474
494
  | `InngestClient` | Client configuration and event operations |
495
+ | `InngestEvent` | Event schema constructor with typed `.make` envelopes |
475
496
  | `NonRetriableError` | Error to skip retries |
476
497
  | `RetryAfterError` | Error to retry after delay |
477
498
 
478
499
  ### Step Methods
479
500
 
480
- | Method | Description |
481
- | ------------------------------------------ | ---------------------------------- |
482
- | `step.run(id, effect)` | Execute an effect with memoization |
483
- | `step.sleep(id, duration)` | Sleep for a duration |
484
- | `step.sleepUntil(id, timestamp)` | Sleep until a timestamp |
485
- | `step.waitForEvent(id, EventSchema, opts)` | Wait for an event with timeout |
486
- | `step.invoke(id, opts)` | Invoke another function |
487
- | `step.sendEvent(id, TaggedEvent)` | Send events to Inngest |
501
+ | Method | Description |
502
+ | ------------------------------------------------ | ----------------------------------------------- |
503
+ | `step.run(id, effect)` | Execute a void effect with memoization |
504
+ | `step.run(id, effect, { schema })` | Execute a value-returning effect with replay IO |
505
+ | `step.sleep(id, duration)` | Sleep for a duration |
506
+ | `step.sleepUntil(id, timestamp)` | Sleep until a timestamp |
507
+ | `step.waitForEvent(id, InngestEvent, opts)` | Wait for an event with timeout |
508
+ | `step.invoke(id, opts)` | Invoke another function |
509
+ | `step.sendEvent(id, InngestEvent.make(payload))` | Send events to Inngest |
488
510
 
489
511
  ---
490
512
 
package/dist/Client.d.ts CHANGED
@@ -1,14 +1,13 @@
1
- import * as HttpClient from "@effect/platform/HttpClient";
2
- import * as Context from "effect/Context";
3
- import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
- import * as Schema from "effect/Schema";
6
- import * as Config from "effect/Config";
7
- import * as ConfigError from "effect/ConfigError";
1
+ import { CheckpointingOption } from "./internal/checkpoint/Config.js";
2
+ import { CheckpointApiError } from "./internal/checkpoint/Error.js";
3
+ import { GeneratorOpcode } from "./internal/protocol.js";
4
+ import { Config, Context, Effect, Layer, Schedule, Schema } from "effect";
5
+ import * as HttpClient from "effect/unstable/http/HttpClient";
6
+ import * as _$effect_Cause0 from "effect/Cause";
8
7
 
9
8
  //#region src/Client.d.ts
10
9
  declare namespace Client_d_exports {
11
- export { EventPayload, InngestClient, layer, layerConfig, layerFromEnv };
10
+ export { CheckpointApiError, CheckpointRetrySchedule, CheckpointingOption, ClientConfig, EventPayload, InngestClient, InngestConfig, layer, layerConfig, layerFromEnv };
12
11
  }
13
12
  /**
14
13
  * @since 0.1.0
@@ -89,28 +88,45 @@ interface ClientConfig {
89
88
  * The path where the Inngest serve handler is mounted. Used for registration.
90
89
  */
91
90
  readonly servePath?: string | undefined;
91
+ /**
92
+ * The framework adapter serving this app, e.g. "bun" or "nodejs". Used for
93
+ * protocol headers and registration metadata.
94
+ */
95
+ readonly framework?: string | undefined;
96
+ /**
97
+ * Whether to use checkpointing by default for executions of functions
98
+ * created using this client.
99
+ *
100
+ * - `false` disables checkpointing for all functions on this client.
101
+ * - `true` enables checkpointing with safe defaults (`bufferedSteps: 1`,
102
+ * `maxInterval: 0`, `maxRuntime: 10s`). Steps are checkpointed to the
103
+ * Inngest API as they complete instead of yielding a 206 per step.
104
+ * - An object lets you tune `bufferedSteps`, `maxInterval`, `maxRuntime`.
105
+ *
106
+ * Per-function `checkpointing` overrides this setting. Defaults to `true`
107
+ * (checkpointing enabled).
108
+ *
109
+ * @default true
110
+ */
111
+ readonly checkpointing?: CheckpointingOption | undefined;
92
112
  }
93
113
  declare const EventPayload: Schema.Struct<{
94
- name: typeof Schema.String;
95
- data: typeof Schema.Unknown;
96
- ts: Schema.optional<typeof Schema.Number>;
97
- id: Schema.optional<typeof Schema.String>;
98
- v: Schema.optional<typeof Schema.String>;
114
+ readonly name: Schema.String;
115
+ readonly data: Schema.Unknown;
116
+ readonly ts: Schema.optional<Schema.Number>;
117
+ readonly id: Schema.optional<Schema.String>;
118
+ readonly v: Schema.optional<Schema.String>;
99
119
  }>;
100
120
  declare const SendEventResponse: Schema.Struct<{
101
- ids: Schema.optionalWith<Schema.Array$<typeof Schema.String>, {
102
- default: () => never[];
103
- }>;
104
- status: Schema.optional<typeof Schema.Number>;
121
+ readonly ids: Schema.withDecodingDefault<Schema.$Array<Schema.String>, never>;
122
+ readonly status: Schema.optional<Schema.Number>;
105
123
  }>;
106
124
  type EventPayload = typeof EventPayload.Type;
107
125
  type SendEventResponse = typeof SendEventResponse.Type;
108
- declare const SendEventError_base: Schema.TaggedErrorClass<SendEventError, "SendEventError", {
109
- readonly _tag: Schema.tag<"SendEventError">;
110
- } & {
111
- message: typeof Schema.String;
112
- events: Schema.Array$<typeof Schema.String>;
113
- }>;
126
+ declare const SendEventError_base: Schema.Class<SendEventError, Schema.TaggedStruct<"SendEventError", {
127
+ readonly message: Schema.String;
128
+ readonly events: Schema.$Array<Schema.String>;
129
+ }>, _$effect_Cause0.YieldableError>;
114
130
  declare class SendEventError extends SendEventError_base {}
115
131
  interface InngestClientService {
116
132
  readonly [TypeId]: TypeId;
@@ -119,8 +135,28 @@ interface InngestClientService {
119
135
  readonly eventBaseUrl: string;
120
136
  readonly apiBaseUrl: string;
121
137
  readonly sendEvent: (events: ReadonlyArray<EventPayload>) => Effect.Effect<SendEventResponse, SendEventError>;
138
+ /**
139
+ * Async checkpoint API call per spec §10.3.1.
140
+ *
141
+ * POSTs `{run_id, fn_id, qi_id, steps, ts}` to
142
+ * `{apiBaseUrl}/v1/checkpoint/{runId}/async` with a hashed-signing-key
143
+ * Bearer token. Retries 5xx responses with exponential backoff + jitter
144
+ * (5 attempts). On 401, falls back to the configured fallback signing key
145
+ * for the remainder of the client's lifetime (one-way switch).
146
+ *
147
+ * @internal
148
+ */
149
+ readonly checkpointAsync: (args: {
150
+ readonly runId: string;
151
+ readonly fnId: string;
152
+ readonly qiId: string;
153
+ readonly requestId?: string;
154
+ readonly generationId?: number;
155
+ readonly requestStartedAt?: number;
156
+ readonly steps: ReadonlyArray<typeof GeneratorOpcode.Type>;
157
+ }) => Effect.Effect<void, CheckpointApiError>;
122
158
  }
123
- declare const InngestClient_base: Context.TagClass<InngestClient, "effect-inngest/InngestClient", InngestClientService>;
159
+ declare const InngestClient_base: Context.ServiceClass<InngestClient, "effect-inngest/InngestClient", InngestClientService>;
124
160
  /**
125
161
  * InngestClient service for communicating with Inngest.
126
162
  *
@@ -128,6 +164,18 @@ declare const InngestClient_base: Context.TagClass<InngestClient, "effect-innges
128
164
  * @category context
129
165
  */
130
166
  declare class InngestClient extends InngestClient_base {}
167
+ declare const InngestConfig_base: Context.ServiceClass<InngestConfig, "effect-inngest/InngestConfig", ClientConfig>;
168
+ /**
169
+ * Static Inngest application configuration.
170
+ *
171
+ * This is the narrow dependency for internals that need app settings but not
172
+ * client behavior such as event sending or checkpoint API calls.
173
+ *
174
+ * @since 0.1.0
175
+ * @category context
176
+ */
177
+ declare class InngestConfig extends InngestConfig_base {}
178
+ declare const CheckpointRetrySchedule: Context.Reference<Schedule.Schedule<unknown, CheckpointApiError, never, never>>;
131
179
  /**
132
180
  * Create an InngestClient layer from a config.
133
181
  *
@@ -141,14 +189,14 @@ declare class InngestClient extends InngestClient_base {}
141
189
  * })
142
190
  * ```
143
191
  */
144
- declare const layer: (config: ClientConfig) => Layer.Layer<InngestClient, never, HttpClient.HttpClient>;
192
+ declare const layer: (config: ClientConfig) => Layer.Layer<InngestClient | InngestConfig, never, HttpClient.HttpClient>;
145
193
  /**
146
194
  * Create an InngestClient layer from Effect Config.
147
195
  *
148
196
  * @since 0.1.0
149
197
  * @category layers
150
198
  */
151
- declare const layerConfig: (config: Config.Config.Wrap<ClientConfig>) => Layer.Layer<InngestClient, ConfigError.ConfigError, HttpClient.HttpClient>;
199
+ declare const layerConfig: (config: Config.Wrap<ClientConfig>) => Layer.Layer<InngestClient | InngestConfig, Config.ConfigError, HttpClient.HttpClient>;
152
200
  /**
153
201
  * Create an InngestClient layer from environment variables.
154
202
  *
@@ -162,6 +210,6 @@ declare const layerConfig: (config: Config.Config.Wrap<ClientConfig>) => Layer.L
162
210
  * @since 0.1.0
163
211
  * @category layers
164
212
  */
165
- declare const layerFromEnv: Layer.Layer<InngestClient, ConfigError.ConfigError, HttpClient.HttpClient>;
213
+ declare const layerFromEnv: Layer.Layer<InngestClient | InngestConfig, Config.ConfigError, HttpClient.HttpClient>;
166
214
  //#endregion
167
- export { Client_d_exports, EventPayload, InngestClient, layer, layerConfig, layerFromEnv };
215
+ export { CheckpointApiError, CheckpointRetrySchedule, type CheckpointingOption, ClientConfig, Client_d_exports, EventPayload, InngestClient, InngestConfig, layer, layerConfig, layerFromEnv };
package/dist/Client.js CHANGED
@@ -1,23 +1,21 @@
1
- import { __exportAll } from "./_virtual/rolldown_runtime.js";
1
+ import { __exportAll } from "./_virtual/_rolldown/runtime.js";
2
+ import { CheckpointApiError } from "./internal/checkpoint/Error.js";
2
3
  import { Headers } from "./internal/protocol.js";
3
- import * as HttpClient from "@effect/platform/HttpClient";
4
- import * as Context from "effect/Context";
5
- import * as Effect from "effect/Effect";
6
- import * as Layer from "effect/Layer";
7
- import * as Option from "effect/Option";
8
- import * as Schema from "effect/Schema";
9
- import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
10
- import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
11
- import * as Config from "effect/Config";
12
- import * as Predicate from "effect/Predicate";
13
-
4
+ import { hashSigningKey } from "./internal/serve/Signature.js";
5
+ import { Clock, Config, Context, Effect, Layer, Option, Predicate, Ref, Schedule, Schema } from "effect";
6
+ import * as HttpClient from "effect/unstable/http/HttpClient";
7
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
8
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
14
9
  //#region src/Client.ts
15
10
  /**
16
11
  * @since 0.1.0
17
12
  */
18
13
  var Client_exports = /* @__PURE__ */ __exportAll({
14
+ CheckpointApiError: () => CheckpointApiError,
15
+ CheckpointRetrySchedule: () => CheckpointRetrySchedule,
19
16
  EventPayload: () => EventPayload,
20
17
  InngestClient: () => InngestClient,
18
+ InngestConfig: () => InngestConfig,
21
19
  layer: () => layer,
22
20
  layerConfig: () => layerConfig,
23
21
  layerFromEnv: () => layerFromEnv
@@ -40,10 +38,10 @@ const EventPayload = Schema.Struct({
40
38
  v: Schema.optional(Schema.String)
41
39
  });
42
40
  const SendEventResponse = Schema.Struct({
43
- ids: Schema.optionalWith(Schema.Array(Schema.String), { default: () => [] }),
41
+ ids: Schema.Array(Schema.String).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
44
42
  status: Schema.optional(Schema.Number)
45
43
  });
46
- var SendEventError = class extends Schema.TaggedError()("SendEventError", {
44
+ var SendEventError = class extends Schema.TaggedErrorClass()("SendEventError", {
47
45
  message: Schema.String,
48
46
  events: Schema.Array(Schema.String)
49
47
  }) {};
@@ -53,23 +51,43 @@ var SendEventError = class extends Schema.TaggedError()("SendEventError", {
53
51
  * @since 0.1.0
54
52
  * @category context
55
53
  */
56
- var InngestClient = class extends Context.Tag("effect-inngest/InngestClient")() {};
54
+ var InngestClient = class extends Context.Service()("effect-inngest/InngestClient") {};
55
+ /**
56
+ * Static Inngest application configuration.
57
+ *
58
+ * This is the narrow dependency for internals that need app settings but not
59
+ * client behavior such as event sending or checkpoint API calls.
60
+ *
61
+ * @since 0.1.0
62
+ * @category context
63
+ */
64
+ var InngestConfig = class extends Context.Service()("effect-inngest/InngestConfig") {};
57
65
  const resolveMode = (config) => config.mode ?? "dev";
58
66
  const resolveEventBaseUrl = (config, mode) => config.eventBaseUrl ?? (mode === "dev" ? DEFAULT_DEV_SERVER_URL : DEFAULT_EVENT_BASE_URL);
59
67
  const resolveApiBaseUrl = (config, mode) => config.apiBaseUrl ?? (mode === "dev" ? DEFAULT_DEV_SERVER_URL : DEFAULT_API_BASE_URL);
60
- const makeClient = (config, httpClient) => {
68
+ const isRetriable = (error) => Predicate.isUndefined(error.status) || error.status >= 500;
69
+ const defaultCheckpointRetrySchedule = Schedule.exponential("100 millis", 2).pipe(Schedule.jittered, Schedule.both(Schedule.recurs(5)), Schedule.while((m) => isRetriable(m.input)));
70
+ const CheckpointRetrySchedule = Context.Reference("effect-inngest/internal/CheckpointRetrySchedule", { defaultValue: () => defaultCheckpointRetrySchedule });
71
+ const makeClient = (config, httpClient) => Effect.gen(function* () {
61
72
  const mode = resolveMode(config);
62
73
  const eventBaseUrl = resolveEventBaseUrl(config, mode);
63
74
  const apiBaseUrl = resolveApiBaseUrl(config, mode);
64
- const sendEvent = (events) => {
65
- if (!config.eventKey && mode === "cloud") return new SendEventError({
75
+ const retrySchedule = yield* CheckpointRetrySchedule;
76
+ /**
77
+ * One-way switch: once the fallback signing key succeeds for any
78
+ * checkpoint API call, all subsequent calls use it first. Per spec
79
+ * §10.3.4.
80
+ */
81
+ const useFallbackKey = yield* Ref.make(false);
82
+ const sendEvent = Effect.fn("InngestClient.sendEvent")(function* (events) {
83
+ if (!config.eventKey && mode === "cloud") return yield* Effect.fail(new SendEventError({
66
84
  message: "Event key is required to send events in cloud mode",
67
85
  events: events.map((e) => e.name)
68
- });
69
- const key = config.eventKey ?? "local";
86
+ }));
87
+ const key = config.eventKey ?? "NO_EVENT_KEY_SET";
70
88
  const url = new URL(`e/${key}`, eventBaseUrl).toString();
71
89
  const eventNames = events.map((e) => e.name);
72
- const now = Date.now();
90
+ const now = yield* Clock.currentTimeMillis;
73
91
  const payloads = events.map((e) => ({
74
92
  name: e.name,
75
93
  data: e.data ?? {},
@@ -82,23 +100,64 @@ const makeClient = (config, httpClient) => {
82
100
  [Headers.SDK]: `effect-ts:v${SDK_VERSION}`,
83
101
  ...config.env ? { [Headers.Env]: config.env } : {}
84
102
  }));
85
- return HttpClientRequest.schemaBodyJson(Schema.Array(EventPayload))(request, payloads).pipe(Effect.flatMap(httpClient.execute), Effect.flatMap(HttpClientResponse.schemaBodyJson(SendEventResponse)), Effect.map((response) => ({
103
+ return yield* HttpClientRequest.schemaBodyJson(Schema.Array(EventPayload))(request, payloads).pipe(Effect.flatMap(httpClient.execute), Effect.flatMap(HttpClientResponse.schemaBodyJson(SendEventResponse)), Effect.map((response) => ({
86
104
  ids: response.ids,
87
105
  status: response.status
88
- })), Effect.scoped, Effect.catchAll((error) => new SendEventError({
106
+ })), Effect.scoped, Effect.catch((error) => Effect.fail(new SendEventError({
89
107
  message: `Failed to send events: ${Predicate.hasProperty(error, "message") ? error.message : "Unknown error"}`,
90
108
  events: eventNames
91
- })));
109
+ }))));
110
+ });
111
+ /**
112
+ * Issue a checkpoint POST with the given signing key. Returns a tagged
113
+ * error on non-2xx so the caller can decide whether to retry, swap keys,
114
+ * or fall back.
115
+ */
116
+ const checkpointRequest = (runId, body, hashedSigningKey) => {
117
+ const url = new URL(`v1/checkpoint/${runId}/async`, apiBaseUrl).toString();
118
+ const request = HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders({
119
+ "Content-Type": "application/json",
120
+ ...config.env ? { [Headers.Env]: config.env } : {}
121
+ }), HttpClientRequest.bearerToken(hashedSigningKey), HttpClientRequest.bodyText(body, "application/json"));
122
+ return httpClient.execute(request).pipe(Effect.scoped, Effect.catch((error) => Effect.fail(new CheckpointApiError({ message: `Network error issuing checkpoint: ${Predicate.hasProperty(error, "message") ? error.message : String(error)}` }))), Effect.flatMap((response) => {
123
+ if (response.status >= 200 && response.status < 300) return Effect.void;
124
+ return Effect.fail(new CheckpointApiError({
125
+ message: `Checkpoint API returned ${response.status}`,
126
+ status: response.status
127
+ }));
128
+ }));
92
129
  };
130
+ const checkpointAsync = Effect.fn("InngestClient.checkpointAsync")(function* (args) {
131
+ const signingKey = config.signingKey;
132
+ if (!signingKey && mode !== "dev") return yield* Effect.fail(new CheckpointApiError({ message: "No signing key configured for checkpoint API" }));
133
+ const now = yield* Clock.currentTimeMillis;
134
+ const body = JSON.stringify({
135
+ run_id: args.runId,
136
+ fn_id: args.fnId,
137
+ qi_id: args.qiId,
138
+ request_id: args.requestId,
139
+ generation_id: args.generationId,
140
+ request_started_at: args.requestStartedAt,
141
+ steps: args.steps,
142
+ ts: now
143
+ });
144
+ const fallbackKey = config.signingKeyFallback;
145
+ return yield* Ref.get(useFallbackKey).pipe(Effect.flatMap((usingFallback) => {
146
+ const primaryKey = usingFallback && fallbackKey ? fallbackKey : signingKey;
147
+ const primaryToken = primaryKey ? hashSigningKey(primaryKey) : "";
148
+ return checkpointRequest(args.runId, body, primaryToken).pipe(Effect.catch((error) => error.status === 401 && !usingFallback && fallbackKey ? checkpointRequest(args.runId, body, hashSigningKey(fallbackKey)).pipe(Effect.andThen(Ref.set(useFallbackKey, true))) : Effect.fail(error)));
149
+ })).pipe(Effect.retry(retrySchedule));
150
+ });
93
151
  return {
94
152
  [TypeId]: TypeId,
95
153
  config,
96
154
  mode,
97
155
  eventBaseUrl,
98
156
  apiBaseUrl,
99
- sendEvent
157
+ sendEvent,
158
+ checkpointAsync
100
159
  };
101
- };
160
+ });
102
161
  /**
103
162
  * Create an InngestClient layer from a config.
104
163
  *
@@ -112,14 +171,18 @@ const makeClient = (config, httpClient) => {
112
171
  * })
113
172
  * ```
114
173
  */
115
- const layer = (config) => Layer.effect(InngestClient, Effect.map(HttpClient.HttpClient, (httpClient) => makeClient(config, httpClient)));
174
+ const layer = (config) => Layer.mergeAll(Layer.succeed(InngestConfig, config), Layer.effect(InngestClient, Effect.gen(function* () {
175
+ return yield* makeClient(config, yield* HttpClient.HttpClient);
176
+ })));
116
177
  /**
117
178
  * Create an InngestClient layer from Effect Config.
118
179
  *
119
180
  * @since 0.1.0
120
181
  * @category layers
121
182
  */
122
- const layerConfig = (config) => Layer.effect(InngestClient, Effect.flatMap(Config.unwrap(config), (resolvedConfig) => Effect.map(HttpClient.HttpClient, (httpClient) => makeClient(resolvedConfig, httpClient))));
183
+ const layerConfig = (config) => Layer.unwrap(Effect.gen(function* () {
184
+ return layer(yield* Config.unwrap(config));
185
+ }));
123
186
  /**
124
187
  * Create an InngestClient layer from environment variables.
125
188
  *
@@ -139,6 +202,5 @@ const layerFromEnv = layerConfig(Config.all({
139
202
  signingKey: Config.string("INNGEST_SIGNING_KEY").pipe(Config.option, Config.map(Option.getOrUndefined)),
140
203
  signingKeyFallback: Config.string("INNGEST_SIGNING_KEY_FALLBACK").pipe(Config.option, Config.map(Option.getOrUndefined))
141
204
  }));
142
-
143
205
  //#endregion
144
- export { Client_exports, EventPayload, InngestClient, layer, layerConfig, layerFromEnv };
206
+ export { CheckpointApiError, CheckpointRetrySchedule, Client_exports, EventPayload, InngestClient, InngestConfig, layer, layerConfig, layerFromEnv };