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
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal step tools implementation.
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
import * as Arr from "effect/Array";
|
|
6
|
+
import * as Context from "effect/Context";
|
|
7
|
+
import * as Duration from "effect/Duration";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as HashMap from "effect/HashMap";
|
|
10
|
+
import * as Match from "effect/Match";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import * as Predicate from "effect/Predicate";
|
|
13
|
+
import * as Ref from "effect/Ref";
|
|
14
|
+
import * as Schema from "effect/Schema";
|
|
15
|
+
import { pipe } from "effect/Function";
|
|
16
|
+
import { InngestClient } from "../Client.js";
|
|
17
|
+
import type { InngestFunction } from "../Function.js";
|
|
18
|
+
import * as Protocol from "./protocol.js";
|
|
19
|
+
import { StepError, SendEventError, isNonRetriableError, isRetryAfterError } from "./errors.js";
|
|
20
|
+
import { timeStr, formatTimestamp } from "./helpers.js";
|
|
21
|
+
import { OtelAttributes } from "./constants.js";
|
|
22
|
+
import { decodeMemo, type Memo } from "./memo.js";
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
StepInterrupt,
|
|
26
|
+
type StepInfo,
|
|
27
|
+
sleepInterrupt,
|
|
28
|
+
waitForEventInterrupt,
|
|
29
|
+
invokeInterrupt,
|
|
30
|
+
plannedInterrupt,
|
|
31
|
+
runInterrupt,
|
|
32
|
+
errorInterrupt,
|
|
33
|
+
} from "./interrupts.js";
|
|
34
|
+
|
|
35
|
+
export { StepInterrupt, type StepInfo } from "./interrupts.js";
|
|
36
|
+
|
|
37
|
+
/** @internal */
|
|
38
|
+
const hashStepId = (id: string, repeatIndex: number = 0): Effect.Effect<string> => {
|
|
39
|
+
const effectiveId = repeatIndex > 0 ? `${id}:${repeatIndex}` : id;
|
|
40
|
+
return Effect.map(
|
|
41
|
+
Effect.promise(() => globalThis.crypto.subtle.digest("SHA-1", new TextEncoder().encode(effectiveId))),
|
|
42
|
+
(buffer) => new Uint8Array(buffer).toHex(),
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const errorOtelAttributes = (err: unknown): Record<string, string> => {
|
|
47
|
+
const attrs: Record<string, string> = {};
|
|
48
|
+
if (err instanceof Error) {
|
|
49
|
+
attrs[OtelAttributes.ExceptionType] = err.name;
|
|
50
|
+
attrs[OtelAttributes.ExceptionMessage] = err.message;
|
|
51
|
+
if (err.stack) attrs[OtelAttributes.ExceptionStacktrace] = err.stack;
|
|
52
|
+
} else if (Predicate.hasProperty(err, "_tag") && typeof err._tag === "string") {
|
|
53
|
+
attrs[OtelAttributes.ExceptionType] = err._tag;
|
|
54
|
+
if (Predicate.hasProperty(err, "message") && typeof err.message === "string") {
|
|
55
|
+
attrs[OtelAttributes.ExceptionMessage] = err.message;
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
attrs[OtelAttributes.ExceptionMessage] = String(err);
|
|
59
|
+
}
|
|
60
|
+
return attrs;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface StepOptions {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly name?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type StepOptionsOrId = string | StepOptions;
|
|
69
|
+
|
|
70
|
+
interface WaitForEventOptions {
|
|
71
|
+
readonly timeout: Duration.DurationInput;
|
|
72
|
+
readonly if?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface InvokeOptionsBase<F extends InngestFunction.Any> {
|
|
76
|
+
readonly function: F;
|
|
77
|
+
readonly user?: Record<string, unknown>;
|
|
78
|
+
readonly v?: string;
|
|
79
|
+
readonly timeout?: Duration.DurationInput;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type InvokeOptions<F extends InngestFunction.Any> = [InngestFunction.EventType<F>] extends [never]
|
|
83
|
+
? InvokeOptionsBase<F>
|
|
84
|
+
: InvokeOptionsBase<F> & { readonly data: InngestFunction.EventType<F> };
|
|
85
|
+
|
|
86
|
+
type EventSchema = Schema.Schema.Any & { readonly _tag: string };
|
|
87
|
+
type TaggedEvent = { readonly _tag: string };
|
|
88
|
+
|
|
89
|
+
interface StepTools {
|
|
90
|
+
readonly run: <A, Err, R>(
|
|
91
|
+
id: StepOptionsOrId,
|
|
92
|
+
effect: Effect.Effect<A, Err, R>,
|
|
93
|
+
) => Effect.Effect<A, StepError | Err, R>;
|
|
94
|
+
readonly sleep: (id: StepOptionsOrId, duration: Duration.DurationInput) => Effect.Effect<void>;
|
|
95
|
+
readonly sleepUntil: (id: StepOptionsOrId, timestamp: Date | number | string) => Effect.Effect<void>;
|
|
96
|
+
readonly waitForEvent: <E extends EventSchema>(
|
|
97
|
+
id: StepOptionsOrId,
|
|
98
|
+
event: E,
|
|
99
|
+
options: WaitForEventOptions,
|
|
100
|
+
) => Effect.Effect<Option.Option<Schema.Schema.Type<E>>>;
|
|
101
|
+
readonly invoke: <F extends InngestFunction.Any>(
|
|
102
|
+
id: StepOptionsOrId,
|
|
103
|
+
options: InvokeOptions<F>,
|
|
104
|
+
) => Effect.Effect<InngestFunction.Success<F>, StepError>;
|
|
105
|
+
readonly sendEvent: (
|
|
106
|
+
id: StepOptionsOrId,
|
|
107
|
+
payload: TaggedEvent | ReadonlyArray<TaggedEvent>,
|
|
108
|
+
) => Effect.Effect<{ readonly ids: ReadonlyArray<string> }, SendEventError>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface RunContext {
|
|
112
|
+
readonly id: string;
|
|
113
|
+
readonly attempt: number;
|
|
114
|
+
readonly maxAttempts: number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface HandlerContext<F extends InngestFunction.Any> {
|
|
118
|
+
readonly event: InngestFunction.EventType<F>;
|
|
119
|
+
readonly step: StepTools;
|
|
120
|
+
readonly run: RunContext;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export class Step extends Context.Tag("effect-inngest/Step")<Step, StepTools>() {}
|
|
124
|
+
|
|
125
|
+
const normalizeOpts = (opts: StepOptionsOrId): { id: string; name: string } =>
|
|
126
|
+
typeof opts === "string" ? { id: opts, name: opts } : { id: opts.id, name: opts.name ?? opts.id };
|
|
127
|
+
|
|
128
|
+
const stepError = (stepId: string, message: string, opts?: { noRetry?: boolean; cause?: unknown }) =>
|
|
129
|
+
Effect.fail(StepError.make({ stepId, message, noRetry: opts?.noRetry, cause: opts?.cause }));
|
|
130
|
+
|
|
131
|
+
export const createStepTools = (
|
|
132
|
+
request: Protocol.SDKRequestBody,
|
|
133
|
+
appName: string,
|
|
134
|
+
stepIdCounts: Ref.Ref<HashMap.HashMap<string, number>>,
|
|
135
|
+
): StepTools => {
|
|
136
|
+
const ctx = request.ctx;
|
|
137
|
+
const steps = request.steps as Record<string, unknown>;
|
|
138
|
+
|
|
139
|
+
const getInfo = (opts: StepOptionsOrId): Effect.Effect<StepInfo> => {
|
|
140
|
+
const { id, name } = normalizeOpts(opts);
|
|
141
|
+
return Effect.flatMap(
|
|
142
|
+
Ref.modify(stepIdCounts, (map) => {
|
|
143
|
+
const count = Option.getOrElse(HashMap.get(map, id), () => 0);
|
|
144
|
+
return [count, HashMap.set(map, id, count + 1)];
|
|
145
|
+
}),
|
|
146
|
+
(count) => Effect.map(hashStepId(id, count), (hash) => ({ id, name, hash })),
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const getMemo = (hash: string): Memo => decodeMemo(steps[hash]);
|
|
151
|
+
const canExecute = (hash: string) => ctx.step_id === hash || ctx.step_id === "step";
|
|
152
|
+
const isBlocked = (hash: string) => ctx.disable_immediate_execution && ctx.step_id !== hash;
|
|
153
|
+
|
|
154
|
+
const sleep = (opts: StepOptionsOrId, duration: Duration.DurationInput): Effect.Effect<void, StepInterrupt> =>
|
|
155
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
156
|
+
pipe(
|
|
157
|
+
getMemo(info.hash),
|
|
158
|
+
Match.value,
|
|
159
|
+
Match.tag("MemoData", () => Effect.void),
|
|
160
|
+
Match.tag("MemoTimeout", () => Effect.void),
|
|
161
|
+
Match.tag("MemoError", () => Effect.void),
|
|
162
|
+
Match.tag("MemoInput", () => Effect.void),
|
|
163
|
+
Match.tag("MemoNone", () => Effect.fail(sleepInterrupt({ info, duration: timeStr(duration) }))),
|
|
164
|
+
Match.exhaustive,
|
|
165
|
+
),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const sleepUntil = (opts: StepOptionsOrId, timestamp: Date | number | string): Effect.Effect<void, StepInterrupt> =>
|
|
169
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
170
|
+
pipe(
|
|
171
|
+
getMemo(info.hash),
|
|
172
|
+
Match.value,
|
|
173
|
+
Match.tag("MemoData", () => Effect.void),
|
|
174
|
+
Match.tag("MemoTimeout", () => Effect.void),
|
|
175
|
+
Match.tag("MemoError", () => Effect.void),
|
|
176
|
+
Match.tag("MemoInput", () => Effect.void),
|
|
177
|
+
Match.tag("MemoNone", () => Effect.fail(sleepInterrupt({ info, duration: formatTimestamp(timestamp) }))),
|
|
178
|
+
Match.exhaustive,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const waitForEvent = <E extends EventSchema>(
|
|
183
|
+
opts: StepOptionsOrId,
|
|
184
|
+
event: E,
|
|
185
|
+
options: WaitForEventOptions,
|
|
186
|
+
): Effect.Effect<Option.Option<Schema.Schema.Type<E>>, StepInterrupt> =>
|
|
187
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
188
|
+
pipe(
|
|
189
|
+
getMemo(info.hash),
|
|
190
|
+
Match.value,
|
|
191
|
+
Match.tag("MemoData", ({ data }) => {
|
|
192
|
+
// null/undefined = timeout (no matching event received)
|
|
193
|
+
if (data == null) return Effect.succeed(Option.none());
|
|
194
|
+
// Inngest returns full event { name, data, id, ts } - extract .data for payload
|
|
195
|
+
// Or may return payload directly - use .data if present, otherwise use data as-is
|
|
196
|
+
const event = data as { data?: unknown };
|
|
197
|
+
const payload = event.data !== undefined ? event.data : data;
|
|
198
|
+
if (payload == null) return Effect.succeed(Option.none());
|
|
199
|
+
return Effect.succeed(Option.some(payload as Schema.Schema.Type<E>));
|
|
200
|
+
}),
|
|
201
|
+
Match.tag("MemoTimeout", () => Effect.succeed(Option.none())),
|
|
202
|
+
Match.tag("MemoError", () => Effect.succeed(Option.none())),
|
|
203
|
+
Match.tag("MemoInput", () => Effect.succeed(Option.none())),
|
|
204
|
+
Match.tag("MemoNone", () =>
|
|
205
|
+
Effect.fail(
|
|
206
|
+
waitForEventInterrupt({ info, event: event._tag, timeout: timeStr(options.timeout), if: options.if }),
|
|
207
|
+
),
|
|
208
|
+
),
|
|
209
|
+
Match.exhaustive,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const invoke = <F extends InngestFunction.Any>(
|
|
214
|
+
opts: StepOptionsOrId,
|
|
215
|
+
options: InvokeOptions<F>,
|
|
216
|
+
): Effect.Effect<InngestFunction.Success<F>, StepInterrupt | StepError> =>
|
|
217
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
218
|
+
pipe(
|
|
219
|
+
getMemo(info.hash),
|
|
220
|
+
Match.value,
|
|
221
|
+
Match.tag("MemoData", ({ data }) => Effect.succeed(data as InngestFunction.Success<F>)),
|
|
222
|
+
Match.tag("MemoError", ({ error }) =>
|
|
223
|
+
stepError(info.id, Predicate.hasProperty(error, "message") ? String(error.message) : "Invoke failed", {
|
|
224
|
+
cause: error,
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
Match.tag("MemoTimeout", () => stepError(info.id, "Invoke timed out", { noRetry: true })),
|
|
228
|
+
Match.tag("MemoInput", () => Effect.succeed(undefined as InngestFunction.Success<F>)),
|
|
229
|
+
Match.tag("MemoNone", () =>
|
|
230
|
+
Effect.fail(
|
|
231
|
+
invokeInterrupt({
|
|
232
|
+
info,
|
|
233
|
+
functionId: `${appName}-${options.function._tag}`,
|
|
234
|
+
payload: {
|
|
235
|
+
data: Predicate.hasProperty(options, "data") ? options.data : undefined,
|
|
236
|
+
user: options.user ?? {},
|
|
237
|
+
v: options.v ?? "1",
|
|
238
|
+
},
|
|
239
|
+
timeout: options.timeout ? timeStr(options.timeout) : "365d",
|
|
240
|
+
}),
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
Match.exhaustive,
|
|
244
|
+
),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const run = <A, Err, R>(
|
|
248
|
+
opts: StepOptionsOrId,
|
|
249
|
+
effect: Effect.Effect<A, Err, R>,
|
|
250
|
+
): Effect.Effect<A, StepInterrupt | StepError | Err, R> =>
|
|
251
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
252
|
+
pipe(
|
|
253
|
+
getMemo(info.hash),
|
|
254
|
+
Match.value,
|
|
255
|
+
Match.tag("MemoData", ({ data }) => Effect.succeed(data as A)),
|
|
256
|
+
Match.tag("MemoError", ({ error }) =>
|
|
257
|
+
stepError(info.id, Predicate.hasProperty(error, "message") ? String(error.message) : "Step failed", {
|
|
258
|
+
noRetry: true,
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
Match.tag("MemoTimeout", () => stepError(info.id, "Step timed out", { noRetry: true })),
|
|
262
|
+
Match.tag("MemoInput", () => stepError(info.id, "Unexpected step result type: input")),
|
|
263
|
+
Match.tag("MemoNone", () => {
|
|
264
|
+
if (isBlocked(info.hash) || !canExecute(info.hash)) {
|
|
265
|
+
return Effect.fail(plannedInterrupt({ info }));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return effect.pipe(
|
|
269
|
+
Effect.withSpan(`inngest.step/run/${info.id}`, {
|
|
270
|
+
attributes: { [OtelAttributes.StepId]: info.id, [OtelAttributes.StepType]: "run" },
|
|
271
|
+
}),
|
|
272
|
+
Effect.matchEffect({
|
|
273
|
+
onFailure: (err) => {
|
|
274
|
+
const noRetry = isNonRetriableError(err) ? true : undefined;
|
|
275
|
+
const retryAfterMs = isRetryAfterError(err) ? Duration.toMillis(err.retryAfter) : undefined;
|
|
276
|
+
return Effect.zipRight(
|
|
277
|
+
Effect.annotateCurrentSpan(errorOtelAttributes(err)),
|
|
278
|
+
Effect.fail(errorInterrupt({ info, error: err, noRetry, retryAfterMs })),
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
onSuccess: (data) => Effect.fail(runInterrupt({ info, data })),
|
|
282
|
+
}),
|
|
283
|
+
Effect.catchAllDefect((defect) =>
|
|
284
|
+
Effect.zipRight(
|
|
285
|
+
Effect.annotateCurrentSpan(errorOtelAttributes(defect)),
|
|
286
|
+
Effect.fail(errorInterrupt({ info, error: defect })),
|
|
287
|
+
),
|
|
288
|
+
),
|
|
289
|
+
);
|
|
290
|
+
}),
|
|
291
|
+
Match.exhaustive,
|
|
292
|
+
),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const sendEvent = (
|
|
296
|
+
opts: StepOptionsOrId,
|
|
297
|
+
payload: TaggedEvent | ReadonlyArray<TaggedEvent>,
|
|
298
|
+
): Effect.Effect<{ readonly ids: ReadonlyArray<string> }, StepInterrupt | SendEventError, InngestClient> =>
|
|
299
|
+
Effect.flatMap(getInfo(opts), (info) =>
|
|
300
|
+
pipe(
|
|
301
|
+
getMemo(info.hash),
|
|
302
|
+
Match.value,
|
|
303
|
+
Match.tag("MemoData", ({ data }) => Effect.succeed(data as { readonly ids: ReadonlyArray<string> })),
|
|
304
|
+
Match.tag("MemoError", () => Effect.fail(SendEventError.make({ message: "SendEvent failed", events: [] }))),
|
|
305
|
+
Match.tag("MemoTimeout", () =>
|
|
306
|
+
Effect.fail(SendEventError.make({ message: "SendEvent timed out", events: [] })),
|
|
307
|
+
),
|
|
308
|
+
Match.tag("MemoInput", () => Effect.succeed({ ids: [] as ReadonlyArray<string> })),
|
|
309
|
+
Match.tag("MemoNone", () => {
|
|
310
|
+
if (isBlocked(info.hash) || !canExecute(info.hash)) {
|
|
311
|
+
return Effect.fail(plannedInterrupt({ info }));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const eventPayloads = Arr.map(Arr.ensure(payload), (e) => ({ name: e._tag, data: e }));
|
|
315
|
+
|
|
316
|
+
return Effect.flatMap(InngestClient, (client) =>
|
|
317
|
+
client.sendEvent(eventPayloads).pipe(
|
|
318
|
+
Effect.withSpan(`inngest.step/sendEvent/${info.id}`, {
|
|
319
|
+
attributes: { [OtelAttributes.StepId]: info.id, [OtelAttributes.StepType]: "sendEvent" },
|
|
320
|
+
}),
|
|
321
|
+
Effect.flatMap((result) => Effect.fail(runInterrupt({ info, data: result }))),
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
}),
|
|
325
|
+
Match.exhaustive,
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
run: run as StepTools["run"],
|
|
331
|
+
sleep: sleep as StepTools["sleep"],
|
|
332
|
+
sleepUntil: sleepUntil as StepTools["sleepUntil"],
|
|
333
|
+
waitForEvent: waitForEvent as StepTools["waitForEvent"],
|
|
334
|
+
invoke: invoke as StepTools["invoke"],
|
|
335
|
+
sendEvent: sendEvent as StepTools["sendEvent"],
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export const buildHandlerContext = <F extends InngestFunction.Any>(
|
|
340
|
+
fn: F,
|
|
341
|
+
step: StepTools,
|
|
342
|
+
request: Protocol.SDKRequestBody,
|
|
343
|
+
): HandlerContext<F> => {
|
|
344
|
+
// Batch mode: function has batchEvents configured - always return array of event data payloads
|
|
345
|
+
const isBatchMode = fn.options?.batchEvents != null;
|
|
346
|
+
if (isBatchMode) {
|
|
347
|
+
const eventDataArray = request.events.map((e) => e.data);
|
|
348
|
+
return {
|
|
349
|
+
event: eventDataArray as InngestFunction.EventType<F>,
|
|
350
|
+
step,
|
|
351
|
+
run: {
|
|
352
|
+
id: request.ctx.run_id,
|
|
353
|
+
attempt: request.ctx.attempt,
|
|
354
|
+
maxAttempts: request.ctx.max_attempts,
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// When invoked via step.invoke, Inngest sends "inngest/function.invoked" event
|
|
360
|
+
// The payload is in event.data but mixed with _inngest metadata - extract just the user data
|
|
361
|
+
let eventData = request.event.data;
|
|
362
|
+
if (request.event.name === "inngest/function.invoked" && typeof eventData === "object" && eventData !== null) {
|
|
363
|
+
// Remove _inngest metadata, keep only the invoke payload
|
|
364
|
+
const { _inngest, ...payload } = eventData as Record<string, unknown>;
|
|
365
|
+
eventData = payload;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
event: eventData as InngestFunction.EventType<F>,
|
|
370
|
+
step,
|
|
371
|
+
run: {
|
|
372
|
+
id: request.ctx.run_id,
|
|
373
|
+
attempt: request.ctx.attempt,
|
|
374
|
+
maxAttempts: request.ctx.max_attempts,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
};
|