effect-inngest 0.3.0-beta.3 → 0.3.0-beta.4
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/dist/Group.d.ts +1 -1
- package/dist/HttpApi.d.ts +3 -3
- package/dist/HttpApi.js +2 -1
- package/dist/internal/checkpoint/Abort.d.ts +1 -0
- package/dist/internal/checkpoint/Abort.js +5 -0
- package/dist/internal/checkpoint/State.d.ts +1 -1
- package/dist/internal/checkpoint/State.js +8 -1
- package/dist/internal/errors.js +1 -1
- package/dist/internal/execution/ExecutionResponse.js +33 -4
- package/dist/internal/handler.js +37 -24
- package/dist/internal/protocol.js +3 -0
- package/dist/internal/serve/HttpApp.js +2 -1
- package/package.json +1 -1
- package/src/HttpApi.ts +7 -1
- package/src/internal/checkpoint/Abort.ts +5 -0
- package/src/internal/checkpoint/State.ts +8 -0
- package/src/internal/errors.ts +1 -3
- package/src/internal/execution/ExecutionResponse.ts +44 -1
- package/src/internal/handler.ts +39 -34
- package/src/internal/protocol.ts +3 -0
- package/src/internal/serve/HttpApp.ts +1 -0
package/dist/Group.d.ts
CHANGED
|
@@ -127,7 +127,7 @@ declare const make: <Fns extends ReadonlyArray<InngestFunction.Any>>(...fns: Fns
|
|
|
127
127
|
* )
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
|
130
|
-
declare const toHttpApp: (group: InngestGroup.Any) => Effect.Effect<_$effect_unstable_http_HttpServerResponse0.HttpServerResponse, never, InngestClient |
|
|
130
|
+
declare const toHttpApp: (group: InngestGroup.Any) => Effect.Effect<_$effect_unstable_http_HttpServerResponse0.HttpServerResponse, never, InngestClient | _$effect_unstable_http_HttpServerRequest0.HttpServerRequest | HttpClient.HttpClient>;
|
|
131
131
|
/**
|
|
132
132
|
* Create a standalone web handler from an InngestGroup.
|
|
133
133
|
*
|
package/dist/HttpApi.d.ts
CHANGED
|
@@ -49,13 +49,13 @@ declare const InngestApiGroup_base: HttpApiGroup.HttpApiGroup<"inngest", HttpApi
|
|
|
49
49
|
readonly authentication_succeeded: Schema.optional<Schema.Union<readonly [Schema.Literal<false>, Schema.Null]>>;
|
|
50
50
|
readonly capabilities: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
|
|
51
51
|
readonly functions: Schema.optionalKey<Schema.$Array<Schema.Unknown>>;
|
|
52
|
-
}>]>>, HttpApiEndpoint.Json<typeof
|
|
52
|
+
}>]>>, HttpApiEndpoint.Json<typeof InvalidRequestError | typeof FunctionNotFoundError | typeof SignatureError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"register", "PUT", "/", HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Struct<{
|
|
53
53
|
readonly message: Schema.String;
|
|
54
54
|
readonly modified: Schema.Boolean;
|
|
55
|
-
}>>, HttpApiEndpoint.Json<typeof
|
|
55
|
+
}>>, HttpApiEndpoint.Json<typeof InvalidRequestError | typeof FunctionNotFoundError | typeof SignatureError>, never, never> | HttpApiEndpoint.HttpApiEndpoint<"execute", "POST", "/", HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.StringTree<Schema.Struct<{
|
|
56
56
|
readonly fnId: Schema.String;
|
|
57
57
|
readonly stepId: Schema.optional<Schema.String>;
|
|
58
|
-
}>>, HttpApiEndpoint.Json<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Unknown>, HttpApiEndpoint.Json<typeof
|
|
58
|
+
}>>, HttpApiEndpoint.Json<never>, HttpApiEndpoint.StringTree<never>, HttpApiEndpoint.Json<Schema.Unknown>, HttpApiEndpoint.Json<typeof InvalidRequestError | typeof FunctionNotFoundError | typeof SignatureError>, never, never>, false>;
|
|
59
59
|
/**
|
|
60
60
|
* @since 0.1.0
|
|
61
61
|
* @category api
|
package/dist/HttpApi.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { StaleDispatch } from "./Abort.js";
|
|
1
2
|
import { Array, Clock, Duration, Effect, Option, Ref, Result } from "effect";
|
|
2
3
|
//#region src/internal/checkpoint/State.ts
|
|
3
4
|
const make = (args) => Effect.sync(() => {
|
|
@@ -5,12 +6,17 @@ const make = (args) => Effect.sync(() => {
|
|
|
5
6
|
const plannedBuffer = Ref.makeUnsafe(Array.empty());
|
|
6
7
|
const intervalStartedAt = Ref.makeUnsafe(Option.none());
|
|
7
8
|
const runtimeExceeded = Ref.makeUnsafe(false);
|
|
9
|
+
const abort = Ref.makeUnsafe(Option.none());
|
|
8
10
|
const maxIntervalMs = Duration.toMillis(args.config.maxInterval);
|
|
9
11
|
const flushInner = Effect.gen(function* () {
|
|
10
12
|
const steps = yield* Ref.modify(buffer, (current) => [current, Array.empty()]);
|
|
11
13
|
if (steps.length === 0) return;
|
|
12
14
|
const result = yield* Effect.result(args.checkpointAsync(steps));
|
|
13
15
|
if (Result.isFailure(result)) {
|
|
16
|
+
if (result.failure.status === 409) {
|
|
17
|
+
yield* Ref.set(abort, Option.some(StaleDispatch.make({})));
|
|
18
|
+
return yield* Effect.interrupt;
|
|
19
|
+
}
|
|
14
20
|
yield* Ref.update(buffer, (current) => [...steps, ...current]);
|
|
15
21
|
return;
|
|
16
22
|
}
|
|
@@ -47,7 +53,8 @@ const make = (args) => Effect.sync(() => {
|
|
|
47
53
|
flush: flushInner,
|
|
48
54
|
takeCompleted,
|
|
49
55
|
markRuntimeExceeded: Ref.set(runtimeExceeded, true),
|
|
50
|
-
isRuntimeExceeded: Ref.get(runtimeExceeded)
|
|
56
|
+
isRuntimeExceeded: Ref.get(runtimeExceeded),
|
|
57
|
+
abort: Ref.get(abort)
|
|
51
58
|
};
|
|
52
59
|
});
|
|
53
60
|
//#endregion
|
package/dist/internal/errors.js
CHANGED
|
@@ -21,6 +21,6 @@ var RetryAfterError = class extends Schema.TaggedErrorClass()("RetryAfterError",
|
|
|
21
21
|
retryAfter: Schema.DurationFromMillis,
|
|
22
22
|
cause: Schema.optional(Schema.Unknown)
|
|
23
23
|
}) {};
|
|
24
|
-
const isRetryAfterError =
|
|
24
|
+
const isRetryAfterError = Schema.is(RetryAfterError);
|
|
25
25
|
//#endregion
|
|
26
26
|
export { NonRetriableError, RetryAfterError, SendEventError, StepError, isNonRetriableError, isRetryAfterError, isStepError };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { GeneratorOpcode } from "../protocol.js";
|
|
2
2
|
import { InngestConfig } from "../../Client.js";
|
|
3
3
|
import { CurrentCheckpoint } from "../runtime/CheckpointContext.js";
|
|
4
|
+
import { StepError } from "../errors.js";
|
|
4
5
|
import { StepCommandBus } from "../runtime/StepCommandBus.js";
|
|
5
6
|
import { ExecutionFailure } from "./ExecutionFailure.js";
|
|
6
7
|
import { RetryDisposition, base } from "./ExecutionHeaders.js";
|
|
@@ -23,7 +24,27 @@ const fromSuccess = (args) => Effect.gen(function* () {
|
|
|
23
24
|
headers: args.headers
|
|
24
25
|
})), Match.tag("CheckpointDeadlineElapsed", () => ExecutionResult.checkpointDeadlineOutsideCheckpoint(args)), Match.exhaustive);
|
|
25
26
|
});
|
|
27
|
+
const fromCheckpointAbort = (args) => Match.value(args.abort).pipe(Match.tag("StaleDispatch", () => {
|
|
28
|
+
const error = new StepError({
|
|
29
|
+
stepId: "checkpoint",
|
|
30
|
+
message: "Stale dispatch: checkpoint returned 409",
|
|
31
|
+
noRetry: true
|
|
32
|
+
});
|
|
33
|
+
return ExecutionResult.userError({
|
|
34
|
+
error,
|
|
35
|
+
headers: args.headers,
|
|
36
|
+
disposition: RetryDisposition.fromError(error)
|
|
37
|
+
});
|
|
38
|
+
}), Match.exhaustive);
|
|
26
39
|
const fromFailure = (args) => Effect.gen(function* () {
|
|
40
|
+
const checkpoint = yield* CurrentCheckpoint;
|
|
41
|
+
if (Cause.hasInterruptsOnly(args.cause) && Option.isSome(checkpoint)) {
|
|
42
|
+
const abort = yield* checkpoint.value.abort;
|
|
43
|
+
if (Option.isSome(abort)) return fromCheckpointAbort({
|
|
44
|
+
abort: abort.value,
|
|
45
|
+
headers: args.headers
|
|
46
|
+
});
|
|
47
|
+
}
|
|
27
48
|
const commands = yield* (yield* StepCommandBus).takeSuspension();
|
|
28
49
|
if (commands.suspendedCount > 0 && Cause.hasInterruptsOnly(args.cause)) return ExecutionResult.opcodesWithRetry({
|
|
29
50
|
opcodes: commands.opcodes,
|
|
@@ -45,10 +66,18 @@ const fromFailure = (args) => Effect.gen(function* () {
|
|
|
45
66
|
});
|
|
46
67
|
});
|
|
47
68
|
const fromExit = (exit) => Effect.gen(function* () {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
const headers = base(yield* InngestConfig);
|
|
70
|
+
if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)) {
|
|
71
|
+
const checkpoint = yield* CurrentCheckpoint;
|
|
72
|
+
if (Option.isSome(checkpoint)) {
|
|
73
|
+
const abort = yield* checkpoint.value.abort;
|
|
74
|
+
if (Option.isSome(abort)) return fromCheckpointAbort({
|
|
75
|
+
abort: abort.value,
|
|
76
|
+
headers
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const planned = yield* (yield* StepCommandBus).takePlanned();
|
|
52
81
|
if (planned.length > 0) return ExecutionResult.opcodes({
|
|
53
82
|
opcodes: planned,
|
|
54
83
|
headers
|
package/dist/internal/handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveConfig } from "./checkpoint/Config.js";
|
|
2
|
-
import { Headers, RegisterServerResponse, SDKRequestBody, SDKRequestContext, UserError } from "./protocol.js";
|
|
2
|
+
import { Headers as Headers$1, RegisterServerResponse, SDKRequestBody, SDKRequestContext, UserError } from "./protocol.js";
|
|
3
3
|
import "./serve/Signature.js";
|
|
4
4
|
import { InngestClient } from "../Client.js";
|
|
5
5
|
import { schemaBodyJson, verifySignature } from "./serve/Request.js";
|
|
@@ -9,16 +9,17 @@ import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
|
9
9
|
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
10
10
|
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
11
11
|
import "effect/unstable/http/HttpServerRequest";
|
|
12
|
+
import * as Headers from "effect/unstable/http/Headers";
|
|
12
13
|
//#region src/internal/handler.ts
|
|
13
14
|
var InvalidRequestError = class extends Schema.TaggedErrorClass()("InvalidRequestError", { message: Schema.String }) {};
|
|
14
15
|
const SDK_VERSION = "2.0.0";
|
|
15
16
|
const baseHeaders = (framework) => ({
|
|
16
17
|
"Content-Type": "application/json",
|
|
17
18
|
"User-Agent": `effect-inngest:v${SDK_VERSION}`,
|
|
18
|
-
[Headers.SDK]: `effect-inngest:v${SDK_VERSION}`,
|
|
19
|
-
[Headers.SDKHandled]: "true",
|
|
20
|
-
[Headers.RequestVersion]: "2",
|
|
21
|
-
...framework ? { [Headers.Framework]: framework } : {}
|
|
19
|
+
[Headers$1.SDK]: `effect-inngest:v${SDK_VERSION}`,
|
|
20
|
+
[Headers$1.SDKHandled]: "true",
|
|
21
|
+
[Headers$1.RequestVersion]: "2",
|
|
22
|
+
...framework ? { [Headers$1.Framework]: framework } : {}
|
|
22
23
|
});
|
|
23
24
|
const buildServeUrl = (args) => {
|
|
24
25
|
const url = new URL(args.requestUrl);
|
|
@@ -66,11 +67,11 @@ const handleRegistration = Effect.fn("effect-inngest/handler/handleRegistration"
|
|
|
66
67
|
const framework = config.framework;
|
|
67
68
|
const registerUrl = new URL("fn/register", client.apiBaseUrl).toString();
|
|
68
69
|
const registerHeaders = baseHeaders(framework);
|
|
69
|
-
delete registerHeaders[Headers.RequestVersion];
|
|
70
|
+
delete registerHeaders[Headers$1.RequestVersion];
|
|
70
71
|
const request = HttpClientRequest.post(registerUrl).pipe(HttpClientRequest.setHeaders({
|
|
71
72
|
...registerHeaders,
|
|
72
73
|
Authorization: `Bearer ${config.signingKey ?? ""}`,
|
|
73
|
-
[Headers.SyncKind]: "out_of_band"
|
|
74
|
+
[Headers$1.SyncKind]: "out_of_band"
|
|
74
75
|
}), HttpClientRequest.bodyJsonUnsafe({
|
|
75
76
|
url: url.href,
|
|
76
77
|
deployType: "ping",
|
|
@@ -99,7 +100,7 @@ const handleRegistration = Effect.fn("effect-inngest/handler/handleRegistration"
|
|
|
99
100
|
status: 200,
|
|
100
101
|
headers: {
|
|
101
102
|
...baseHeaders(config.framework),
|
|
102
|
-
[Headers.SyncKind]: "out_of_band"
|
|
103
|
+
[Headers$1.SyncKind]: "out_of_band"
|
|
103
104
|
},
|
|
104
105
|
body: {
|
|
105
106
|
message: "Successfully registered",
|
|
@@ -131,7 +132,7 @@ const handleExecution = Effect.fn("effect-inngest/handler/handleExecution")(func
|
|
|
131
132
|
status: 500,
|
|
132
133
|
headers: {
|
|
133
134
|
...baseHeaders(client.config.framework),
|
|
134
|
-
[Headers.NoRetry]: "false"
|
|
135
|
+
[Headers$1.NoRetry]: "false"
|
|
135
136
|
},
|
|
136
137
|
body: UserError.make({
|
|
137
138
|
name: "FunctionNotFoundError",
|
|
@@ -139,27 +140,39 @@ const handleExecution = Effect.fn("effect-inngest/handler/handleExecution")(func
|
|
|
139
140
|
})
|
|
140
141
|
};
|
|
141
142
|
const requestedStepId = args.urlStepId === "step" ? void 0 : args.urlStepId;
|
|
143
|
+
const requestId = (args.headers ? Option.getOrUndefined(Headers.get(args.headers, Headers$1.RequestId)) : void 0) ?? args.body.ctx.request_id;
|
|
144
|
+
const rawGenerationId = args.headers ? Option.getOrUndefined(Headers.get(args.headers, Headers$1.GenerationId)) : void 0;
|
|
145
|
+
const parsedGenerationId = rawGenerationId ? Number(rawGenerationId) : void 0;
|
|
146
|
+
const generationId = Predicate.isNumber(parsedGenerationId) && Number.isInteger(parsedGenerationId) ? parsedGenerationId : args.body.ctx.generation_id;
|
|
147
|
+
const withExecutionHeaders = (ctx) => SDKRequestContext.make({
|
|
148
|
+
fn_id: ctx.fn_id,
|
|
149
|
+
run_id: ctx.run_id,
|
|
150
|
+
env: ctx.env,
|
|
151
|
+
step_id: requestedStepId ?? ctx.step_id,
|
|
152
|
+
attempt: ctx.attempt,
|
|
153
|
+
max_attempts: ctx.max_attempts,
|
|
154
|
+
qi_id: ctx.qi_id,
|
|
155
|
+
...Predicate.isNotUndefined(requestId) ? { request_id: requestId } : {},
|
|
156
|
+
...Predicate.isNotUndefined(generationId) ? { generation_id: generationId } : {},
|
|
157
|
+
disable_immediate_execution: ctx.disable_immediate_execution,
|
|
158
|
+
use_api: ctx.use_api,
|
|
159
|
+
stack: ctx.stack
|
|
160
|
+
});
|
|
142
161
|
const effectiveBody = requestedStepId && requestedStepId !== args.body.ctx.step_id ? SDKRequestBody.make({
|
|
143
162
|
event: args.body.event,
|
|
144
163
|
events: args.body.events,
|
|
145
164
|
steps: args.body.steps,
|
|
146
|
-
ctx:
|
|
147
|
-
fn_id: args.body.ctx.fn_id,
|
|
148
|
-
run_id: args.body.ctx.run_id,
|
|
149
|
-
env: args.body.ctx.env,
|
|
150
|
-
step_id: requestedStepId,
|
|
151
|
-
attempt: args.body.ctx.attempt,
|
|
152
|
-
max_attempts: args.body.ctx.max_attempts,
|
|
153
|
-
qi_id: args.body.ctx.qi_id,
|
|
154
|
-
...Predicate.isNotUndefined(args.body.ctx.request_id) ? { request_id: args.body.ctx.request_id } : {},
|
|
155
|
-
...Predicate.isNotUndefined(args.body.ctx.generation_id) ? { generation_id: args.body.ctx.generation_id } : {},
|
|
156
|
-
disable_immediate_execution: args.body.ctx.disable_immediate_execution,
|
|
157
|
-
use_api: args.body.ctx.use_api,
|
|
158
|
-
stack: args.body.ctx.stack
|
|
159
|
-
}),
|
|
165
|
+
ctx: withExecutionHeaders(args.body.ctx),
|
|
160
166
|
version: args.body.version,
|
|
161
167
|
use_api: args.body.use_api
|
|
162
|
-
}) :
|
|
168
|
+
}) : SDKRequestBody.make({
|
|
169
|
+
event: args.body.event,
|
|
170
|
+
events: args.body.events,
|
|
171
|
+
steps: args.body.steps,
|
|
172
|
+
ctx: withExecutionHeaders(args.body.ctx),
|
|
173
|
+
version: args.body.version,
|
|
174
|
+
use_api: args.body.use_api
|
|
175
|
+
});
|
|
163
176
|
const checkpointConfig = !requestedStepId && effectiveBody.ctx.fn_id !== "" && !effectiveBody.ctx.disable_immediate_execution && effectiveBody.ctx.attempt === 0 ? Option.fromNullishOr(resolveConfig(fn.options.checkpointing, client.config.checkpointing)) : Option.none();
|
|
164
177
|
const result = yield* Effect.provide(execute({
|
|
165
178
|
fn,
|
|
@@ -90,6 +90,9 @@ const Headers = {
|
|
|
90
90
|
SyncKind: "x-inngest-sync-kind",
|
|
91
91
|
ServerKind: "X-Inngest-Server-Kind",
|
|
92
92
|
ExpectedServerKind: "X-Inngest-Expected-Server-Kind",
|
|
93
|
+
RequestId: "x-request-id",
|
|
94
|
+
GenerationId: "x-inngest-generation-id",
|
|
95
|
+
JobId: "x-inngest-job-id",
|
|
93
96
|
RunID: "X-Run-ID",
|
|
94
97
|
Framework: "X-Inngest-Framework",
|
|
95
98
|
Platform: "X-Inngest-Platform",
|
package/package.json
CHANGED
package/src/HttpApi.ts
CHANGED
|
@@ -78,7 +78,13 @@ export const layerGroup = <ApiId extends string, Groups extends HttpApiGroup.Any
|
|
|
78
78
|
.handleRaw("execute", ({ query, request }) =>
|
|
79
79
|
InternalHandler.verifyAndParseRequestBody(request).pipe(
|
|
80
80
|
Effect.flatMap((payload) =>
|
|
81
|
-
InternalHandler.handleExecution({
|
|
81
|
+
InternalHandler.handleExecution({
|
|
82
|
+
group,
|
|
83
|
+
fnId: query.fnId,
|
|
84
|
+
urlStepId: query.stepId,
|
|
85
|
+
body: payload,
|
|
86
|
+
headers: request.headers,
|
|
87
|
+
}),
|
|
82
88
|
),
|
|
83
89
|
Effect.map((r) => r.body),
|
|
84
90
|
),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Array as Arr, Clock, Duration, Effect, Option, Ref, Result } from "effect";
|
|
2
|
+
import * as CheckpointAbort from "./Abort.js";
|
|
2
3
|
import type { CheckpointConfig } from "./Config.js";
|
|
3
4
|
import type { CheckpointApiError } from "./Error.js";
|
|
4
5
|
import type * as StepCommand from "../domain/StepCommand.js";
|
|
@@ -16,6 +17,7 @@ export interface CheckpointState {
|
|
|
16
17
|
readonly takeCompleted: () => Effect.Effect<ReadonlyArray<typeof Protocol.GeneratorOpcode.Type>>;
|
|
17
18
|
readonly markRuntimeExceeded: Effect.Effect<void>;
|
|
18
19
|
readonly isRuntimeExceeded: Effect.Effect<boolean>;
|
|
20
|
+
readonly abort: Effect.Effect<Option.Option<CheckpointAbort.CheckpointAbort>>;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export const make = (args: {
|
|
@@ -32,6 +34,7 @@ export const make = (args: {
|
|
|
32
34
|
const plannedBuffer = Ref.makeUnsafe<ReadonlyArray<StepCommand.PlannedOpcode>>(Arr.empty());
|
|
33
35
|
const intervalStartedAt = Ref.makeUnsafe<Option.Option<number>>(Option.none());
|
|
34
36
|
const runtimeExceeded = Ref.makeUnsafe(false);
|
|
37
|
+
const abort = Ref.makeUnsafe<Option.Option<CheckpointAbort.CheckpointAbort>>(Option.none());
|
|
35
38
|
|
|
36
39
|
const maxIntervalMs = Duration.toMillis(args.config.maxInterval);
|
|
37
40
|
|
|
@@ -50,6 +53,10 @@ export const make = (args: {
|
|
|
50
53
|
}
|
|
51
54
|
const result = yield* Effect.result(args.checkpointAsync(steps));
|
|
52
55
|
if (Result.isFailure(result)) {
|
|
56
|
+
if (result.failure.status === 409) {
|
|
57
|
+
yield* Ref.set(abort, Option.some(CheckpointAbort.StaleDispatch.make({})));
|
|
58
|
+
return yield* Effect.interrupt;
|
|
59
|
+
}
|
|
53
60
|
// Restore at the head so subsequent `completed` includes them in the 206.
|
|
54
61
|
yield* Ref.update(buffer, (current) => [...steps, ...current]);
|
|
55
62
|
return;
|
|
@@ -103,5 +110,6 @@ export const make = (args: {
|
|
|
103
110
|
takeCompleted,
|
|
104
111
|
markRuntimeExceeded: Ref.set(runtimeExceeded, true),
|
|
105
112
|
isRuntimeExceeded: Ref.get(runtimeExceeded),
|
|
113
|
+
abort: Ref.get(abort),
|
|
106
114
|
};
|
|
107
115
|
});
|
package/src/internal/errors.ts
CHANGED
|
@@ -31,6 +31,4 @@ export class RetryAfterError extends Schema.TaggedErrorClass<RetryAfterError>()(
|
|
|
31
31
|
cause: Schema.optional(Schema.Unknown),
|
|
32
32
|
}) {}
|
|
33
33
|
|
|
34
|
-
export const isRetryAfterError
|
|
35
|
-
u: unknown,
|
|
36
|
-
) => u is RetryAfterError;
|
|
34
|
+
export const isRetryAfterError = Schema.is(RetryAfterError);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type * as Headers from "effect/unstable/http/Headers";
|
|
2
2
|
import { Cause, Effect, Exit, Match, Option } from "effect";
|
|
3
3
|
import { InngestConfig } from "../../Client.js";
|
|
4
|
+
import type * as CheckpointAbort from "../checkpoint/Abort.js";
|
|
4
5
|
import { CurrentCheckpoint } from "../runtime/CheckpointContext.js";
|
|
5
6
|
import { StepCommandBus } from "../runtime/StepCommandBus.js";
|
|
7
|
+
import { StepError } from "../errors.js";
|
|
6
8
|
import * as Protocol from "../protocol.js";
|
|
7
9
|
import { ExecutionFailure } from "./ExecutionFailure.js";
|
|
8
10
|
import * as ExecutionHeaders from "./ExecutionHeaders.js";
|
|
@@ -34,8 +36,38 @@ const fromSuccess = (args: { readonly completion: HandlerRun.HandlerCompletion;
|
|
|
34
36
|
);
|
|
35
37
|
});
|
|
36
38
|
|
|
39
|
+
const fromCheckpointAbort = (args: {
|
|
40
|
+
readonly abort: CheckpointAbort.CheckpointAbort;
|
|
41
|
+
readonly headers: Headers.Headers;
|
|
42
|
+
}) =>
|
|
43
|
+
Match.value(args.abort).pipe(
|
|
44
|
+
Match.tag("StaleDispatch", () => {
|
|
45
|
+
const error = new StepError({
|
|
46
|
+
stepId: "checkpoint",
|
|
47
|
+
message: "Stale dispatch: checkpoint returned 409",
|
|
48
|
+
noRetry: true,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return ExecutionResult.userError({
|
|
52
|
+
error,
|
|
53
|
+
headers: args.headers,
|
|
54
|
+
disposition: ExecutionHeaders.RetryDisposition.fromError(error),
|
|
55
|
+
});
|
|
56
|
+
}),
|
|
57
|
+
Match.exhaustive,
|
|
58
|
+
);
|
|
59
|
+
|
|
37
60
|
const fromFailure = (args: { readonly cause: Cause.Cause<unknown>; readonly headers: Headers.Headers }) =>
|
|
38
61
|
Effect.gen(function* () {
|
|
62
|
+
const checkpoint = yield* CurrentCheckpoint;
|
|
63
|
+
|
|
64
|
+
if (Cause.hasInterruptsOnly(args.cause) && Option.isSome(checkpoint)) {
|
|
65
|
+
const abort = yield* checkpoint.value.abort;
|
|
66
|
+
if (Option.isSome(abort)) {
|
|
67
|
+
return fromCheckpointAbort({ abort: abort.value, headers: args.headers });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
39
71
|
const bus = yield* StepCommandBus;
|
|
40
72
|
const commands = yield* bus.takeSuspension();
|
|
41
73
|
|
|
@@ -64,8 +96,19 @@ const fromFailure = (args: { readonly cause: Cause.Cause<unknown>; readonly head
|
|
|
64
96
|
export const fromExit = (exit: Exit.Exit<HandlerRun.HandlerCompletion, unknown>) =>
|
|
65
97
|
Effect.gen(function* () {
|
|
66
98
|
const config = yield* InngestConfig;
|
|
67
|
-
const bus = yield* StepCommandBus;
|
|
68
99
|
const headers = ExecutionHeaders.base(config);
|
|
100
|
+
|
|
101
|
+
if (Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)) {
|
|
102
|
+
const checkpoint = yield* CurrentCheckpoint;
|
|
103
|
+
if (Option.isSome(checkpoint)) {
|
|
104
|
+
const abort = yield* checkpoint.value.abort;
|
|
105
|
+
if (Option.isSome(abort)) {
|
|
106
|
+
return fromCheckpointAbort({ abort: abort.value, headers });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const bus = yield* StepCommandBus;
|
|
69
112
|
const planned = yield* bus.takePlanned();
|
|
70
113
|
|
|
71
114
|
if (planned.length > 0) {
|
package/src/internal/handler.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
2
2
|
import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
|
|
3
3
|
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
4
|
+
import * as Headers from "effect/unstable/http/Headers";
|
|
4
5
|
import * as HttpServerRequest from "effect/unstable/http/HttpServerRequest";
|
|
5
6
|
import { Cause, Context, Effect, Option, Predicate, Schema } from "effect";
|
|
6
7
|
import { InngestClient } from "../Client.js";
|
|
7
8
|
import type { InngestFunction } from "../Function.js";
|
|
8
9
|
import type { InngestGroup } from "../Group.js";
|
|
9
10
|
import * as Checkpoint from "./checkpoint.js";
|
|
10
|
-
import { SignatureError } from "./serve/Signature.js";
|
|
11
11
|
import * as SdkRequest from "./serve/Request.js";
|
|
12
12
|
import * as Protocol from "./protocol.js";
|
|
13
13
|
export { SignatureError } from "./serve/Signature.js";
|
|
@@ -130,9 +130,6 @@ export const handleRegistration = Effect.fn("effect-inngest/handler/handleRegist
|
|
|
130
130
|
}),
|
|
131
131
|
);
|
|
132
132
|
|
|
133
|
-
// Spec §4.3.1: PUT sync responds with `{ message: string; modified: boolean }`.
|
|
134
|
-
// Spec §4.3.3 / §4.3.4: 500 on failure with `modified: false`; 200 on success
|
|
135
|
-
// with the executor-supplied `modified` value (or `false` if omitted).
|
|
136
133
|
return yield* Effect.gen(function* () {
|
|
137
134
|
const response = yield* httpClient.execute(request).pipe(Effect.scoped);
|
|
138
135
|
const responseBody = yield* HttpClientResponse.schemaBodyJson(Protocol.RegisterServerResponse)(response).pipe(
|
|
@@ -163,9 +160,6 @@ export const handleRegistration = Effect.fn("effect-inngest/handler/handleRegist
|
|
|
163
160
|
} as HandlerResponse<typeof Protocol.RegisterResponse.Type>;
|
|
164
161
|
}).pipe(
|
|
165
162
|
Effect.catchCause((cause) => {
|
|
166
|
-
// Network/transport failure (e.g. die from HttpClient mock). Per
|
|
167
|
-
// §4.3.3 SHOULD return 500 with the body shape; never let the
|
|
168
|
-
// toWebHandler default 500 wrapper escape.
|
|
169
163
|
const errorOpt = Cause.findErrorOption(cause);
|
|
170
164
|
const dieReason = cause.reasons.find(Cause.isDieReason);
|
|
171
165
|
const message = Option.isSome(errorOpt)
|
|
@@ -197,6 +191,7 @@ export const handleExecution = Effect.fn("effect-inngest/handler/handleExecution
|
|
|
197
191
|
readonly fnId: string;
|
|
198
192
|
readonly urlStepId: string | undefined;
|
|
199
193
|
readonly body: typeof Protocol.SDKRequestBody.Type;
|
|
194
|
+
readonly headers?: Headers.Headers;
|
|
200
195
|
}) {
|
|
201
196
|
const client = yield* InngestClient;
|
|
202
197
|
const context = yield* Effect.context<never>();
|
|
@@ -209,9 +204,6 @@ export const handleExecution = Effect.fn("effect-inngest/handler/handleExecution
|
|
|
209
204
|
const entry = fn ? (context.mapUnsafe.get(fn.key) as Handler<string> | undefined) : undefined;
|
|
210
205
|
|
|
211
206
|
if (!fn || !entry) {
|
|
212
|
-
// Spec §4.4.1: unknown function MUST return 500 with the error payload
|
|
213
|
-
// at the response root (no { error: ... } envelope).
|
|
214
|
-
// Spec §4.4.3 pair rule: 500 MUST pair with X-Inngest-No-Retry: false.
|
|
215
207
|
return {
|
|
216
208
|
status: 500 as const,
|
|
217
209
|
headers: { ...baseHeaders(client.config.framework), [Protocol.Headers.NoRetry]: "false" },
|
|
@@ -220,41 +212,54 @@ export const handleExecution = Effect.fn("effect-inngest/handler/handleExecution
|
|
|
220
212
|
}
|
|
221
213
|
|
|
222
214
|
const requestedStepId = args.urlStepId === "step" ? undefined : args.urlStepId;
|
|
215
|
+
const headerRequestId = args.headers
|
|
216
|
+
? Option.getOrUndefined(Headers.get(args.headers, Protocol.Headers.RequestId))
|
|
217
|
+
: undefined;
|
|
218
|
+
const requestId = headerRequestId ?? args.body.ctx.request_id;
|
|
219
|
+
const rawGenerationId = args.headers
|
|
220
|
+
? Option.getOrUndefined(Headers.get(args.headers, Protocol.Headers.GenerationId))
|
|
221
|
+
: undefined;
|
|
222
|
+
const parsedGenerationId = rawGenerationId ? Number(rawGenerationId) : undefined;
|
|
223
|
+
const generationId =
|
|
224
|
+
Predicate.isNumber(parsedGenerationId) && Number.isInteger(parsedGenerationId)
|
|
225
|
+
? parsedGenerationId
|
|
226
|
+
: args.body.ctx.generation_id;
|
|
227
|
+
|
|
228
|
+
const withExecutionHeaders = (ctx: typeof Protocol.SDKRequestContext.Type) =>
|
|
229
|
+
Protocol.SDKRequestContext.make({
|
|
230
|
+
fn_id: ctx.fn_id,
|
|
231
|
+
run_id: ctx.run_id,
|
|
232
|
+
env: ctx.env,
|
|
233
|
+
step_id: requestedStepId ?? ctx.step_id,
|
|
234
|
+
attempt: ctx.attempt,
|
|
235
|
+
max_attempts: ctx.max_attempts,
|
|
236
|
+
qi_id: ctx.qi_id,
|
|
237
|
+
...(Predicate.isNotUndefined(requestId) ? { request_id: requestId } : {}),
|
|
238
|
+
...(Predicate.isNotUndefined(generationId) ? { generation_id: generationId } : {}),
|
|
239
|
+
disable_immediate_execution: ctx.disable_immediate_execution,
|
|
240
|
+
use_api: ctx.use_api,
|
|
241
|
+
stack: ctx.stack,
|
|
242
|
+
});
|
|
223
243
|
|
|
224
|
-
// Non-root URL stepId takes precedence over body.ctx.step_id. The root
|
|
225
|
-
// `stepId=step` identifies the function run step, not a targeted child step.
|
|
226
244
|
const effectiveBody =
|
|
227
245
|
requestedStepId && requestedStepId !== args.body.ctx.step_id
|
|
228
246
|
? Protocol.SDKRequestBody.make({
|
|
229
247
|
event: args.body.event,
|
|
230
248
|
events: args.body.events,
|
|
231
249
|
steps: args.body.steps,
|
|
232
|
-
ctx:
|
|
233
|
-
fn_id: args.body.ctx.fn_id,
|
|
234
|
-
run_id: args.body.ctx.run_id,
|
|
235
|
-
env: args.body.ctx.env,
|
|
236
|
-
step_id: requestedStepId,
|
|
237
|
-
attempt: args.body.ctx.attempt,
|
|
238
|
-
max_attempts: args.body.ctx.max_attempts,
|
|
239
|
-
qi_id: args.body.ctx.qi_id,
|
|
240
|
-
...(Predicate.isNotUndefined(args.body.ctx.request_id) ? { request_id: args.body.ctx.request_id } : {}),
|
|
241
|
-
...(Predicate.isNotUndefined(args.body.ctx.generation_id)
|
|
242
|
-
? { generation_id: args.body.ctx.generation_id }
|
|
243
|
-
: {}),
|
|
244
|
-
disable_immediate_execution: args.body.ctx.disable_immediate_execution,
|
|
245
|
-
use_api: args.body.ctx.use_api,
|
|
246
|
-
stack: args.body.ctx.stack,
|
|
247
|
-
}),
|
|
250
|
+
ctx: withExecutionHeaders(args.body.ctx),
|
|
248
251
|
version: args.body.version,
|
|
249
252
|
use_api: args.body.use_api,
|
|
250
253
|
})
|
|
251
|
-
:
|
|
254
|
+
: Protocol.SDKRequestBody.make({
|
|
255
|
+
event: args.body.event,
|
|
256
|
+
events: args.body.events,
|
|
257
|
+
steps: args.body.steps,
|
|
258
|
+
ctx: withExecutionHeaders(args.body.ctx),
|
|
259
|
+
version: args.body.version,
|
|
260
|
+
use_api: args.body.use_api,
|
|
261
|
+
});
|
|
252
262
|
|
|
253
|
-
// Checkpoint mode entry decision (spec §10):
|
|
254
|
-
// - non-root URL stepId NOT set (we are not running a targeted child step)
|
|
255
|
-
// - ctx.fn_id present (executor knows the function)
|
|
256
|
-
// - ctx.disable_immediate_execution false (executor allowed it)
|
|
257
|
-
// - resolved per-fn or per-client config not opted out
|
|
258
263
|
const enterCheckpoint =
|
|
259
264
|
!requestedStepId &&
|
|
260
265
|
effectiveBody.ctx.fn_id !== "" &&
|
package/src/internal/protocol.ts
CHANGED
|
@@ -115,6 +115,9 @@ export const Headers = {
|
|
|
115
115
|
SyncKind: "x-inngest-sync-kind",
|
|
116
116
|
ServerKind: "X-Inngest-Server-Kind",
|
|
117
117
|
ExpectedServerKind: "X-Inngest-Expected-Server-Kind",
|
|
118
|
+
RequestId: "x-request-id",
|
|
119
|
+
GenerationId: "x-inngest-generation-id",
|
|
120
|
+
JobId: "x-inngest-job-id",
|
|
118
121
|
RunID: "X-Run-ID",
|
|
119
122
|
Framework: "X-Inngest-Framework",
|
|
120
123
|
Platform: "X-Inngest-Platform",
|