effect-inngest 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -124,11 +124,12 @@ class UserWelcomeSent extends Schema.TaggedClass<UserWelcomeSent>()("user/welcom
124
124
  // 2. Define your functions
125
125
  const ProcessSignup = InngestFunction.make("process-signup", {
126
126
  trigger: { event: UserSignup },
127
- success: { welcomed: Schema.Boolean },
127
+ success: Schema.Struct({ welcomed: Schema.Boolean }),
128
128
  });
129
129
 
130
130
  const DailyDigest = InngestFunction.make("daily-digest", {
131
131
  trigger: { cron: "0 9 * * *" },
132
+ success: Schema.Void,
132
133
  });
133
134
 
134
135
  // 3. Create function group and implement handlers
@@ -161,15 +162,13 @@ const Client = InngestClient.layer({
161
162
  mode: "dev",
162
163
  }).pipe(Layer.provide(FetchHttpClient.layer));
163
164
 
164
- const Server = Layer.unwrapEffect(
165
- Effect.gen(function* () {
166
- const app = yield* InngestGroup.toHttpApp(App);
167
- return HttpServer.serve(app, HttpMiddleware.logger).pipe(
168
- HttpServer.withLogAddress,
169
- Layer.provide(BunHttpServer.layer({ port: 3000 })),
170
- );
171
- }),
172
- ).pipe(Layer.provide(Handlers), Layer.provide(Client), Layer.provide(FetchHttpClient.layer));
165
+ const Server = HttpServer.serve(InngestGroup.toHttpApp(App), HttpMiddleware.logger).pipe(
166
+ HttpServer.withLogAddress,
167
+ Layer.provide(BunHttpServer.layer({ port: 3000 })),
168
+ Layer.provide(Handlers),
169
+ Layer.provide(Client),
170
+ Layer.provide(FetchHttpClient.layer),
171
+ );
173
172
 
174
173
  // Run it
175
174
  BunRuntime.runMain(Layer.launch(Server));
@@ -187,6 +186,78 @@ bunx inngest-cli@latest dev -u http://localhost:3000
187
186
 
188
187
  Open http://localhost:8288 to trigger events and watch your functions run.
189
188
 
189
+ ### Using HttpApiBuilder
190
+
191
+ For more complex applications, you can compose Inngest into an existing `HttpApi`:
192
+
193
+ ```typescript
194
+ import * as HttpApi from "@effect/platform/HttpApi";
195
+ import * as HttpApiBuilder from "@effect/platform/HttpApiBuilder";
196
+ import { FetchHttpClient } from "@effect/platform";
197
+ import { BunHttpServer, BunRuntime } from "@effect/platform-bun";
198
+ import { Duration, Effect, Layer, Schema } from "effect";
199
+ import { InngestApiGroup, layerGroup } from "effect-inngest/HttpApi";
200
+ import { InngestClient, InngestFunction, InngestGroup } from "effect-inngest";
201
+
202
+ // 1. Define your events
203
+ class UserSignup extends Schema.TaggedClass<UserSignup>()("user/signup", {
204
+ userId: Schema.String,
205
+ email: Schema.String,
206
+ }) {}
207
+
208
+ class UserWelcomeSent extends Schema.TaggedClass<UserWelcomeSent>()("user/welcome-sent", {
209
+ userId: Schema.String,
210
+ }) {}
211
+
212
+ // 2. Define functions
213
+ const ProcessSignup = InngestFunction.make("process-signup", {
214
+ trigger: { event: UserSignup },
215
+ success: Schema.Struct({ welcomed: Schema.Boolean }),
216
+ });
217
+
218
+ const DailyDigest = InngestFunction.make("daily-digest", {
219
+ trigger: { cron: "0 9 * * *" },
220
+ success: Schema.Void,
221
+ });
222
+
223
+ // 3. Create group and handlers
224
+ const App = InngestGroup.make(ProcessSignup, DailyDigest);
225
+
226
+ const Handlers = App.toLayer({
227
+ "process-signup": ({ event, step }) =>
228
+ 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 }));
231
+ yield* step.sleep("welcome-delay", Duration.seconds(5));
232
+ yield* step.sendEvent("notify", new UserWelcomeSent({ userId: user.id }));
233
+ return { welcomed: true };
234
+ }),
235
+ "daily-digest": ({ step }) => step.run("send-digest", Effect.log("Sending daily digest...")),
236
+ });
237
+
238
+ // 4. Create client
239
+ const Client = InngestClient.layer({
240
+ id: "my-app",
241
+ mode: "dev",
242
+ }).pipe(Layer.provide(FetchHttpClient.layer));
243
+
244
+ // 5. Create API with Inngest group at /inngest prefix
245
+ const MyApi = HttpApi.make("my-api").add(InngestApiGroup.prefix("/inngest"));
246
+
247
+ // 6. Build API layer
248
+ const ApiLive = HttpApiBuilder.api(MyApi).pipe(
249
+ Layer.provide(layerGroup(MyApi, App)),
250
+ Layer.provide(Handlers),
251
+ Layer.provide(Client),
252
+ Layer.provide(FetchHttpClient.layer),
253
+ );
254
+
255
+ // 7. Serve
256
+ const Server = HttpApiBuilder.serve().pipe(Layer.provide(ApiLive), Layer.provide(BunHttpServer.layer({ port: 3000 })));
257
+
258
+ BunRuntime.runMain(Layer.launch(Server));
259
+ ```
260
+
190
261
  ---
191
262
 
192
263
  ## Getting Started
@@ -217,12 +288,13 @@ import { InngestFunction } from "effect-inngest";
217
288
  // Event-triggered function
218
289
  const ProcessSignup = InngestFunction.make("process-signup", {
219
290
  trigger: [{ event: UserSignup }], // pass multiple triggers as Array.
220
- success: { welcomeEmailSent: Schema.Boolean },
291
+ success: Schema.Struct({ welcomeEmailSent: Schema.Boolean }),
221
292
  });
222
293
 
223
294
  // Cron-triggered function
224
295
  const DailyReport = InngestFunction.make("daily-report", {
225
296
  trigger: { cron: "0 9 * * *" },
297
+ success: Schema.Void,
226
298
  });
227
299
  ```
228
300
 
@@ -420,19 +492,6 @@ yield * new RetryAfterError({ message: "Rate limited", retryAfter: 60000 });
420
492
 
421
493
  See the [`examples/`](./examples) directory:
422
494
 
423
- | Example | Description |
424
- | ------------------------------------------- | ---------------------------------------------- |
425
- | [simple.ts](./examples/simple.ts) | Minimal hello world with parallel steps |
426
- | [KitchenSink.ts](./examples/KitchenSink.ts) | Complete e-commerce workflow with all features |
427
-
428
- ```bash
429
- # Run an example
430
- bun examples/simple.ts
431
-
432
- # Start Inngest dev server
433
- bunx inngest-cli@latest dev -u http://localhost:9999
434
- ```
435
-
436
495
  ---
437
496
 
438
497
  ## Current Limitations
@@ -447,11 +506,6 @@ This library is under active development. The following Inngest features are **n
447
506
 
448
507
  ---
449
508
 
450
- ## Requirements
451
-
452
- - **TypeScript** 5.4+ with `strict: true`
453
- - **Effect** 3.x
454
-
455
509
  ## License
456
510
 
457
511
  MIT
@@ -313,6 +313,40 @@ interface FunctionRegistration {
313
313
  start?: string;
314
314
  finish?: string;
315
315
  };
316
+ readonly rateLimit?: {
317
+ readonly key?: string;
318
+ readonly limit: number;
319
+ readonly period: string;
320
+ };
321
+ readonly throttle?: {
322
+ readonly key?: string;
323
+ readonly limit: number;
324
+ readonly period: string;
325
+ readonly burst?: number;
326
+ };
327
+ readonly debounce?: {
328
+ readonly key?: string;
329
+ readonly period: string;
330
+ readonly timeout?: string;
331
+ };
332
+ readonly concurrency?: ReadonlyArray<{
333
+ readonly key?: string;
334
+ readonly limit: number;
335
+ readonly scope?: string;
336
+ }>;
337
+ readonly priority?: {
338
+ readonly run?: string;
339
+ };
340
+ readonly singleton?: {
341
+ readonly key?: string;
342
+ readonly mode: string;
343
+ };
344
+ readonly batchEvents?: {
345
+ readonly maxSize: number;
346
+ readonly timeout: string;
347
+ readonly key?: string;
348
+ };
349
+ readonly idempotency?: string;
316
350
  }
317
351
  /**
318
352
  * An Inngest function definition.
package/dist/Function.js CHANGED
@@ -35,6 +35,38 @@ const Proto = {
35
35
  start: opts.timeouts.start ? timeStr(opts.timeouts.start) : void 0,
36
36
  finish: opts.timeouts.finish ? timeStr(opts.timeouts.finish) : void 0
37
37
  } : void 0;
38
+ const rateLimit = opts.rateLimit ? {
39
+ key: opts.rateLimit.key,
40
+ limit: opts.rateLimit.limit,
41
+ period: timeStr(opts.rateLimit.period)
42
+ } : void 0;
43
+ const throttle = opts.throttle ? {
44
+ key: opts.throttle.key,
45
+ limit: opts.throttle.limit,
46
+ period: timeStr(opts.throttle.period),
47
+ burst: opts.throttle.burst
48
+ } : void 0;
49
+ const debounce = opts.debounce ? {
50
+ key: opts.debounce.key,
51
+ period: timeStr(opts.debounce.period),
52
+ timeout: opts.debounce.timeout ? timeStr(opts.debounce.timeout) : void 0
53
+ } : void 0;
54
+ const concurrency = opts.concurrency != null ? typeof opts.concurrency === "number" ? [{ limit: opts.concurrency }] : Array.ensure(opts.concurrency).map((c) => ({
55
+ key: c.key,
56
+ limit: c.limit,
57
+ scope: c.scope
58
+ })) : void 0;
59
+ const priority = opts.priority ? { run: opts.priority.run } : void 0;
60
+ const singleton = opts.singleton ? {
61
+ key: opts.singleton.key,
62
+ mode: opts.singleton.mode
63
+ } : void 0;
64
+ const batchEvents = opts.batchEvents ? {
65
+ maxSize: opts.batchEvents.maxSize,
66
+ timeout: timeStr(opts.batchEvents.timeout),
67
+ key: opts.batchEvents.key
68
+ } : void 0;
69
+ const idempotency = opts.idempotency;
38
70
  const fnId = `${config.appId}-${this._tag}`;
39
71
  const stepUrl = new URL(config.url);
40
72
  stepUrl.searchParams.set("fnId", fnId);
@@ -53,7 +85,15 @@ const Proto = {
53
85
  retries: { attempts: opts.retries ?? 3 }
54
86
  } },
55
87
  cancel: cancel && cancel.length > 0 ? cancel : void 0,
56
- timeouts
88
+ timeouts,
89
+ rateLimit,
90
+ throttle,
91
+ debounce,
92
+ concurrency,
93
+ priority,
94
+ singleton,
95
+ batchEvents,
96
+ idempotency
57
97
  };
58
98
  }
59
99
  };
package/dist/Group.d.ts CHANGED
@@ -143,9 +143,8 @@ declare const toHttpApp: (group: InngestGroup.Any) => HttpApp.Default<never, Inn
143
143
  */
144
144
  declare const toWebHandler: <R, E>(group: InngestGroup.Any, options: {
145
145
  readonly layer: Layer.Layer<InngestClient | HttpClient.HttpClient | R, E, never>;
146
- readonly memoMap?: Layer.MemoMap;
147
146
  }) => {
148
- readonly handler: (request: Request) => Promise<Response>;
147
+ readonly handler: (request: Request, context?: Context.Context<never>) => Promise<Response>;
149
148
  readonly dispose: () => Promise<void>;
150
149
  };
151
150
  //#endregion
package/dist/Group.js CHANGED
@@ -158,7 +158,7 @@ const toHttpApp = (group) => Effect.gen(function* () {
158
158
  * process.on("SIGTERM", dispose)
159
159
  * ```
160
160
  */
161
- const toWebHandler = (group, options) => HttpApp.toWebHandlerLayer(toHttpApp(group), Layer.mergeAll(options.layer, Layer.scope), { memoMap: options.memoMap });
161
+ const toWebHandler = (group, options) => HttpApp.toWebHandlerLayer(toHttpApp(group), Layer.mergeAll(options.layer, Layer.scope));
162
162
 
163
163
  //#endregion
164
164
  export { Group_exports, TypeId, make, toHttpApp, toWebHandler };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-inngest",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Native Effect client library for Inngest - build durable, type-safe workflows",
5
5
  "keywords": [
6
6
  "background-jobs",
@@ -23,9 +23,6 @@
23
23
  "type": "git",
24
24
  "url": "https://github.com/erikshestopal/effect-inngest.git"
25
25
  },
26
- "workspaces": [
27
- "examples"
28
- ],
29
26
  "files": [
30
27
  "dist/**/*.d.ts",
31
28
  "dist/**/*.d.ts.map",
@@ -106,16 +103,21 @@
106
103
  "devDependencies": {
107
104
  "@changesets/changelog-github": "^0.5.2",
108
105
  "@changesets/cli": "^2.29.8",
106
+ "@effect/cluster": "^0.56.1",
109
107
  "@effect/language-service": "0.72.0",
108
+ "@effect/platform": "^0.94.2",
109
+ "@effect/platform-bun": "^0.87.0",
110
110
  "@types/bun": "1.3.6",
111
111
  "@typescript/native-preview": "^7.0.0-dev.20260124.1",
112
+ "effect": "^3.15.0",
112
113
  "oxfmt": "0.26.0",
113
114
  "oxlint": "1.41.0",
114
115
  "oxlint-tsgolint": "0.11.1",
115
- "tsdown": "0.20.1"
116
+ "tsdown": "0.20.1",
117
+ "typescript": "^5.9.3"
116
118
  },
117
119
  "peerDependencies": {
118
- "@effect/platform": ">=0.80.0",
119
- "effect": ">=3.14.0"
120
+ "@effect/platform": ">=0.87.0",
121
+ "effect": ">=3.15.0"
120
122
  }
121
123
  }
package/src/Function.ts CHANGED
@@ -331,7 +331,11 @@ interface RegistrationConfig {
331
331
  interface FunctionRegistration {
332
332
  readonly id: string;
333
333
  readonly name: string;
334
- readonly triggers: ReadonlyArray<{ event?: string; cron?: string; expression?: string }>;
334
+ readonly triggers: ReadonlyArray<{
335
+ event?: string;
336
+ cron?: string;
337
+ expression?: string;
338
+ }>;
335
339
  readonly steps: {
336
340
  readonly step: {
337
341
  readonly id: string;
@@ -340,8 +344,41 @@ interface FunctionRegistration {
340
344
  readonly retries?: { readonly attempts: number };
341
345
  };
342
346
  };
343
- readonly cancel?: ReadonlyArray<{ event: string; if?: string; timeout?: string }>;
347
+ readonly cancel?: ReadonlyArray<{
348
+ event: string;
349
+ if?: string;
350
+ timeout?: string;
351
+ }>;
344
352
  readonly timeouts?: { start?: string; finish?: string };
353
+ readonly rateLimit?: {
354
+ readonly key?: string;
355
+ readonly limit: number;
356
+ readonly period: string;
357
+ };
358
+ readonly throttle?: {
359
+ readonly key?: string;
360
+ readonly limit: number;
361
+ readonly period: string;
362
+ readonly burst?: number;
363
+ };
364
+ readonly debounce?: {
365
+ readonly key?: string;
366
+ readonly period: string;
367
+ readonly timeout?: string;
368
+ };
369
+ readonly concurrency?: ReadonlyArray<{
370
+ readonly key?: string;
371
+ readonly limit: number;
372
+ readonly scope?: string;
373
+ }>;
374
+ readonly priority?: { readonly run?: string };
375
+ readonly singleton?: { readonly key?: string; readonly mode: string };
376
+ readonly batchEvents?: {
377
+ readonly maxSize: number;
378
+ readonly timeout: string;
379
+ readonly key?: string;
380
+ };
381
+ readonly idempotency?: string;
345
382
  }
346
383
 
347
384
  /**
@@ -392,7 +429,11 @@ const Proto = {
392
429
  [TypeId]: TypeId,
393
430
 
394
431
  toRegistration(this: InngestFunction.Any, config: RegistrationConfig): FunctionRegistration {
395
- const triggers: Array<{ event?: string; cron?: string; expression?: string }> = [];
432
+ const triggers: Array<{
433
+ event?: string;
434
+ cron?: string;
435
+ expression?: string;
436
+ }> = [];
396
437
 
397
438
  for (const t of this.triggers) {
398
439
  if (isEventTrigger(t)) {
@@ -416,6 +457,54 @@ const Proto = {
416
457
  }
417
458
  : undefined;
418
459
 
460
+ const rateLimit = opts.rateLimit
461
+ ? {
462
+ key: opts.rateLimit.key,
463
+ limit: opts.rateLimit.limit,
464
+ period: timeStr(opts.rateLimit.period),
465
+ }
466
+ : undefined;
467
+
468
+ const throttle = opts.throttle
469
+ ? {
470
+ key: opts.throttle.key,
471
+ limit: opts.throttle.limit,
472
+ period: timeStr(opts.throttle.period),
473
+ burst: opts.throttle.burst,
474
+ }
475
+ : undefined;
476
+
477
+ const debounce = opts.debounce
478
+ ? {
479
+ key: opts.debounce.key,
480
+ period: timeStr(opts.debounce.period),
481
+ timeout: opts.debounce.timeout ? timeStr(opts.debounce.timeout) : undefined,
482
+ }
483
+ : undefined;
484
+
485
+ const concurrency =
486
+ opts.concurrency != null
487
+ ? typeof opts.concurrency === "number"
488
+ ? [{ limit: opts.concurrency }]
489
+ : Arr.ensure(opts.concurrency).map((c) => ({
490
+ key: c.key,
491
+ limit: c.limit,
492
+ scope: c.scope,
493
+ }))
494
+ : undefined;
495
+
496
+ const priority = opts.priority ? { run: opts.priority.run } : undefined;
497
+ const singleton = opts.singleton ? { key: opts.singleton.key, mode: opts.singleton.mode } : undefined;
498
+ const batchEvents = opts.batchEvents
499
+ ? {
500
+ maxSize: opts.batchEvents.maxSize,
501
+ timeout: timeStr(opts.batchEvents.timeout),
502
+ key: opts.batchEvents.key,
503
+ }
504
+ : undefined;
505
+
506
+ const idempotency = opts.idempotency;
507
+
419
508
  const fnId = `${config.appId}-${this._tag}`;
420
509
  const stepUrl = new URL(config.url);
421
510
  stepUrl.searchParams.set("fnId", fnId);
@@ -435,6 +524,14 @@ const Proto = {
435
524
  },
436
525
  cancel: cancel && cancel.length > 0 ? cancel : undefined,
437
526
  timeouts,
527
+ rateLimit,
528
+ throttle,
529
+ debounce,
530
+ concurrency,
531
+ priority,
532
+ singleton,
533
+ batchEvents,
534
+ idempotency,
438
535
  };
439
536
  },
440
537
  };
package/src/Group.ts CHANGED
@@ -303,12 +303,8 @@ export const toWebHandler = <R, E>(
303
303
  group: InngestGroup.Any,
304
304
  options: {
305
305
  readonly layer: Layer.Layer<InngestClient | HttpClient.HttpClient | R, E, never>;
306
- readonly memoMap?: Layer.MemoMap;
307
306
  },
308
307
  ): {
309
- readonly handler: (request: Request) => Promise<Response>;
308
+ readonly handler: (request: Request, context?: Context.Context<never>) => Promise<Response>;
310
309
  readonly dispose: () => Promise<void>;
311
- } =>
312
- HttpApp.toWebHandlerLayer(toHttpApp(group), Layer.mergeAll(options.layer, Layer.scope), {
313
- memoMap: options.memoMap,
314
- });
310
+ } => HttpApp.toWebHandlerLayer(toHttpApp(group), Layer.mergeAll(options.layer, Layer.scope));