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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +457 -0
  3. package/dist/Client.d.ts +167 -0
  4. package/dist/Client.js +144 -0
  5. package/dist/Events.d.ts +110 -0
  6. package/dist/Events.js +93 -0
  7. package/dist/Function.d.ts +384 -0
  8. package/dist/Function.js +104 -0
  9. package/dist/Group.d.ts +152 -0
  10. package/dist/Group.js +164 -0
  11. package/dist/HttpApi.d.ts +75 -0
  12. package/dist/HttpApi.js +47 -0
  13. package/dist/_virtual/rolldown_runtime.js +18 -0
  14. package/dist/index.d.ts +7 -0
  15. package/dist/index.js +8 -0
  16. package/dist/internal/constants.js +15 -0
  17. package/dist/internal/driver.d.ts +5 -0
  18. package/dist/internal/driver.js +117 -0
  19. package/dist/internal/errors.d.ts +56 -0
  20. package/dist/internal/errors.js +61 -0
  21. package/dist/internal/handler.d.ts +20 -0
  22. package/dist/internal/handler.js +145 -0
  23. package/dist/internal/helpers.js +44 -0
  24. package/dist/internal/interrupts.d.ts +2 -0
  25. package/dist/internal/interrupts.js +45 -0
  26. package/dist/internal/memo.js +56 -0
  27. package/dist/internal/protocol.d.ts +1 -0
  28. package/dist/internal/protocol.js +191 -0
  29. package/dist/internal/signature.d.ts +18 -0
  30. package/dist/internal/signature.js +97 -0
  31. package/dist/internal/step.d.ts +59 -0
  32. package/dist/internal/step.js +183 -0
  33. package/package.json +121 -0
  34. package/src/Client.ts +279 -0
  35. package/src/Events.ts +87 -0
  36. package/src/Function.ts +493 -0
  37. package/src/Group.ts +314 -0
  38. package/src/HttpApi.ts +82 -0
  39. package/src/index.ts +171 -0
  40. package/src/internal/constants.ts +11 -0
  41. package/src/internal/driver.ts +194 -0
  42. package/src/internal/errors.ts +130 -0
  43. package/src/internal/handler.ts +222 -0
  44. package/src/internal/helpers.ts +58 -0
  45. package/src/internal/interrupts.ts +62 -0
  46. package/src/internal/memo.ts +73 -0
  47. package/src/internal/protocol.ts +218 -0
  48. package/src/internal/signature.ts +158 -0
  49. package/src/internal/step.ts +377 -0
