@requence/service 0.8.3-edge.1190 → 0.8.3
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/CHANGELOG.md +79 -0
- package/build/index.js +40 -7
- package/build/index.js.map +5 -5
- package/build/types/helpers/src/utils/requenceCallback.d.ts +58 -1
- package/build/types/helpers/src/utils/requenceCallback.d.ts.map +1 -1
- package/build/types/service/src/createAmqpConnection.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @requence/service
|
|
2
2
|
|
|
3
|
+
## 0.8.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 621b48b: A callback that carries no data is now called with no argument:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
await context.input.mail.delete();
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The `null` it used to need was never the caller's to write. It is how the
|
|
14
|
+
**output schema** spells "this position carries nothing": `RequenceCallback<void>`
|
|
15
|
+
compiles to nothing typescript-json-schema has a root type for, and
|
|
16
|
+
`RequenceCallback<undefined>` compiles but silently drops the `callback` marker
|
|
17
|
+
along with the argument — so `RequenceCallback<null>` is the only declaration
|
|
18
|
+
available, and every call site paid for that. A callback that does carry data is
|
|
19
|
+
untouched, and a forgotten argument is still an error.
|
|
20
|
+
|
|
21
|
+
`RequenceCallback<T>` makes the argument optional exactly when `null extends T`,
|
|
22
|
+
through a conditional **tuple**: a conditional on the parameter type alone cannot
|
|
23
|
+
change the ARITY, and the arity is the whole point. Writing the `null` out still
|
|
24
|
+
works and still means the same thing.
|
|
25
|
+
|
|
26
|
+
Optionality at the call site is not enough by itself. The operator validates
|
|
27
|
+
every invocation against the callback's declared argument schema, which for this
|
|
28
|
+
kind of callback is `{ type: 'null' }` — so both reconstruction sites, the
|
|
29
|
+
`deserialize` in `@requence/callbacks` and its Python mirror, default an omitted
|
|
30
|
+
argument to `null` rather than letting `undefined` reach the wire. That default
|
|
31
|
+
is unconditional: a boundary holds a reference, never the emitting node's output
|
|
32
|
+
schema, and a callback that DOES declare data fails the same validation either
|
|
33
|
+
way — turning `undefined` into `null` there only changes which of the two it is
|
|
34
|
+
refused as.
|
|
35
|
+
|
|
36
|
+
The declaration is written out four times — `@requence/helpers` for both SDKs,
|
|
37
|
+
`RequenceTypes.ts` for the logic-node editor, and the two assignability
|
|
38
|
+
harnesses. The fifth copy, in `createJsonSchema.ts`, is deliberately an object
|
|
39
|
+
shape with no call signature and does not move with them. The editor's copy is
|
|
40
|
+
now asserted through the real language service, since it is the one that decides
|
|
41
|
+
whether a script compiles.
|
|
42
|
+
|
|
43
|
+
- f71e500: A durable callback that could not be run is now reported on the node that emitted
|
|
44
|
+
it, instead of only in one service process's log.
|
|
45
|
+
|
|
46
|
+
Two things can go wrong once an invocation has been consumed: the deployed version
|
|
47
|
+
no longer declares the name, or the handler throws. Both were logged by the service
|
|
48
|
+
and acked — a requeue would rerun a side-effecting callback forever — and that log
|
|
49
|
+
is nowhere a template author looks. Combined with imapflow's `messageDelete`
|
|
50
|
+
returning `true` when the _server_ accepted the command rather than when it matched
|
|
51
|
+
a mail, a failed `mail.delete()` was indistinguishable from a delete that did
|
|
52
|
+
nothing. That silence is what made the stale computed-configuration bug beside it
|
|
53
|
+
cost an afternoon.
|
|
54
|
+
|
|
55
|
+
The counterpart already existed for an invocation **nobody consumed**
|
|
56
|
+
(`DURABLE_CALLBACK_INVOCATION_HEADER`, reported by the operator off the returned
|
|
57
|
+
copy). This is the other half, and only the consuming instance can see it, so both
|
|
58
|
+
SDKs publish it: `DURABLE_CALLBACK_ERROR_HEADER`, carrying the emitting node, the
|
|
59
|
+
declaration name and the error's **message** — not its stack, which belongs in the
|
|
60
|
+
service's log — cut at 500 characters at the source as well as in the update
|
|
61
|
+
schema.
|
|
62
|
+
|
|
63
|
+
It rides the ordinary result exchange with **no correlation id and no index**,
|
|
64
|
+
exactly like a surface interaction failure and a resume announcement: the handler
|
|
65
|
+
ran for no dispatch, so it has no message to be a result FOR, and every other arm
|
|
66
|
+
of the result path would have failed the emitting node for a callback the graph
|
|
67
|
+
chose to call.
|
|
68
|
+
|
|
69
|
+
The operator turns it into a `NODE_DEBUG` on that node — `severity: 'system'`
|
|
70
|
+
because the operator is speaking, `event: null` because nothing was re-sent and no
|
|
71
|
+
run ended. The node lookup, the "nowhere to report" arm and the update's shape are
|
|
72
|
+
shared with the undelivered path through one `reportCallbackOnNode`, since only the
|
|
73
|
+
sentence differs. Every failure of that lookup stays "nowhere to report": a run can
|
|
74
|
+
end while an invocation of it is still in flight, and a throw escaping there nacks a
|
|
75
|
+
delivery at the head of a single-active-consumer FIFO queue, where it wedges every
|
|
76
|
+
task on that shard.
|
|
77
|
+
|
|
78
|
+
**This makes a failure visible, not catchable.** The graph called the callback and
|
|
79
|
+
moved on long before the handler ran, so there is no call site left to throw at. A
|
|
80
|
+
callback is still `(arg) => void` in the call direction.
|
|
81
|
+
|
|
3
82
|
## 0.8.2
|
|
4
83
|
|
|
5
84
|
### Patch Changes
|
package/build/index.js
CHANGED
|
@@ -205,8 +205,9 @@ function isCallbackObject(data) {
|
|
|
205
205
|
function createCallback(callback) {
|
|
206
206
|
const { promise, resolve: resolve2 } = Promise.withResolvers();
|
|
207
207
|
const executor = (arg) => {
|
|
208
|
-
|
|
209
|
-
|
|
208
|
+
const value = arg === undefined ? null : arg;
|
|
209
|
+
resolve2(value);
|
|
210
|
+
callback?.(value);
|
|
210
211
|
};
|
|
211
212
|
return Object.assign(executor, {
|
|
212
213
|
response() {
|
|
@@ -251,6 +252,13 @@ var durableCallbackInvocationSchema = z5.object({
|
|
|
251
252
|
arg: z5.unknown().optional(),
|
|
252
253
|
configuration: z5.unknown().optional()
|
|
253
254
|
});
|
|
255
|
+
var MAX_DURABLE_CALLBACK_ERROR_CHARS = 500;
|
|
256
|
+
var DURABLE_CALLBACK_ERROR_HEADER = "durableCallbackError";
|
|
257
|
+
var durableCallbackErrorReportSchema = z5.object({
|
|
258
|
+
nodeId: z5.string(),
|
|
259
|
+
name: z5.string(),
|
|
260
|
+
message: z5.string()
|
|
261
|
+
});
|
|
254
262
|
|
|
255
263
|
// ../helpers/src/utils/serviceRender.ts
|
|
256
264
|
import { z as z6 } from "zod/v4";
|
|
@@ -1084,7 +1092,7 @@ function createRequenceCallbackHandler() {
|
|
|
1084
1092
|
},
|
|
1085
1093
|
isReference: (value) => isCallbackObject(value),
|
|
1086
1094
|
deserialize(ref, ctx) {
|
|
1087
|
-
return (arg) => {
|
|
1095
|
+
return (arg = null) => {
|
|
1088
1096
|
ctx.onCallbackInvoke?.({ ...ref, arg });
|
|
1089
1097
|
};
|
|
1090
1098
|
}
|
|
@@ -2000,7 +2008,7 @@ function createConnection({
|
|
|
2000
2008
|
clientProperties: {
|
|
2001
2009
|
...connectionOptions?.clientProperties,
|
|
2002
2010
|
...buildServiceClientProperties({
|
|
2003
|
-
libVersion: "javascript@0.8.3
|
|
2011
|
+
libVersion: "javascript@0.8.3",
|
|
2004
2012
|
serviceVersion: version,
|
|
2005
2013
|
connectionId,
|
|
2006
2014
|
prefetch,
|
|
@@ -2142,6 +2150,28 @@ function createConnection({
|
|
|
2142
2150
|
simple_default.error(`Requence: could not report a failed surface interaction on |${params.surface}|:`, error instanceof Error ? error.message : error);
|
|
2143
2151
|
});
|
|
2144
2152
|
};
|
|
2153
|
+
const reportCallbackFailure = (payload, error) => {
|
|
2154
|
+
const parsed = durableCallbackInvocationSchema.safeParse(payload);
|
|
2155
|
+
if (!parsed.success) {
|
|
2156
|
+
simple_default.error("Requence: cannot report a failed durable callback — the invocation names no task and node");
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const { taskId, nodeId, name: name2 } = parsed.data;
|
|
2160
|
+
channelWrapper.publish(exchange, "", Buffer.from(JSON.stringify({
|
|
2161
|
+
nodeId,
|
|
2162
|
+
name: name2,
|
|
2163
|
+
message: (error instanceof Error ? error.message : String(error)).slice(0, MAX_DURABLE_CALLBACK_ERROR_CHARS)
|
|
2164
|
+
})), {
|
|
2165
|
+
contentType: "application/json",
|
|
2166
|
+
persistent: true,
|
|
2167
|
+
headers: {
|
|
2168
|
+
taskId,
|
|
2169
|
+
[DURABLE_CALLBACK_ERROR_HEADER]: true
|
|
2170
|
+
}
|
|
2171
|
+
}).catch((publishError) => {
|
|
2172
|
+
simple_default.error(`Requence: could not report the failed durable callback |${name2}| of task |${taskId}|:`, publishError instanceof Error ? publishError.message : publishError);
|
|
2173
|
+
});
|
|
2174
|
+
};
|
|
2145
2175
|
const announceResume = (params) => {
|
|
2146
2176
|
channelWrapper.publish(exchange, "", Buffer.from(JSON.stringify({ messageId: params.messageId })), {
|
|
2147
2177
|
correlationId: params.messageId,
|
|
@@ -2252,10 +2282,13 @@ function createConnection({
|
|
|
2252
2282
|
if (!message) {
|
|
2253
2283
|
return;
|
|
2254
2284
|
}
|
|
2285
|
+
let payload;
|
|
2255
2286
|
try {
|
|
2256
|
-
|
|
2287
|
+
payload = JSON.parse(message.content.toString());
|
|
2288
|
+
await runDurableCallbackInvocation(payload);
|
|
2257
2289
|
} catch (error) {
|
|
2258
2290
|
simple_default.error("Requence: durable callback threw:", error instanceof Error ? error.stack ?? error.message : error);
|
|
2291
|
+
reportCallbackFailure(payload, error);
|
|
2259
2292
|
}
|
|
2260
2293
|
safeAck(channel, message);
|
|
2261
2294
|
});
|
|
@@ -2536,7 +2569,7 @@ function createConnection({
|
|
|
2536
2569
|
begin: true,
|
|
2537
2570
|
epoch,
|
|
2538
2571
|
...buildServiceIdentityHeaders({
|
|
2539
|
-
libVersion: "javascript@0.8.3
|
|
2572
|
+
libVersion: "javascript@0.8.3",
|
|
2540
2573
|
name,
|
|
2541
2574
|
holdsSlot: !(isGeneratorHandler && mode === "CONTINUOUS")
|
|
2542
2575
|
})
|
|
@@ -3107,5 +3140,5 @@ export {
|
|
|
3107
3140
|
PayloadTooLargeError
|
|
3108
3141
|
};
|
|
3109
3142
|
|
|
3110
|
-
//# debugId=
|
|
3143
|
+
//# debugId=A75FE0293DB162D764756E2164756E21
|
|
3111
3144
|
//# sourceMappingURL=index.js.map
|