effect-inngest 0.1.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/LICENSE +21 -0
- package/README.md +457 -0
- package/dist/Client.d.ts +167 -0
- package/dist/Client.js +144 -0
- package/dist/Events.d.ts +110 -0
- package/dist/Events.js +93 -0
- package/dist/Function.d.ts +384 -0
- package/dist/Function.js +104 -0
- package/dist/Group.d.ts +152 -0
- package/dist/Group.js +164 -0
- package/dist/HttpApi.d.ts +75 -0
- package/dist/HttpApi.js +47 -0
- package/dist/_virtual/rolldown_runtime.js +18 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/internal/constants.js +15 -0
- package/dist/internal/driver.d.ts +5 -0
- package/dist/internal/driver.js +117 -0
- package/dist/internal/errors.d.ts +56 -0
- package/dist/internal/errors.js +61 -0
- package/dist/internal/handler.d.ts +20 -0
- package/dist/internal/handler.js +145 -0
- package/dist/internal/helpers.js +44 -0
- package/dist/internal/interrupts.d.ts +2 -0
- package/dist/internal/interrupts.js +45 -0
- package/dist/internal/memo.js +56 -0
- package/dist/internal/protocol.d.ts +1 -0
- package/dist/internal/protocol.js +191 -0
- package/dist/internal/signature.d.ts +18 -0
- package/dist/internal/signature.js +97 -0
- package/dist/internal/step.d.ts +59 -0
- package/dist/internal/step.js +183 -0
- package/package.json +121 -0
- package/src/Client.ts +279 -0
- package/src/Events.ts +87 -0
- package/src/Function.ts +493 -0
- package/src/Group.ts +314 -0
- package/src/HttpApi.ts +82 -0
- package/src/index.ts +171 -0
- package/src/internal/constants.ts +11 -0
- package/src/internal/driver.ts +194 -0
- package/src/internal/errors.ts +130 -0
- package/src/internal/handler.ts +222 -0
- package/src/internal/helpers.ts +58 -0
- package/src/internal/interrupts.ts +62 -0
- package/src/internal/memo.ts +73 -0
- package/src/internal/protocol.ts +218 -0
- package/src/internal/signature.ts +158 -0
- package/src/internal/step.ts +377 -0
package/src/Group.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 0.1.0
|
|
3
|
+
*/
|
|
4
|
+
import * as HttpApp from "@effect/platform/HttpApp";
|
|
5
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
6
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
7
|
+
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
8
|
+
import * as UrlParams from "@effect/platform/UrlParams";
|
|
9
|
+
import * as Context from "effect/Context";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Layer from "effect/Layer";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import type { InngestFunction } from "./Function.js";
|
|
15
|
+
import { InngestClient } from "./Client.js";
|
|
16
|
+
import * as InternalHandler from "./internal/handler.js";
|
|
17
|
+
import * as Protocol from "./internal/protocol.js";
|
|
18
|
+
import { SignatureLive } from "./internal/signature.js";
|
|
19
|
+
|
|
20
|
+
import { type HandlerContext } from "./internal/step.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Re-export HandlerContext from internal step module.
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
* @category models
|
|
26
|
+
*/
|
|
27
|
+
export { type HandlerContext };
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 0.1.0
|
|
31
|
+
* @category type ids
|
|
32
|
+
*/
|
|
33
|
+
export const TypeId: unique symbol = Symbol.for("effect-inngest/Group");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @since 0.1.0
|
|
37
|
+
* @category type ids
|
|
38
|
+
*/
|
|
39
|
+
export type TypeId = typeof TypeId;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
* @category models
|
|
44
|
+
*/
|
|
45
|
+
export type HandlerFn<F extends InngestFunction.Any> = (
|
|
46
|
+
context: HandlerContext<F>,
|
|
47
|
+
) => Effect.Effect<InngestFunction.Success<F>, unknown, unknown>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
* @category models
|
|
52
|
+
*/
|
|
53
|
+
export type HandlersFrom<Fns extends InngestFunction.Any> = {
|
|
54
|
+
readonly [F in Fns as InngestFunction.Tag<F>]: HandlerFn<F>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @since 0.1.0
|
|
59
|
+
* @category models
|
|
60
|
+
*/
|
|
61
|
+
export type HandlerFrom<Fns extends InngestFunction.Any, Tag extends InngestFunction.Tag<Fns>> =
|
|
62
|
+
Extract<Fns, { readonly _tag: Tag }> extends infer Current
|
|
63
|
+
? Current extends InngestFunction.Any
|
|
64
|
+
? HandlerFn<Current>
|
|
65
|
+
: never
|
|
66
|
+
: never;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
* @category models
|
|
71
|
+
*/
|
|
72
|
+
export type HandlersRequirements<H> =
|
|
73
|
+
H extends Record<string, (...args: ReadonlyArray<unknown>) => Effect.Effect<unknown, unknown, infer R>> ? R : never;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @category models
|
|
78
|
+
*/
|
|
79
|
+
export type HandlerRequirements<Handler> = Handler extends (
|
|
80
|
+
...args: ReadonlyArray<unknown>
|
|
81
|
+
) => Effect.Effect<unknown, unknown, infer R>
|
|
82
|
+
? R
|
|
83
|
+
: never;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A nominal type representing a registered handler for a specific function tag.
|
|
87
|
+
* @since 0.1.0
|
|
88
|
+
* @category models
|
|
89
|
+
*/
|
|
90
|
+
export interface Handler<Tag extends string> {
|
|
91
|
+
readonly _: unique symbol;
|
|
92
|
+
readonly tag: Tag;
|
|
93
|
+
readonly handler: (context: unknown) => Effect.Effect<unknown, unknown, unknown>;
|
|
94
|
+
readonly context: Context.Context<never>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Maps a function to its Handler type.
|
|
99
|
+
* @since 0.1.0
|
|
100
|
+
* @category models
|
|
101
|
+
*/
|
|
102
|
+
export type ToHandler<F extends InngestFunction.Any> =
|
|
103
|
+
F extends InngestFunction<infer _Tag, infer _Triggers, infer _Success> ? Handler<_Tag> : never;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @since 0.1.0
|
|
107
|
+
* @category models
|
|
108
|
+
*/
|
|
109
|
+
export interface InngestGroup<Fns extends InngestFunction.Any> {
|
|
110
|
+
readonly [TypeId]: TypeId;
|
|
111
|
+
readonly functions: ReadonlyMap<string, Fns>;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Implement all handlers for the functions in this group.
|
|
115
|
+
*/
|
|
116
|
+
readonly toLayer: <H extends HandlersFrom<Fns>>(
|
|
117
|
+
handlers: H,
|
|
118
|
+
) => Layer.Layer<ToHandler<Fns>, never, HandlersRequirements<H>>;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Implement a single handler from the group.
|
|
122
|
+
*/
|
|
123
|
+
readonly toLayerHandler: <Tag extends InngestFunction.Tag<Fns>, H extends HandlerFrom<Fns, Tag>>(
|
|
124
|
+
tag: Tag,
|
|
125
|
+
handler: H,
|
|
126
|
+
) => Layer.Layer<Handler<Tag>, never, HandlerRequirements<H>>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @since 0.1.0
|
|
131
|
+
* @category models
|
|
132
|
+
*/
|
|
133
|
+
export declare namespace InngestGroup {
|
|
134
|
+
export type Any = InngestGroup<InngestFunction.Any>;
|
|
135
|
+
export type Functions<G> = G extends InngestGroup<infer Fns> ? Fns : never;
|
|
136
|
+
export type FunctionTags<G> = G extends InngestGroup<infer Fns> ? InngestFunction.Tag<Fns> : never;
|
|
137
|
+
export type Handlers<G> = G extends InngestGroup<infer Fns> ? HandlersFrom<Fns> : never;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const Proto = {
|
|
141
|
+
[TypeId]: TypeId,
|
|
142
|
+
|
|
143
|
+
toLayer(this: InngestGroup<InngestFunction.Any>, handlers: Record<string, unknown>) {
|
|
144
|
+
const functions = this.functions;
|
|
145
|
+
return Layer.effectContext(
|
|
146
|
+
Effect.gen(function* () {
|
|
147
|
+
const context = yield* Effect.context<never>();
|
|
148
|
+
const contextMap = new Map<string, unknown>();
|
|
149
|
+
for (const [tag, handler] of Object.entries(handlers)) {
|
|
150
|
+
const fn = functions.get(tag)!;
|
|
151
|
+
contextMap.set(fn.key, { handler, context });
|
|
152
|
+
}
|
|
153
|
+
return Context.unsafeMake(contextMap);
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
toLayerHandler(this: InngestGroup<InngestFunction.Any>, tag: string, handler: unknown) {
|
|
159
|
+
const fn = this.functions.get(tag)!;
|
|
160
|
+
return Layer.effectContext(
|
|
161
|
+
Effect.gen(function* () {
|
|
162
|
+
const context = yield* Effect.context<never>();
|
|
163
|
+
const contextMap = new Map<string, unknown>();
|
|
164
|
+
contextMap.set(fn.key, { handler, context });
|
|
165
|
+
return Context.unsafeMake(contextMap);
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @since 0.1.0
|
|
173
|
+
* @category constructors
|
|
174
|
+
*/
|
|
175
|
+
export const make = <Fns extends ReadonlyArray<InngestFunction.Any>>(...fns: Fns): InngestGroup<Fns[number]> => {
|
|
176
|
+
const functions = new Map(fns.map((fn) => [fn._tag, fn]));
|
|
177
|
+
const group = Object.create(Proto);
|
|
178
|
+
group.functions = functions;
|
|
179
|
+
return group;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Build an HttpApp from an InngestGroup.
|
|
184
|
+
*
|
|
185
|
+
* @since 0.1.0
|
|
186
|
+
* @category http
|
|
187
|
+
* @example
|
|
188
|
+
* ```ts
|
|
189
|
+
* import { Effect, Layer } from "effect"
|
|
190
|
+
* import { HttpServer } from "@effect/platform"
|
|
191
|
+
* import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
|
|
192
|
+
* import { InngestGroup, InngestClient } from "effect-inngest"
|
|
193
|
+
*
|
|
194
|
+
* InngestGroup.toHttpApp(MyGroup).pipe(
|
|
195
|
+
* Effect.flatMap((app) => HttpServer.serve(app)),
|
|
196
|
+
* Effect.provide(InngestClient.layer({ id: "my-app" })),
|
|
197
|
+
* Effect.provide(NodeHttpServer.layer({ port: 3000 })),
|
|
198
|
+
* NodeRuntime.runMain,
|
|
199
|
+
* )
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
export const toHttpApp = (group: InngestGroup.Any): HttpApp.Default<never, InngestClient | HttpClient.HttpClient> =>
|
|
203
|
+
Effect.gen(function* () {
|
|
204
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
205
|
+
const method = request.method;
|
|
206
|
+
const requestUrl = Option.match(HttpServerRequest.toURL(request), {
|
|
207
|
+
onNone: () => request.url,
|
|
208
|
+
onSome: (url) => url.toString(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
if (method === "GET") {
|
|
212
|
+
const result = yield* InternalHandler.handleIntrospection(group, requestUrl);
|
|
213
|
+
return yield* HttpServerResponse.json(result.body, {
|
|
214
|
+
status: result.status,
|
|
215
|
+
headers: result.headers,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (method === "PUT") {
|
|
220
|
+
const result = yield* InternalHandler.handleRegistration(group, requestUrl);
|
|
221
|
+
return yield* HttpServerResponse.json(result.body, {
|
|
222
|
+
status: result.status,
|
|
223
|
+
headers: result.headers,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (method === "POST") {
|
|
228
|
+
const url = Option.getOrThrow(HttpServerRequest.toURL(request));
|
|
229
|
+
|
|
230
|
+
const ExecuteParams = Schema.Struct({
|
|
231
|
+
fnId: Schema.String,
|
|
232
|
+
stepId: Schema.optional(Schema.String),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const params = yield* UrlParams.schemaStruct(ExecuteParams)(UrlParams.fromInput(url.searchParams)).pipe(
|
|
236
|
+
Effect.catchAll(() =>
|
|
237
|
+
Effect.fail(
|
|
238
|
+
HttpServerResponse.unsafeJson(
|
|
239
|
+
{ error: "Missing or invalid fnId query parameter" },
|
|
240
|
+
{ status: 400, headers: { [Protocol.Headers.NoRetry]: "true" } },
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const body = yield* InternalHandler.verifyAndParseRequestBody(request).pipe(
|
|
247
|
+
Effect.provide(SignatureLive),
|
|
248
|
+
Effect.catchAll((error) =>
|
|
249
|
+
Effect.fail(
|
|
250
|
+
HttpServerResponse.unsafeJson(
|
|
251
|
+
{ error: error.message },
|
|
252
|
+
{
|
|
253
|
+
status: error._tag === "SignatureError" ? 401 : 400,
|
|
254
|
+
headers: { [Protocol.Headers.NoRetry]: "true" },
|
|
255
|
+
},
|
|
256
|
+
),
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const result = yield* InternalHandler.handleExecution(group, params.fnId, params.stepId, body);
|
|
262
|
+
|
|
263
|
+
return yield* HttpServerResponse.json(result.body, {
|
|
264
|
+
status: result.status,
|
|
265
|
+
headers: result.headers,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return yield* HttpServerResponse.json({ error: `Method ${method} not allowed` }, { status: 405 });
|
|
270
|
+
}).pipe(
|
|
271
|
+
Effect.catchAllCause((cause) =>
|
|
272
|
+
HttpServerResponse.json({ error: "Internal server error", cause: String(cause) }, { status: 500 }).pipe(
|
|
273
|
+
Effect.orDie,
|
|
274
|
+
),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Create a standalone web handler from an InngestGroup.
|
|
280
|
+
*
|
|
281
|
+
* @since 0.1.0
|
|
282
|
+
* @category http
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* import { InngestGroup, InngestClient } from "effect-inngest"
|
|
286
|
+
* import { HttpClient } from "@effect/platform"
|
|
287
|
+
* import { FetchHttpClient } from "@effect/platform"
|
|
288
|
+
*
|
|
289
|
+
* const { handler, dispose } = InngestGroup.toWebHandler(MyGroup, {
|
|
290
|
+
* layer: InngestClient.layer({ id: "my-app" }).pipe(
|
|
291
|
+
* Layer.provide(FetchHttpClient.layer),
|
|
292
|
+
* ),
|
|
293
|
+
* })
|
|
294
|
+
*
|
|
295
|
+
* // Use with any web framework
|
|
296
|
+
* Bun.serve({ fetch: handler, port: 3000 })
|
|
297
|
+
*
|
|
298
|
+
* // Call dispose() on shutdown
|
|
299
|
+
* process.on("SIGTERM", dispose)
|
|
300
|
+
* ```
|
|
301
|
+
*/
|
|
302
|
+
export const toWebHandler = <R, E>(
|
|
303
|
+
group: InngestGroup.Any,
|
|
304
|
+
options: {
|
|
305
|
+
readonly layer: Layer.Layer<InngestClient | HttpClient.HttpClient | R, E, never>;
|
|
306
|
+
readonly memoMap?: Layer.MemoMap;
|
|
307
|
+
},
|
|
308
|
+
): {
|
|
309
|
+
readonly handler: (request: Request) => Promise<Response>;
|
|
310
|
+
readonly dispose: () => Promise<void>;
|
|
311
|
+
} =>
|
|
312
|
+
HttpApp.toWebHandlerLayer(toHttpApp(group), Layer.mergeAll(options.layer, Layer.scope), {
|
|
313
|
+
memoMap: options.memoMap,
|
|
314
|
+
});
|
package/src/HttpApi.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 0.1.0
|
|
3
|
+
*/
|
|
4
|
+
import * as HttpApi from "@effect/platform/HttpApi";
|
|
5
|
+
import * as HttpApiBuilder from "@effect/platform/HttpApiBuilder";
|
|
6
|
+
import * as HttpApiEndpoint from "@effect/platform/HttpApiEndpoint";
|
|
7
|
+
import * as HttpApiGroup from "@effect/platform/HttpApiGroup";
|
|
8
|
+
import * as HttpClient from "@effect/platform/HttpClient";
|
|
9
|
+
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Layer from "effect/Layer";
|
|
12
|
+
import * as Option from "effect/Option";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import { InngestClient } from "./Client.js";
|
|
15
|
+
import type { InngestGroup } from "./Group.js";
|
|
16
|
+
import * as InternalHandler from "./internal/handler.js";
|
|
17
|
+
import * as Protocol from "./internal/protocol.js";
|
|
18
|
+
import { SignatureLive } from "./internal/signature.js";
|
|
19
|
+
|
|
20
|
+
const ExecuteParams = Schema.Struct({
|
|
21
|
+
fnId: Schema.String,
|
|
22
|
+
stepId: Schema.optional(Schema.String),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
class FunctionNotFoundError extends Schema.TaggedError<FunctionNotFoundError>()("FunctionNotFoundError", {
|
|
26
|
+
message: Schema.String,
|
|
27
|
+
}) {}
|
|
28
|
+
|
|
29
|
+
const IntrospectEndpoint = HttpApiEndpoint.get("introspect", "/").addSuccess(Protocol.IntrospectionResponse);
|
|
30
|
+
const RegisterEndpoint = HttpApiEndpoint.put("register", "/").addSuccess(Protocol.RegisterResponse);
|
|
31
|
+
const ExecuteEndpoint = HttpApiEndpoint.post("execute", "/").setUrlParams(ExecuteParams).addSuccess(Schema.Unknown);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @since 0.1.0
|
|
35
|
+
* @category api
|
|
36
|
+
*/
|
|
37
|
+
export const InngestApiGroup = HttpApiGroup.make("inngest")
|
|
38
|
+
.add(IntrospectEndpoint)
|
|
39
|
+
.add(RegisterEndpoint)
|
|
40
|
+
.add(ExecuteEndpoint)
|
|
41
|
+
.addError(FunctionNotFoundError, { status: 404 })
|
|
42
|
+
.addError(InternalHandler.InvalidRequestError, { status: 400 })
|
|
43
|
+
.addError(InternalHandler.SignatureError, { status: 401 });
|
|
44
|
+
|
|
45
|
+
type InngestApiGroupType = typeof InngestApiGroup;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @since 0.1.0
|
|
49
|
+
* @category layers
|
|
50
|
+
*/
|
|
51
|
+
export const layerGroup = <ApiId extends string, Groups extends HttpApiGroup.HttpApiGroup.Any, ApiError, ApiR>(
|
|
52
|
+
api: HttpApi.HttpApi<ApiId, Groups, ApiError, ApiR>,
|
|
53
|
+
group: InngestGroup.Any,
|
|
54
|
+
): Layer.Layer<HttpApiGroup.ApiGroup<ApiId, "inngest">, never, InngestClient | HttpClient.HttpClient> => {
|
|
55
|
+
const toUrl = (req: HttpServerRequest.HttpServerRequest) =>
|
|
56
|
+
Option.match(HttpServerRequest.toURL(req), {
|
|
57
|
+
onNone: () => req.url,
|
|
58
|
+
onSome: (url) => url.toString(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return HttpApiBuilder.group(
|
|
62
|
+
api as unknown as HttpApi.HttpApi<ApiId, InngestApiGroupType, ApiError, ApiR>,
|
|
63
|
+
"inngest",
|
|
64
|
+
(handlers) =>
|
|
65
|
+
handlers
|
|
66
|
+
.handle("introspect", ({ request }) =>
|
|
67
|
+
InternalHandler.handleIntrospection(group, toUrl(request)).pipe(Effect.map((r) => r.body)),
|
|
68
|
+
)
|
|
69
|
+
.handle("register", ({ request }) =>
|
|
70
|
+
InternalHandler.handleRegistration(group, toUrl(request)).pipe(Effect.map((r) => r.body)),
|
|
71
|
+
)
|
|
72
|
+
.handleRaw("execute", ({ urlParams, request }) =>
|
|
73
|
+
InternalHandler.verifyAndParseRequestBody(request).pipe(
|
|
74
|
+
Effect.provide(SignatureLive),
|
|
75
|
+
Effect.flatMap((payload) =>
|
|
76
|
+
InternalHandler.handleExecution(group, urlParams.fnId, urlParams.stepId, payload),
|
|
77
|
+
),
|
|
78
|
+
Effect.map((r) => r.body),
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
) as Layer.Layer<HttpApiGroup.ApiGroup<ApiId, "inngest">, never, InngestClient | HttpClient.HttpClient>;
|
|
82
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module provides types and functions for defining Inngest functions.
|
|
3
|
+
*
|
|
4
|
+
* Functions are the core building blocks of Inngest applications. Each function
|
|
5
|
+
* defines what events trigger it, what it returns on success, and optional
|
|
6
|
+
* configuration like retries, concurrency limits, rate limiting, and more.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { Effect, Schema } from "effect"
|
|
11
|
+
* import { InngestFunction, InngestGroup } from "effect-inngest"
|
|
12
|
+
*
|
|
13
|
+
* // Define events as TaggedClass
|
|
14
|
+
* class UserCreated extends Schema.TaggedClass<UserCreated>()("user/created", {
|
|
15
|
+
* userId: Schema.String,
|
|
16
|
+
* email: Schema.String,
|
|
17
|
+
* }) {}
|
|
18
|
+
*
|
|
19
|
+
* // Define functions
|
|
20
|
+
* const SendWelcomeEmail = InngestFunction.make("send-welcome-email", {
|
|
21
|
+
* event: UserCreated,
|
|
22
|
+
* success: Schema.Void,
|
|
23
|
+
* retries: 3,
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* // Create a group
|
|
27
|
+
* const MyGroup = InngestGroup.make(SendWelcomeEmail)
|
|
28
|
+
*
|
|
29
|
+
* // Implement handlers
|
|
30
|
+
* const HandlersLive = MyGroup.toLayer({
|
|
31
|
+
* "send-welcome-email": (event) =>
|
|
32
|
+
* Effect.gen(function* () {
|
|
33
|
+
* yield* sendEmail(event.email)
|
|
34
|
+
* }),
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @module InngestFunction
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
export * as InngestFunction from "./Function.js";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* This module provides types and functions for grouping Inngest functions
|
|
45
|
+
* and creating handler layers.
|
|
46
|
+
*
|
|
47
|
+
* Groups aggregate functions and provide a way to define handlers as a single
|
|
48
|
+
* layer that can be composed with the rest of your Effect application.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { Effect } from "effect"
|
|
53
|
+
* import { InngestGroup } from "effect-inngest"
|
|
54
|
+
*
|
|
55
|
+
* const MyGroup = InngestGroup.make(ProcessOrder, SendNotification)
|
|
56
|
+
*
|
|
57
|
+
* const HandlersLive = MyGroup.toLayer({
|
|
58
|
+
* "process-order": (event) => Effect.succeed({ processed: true }),
|
|
59
|
+
* "send-notification": (event) => Effect.void,
|
|
60
|
+
* })
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* @module InngestGroup
|
|
64
|
+
* @since 0.1.0
|
|
65
|
+
*/
|
|
66
|
+
export * as InngestGroup from "./Group.js";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* This module provides the InngestClient service for communicating with Inngest.
|
|
70
|
+
*
|
|
71
|
+
* The client handles event sending, function registration, and API communication.
|
|
72
|
+
* It supports both development (local dev server) and cloud modes, with automatic
|
|
73
|
+
* mode detection from environment variables.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { Effect } from "effect"
|
|
78
|
+
* import { HttpClient } from "@effect/platform"
|
|
79
|
+
* import { InngestClient } from "effect-inngest"
|
|
80
|
+
*
|
|
81
|
+
* // Create a layer with explicit config
|
|
82
|
+
* const ClientLive = InngestClient.layer({
|
|
83
|
+
* id: "my-app",
|
|
84
|
+
* eventKey: "my-event-key",
|
|
85
|
+
* })
|
|
86
|
+
*
|
|
87
|
+
* // Or use environment variables
|
|
88
|
+
* const ClientFromEnv = InngestClient.layerFromEnv
|
|
89
|
+
*
|
|
90
|
+
* // Use in your program
|
|
91
|
+
* const program = Effect.gen(function* () {
|
|
92
|
+
* const client = yield* InngestClient.InngestClientTag
|
|
93
|
+
* yield* client.sendEvent([{ name: "user/created", data: { userId: "123" } }])
|
|
94
|
+
* })
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @module InngestClient
|
|
98
|
+
* @since 0.1.0
|
|
99
|
+
*/
|
|
100
|
+
export * as InngestClient from "./Client.js";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* This module provides integration with `@effect/platform` HttpApi.
|
|
104
|
+
*
|
|
105
|
+
* Use `InngestHttpApi.InngestApiGroup` to add Inngest endpoints to your HttpApi,
|
|
106
|
+
* then use `InngestHttpApi.layerGroup` to implement the handlers.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* import { HttpApi, HttpApiBuilder } from "@effect/platform"
|
|
111
|
+
* import { InngestHttpApi, InngestGroup, InngestClient } from "effect-inngest"
|
|
112
|
+
* import { Layer } from "effect"
|
|
113
|
+
*
|
|
114
|
+
* // Add InngestApiGroup to your HttpApi
|
|
115
|
+
* class MyApi extends HttpApi.make("my-api")
|
|
116
|
+
* .add(InngestHttpApi.InngestApiGroup.prefix("/api/inngest")) {}
|
|
117
|
+
*
|
|
118
|
+
* // Create the implementation layer
|
|
119
|
+
* const InngestLayer = InngestHttpApi.layerGroup(MyApi, MyFunctions)
|
|
120
|
+
*
|
|
121
|
+
* // Wire it up with HttpApiBuilder
|
|
122
|
+
* const ApiLive = HttpApiBuilder.api(MyApi).pipe(
|
|
123
|
+
* Layer.provide(InngestLayer),
|
|
124
|
+
* Layer.provide(FunctionsLayer),
|
|
125
|
+
* Layer.provide(InngestClient.layer({ id: "my-app" })),
|
|
126
|
+
* )
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* @module InngestHttpApi
|
|
130
|
+
* @since 0.1.0
|
|
131
|
+
*/
|
|
132
|
+
export * as InngestHttpApi from "./HttpApi.js";
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Internal Inngest events that the platform sends automatically.
|
|
136
|
+
* Use these as triggers to react to function lifecycle events.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* import { Effect } from "effect"
|
|
141
|
+
* import { InngestFunction, InngestEvents } from "effect-inngest"
|
|
142
|
+
*
|
|
143
|
+
* // React to function failures
|
|
144
|
+
* const HandleFailure = InngestFunction.make("handle-failure", {
|
|
145
|
+
* trigger: { event: InngestEvents.FunctionFailed },
|
|
146
|
+
* success: Schema.Void,
|
|
147
|
+
* })
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @module InngestEvents
|
|
151
|
+
* @since 0.1.0
|
|
152
|
+
*/
|
|
153
|
+
export * as InngestEvents from "./Events.js";
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Error thrown to indicate that an operation should not be retried.
|
|
157
|
+
* Use this when you know retrying won't help (e.g., validation errors, auth failures).
|
|
158
|
+
*
|
|
159
|
+
* @since 0.1.0
|
|
160
|
+
* @category errors
|
|
161
|
+
*/
|
|
162
|
+
export { NonRetriableError } from "./internal/errors.js";
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Error thrown to indicate that an operation should be retried after a specific delay.
|
|
166
|
+
* Use this for rate limiting or when you know when a resource will become available.
|
|
167
|
+
*
|
|
168
|
+
* @since 0.1.0
|
|
169
|
+
* @category errors
|
|
170
|
+
*/
|
|
171
|
+
export { RetryAfterError } from "./internal/errors.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const OtelAttributes = {
|
|
2
|
+
RunId: "inngest.run_id",
|
|
3
|
+
FunctionId: "inngest.function_id",
|
|
4
|
+
AppId: "inngest.app_id",
|
|
5
|
+
Attempt: "inngest.attempt",
|
|
6
|
+
StepId: "inngest.step.id",
|
|
7
|
+
StepType: "inngest.step.type",
|
|
8
|
+
ExceptionType: "exception.type",
|
|
9
|
+
ExceptionMessage: "exception.message",
|
|
10
|
+
ExceptionStacktrace: "exception.stacktrace",
|
|
11
|
+
} as const;
|