@requence/service 0.8.3-edge.1191 → 0.8.4-edge.1197

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 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
@@ -2008,7 +2008,7 @@ function createConnection({
2008
2008
  clientProperties: {
2009
2009
  ...connectionOptions?.clientProperties,
2010
2010
  ...buildServiceClientProperties({
2011
- libVersion: "javascript@0.8.3-edge.1191",
2011
+ libVersion: "javascript@0.8.4-edge.1197",
2012
2012
  serviceVersion: version,
2013
2013
  connectionId,
2014
2014
  prefetch,
@@ -2569,7 +2569,7 @@ function createConnection({
2569
2569
  begin: true,
2570
2570
  epoch,
2571
2571
  ...buildServiceIdentityHeaders({
2572
- libVersion: "javascript@0.8.3-edge.1191",
2572
+ libVersion: "javascript@0.8.4-edge.1197",
2573
2573
  name,
2574
2574
  holdsSlot: !(isGeneratorHandler && mode === "CONTINUOUS")
2575
2575
  })
@@ -2805,107 +2805,113 @@ function createConnection({
2805
2805
  throw new Error("a defer key can only be used once");
2806
2806
  }
2807
2807
  activeMessageKeys.add(messageKey);
2808
- let publishIndex = 0;
2809
- const { taskId, messageId, ...options } = messageKeySchema.parse(messageKey);
2810
- const activeMessage = async () => {
2811
- if (activeMessages.has(messageId)) {
2812
- await activeMessages.get(messageId);
2813
- }
2814
- };
2815
- const registeredCallbackIds = new Set;
2816
- const outbound = createOutboundBoundary(options);
2817
- const growth = callbackGrowth.forDelivery({ taskId });
2818
- const registerCallback = (callback, path2) => {
2819
- const callbackId = crypto.randomUUID();
2820
- registeredCallbackIds.add(callbackId);
2821
- callbacks.set(callbackId, {
2822
- callback,
2823
- onCallbackInvoke: dispatchInvocation
2824
- });
2825
- growth.record(path2);
2826
- return { sourceId: connectionId, callbackId };
2827
- };
2828
- const dispatchInvocation = (invocation) => {
2829
- outbound.track(outbound.send(invocation.arg, {
2830
- outputName: invocation.outputName,
2831
- onCallback: registerCallback
2832
- }).then((data) => publish({
2833
- index: 0,
2834
- version,
2808
+ try {
2809
+ let publishIndex = 0;
2810
+ const { taskId, messageId, ...options } = messageKeySchema.parse(messageKey);
2811
+ const activeMessage = async () => {
2812
+ if (activeMessages.has(messageId)) {
2813
+ await activeMessages.get(messageId);
2814
+ }
2815
+ };
2816
+ const registeredCallbackIds = new Set;
2817
+ const outbound = createOutboundBoundary(options);
2818
+ const growth = callbackGrowth.forDelivery({ taskId });
2819
+ const registerCallback = (callback, path2) => {
2820
+ const callbackId = crypto.randomUUID();
2821
+ registeredCallbackIds.add(callbackId);
2822
+ callbacks.set(callbackId, {
2823
+ callback,
2824
+ onCallbackInvoke: dispatchInvocation
2825
+ });
2826
+ growth.record(path2);
2827
+ return { sourceId: connectionId, callbackId };
2828
+ };
2829
+ const dispatchInvocation = (invocation) => {
2830
+ outbound.track(outbound.send(invocation.arg, {
2831
+ outputName: invocation.outputName,
2832
+ onCallback: registerCallback
2833
+ }).then((data) => publish({
2834
+ index: 0,
2835
+ version,
2836
+ taskId,
2837
+ messageId,
2838
+ data: invocationPayload(invocation, data),
2839
+ outputName: invocation.outputName,
2840
+ propertyPath: invocation.propertyPath,
2841
+ ...invocationHeaders(invocation, options.nodeId)
2842
+ })));
2843
+ };
2844
+ const publishDeps = {
2845
+ outbound,
2846
+ registerCallback,
2835
2847
  taskId,
2836
2848
  messageId,
2837
- data: invocationPayload(invocation, data),
2838
- outputName: invocation.outputName,
2839
- propertyPath: invocation.propertyPath,
2840
- ...invocationHeaders(invocation, options.nodeId)
2841
- })));
2842
- };
2843
- const publishDeps = {
2844
- outbound,
2845
- registerCallback,
2846
- taskId,
2847
- messageId,
2848
- nextIndex: () => publishIndex++,
2849
- gate: activeMessage
2850
- };
2851
- const renderSurface = buildRender(publishDeps);
2852
- const sendApi = {
2853
- ...buildSendApi(publishDeps),
2854
- render(ui, props, renderOptions) {
2855
- renderSurface({
2856
- ui,
2857
- props,
2858
- surfaceId: renderOptions?.surfaceId ?? DEFAULT_SURFACE_ID
2859
- });
2860
- },
2861
- async abort(error) {
2862
- await activeMessage();
2863
- channelWrapper.publish(`${exchange}-retry`, "operator", error instanceof Error ? error.message : error, {
2864
- persistent: true,
2865
- correlationId: messageId,
2866
- expiration: 0,
2867
- priority: 0,
2868
- contentType: "text/plain",
2869
- headers: {
2870
- taskId,
2871
- index: publishIndex++,
2872
- abort: true
2849
+ nextIndex: () => publishIndex++,
2850
+ gate: activeMessage
2851
+ };
2852
+ const renderSurface = buildRender(publishDeps);
2853
+ const sendApi = {
2854
+ ...buildSendApi(publishDeps),
2855
+ render(ui, props, renderOptions) {
2856
+ renderSurface({
2857
+ ui,
2858
+ props,
2859
+ surfaceId: renderOptions?.surfaceId ?? DEFAULT_SURFACE_ID
2860
+ });
2861
+ },
2862
+ async abort(error) {
2863
+ await activeMessage();
2864
+ channelWrapper.publish(`${exchange}-retry`, "operator", error instanceof Error ? error.message : error, {
2865
+ persistent: true,
2866
+ correlationId: messageId,
2867
+ expiration: 0,
2868
+ priority: 0,
2869
+ contentType: "text/plain",
2870
+ headers: {
2871
+ taskId,
2872
+ index: publishIndex++,
2873
+ abort: true
2874
+ }
2875
+ });
2876
+ }
2877
+ };
2878
+ announceResume({ taskId, messageId });
2879
+ try {
2880
+ const result = await actor(sendApi);
2881
+ if (result != null && typeof result === "object" && ((Symbol.asyncIterator in result) || (Symbol.iterator in result))) {
2882
+ const iterable = result;
2883
+ for await (const value of iterable) {
2884
+ if (value !== undefined) {
2885
+ await sendApi.send(value);
2886
+ }
2873
2887
  }
2874
- });
2875
- }
2876
- };
2877
- announceResume({ taskId, messageId });
2878
- const result = await actor(sendApi);
2879
- if (result != null && typeof result === "object" && ((Symbol.asyncIterator in result) || (Symbol.iterator in result))) {
2880
- const iterable = result;
2881
- for await (const value of iterable) {
2882
- if (value !== undefined) {
2883
- await sendApi.send(value);
2888
+ } else if (result !== undefined) {
2889
+ await sendApi.send(result);
2890
+ }
2891
+ await new Promise((resolve2) => setImmediate(resolve2));
2892
+ await activeMessage();
2893
+ await outbound.settle();
2894
+ } catch (error) {
2895
+ registeredCallbackIds.forEach((id) => callbacks.delete(id));
2896
+ const first = error instanceof AggregateError && error.errors[0] instanceof Error ? error.errors[0] : error instanceof Error ? error : new Error(String(error));
2897
+ try {
2898
+ await sendApi.abort(first);
2899
+ } catch (abortError) {
2900
+ simple_default.error("Requence act() could not report its failure to the operator:", abortError);
2884
2901
  }
2902
+ throw error;
2885
2903
  }
2886
- } else if (result !== undefined) {
2887
- await sendApi.send(result);
2888
- }
2889
- await new Promise((resolve2) => setImmediate(resolve2));
2890
- await activeMessage();
2891
- try {
2892
- await outbound.settle();
2893
- } catch (error) {
2894
2904
  registeredCallbackIds.forEach((id) => callbacks.delete(id));
2895
- const first = error instanceof AggregateError && error.errors[0] instanceof Error ? error.errors[0] : error instanceof Error ? error : new Error(String(error));
2896
- await sendApi.abort(first);
2905
+ await publish({
2906
+ index: publishIndex++,
2907
+ version,
2908
+ messageId,
2909
+ taskId,
2910
+ done: true
2911
+ });
2912
+ } finally {
2897
2913
  activeMessageKeys.delete(messageKey);
2898
- throw error;
2899
2914
  }
2900
- registeredCallbackIds.forEach((id) => callbacks.delete(id));
2901
- await publish({
2902
- index: publishIndex++,
2903
- version,
2904
- messageId,
2905
- taskId,
2906
- done: true
2907
- });
2908
- activeMessageKeys.delete(messageKey);
2909
2915
  }
2910
2916
  };
2911
2917
  return api;
@@ -3140,5 +3146,5 @@ export {
3140
3146
  PayloadTooLargeError
3141
3147
  };
3142
3148
 
3143
- //# debugId=8CEE2F38CC05117264756E2164756E21
3149
+ //# debugId=59535360ADC9F22B64756E2164756E21
3144
3150
  //# sourceMappingURL=index.js.map