@@ -0,0 +1,145 @@
1
+ import { Headers, RegisterResponse, SDKRequestBody, SDKRequestContext, UserError } from "./protocol.js";
2
+ import { InngestClient } from "../Client.js";
3
+ import { Signature, SignatureError } from "./signature.js";
4
+ import { execute } from "./driver.js";
5
+ import * as HttpClient from "@effect/platform/HttpClient";
6
+ import "@effect/platform/HttpServerRequest";
7
+ import "effect/Context";
8
+ import * as Effect from "effect/Effect";
9
+ import * as Schema from "effect/Schema";
10
+ import * as HttpClientRequest from "@effect/platform/HttpClientRequest";
11
+ import * as HttpClientResponse from "@effect/platform/HttpClientResponse";
12
+ import * as Predicate from "effect/Predicate";
13
+
14
+ //#region src/internal/handler.ts
15
+ /**
16
+ * Internal handler implementation.
17
+ * @internal
18
+ */
19
+ var InvalidRequestError = class extends Schema.TaggedError()("InvalidRequestError", { message: Schema.String }) {};
20
+ const SDK_VERSION = "2.0.0";
21
+ const baseHeaders = () => ({
22
+ "Content-Type": "application/json",
23
+ [Headers.SDK]: `effect-inngest:v${SDK_VERSION}`,
24
+ [Headers.RequestVersion]: "1"
25
+ });
26
+ const buildServeUrl = (requestUrl, serveHost, servePath) => {
27
+ const url = new URL(requestUrl);
28
+ if (servePath) url.pathname = servePath;
29
+ if (serveHost) return new URL(url.pathname + url.search, serveHost);
30
+ return url;
31
+ };
32
+ const verifyAndParseRequestBody = (request) => Effect.gen(function* () {
33
+ const client = yield* InngestClient;
34
+ const sig = yield* Signature;
35
+ const config = client.config;
36
+ const isDev = client.mode === "dev";
37
+ const bodyText = yield* request.text.pipe(Effect.mapError((error) => new InvalidRequestError({ message: `Failed to read request body: ${String(error)}` })));
38
+ yield* sig.verify({
39
+ body: new TextEncoder().encode(bodyText),
40
+ signatureHeader: request.headers[Headers.Signature.toLowerCase()],
41
+ signingKey: config.signingKey,
42
+ signingKeyFallback: config.signingKeyFallback,
43
+ isDev
44
+ });
45
+ return yield* Schema.decodeUnknown(Schema.parseJson(SDKRequestBody))(bodyText).pipe(Effect.mapError((error) => new InvalidRequestError({ message: `Invalid request body: ${String(error)}` })));
46
+ });
47
+ const handleIntrospection = (group, _requestUrl) => Effect.gen(function* () {
48
+ const client = yield* InngestClient;
49
+ const config = client.config;
50
+ const body = {
51
+ function_count: group.functions.size,
52
+ has_event_key: config.eventKey !== void 0,
53
+ has_signing_key: config.signingKey !== void 0,
54
+ has_signing_key_fallback: config.signingKeyFallback !== void 0,
55
+ mode: client.mode === "dev" ? "dev" : "cloud",
56
+ schema_version: "2024-05-24",
57
+ authentication_succeeded: null
58
+ };
59
+ return {
60
+ status: 200,
61
+ headers: baseHeaders(),
62
+ body
63
+ };
64
+ });
65
+ const RegisterRequest = Schema.Struct({
66
+ url: Schema.String,
67
+ v: Schema.String,
68
+ deployType: Schema.Literal("ping"),
69
+ sdk: Schema.String,
70
+ appName: Schema.String,
71
+ framework: Schema.String,
72
+ functions: Schema.Array(Schema.Unknown)
73
+ });
74
+ const handleRegistration = (group, requestUrl) => Effect.gen(function* () {
75
+ const client = yield* InngestClient;
76
+ const httpClient = yield* HttpClient.HttpClient;
77
+ const config = client.config;
78
+ const url = buildServeUrl(requestUrl, config.serveHost, config.servePath);
79
+ const functions = Array.from(group.functions.values()).map((fn) => fn.toRegistration({
80
+ appId: config.id,
81
+ url: url.href
82
+ }));
83
+ const registerUrl = new URL("fn/register", client.apiBaseUrl).toString();
84
+ const response = yield* HttpClientRequest.post(registerUrl).pipe(HttpClientRequest.setHeaders({
85
+ ...baseHeaders(),
86
+ Authorization: `Bearer ${config.signingKey ?? ""}`
87
+ }), HttpClientRequest.schemaBodyJson(RegisterRequest)({
88
+ url: url.href,
89
+ v: "0.1",
90
+ deployType: "ping",
91
+ sdk: `effect-inngest:v${SDK_VERSION}`,
92
+ appName: config.id,
93
+ framework: "effect",
94
+ functions
95
+ })).pipe(Effect.flatMap(httpClient.execute), Effect.flatMap(HttpClientResponse.schemaBodyJson(RegisterResponse)), Effect.scoped, Effect.catchAll((error) => Effect.succeed({ message: `Registration failed: ${Predicate.hasProperty(error, "message") ? error.message : "Unknown error"}` })));
96
+ return {
97
+ status: 200,
98
+ headers: baseHeaders(),
99
+ body: response
100
+ };
101
+ });
102
+ const handleExecution = (group, fnId, urlStepId, body, traceHeaders = {}) => Effect.gen(function* () {
103
+ const client = yield* InngestClient;
104
+ const context = yield* Effect.context();
105
+ const appId = client.config.id;
106
+ const prefix = `${appId}-`;
107
+ const fnTag = fnId.startsWith(prefix) ? fnId.slice(prefix.length) : fnId;
108
+ const fn = group.functions.get(fnTag);
109
+ const entry = fn ? context.unsafeMap.get(fn.key) : void 0;
110
+ if (!fn || !entry) return {
111
+ status: 404,
112
+ headers: {
113
+ ...baseHeaders(),
114
+ [Headers.NoRetry]: "true"
115
+ },
116
+ body: { error: UserError.make({
117
+ name: "FunctionNotFoundError",
118
+ message: `Unknown function: ${fnId}`
119
+ }) }
120
+ };
121
+ const effectiveBody = urlStepId && urlStepId !== body.ctx.step_id ? SDKRequestBody.make({
122
+ event: body.event,
123
+ events: body.events,
124
+ steps: body.steps,
125
+ ctx: SDKRequestContext.make({
126
+ fn_id: body.ctx.fn_id,
127
+ run_id: body.ctx.run_id,
128
+ step_id: urlStepId,
129
+ attempt: body.ctx.attempt,
130
+ disable_immediate_execution: body.ctx.disable_immediate_execution,
131
+ use_api: body.ctx.use_api,
132
+ stack: body.ctx.stack
133
+ }),
134
+ use_api: body.use_api
135
+ }) : body;
136
+ const result = yield* Effect.provide(execute(fn, entry.handler, effectiveBody, appId, traceHeaders), entry.context);
137
+ return {
138
+ status: result.status,
139
+ headers: result.headers,
140
+ body: result.body
141
+ };
142
+ });
143
+
144
+ //#endregion
145
+ export { InvalidRequestError, handleExecution, handleIntrospection, handleRegistration, verifyAndParseRequestBody };
@@ -0,0 +1,44 @@
1
+ import * as Duration from "effect/Duration";
2
+
3
+ //#region src/internal/helpers.ts
4
+ /**
5
+ * Internal helper utilities.
6
+ * @internal
7
+ */
8
+ const second = 1 * 1e3;
9
+ const minute = second * 60;
10
+ const hour = minute * 60;
11
+ const day = hour * 24;
12
+ const periods = [
13
+ ["w", day * 7],
14
+ ["d", day],
15
+ ["h", hour],
16
+ ["m", minute],
17
+ ["s", second]
18
+ ];
19
+ /**
20
+ * Convert a Duration to an Inngest-compatible time string (e.g. `"1d"` or `"2h30m"`).
21
+ *
22
+ * Supports weeks, days, hours, minutes, and seconds. Years/months are converted
23
+ * to their equivalent in weeks/days.
24
+ */
25
+ const timeStr = (input) => {
26
+ let ms = Duration.toMillis(Duration.decode(input));
27
+ const [, result] = periods.reduce(([num, str], [suffix, period]) => {
28
+ const numPeriods = Math.floor(num / period);
29
+ if (numPeriods > 0) return [num % period, `${str}${numPeriods}${suffix}`];
30
+ return [num, str];
31
+ }, [ms, ""]);
32
+ return result || "0s";
33
+ };
34
+ /**
35
+ * Format a timestamp as an ISO string for Inngest's sleepUntil.
36
+ */
37
+ const formatTimestamp = (timestamp) => {
38
+ if (timestamp instanceof Date) return timestamp.toISOString();
39
+ if (typeof timestamp === "number") return new Date(timestamp).toISOString();
40
+ return timestamp;
41
+ };
42
+
43
+ //#endregion
44
+ export { formatTimestamp, timeStr };
@@ -0,0 +1,2 @@
1
+ import "./protocol.js";
2
+ import "effect/Schema";
@@ -0,0 +1,45 @@
1
+ import { GeneratorOpcode, UserError, invokeFunction, sleep, stepError, stepPlanned, stepRun, waitForEvent } from "./protocol.js";
2
+ import * as Schema from "effect/Schema";
3
+ import * as Predicate from "effect/Predicate";
4
+
5
+ //#region src/internal/interrupts.ts
6
+ /**
7
+ * StepInterrupt schema and factory functions.
8
+ * @internal
9
+ */
10
+ /** @internal */
11
+ var StepInterrupt = class extends Schema.TaggedClass()("StepInterrupt", {
12
+ opcode: GeneratorOpcode,
13
+ retryAfterMs: Schema.optional(Schema.Number)
14
+ }) {};
15
+ const toUserError = (error) => UserError.make({
16
+ name: Predicate.hasProperty(error, "name") ? String(error.name) : "Error",
17
+ message: Predicate.hasProperty(error, "message") ? String(error.message) : String(error),
18
+ stack: Predicate.hasProperty(error, "stack") ? String(error.stack) : void 0
19
+ });
20
+ /** @internal */
21
+ const sleepInterrupt = (opts) => StepInterrupt.make({ opcode: sleep(opts.info, opts.duration) });
22
+ /** @internal */
23
+ const waitForEventInterrupt = (opts) => StepInterrupt.make({ opcode: waitForEvent(opts.info, {
24
+ event: opts.event,
25
+ timeout: opts.timeout,
26
+ if: opts.if
27
+ }) });
28
+ /** @internal */
29
+ const invokeInterrupt = (opts) => StepInterrupt.make({ opcode: invokeFunction(opts.info, {
30
+ function_id: opts.functionId,
31
+ payload: opts.payload,
32
+ timeout: opts.timeout
33
+ }) });
34
+ /** @internal */
35
+ const plannedInterrupt = (opts) => StepInterrupt.make({ opcode: stepPlanned(opts.info) });
36
+ /** @internal */
37
+ const runInterrupt = (opts) => StepInterrupt.make({ opcode: stepRun(opts.info, opts.data) });
38
+ /** @internal */
39
+ const errorInterrupt = (opts) => StepInterrupt.make({
40
+ opcode: stepError(opts.info, toUserError(opts.error), opts.noRetry),
41
+ retryAfterMs: opts.retryAfterMs
42
+ });
43
+
44
+ //#endregion
45
+ export { StepInterrupt, errorInterrupt, invokeInterrupt, plannedInterrupt, runInterrupt, sleepInterrupt, waitForEventInterrupt };
@@ -0,0 +1,56 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as Predicate from "effect/Predicate";
3
+
4
+ //#region src/internal/memo.ts
5
+ /**
6
+ * Step memoization schemas for decoding cached step results.
7
+ * @internal
8
+ */
9
+ const hasKey = (key) => (u) => Predicate.isRecord(u) && Predicate.hasProperty(u, key);
10
+ const MemoDataSchema = Schema.TaggedStruct("MemoData", { data: Schema.Unknown });
11
+ const MemoErrorSchema = Schema.TaggedStruct("MemoError", { error: Schema.Unknown });
12
+ const MemoInputSchema = Schema.TaggedStruct("MemoInput", { input: Schema.Unknown });
13
+ const MemoTimeoutSchema = Schema.TaggedStruct("MemoTimeout", {});
14
+ const MemoNoneSchema = Schema.TaggedStruct("MemoNone", {});
15
+ const DataWire = Schema.Unknown.pipe(Schema.filter(hasKey("data")), Schema.transform(MemoDataSchema, {
16
+ decode: (v) => ({
17
+ _tag: "MemoData",
18
+ data: v.data
19
+ }),
20
+ encode: ({ data }) => ({ data })
21
+ }));
22
+ const ErrorWire = Schema.Unknown.pipe(Schema.filter(hasKey("error")), Schema.transform(MemoErrorSchema, {
23
+ decode: (v) => ({
24
+ _tag: "MemoError",
25
+ error: v.error
26
+ }),
27
+ encode: ({ error }) => ({ error })
28
+ }));
29
+ const InputWire = Schema.Unknown.pipe(Schema.filter(hasKey("input")), Schema.transform(MemoInputSchema, {
30
+ decode: (v) => ({
31
+ _tag: "MemoInput",
32
+ input: v.input
33
+ }),
34
+ encode: ({ input }) => ({ input })
35
+ }));
36
+ const TimeoutWire = Schema.transform(Schema.Null, MemoTimeoutSchema, {
37
+ decode: () => ({ _tag: "MemoTimeout" }),
38
+ encode: () => null
39
+ });
40
+ const NoneWire = Schema.transform(Schema.Undefined, MemoNoneSchema, {
41
+ decode: () => ({ _tag: "MemoNone" }),
42
+ encode: () => void 0
43
+ });
44
+ const MemoSchema = Schema.Union(ErrorWire, InputWire, DataWire, TimeoutWire, NoneWire);
45
+ /**
46
+ * Decode a step result into a Memo type.
47
+ * Order matters: error > input > data (more specific properties first).
48
+ */
49
+ const decodeMemo = (value) => {
50
+ const result = Schema.decodeUnknownOption(MemoSchema)(value);
51
+ if (result._tag === "Some") return result.value;
52
+ return MemoNoneSchema.make();
53
+ };
54
+
55
+ //#endregion
56
+ export { decodeMemo };
@@ -0,0 +1 @@
1
+ import "effect/Schema";
@@ -0,0 +1,191 @@
1
+ import { Predicate, Struct } from "effect";
2
+ import * as Schema$1 from "effect/Schema";
3
+
4
+ //#region src/internal/protocol.ts
5
+ /**
6
+ * Wire protocol schemas and opcode factories for Inngest communication.
7
+ * @internal
8
+ */
9
+ const stripTags = (value) => {
10
+ if (Predicate.isRecord(value)) {
11
+ const stripped = Struct.omit(value, "_tag");
12
+ return Object.fromEntries(Object.entries(stripped).map(([k, v]) => [k, stripTags(v)]));
13
+ }
14
+ if (Array.isArray(value)) return value.map(stripTags);
15
+ return value;
16
+ };
17
+ const WireUnknown = Schema$1.transform(Schema$1.Unknown, Schema$1.Unknown, {
18
+ strict: true,
19
+ decode: (value) => value,
20
+ encode: (value) => stripTags(value)
21
+ });
22
+ const Opcode = {
23
+ None: "None",
24
+ Step: "Step",
25
+ StepRun: "StepRun",
26
+ StepError: "StepError",
27
+ StepPlanned: "StepPlanned",
28
+ Sleep: "Sleep",
29
+ WaitForEvent: "WaitForEvent",
30
+ InvokeFunction: "InvokeFunction",
31
+ AIGateway: "AIGateway",
32
+ Gateway: "Gateway",
33
+ WaitForSignal: "WaitForSignal",
34
+ RunComplete: "RunComplete",
35
+ StepFailed: "StepFailed",
36
+ SyncRunComplete: "SyncRunComplete",
37
+ DiscoveryRequest: "DiscoveryRequest"
38
+ };
39
+ var UserError = class extends Schema$1.Class("UserError")({
40
+ name: Schema$1.String,
41
+ message: Schema$1.String,
42
+ stack: Schema$1.optional(Schema$1.String),
43
+ data: Schema$1.optional(Schema$1.Unknown),
44
+ noRetry: Schema$1.optional(Schema$1.Boolean),
45
+ cause: Schema$1.optional(Schema$1.Unknown)
46
+ }) {};
47
+ const StepResult = Schema$1.NullOr(Schema$1.Record({
48
+ key: Schema$1.String,
49
+ value: Schema$1.Unknown
50
+ }).pipe(Schema$1.annotations({ identifier: "StepResultObject" })));
51
+ var FunctionStack = class extends Schema$1.Class("FunctionStack")({
52
+ stack: Schema$1.Array(Schema$1.String),
53
+ current: Schema$1.Number
54
+ }) {};
55
+ var InngestEvent = class extends Schema$1.Class("InngestEvent")({
56
+ id: Schema$1.optional(Schema$1.String),
57
+ name: Schema$1.String,
58
+ data: Schema$1.optionalWith(Schema$1.Record({
59
+ key: Schema$1.String,
60
+ value: Schema$1.Unknown
61
+ }), {
62
+ default: () => ({}),
63
+ nullable: true
64
+ }),
65
+ ts: Schema$1.optional(Schema$1.Number),
66
+ user: Schema$1.optional(Schema$1.Record({
67
+ key: Schema$1.String,
68
+ value: Schema$1.Unknown
69
+ })),
70
+ v: Schema$1.optional(Schema$1.String)
71
+ }) {};
72
+ var SDKRequestContext = class extends Schema$1.Class("SDKRequestContext")({
73
+ fn_id: Schema$1.String,
74
+ run_id: Schema$1.String,
75
+ env: Schema$1.optionalWith(Schema$1.String, { default: () => "dev" }),
76
+ step_id: Schema$1.optionalWith(Schema$1.String, { default: () => "step" }),
77
+ attempt: Schema$1.optionalWith(Schema$1.Number, { default: () => 0 }),
78
+ max_attempts: Schema$1.optionalWith(Schema$1.Number, { default: () => 4 }),
79
+ stack: Schema$1.optionalWith(FunctionStack, { default: () => FunctionStack.make({
80
+ stack: [],
81
+ current: 0
82
+ }) }),
83
+ qi_id: Schema$1.optionalWith(Schema$1.String, { default: () => "" }),
84
+ disable_immediate_execution: Schema$1.optionalWith(Schema$1.Boolean, { default: () => false }),
85
+ use_api: Schema$1.optionalWith(Schema$1.Boolean, { default: () => false })
86
+ }) {};
87
+ var SDKRequestBody = class extends Schema$1.Class("SDKRequestBody")({
88
+ event: InngestEvent,
89
+ events: Schema$1.Array(InngestEvent),
90
+ steps: Schema$1.optionalWith(Schema$1.Record({
91
+ key: Schema$1.String,
92
+ value: StepResult
93
+ }), { default: () => ({}) }),
94
+ ctx: SDKRequestContext,
95
+ version: Schema$1.optionalWith(Schema$1.Number, { default: () => 1 }),
96
+ use_api: Schema$1.optionalWith(Schema$1.Boolean, { default: () => false })
97
+ }) {};
98
+ const Headers = {
99
+ SDK: "X-Inngest-SDK",
100
+ Signature: "X-Inngest-Signature",
101
+ RequestVersion: "x-inngest-req-version",
102
+ NoRetry: "X-Inngest-No-Retry",
103
+ RetryAfter: "Retry-After",
104
+ ServerKind: "X-Inngest-Server-Kind",
105
+ ExpectedServerKind: "X-Inngest-Expected-Server-Kind",
106
+ RunID: "X-Run-ID",
107
+ Framework: "X-Inngest-Framework",
108
+ Platform: "X-Inngest-Platform",
109
+ Env: "X-Inngest-Env"
110
+ };
111
+ var GeneratorOpcode = class extends Schema$1.Class("GeneratorOpcode")({
112
+ op: Schema$1.Literal(Opcode.None, Opcode.Step, Opcode.StepRun, Opcode.StepError, Opcode.StepPlanned, Opcode.Sleep, Opcode.WaitForEvent, Opcode.InvokeFunction, Opcode.AIGateway, Opcode.Gateway, Opcode.WaitForSignal, Opcode.RunComplete, Opcode.StepFailed, Opcode.SyncRunComplete, Opcode.DiscoveryRequest),
113
+ id: Schema$1.String,
114
+ name: Schema$1.String,
115
+ mode: Schema$1.optional(Schema$1.Literal("sync", "async")),
116
+ opts: Schema$1.optional(WireUnknown),
117
+ data: Schema$1.optional(WireUnknown),
118
+ error: Schema$1.optional(UserError),
119
+ displayName: Schema$1.optional(Schema$1.String),
120
+ userland: Schema$1.optional(Schema$1.Struct({ id: Schema$1.String }))
121
+ }) {};
122
+ const mkOpcode = (info, op, extra) => GeneratorOpcode.make({
123
+ op,
124
+ id: info.hash,
125
+ name: info.id,
126
+ displayName: info.name,
127
+ ...extra
128
+ });
129
+ const stepPlanned = (info) => mkOpcode(info, Opcode.StepPlanned);
130
+ const stepRun = (info, data) => mkOpcode(info, Opcode.StepRun, { data });
131
+ const stepError = (info, error, noRetry) => {
132
+ const errorWithNoRetry = noRetry !== void 0 ? UserError.make({
133
+ name: error.name,
134
+ message: error.message,
135
+ stack: error.stack,
136
+ noRetry
137
+ }) : error;
138
+ return mkOpcode(info, Opcode.StepError, { error: errorWithNoRetry });
139
+ };
140
+ const sleep = (info, duration) => GeneratorOpcode.make({
141
+ op: Opcode.Sleep,
142
+ id: info.hash,
143
+ name: duration,
144
+ displayName: info.name,
145
+ mode: "async"
146
+ });
147
+ const waitForEvent = (info, opts) => mkOpcode(info, Opcode.WaitForEvent, {
148
+ mode: "async",
149
+ opts
150
+ });
151
+ const invokeFunction = (info, opts) => mkOpcode(info, Opcode.InvokeFunction, {
152
+ mode: "async",
153
+ opts,
154
+ userland: { id: info.id }
155
+ });
156
+ const IntrospectionBase = Schema$1.Struct({
157
+ function_count: Schema$1.Number,
158
+ has_event_key: Schema$1.Boolean,
159
+ has_signing_key: Schema$1.Boolean,
160
+ has_signing_key_fallback: Schema$1.Boolean,
161
+ mode: Schema$1.Literal("cloud", "dev"),
162
+ schema_version: Schema$1.Literal("2024-05-24"),
163
+ extra: Schema$1.optional(Schema$1.Record({
164
+ key: Schema$1.String,
165
+ value: Schema$1.Unknown
166
+ }))
167
+ });
168
+ const IntrospectionUnauthenticated = Schema$1.extend(IntrospectionBase, Schema$1.Struct({
169
+ authentication_succeeded: Schema$1.Union(Schema$1.Literal(false), Schema$1.Null),
170
+ functions: Schema$1.optionalWith(Schema$1.Array(Schema$1.Unknown), { exact: true })
171
+ }));
172
+ const IntrospectionAuthenticated = Schema$1.extend(IntrospectionBase, Schema$1.Struct({
173
+ authentication_succeeded: Schema$1.Literal(true),
174
+ api_origin: Schema$1.String,
175
+ app_id: Schema$1.String,
176
+ env: Schema$1.NullOr(Schema$1.String),
177
+ event_api_origin: Schema$1.String,
178
+ event_key_hash: Schema$1.NullOr(Schema$1.String),
179
+ framework: Schema$1.String,
180
+ sdk_language: Schema$1.String,
181
+ sdk_version: Schema$1.String,
182
+ serve_origin: Schema$1.NullOr(Schema$1.String),
183
+ serve_path: Schema$1.NullOr(Schema$1.String),
184
+ signing_key_fallback_hash: Schema$1.NullOr(Schema$1.String),
185
+ signing_key_hash: Schema$1.NullOr(Schema$1.String)
186
+ }));
187
+ const IntrospectionResponse = Schema$1.Union(IntrospectionAuthenticated, IntrospectionUnauthenticated);
188
+ const RegisterResponse = Schema$1.Struct({ message: Schema$1.optional(Schema$1.String) });
189
+
190
+ //#endregion
191
+ export { GeneratorOpcode, Headers, IntrospectionResponse, Opcode, RegisterResponse, SDKRequestBody, SDKRequestContext, UserError, invokeFunction, sleep, stepError, stepPlanned, stepRun, waitForEvent };
@@ -0,0 +1,18 @@
1
+ import "effect/Context";
2
+ import "effect/Effect";
3
+ import "effect/Layer";
4
+ import * as Schema from "effect/Schema";
5
+
6
+ //#region src/internal/signature.d.ts
7
+ declare const SignatureError_base: Schema.TaggedErrorClass<SignatureError, "SignatureError", {
8
+ readonly _tag: Schema.tag<"SignatureError">;
9
+ } & {
10
+ reason: Schema.Literal<["missing_header", "invalid_format", "expired", "invalid_signature", "missing_signing_key"]>;
11
+ message: typeof Schema.String;
12
+ }>;
13
+ /**
14
+ * @internal
15
+ */
16
+ declare class SignatureError extends SignatureError_base {}
17
+ //#endregion
18
+ export { SignatureError };
@@ -0,0 +1,97 @@
1
+ import { Headers } from "./protocol.js";
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 Crypto from "node:crypto";
7
+ import * as DateTime from "effect/DateTime";
8
+
9
+ //#region src/internal/signature.ts
10
+ /**
11
+ * Signature verification service for Inngest requests.
12
+ * @internal
13
+ */
14
+ /**
15
+ * @internal
16
+ */
17
+ var SignatureError = class extends Schema.TaggedError()("SignatureError", {
18
+ reason: Schema.Literal("missing_header", "invalid_format", "expired", "invalid_signature", "missing_signing_key"),
19
+ message: Schema.String
20
+ }) {};
21
+ /**
22
+ * @internal
23
+ */
24
+ var Signature = class extends Context.Tag("effect-inngest/Signature")() {};
25
+ const TimestampSeconds = Schema.NumberFromString.pipe(Schema.int(), Schema.positive());
26
+ const SignatureHex = Schema.String.pipe(Schema.pattern(/^[a-fA-F0-9]{64}$/), Schema.transform(Schema.String, {
27
+ decode: (s) => s.toLowerCase(),
28
+ encode: (s) => s
29
+ }));
30
+ const SignatureParams = Schema.Struct({
31
+ t: TimestampSeconds,
32
+ s: SignatureHex
33
+ });
34
+ const SIGNATURE_VALIDITY_WINDOW_MS = 300 * 1e3;
35
+ const parseSignatureHeader = (header) => {
36
+ const params = new URLSearchParams(header);
37
+ const raw = {
38
+ t: params.get("t") ?? "",
39
+ s: params.get("s") ?? ""
40
+ };
41
+ return Schema.decodeUnknown(SignatureParams)(raw).pipe(Effect.mapError(() => new SignatureError({
42
+ reason: "invalid_format",
43
+ message: `Invalid signature format: expected t=<int>&s=<64-hex>, got: ${header}`
44
+ })));
45
+ };
46
+ const extractKeyBytes = (signingKey) => {
47
+ const keyWithoutPrefix = signingKey.replace(/^signkey-\w+-/, "");
48
+ return Buffer.from(keyWithoutPrefix, "hex");
49
+ };
50
+ const computeSignature = (keyBytes, body, timestamp) => Crypto.createHmac("sha256", keyBytes).update(body).update(timestamp).digest("hex");
51
+ const timingSafeEqual = (a, b) => {
52
+ if (a.length !== b.length) return false;
53
+ try {
54
+ return Crypto.timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
55
+ } catch {
56
+ return false;
57
+ }
58
+ };
59
+ const checkSignature = (signature, body, timestamp, signingKey) => {
60
+ return timingSafeEqual(signature, computeSignature(extractKeyBytes(signingKey), body, timestamp));
61
+ };
62
+ /**
63
+ * @internal
64
+ */
65
+ const SignatureLive = Layer.effect(Signature, Effect.succeed({
66
+ verify: ({ body, signatureHeader, signingKey, signingKeyFallback, isDev }) => Effect.gen(function* () {
67
+ if (isDev) return true;
68
+ if (!signingKey) return yield* new SignatureError({
69
+ reason: "missing_signing_key",
70
+ message: "No signing key configured for production mode"
71
+ });
72
+ if (!signatureHeader) return yield* new SignatureError({
73
+ reason: "missing_header",
74
+ message: `Missing ${Headers.Signature} header`
75
+ });
76
+ const { t: timestampSeconds, s: signature } = yield* parseSignatureHeader(signatureHeader);
77
+ const timestampMs = timestampSeconds * 1e3;
78
+ const now = yield* DateTime.now;
79
+ if (Math.abs(now.epochMillis - timestampMs) > SIGNATURE_VALIDITY_WINDOW_MS) return yield* new SignatureError({
80
+ reason: "expired",
81
+ message: `Signature expired: timestamp ${timestampSeconds} is outside the validity window`
82
+ });
83
+ const timestamp = String(timestampSeconds);
84
+ if ([signingKey, signingKeyFallback].filter(Boolean).some((key) => checkSignature(signature, body, timestamp, key))) return true;
85
+ return yield* new SignatureError({
86
+ reason: "invalid_signature",
87
+ message: "Invalid signature"
88
+ });
89
+ }),
90
+ sign: (body, signingKey) => DateTime.now.pipe(Effect.map((now) => {
91
+ const ts = Math.floor(now.epochMillis / 1e3);
92
+ return `t=${ts}&s=${computeSignature(extractKeyBytes(signingKey), body, String(ts))}`;
93
+ }))
94
+ }));
95
+
96
+ //#endregion
97
+ export { Signature, SignatureError, SignatureLive };
@@ -0,0 +1,59 @@
1
+ import { InngestFunction } from "../Function.js";
2
+ import "./protocol.js";
3
+ import { SendEventError, StepError } from "./errors.js";
4
+ import "./interrupts.js";
5
+ import * as Duration from "effect/Duration";
6
+ import "effect/Context";
7
+ import * as Effect from "effect/Effect";
8
+ import * as Option from "effect/Option";
9
+ import * as Schema from "effect/Schema";
10
+ import "effect/HashMap";
11
+ import "effect/Ref";
12
+
13
+ //#region src/internal/step.d.ts
14
+ interface StepOptions {
15
+ readonly id: string;
16
+ readonly name?: string;
17
+ }
18
+ type StepOptionsOrId = string | StepOptions;
19
+ interface WaitForEventOptions {
20
+ readonly timeout: Duration.DurationInput;
21
+ readonly if?: string;
22
+ }
23
+ interface InvokeOptionsBase<F extends InngestFunction.Any> {
24
+ readonly function: F;
25
+ readonly user?: Record<string, unknown>;
26
+ readonly v?: string;
27
+ readonly timeout?: Duration.DurationInput;
28
+ }
29
+ type InvokeOptions<F extends InngestFunction.Any> = [InngestFunction.EventType<F>] extends [never] ? InvokeOptionsBase<F> : InvokeOptionsBase<F> & {
30
+ readonly data: InngestFunction.EventType<F>;
31
+ };
32
+ type EventSchema = Schema.Schema.Any & {
33
+ readonly _tag: string;
34
+ };
35
+ type TaggedEvent = {
36
+ readonly _tag: string;
37
+ };
38
+ interface StepTools {
39
+ readonly run: <A, Err, R>(id: StepOptionsOrId, effect: Effect.Effect<A, Err, R>) => Effect.Effect<A, StepError | Err, R>;
40
+ readonly sleep: (id: StepOptionsOrId, duration: Duration.DurationInput) => Effect.Effect<void>;
41
+ readonly sleepUntil: (id: StepOptionsOrId, timestamp: Date | number | string) => Effect.Effect<void>;
42
+ readonly waitForEvent: <E extends EventSchema>(id: StepOptionsOrId, event: E, options: WaitForEventOptions) => Effect.Effect<Option.Option<Schema.Schema.Type<E>>>;
43
+ readonly invoke: <F extends InngestFunction.Any>(id: StepOptionsOrId, options: InvokeOptions<F>) => Effect.Effect<InngestFunction.Success<F>, StepError>;
44
+ readonly sendEvent: (id: StepOptionsOrId, payload: TaggedEvent | ReadonlyArray<TaggedEvent>) => Effect.Effect<{
45
+ readonly ids: ReadonlyArray<string>;
46
+ }, SendEventError>;
47
+ }
48
+ interface RunContext {
49
+ readonly id: string;
50
+ readonly attempt: number;
51
+ readonly maxAttempts: number;
52
+ }
53
+ interface HandlerContext<F extends InngestFunction.Any> {
54
+ readonly event: InngestFunction.EventType<F>;
55
+ readonly step: StepTools;
56
+ readonly run: RunContext;
57
+ }
58
+ //#endregion
59
+ export { HandlerContext };