@moltzap/client 2026.508.0 → 2026.508.1

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 (62) hide show
  1. package/dist/channel-core.d.ts +118 -2
  2. package/dist/channel-core.d.ts.map +1 -1
  3. package/dist/channel-core.js +195 -27
  4. package/dist/channel-core.js.map +1 -1
  5. package/dist/channel-core.test.js +117 -26
  6. package/dist/channel-core.test.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/runtime/frame.d.ts +6 -10
  11. package/dist/runtime/frame.d.ts.map +1 -1
  12. package/dist/runtime/frame.js +4 -28
  13. package/dist/runtime/frame.js.map +1 -1
  14. package/dist/service.d.ts +18 -3
  15. package/dist/service.d.ts.map +1 -1
  16. package/dist/service.js +28 -50
  17. package/dist/service.js.map +1 -1
  18. package/dist/service.test.js +27 -25
  19. package/dist/service.test.js.map +1 -1
  20. package/dist/test-utils/channel-service-fixture.d.ts +2 -1
  21. package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
  22. package/dist/test-utils/channel-service-fixture.js +8 -0
  23. package/dist/test-utils/channel-service-fixture.js.map +1 -1
  24. package/dist/ws-client.d.ts +5 -26
  25. package/dist/ws-client.d.ts.map +1 -1
  26. package/dist/ws-client.js +71 -97
  27. package/dist/ws-client.js.map +1 -1
  28. package/dist/ws-client.test.js +40 -12
  29. package/dist/ws-client.test.js.map +1 -1
  30. package/package.json +3 -3
  31. package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +0 -2
  32. package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +0 -1
  33. package/dist/internal/__tests__/app-callback-partition-key.test.js +0 -20
  34. package/dist/internal/__tests__/app-callback-partition-key.test.js.map +0 -1
  35. package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +0 -2
  36. package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +0 -1
  37. package/dist/internal/__tests__/app-callback-partition-worker.test.js +0 -265
  38. package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +0 -1
  39. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +0 -2
  40. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +0 -1
  41. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js +0 -228
  42. package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +0 -1
  43. package/dist/internal/__tests__/app-callback-test-requests.d.ts +0 -22
  44. package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +0 -1
  45. package/dist/internal/__tests__/app-callback-test-requests.js +0 -45
  46. package/dist/internal/__tests__/app-callback-test-requests.js.map +0 -1
  47. package/dist/internal/app-callback-dispatcher-errors.d.ts +0 -50
  48. package/dist/internal/app-callback-dispatcher-errors.d.ts.map +0 -1
  49. package/dist/internal/app-callback-dispatcher-errors.js +0 -36
  50. package/dist/internal/app-callback-dispatcher-errors.js.map +0 -1
  51. package/dist/internal/app-callback-partition-key.d.ts +0 -58
  52. package/dist/internal/app-callback-partition-key.d.ts.map +0 -1
  53. package/dist/internal/app-callback-partition-key.js +0 -80
  54. package/dist/internal/app-callback-partition-key.js.map +0 -1
  55. package/dist/internal/app-callback-partition-worker.d.ts +0 -160
  56. package/dist/internal/app-callback-partition-worker.d.ts.map +0 -1
  57. package/dist/internal/app-callback-partition-worker.js +0 -141
  58. package/dist/internal/app-callback-partition-worker.js.map +0 -1
  59. package/dist/internal/app-callback-partitioned-dispatcher.d.ts +0 -180
  60. package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +0 -1
  61. package/dist/internal/app-callback-partitioned-dispatcher.js +0 -282
  62. package/dist/internal/app-callback-partitioned-dispatcher.js.map +0 -1
@@ -1,50 +0,0 @@
1
- import type { JsonRpcId } from "@moltzap/protocol";
2
- declare const PartitionLimitError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
3
- readonly _tag: "PartitionLimitError";
4
- } & Readonly<A>;
5
- /**
6
- * The active-partition count has reached `maxPartitions` (soft cap, default
7
- * 256). New partitions cannot be allocated until an existing partition is
8
- * reaped by the idle reaper or the connection scope closes. Per-tenant DoS
9
- * guard.
10
- *
11
- * Failure mode is liveness — the request is rejected without contention
12
- * with existing partitions. Reader synthesizes a `-32000 Server busy`
13
- * error response so the server retries / falls through.
14
- */
15
- export declare class PartitionLimitError extends PartitionLimitError_base<{
16
- readonly key: string;
17
- readonly activePartitions: number;
18
- readonly maxPartitions: number;
19
- readonly requestId: JsonRpcId;
20
- }> {
21
- }
22
- declare const PartitionQueueFullError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
23
- readonly _tag: "PartitionQueueFullError";
24
- } & Readonly<A>;
25
- /**
26
- * The target partition's bounded queue is at capacity (default 32). Slow
27
- * handler is backpressuring its own partition. Other partitions are
28
- * unaffected.
29
- *
30
- * Failure mode is per-partition liveness — the queue is full ONLY for this
31
- * tuple. Reader synthesizes a `-32000 Partition busy` error response;
32
- * server may retry or surface the failure to the originator (AppHost
33
- * fail-CLOSED verdict for hooks).
34
- */
35
- export declare class PartitionQueueFullError extends PartitionQueueFullError_base<{
36
- readonly key: string;
37
- readonly capacity: number;
38
- readonly requestId: JsonRpcId;
39
- }> {
40
- }
41
- /**
42
- * Discriminated union over every reason `PartitionedDispatcher.offer` can
43
- * reject an inbound request. Exhaustive: adding a new branch fails every
44
- * `match` site at compile time. Branch handlers in the reader's
45
- * frame-routing path discriminate on `_tag` to pick the right wire-error
46
- * code.
47
- */
48
- export type OfferRejected = PartitionLimitError | PartitionQueueFullError;
49
- export {};
50
- //# sourceMappingURL=app-callback-dispatcher-errors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-dispatcher-errors.d.ts","sourceRoot":"","sources":["../../src/internal/app-callback-dispatcher-errors.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;;;;AAEnD;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,yBAEvC;IACA,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,CAAC;CAAG;;;;AAEL;;;;;;;;;GASG;AACH,qBAAa,uBAAwB,SAAQ,6BAE3C;IACA,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B,CAAC;CAAG;AAEL;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,uBAAuB,CAAC"}
@@ -1,36 +0,0 @@
1
- /**
2
- * Tagged error types for the partitioned appCallback dispatcher.
3
- *
4
- * Spec: moltzap#356 — partitioned dispatch keyed on
5
- * `(taskId, conversationId, hookKind)`.
6
- *
7
- * All errors are surfaced on `PartitionedDispatcher.offer`'s typed channel.
8
- * The reader fiber translates each tag into an immediate appCallback error response
9
- * so the server's `Deferred.await` resolves deterministically (no hangs).
10
- */
11
- import { Data } from "effect";
12
- /**
13
- * The active-partition count has reached `maxPartitions` (soft cap, default
14
- * 256). New partitions cannot be allocated until an existing partition is
15
- * reaped by the idle reaper or the connection scope closes. Per-tenant DoS
16
- * guard.
17
- *
18
- * Failure mode is liveness — the request is rejected without contention
19
- * with existing partitions. Reader synthesizes a `-32000 Server busy`
20
- * error response so the server retries / falls through.
21
- */
22
- export class PartitionLimitError extends Data.TaggedError("PartitionLimitError") {
23
- }
24
- /**
25
- * The target partition's bounded queue is at capacity (default 32). Slow
26
- * handler is backpressuring its own partition. Other partitions are
27
- * unaffected.
28
- *
29
- * Failure mode is per-partition liveness — the queue is full ONLY for this
30
- * tuple. Reader synthesizes a `-32000 Partition busy` error response;
31
- * server may retry or surface the failure to the originator (AppHost
32
- * fail-CLOSED verdict for hooks).
33
- */
34
- export class PartitionQueueFullError extends Data.TaggedError("PartitionQueueFullError") {
35
- }
36
- //# sourceMappingURL=app-callback-dispatcher-errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-dispatcher-errors.js","sourceRoot":"","sources":["../../src/internal/app-callback-dispatcher-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAG9B;;;;;;;;;GASG;AACH,MAAM,OAAO,mBAAoB,SAAQ,IAAI,CAAC,WAAW,CACvD,qBAAqB,CAMrB;CAAG;AAEL;;;;;;;;;GASG;AACH,MAAM,OAAO,uBAAwB,SAAQ,IAAI,CAAC,WAAW,CAC3D,yBAAyB,CAKzB;CAAG"}
@@ -1,58 +0,0 @@
1
- /**
2
- * Pure partition-key extractor for the partitioned appCallback dispatcher.
3
- *
4
- * Spec: moltzap#356.
5
- *
6
- * `decodeFrames` is the wire boundary. It matches the JSON-RPC method to an
7
- * app-callback descriptor, validates params against that descriptor, and
8
- * projects the routing tuple. This module only receives that typed decoded
9
- * request and turns the tuple into an opaque branded `PartitionKey`.
10
- *
11
- * There is no malformed-input branch here: malformed app-callback frames are
12
- * rejected before a request can enter the dispatcher.
13
- */
14
- import { Brand } from "effect";
15
- import { type AnyTaskCallbackRpcDefinition, type DecodedRpcRequest, type JsonRpcId } from "@moltzap/protocol";
16
- /**
17
- * Branded partition-key string. Constructed only by `extractPartitionKey`.
18
- * Format is opaque to consumers; equality is value equality.
19
- */
20
- export type PartitionKey = string & Brand.Brand<"PartitionKey">;
21
- export declare const PartitionKey: Brand.Brand.Constructor<PartitionKey>;
22
- declare const PartitionKeyInvariantError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
23
- readonly _tag: "PartitionKeyInvariantError";
24
- } & Readonly<A>;
25
- export declare class PartitionKeyInvariantError extends PartitionKeyInvariantError_base<{
26
- readonly key: PartitionKey;
27
- readonly message: string;
28
- }> {
29
- }
30
- export interface AppCallbackPartitionRoute {
31
- readonly taskId: string;
32
- readonly conversationId: string;
33
- }
34
- /**
35
- * Shape the dispatcher receives after the wire decoder has matched an
36
- * app-callback descriptor and validated params against that descriptor.
37
- */
38
- export type PartitionableRequest = DecodedRpcRequest<AnyTaskCallbackRpcDefinition> & {
39
- readonly id: JsonRpcId;
40
- readonly partition: AppCallbackPartitionRoute;
41
- };
42
- /**
43
- * Project a partition key from a typed appCallback request. The wire decoder
44
- * has already validated method membership and params shape, so this function
45
- * has no malformed-input branch.
46
- */
47
- export declare function extractPartitionKey(request: PartitionableRequest): PartitionKey;
48
- /**
49
- * Inverse projection for tests + observability. Pure deconstruction of a
50
- * `PartitionKey` back into its three components.
51
- */
52
- export declare function describePartitionKey(key: PartitionKey): {
53
- readonly taskId: string;
54
- readonly conversationId: string;
55
- readonly definition: AnyTaskCallbackRpcDefinition;
56
- };
57
- export {};
58
- //# sourceMappingURL=app-callback-partition-key.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-partition-key.d.ts","sourceRoot":"","sources":["../../src/internal/app-callback-partition-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,EAAQ,MAAM,QAAQ,CAAC;AAErC,OAAO,EACL,KAAK,4BAA4B,EACjC,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;AAChE,eAAO,MAAM,YAAY,uCAAgC,CAAC;;;;AAE1D,qBAAa,0BAA2B,SAAQ,gCAE9C;IACA,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAwBL,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC9B,iBAAiB,CAAC,4BAA4B,CAAC,GAAG;IAChD,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,yBAAyB,CAAC;CAC/C,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,YAAY,CAId;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,GAAG;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,4BAA4B,CAAC;CACnD,CA6BA"}
@@ -1,80 +0,0 @@
1
- /**
2
- * Pure partition-key extractor for the partitioned appCallback dispatcher.
3
- *
4
- * Spec: moltzap#356.
5
- *
6
- * `decodeFrames` is the wire boundary. It matches the JSON-RPC method to an
7
- * app-callback descriptor, validates params against that descriptor, and
8
- * projects the routing tuple. This module only receives that typed decoded
9
- * request and turns the tuple into an opaque branded `PartitionKey`.
10
- *
11
- * There is no malformed-input branch here: malformed app-callback frames are
12
- * rejected before a request can enter the dispatcher.
13
- */
14
- import { Brand, Data } from "effect";
15
- import {} from "@moltzap/protocol";
16
- export const PartitionKey = Brand.nominal();
17
- export class PartitionKeyInvariantError extends Data.TaggedError("PartitionKeyInvariantError") {
18
- }
19
- /**
20
- * Field separator inside a `PartitionKey`. ASCII vertical bar; not legal
21
- * in a UUID and not legal in any of our `appId` values, so the encoded
22
- * key parses unambiguously.
23
- */
24
- const KEY_SEP = "|";
25
- const definitionSegments = new WeakMap();
26
- const definitionsBySegment = new Map();
27
- let nextDefinitionSegment = 0;
28
- const segmentForDefinition = (definition) => {
29
- const existing = definitionSegments.get(definition);
30
- if (existing !== undefined)
31
- return existing;
32
- const segment = `rpc${nextDefinitionSegment}`;
33
- nextDefinitionSegment += 1;
34
- definitionSegments.set(definition, segment);
35
- definitionsBySegment.set(segment, definition);
36
- return segment;
37
- };
38
- /**
39
- * Project a partition key from a typed appCallback request. The wire decoder
40
- * has already validated method membership and params shape, so this function
41
- * has no malformed-input branch.
42
- */
43
- export function extractPartitionKey(request) {
44
- return PartitionKey(`${request.partition.taskId}${KEY_SEP}${request.partition.conversationId}${KEY_SEP}${segmentForDefinition(request.definition)}`);
45
- }
46
- /**
47
- * Inverse projection for tests + observability. Pure deconstruction of a
48
- * `PartitionKey` back into its three components.
49
- */
50
- export function describePartitionKey(key) {
51
- // Method is the only field that can contain a slash; taskId is a
52
- // UUID and conversationId is a UUID or the lifecycle sentinel. Split
53
- // on the first two `KEY_SEP` occurrences only so a hypothetical
54
- // future method name with `KEY_SEP` (impossible today; ASCII `|`)
55
- // would still round-trip cleanly.
56
- const first = key.indexOf(KEY_SEP);
57
- const second = first === -1 ? -1 : key.indexOf(KEY_SEP, first + 1);
58
- if (first === -1 || second === -1) {
59
- // `extractPartitionKey` is the only constructor; an unparsable key
60
- // means a caller bypassed branding. This is a programmer error,
61
- // not a runtime user-input failure — fail loudly.
62
- throw new PartitionKeyInvariantError({
63
- key,
64
- message: `describePartitionKey: malformed PartitionKey: ${key}`,
65
- });
66
- }
67
- const definition = definitionsBySegment.get(key.slice(second + 1));
68
- if (definition === undefined) {
69
- throw new PartitionKeyInvariantError({
70
- key,
71
- message: `describePartitionKey: unknown descriptor segment: ${key}`,
72
- });
73
- }
74
- return {
75
- taskId: key.slice(0, first),
76
- conversationId: key.slice(first + 1, second),
77
- definition,
78
- };
79
- }
80
- //# sourceMappingURL=app-callback-partition-key.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-partition-key.js","sourceRoot":"","sources":["../../src/internal/app-callback-partition-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAErC,OAAO,EAIN,MAAM,mBAAmB,CAAC;AAO3B,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,EAAgB,CAAC;AAE1D,MAAM,OAAO,0BAA2B,SAAQ,IAAI,CAAC,WAAW,CAC9D,4BAA4B,CAI5B;CAAG;AAEL;;;;GAIG;AACH,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,kBAAkB,GAAG,IAAI,OAAO,EAAwC,CAAC;AAC/E,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAwC,CAAC;AAC7E,IAAI,qBAAqB,GAAG,CAAC,CAAC;AAE9B,MAAM,oBAAoB,GAAG,CAC3B,UAAwC,EAChC,EAAE;IACV,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACpD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,qBAAqB,EAAE,CAAC;IAC9C,qBAAqB,IAAI,CAAC,CAAC;IAC3B,kBAAkB,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC5C,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAiBF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAA6B;IAE7B,OAAO,YAAY,CACjB,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,cAAc,GAAG,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAChI,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAiB;IAKpD,iEAAiE;IACjE,qEAAqE;IACrE,gEAAgE;IAChE,kEAAkE;IAClE,kCAAkC;IAClC,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IACnE,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QAClC,mEAAmE;QACnE,gEAAgE;QAChE,kDAAkD;QAClD,MAAM,IAAI,0BAA0B,CAAC;YACnC,GAAG;YACH,OAAO,EAAE,iDAAiD,GAAG,EAAE;SAChE,CAAC,CAAC;IACL,CAAC;IACD,MAAM,UAAU,GAAG,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,0BAA0B,CAAC;YACnC,GAAG;YACH,OAAO,EAAE,qDAAqD,GAAG,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;QAC3B,cAAc,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC;QAC5C,UAAU;KACX,CAAC;AACJ,CAAC"}
@@ -1,160 +0,0 @@
1
- /**
2
- * Per-partition worker — bounded queue + forked fiber, owning ordering
3
- * within one `(taskId, conversationId, hookKind)` tuple.
4
- *
5
- * Spec: moltzap#356.
6
- *
7
- * Lifecycle is anchored in a per-worker `Scope.CloseableScope` provided
8
- * by the dispatcher (`app-callback-partitioned-dispatcher.ts`'s
9
- * `getOrCreatePartitionWorker` calls `Scope.fork(dispatcherScope, …)`
10
- * for each new worker and hands the child scope in via the config).
11
- * Closing the worker's scope shuts the queue, runs registered
12
- * finalizers, and interrupts the drain fiber — cascading finalization
13
- * with no orphan references on the dispatcher scope. Closing the
14
- * dispatcher scope still cascades to every worker via the parent-child
15
- * scope link from `Scope.fork`.
16
- *
17
- * Backpressure: the queue is `Queue.bounded(capacity)`. `offer` is
18
- * **non-blocking** — if the queue is full it fails with
19
- * `PartitionQueueFullError` rather than suspending the caller. The
20
- * reader fiber must NOT block; per-partition fullness is surfaced as
21
- * a typed error and translated to a wire-level error response.
22
- *
23
- * Single-producer guarantee: only the dispatcher's reader-fiber path
24
- * calls `offer` for any given partition. That makes the
25
- * `Queue.size + offer` pre-check race-free for "full" detection: if
26
- * size < capacity at the check, the subsequent `offer` cannot suspend
27
- * because no other producer can fill the queue between check and
28
- * offer. The drain fiber only consumes; it cannot push us past
29
- * capacity.
30
- *
31
- * Why `idleSince` is a `MutableRef` (synchronous cell) rather than a
32
- * `Ref`: the dispatcher's reaper inspects `idleSince._tag === "Idle"`
33
- * inside a `Ref.modify` lambda over the partition map (synchronous
34
- * lambda body — no `yield`). A `Ref.get` would require an Effect step
35
- * we cannot take inside the lambda; `MutableRef.get` is synchronous.
36
- * Producer (offer) and drain-loop sites use `MutableRef.set`. JS's
37
- * single-threaded execution model makes the `Idle ↔ Busy` transitions
38
- * race-free between fibers, and the dispatcher's atomic
39
- * `Ref.modify(partitionsRef)` is the synchronization point that
40
- * ensures snapshot+remove on idle partitions runs without an offer
41
- * landing in between.
42
- */
43
- import { Effect, Fiber, MutableRef, Scope } from "effect";
44
- import { PartitionQueueFullError } from "./app-callback-dispatcher-errors.js";
45
- import type { PartitionKey, PartitionableRequest } from "./app-callback-partition-key.js";
46
- import type { WsClientLogger } from "../ws-client.js";
47
- /**
48
- * The handler the worker drains its queue against. Mirrors the existing
49
- * `dispatchInboundServerRequest` signature in `ws-client.ts:912-965` —
50
- * the dispatcher passes an already-bound function so the worker has no
51
- * direct knowledge of the registered appCallback handler registry.
52
- *
53
- * Defects inside `handle` are caught and logged by the worker; they
54
- * never escape into the per-connection scope's failure channel.
55
- */
56
- export type PartitionHandler = (request: PartitionableRequest) => Effect.Effect<void, never>;
57
- /**
58
- * Idle / busy / retiring sum-type tracked per worker. See
59
- * `PartitionWorker.idleSince`.
60
- *
61
- * - `Idle` — drain queue empty, last handler returned at `sinceMs`.
62
- * Eligible for reap once `sinceMs` ages past `idlePartitionTtlMs`.
63
- * - `Busy` — handler running OR producer has reserved this worker
64
- * for a pending `Queue.offer`. Reaper skips Busy workers.
65
- * - `Retiring` — reaper has claimed this worker for teardown
66
- * (transition done atomically inside the reaper's `Ref.modify`
67
- * lambda over the partition map). Producer's `worker.offer` MUST
68
- * refuse to enqueue when it observes `Retiring`; otherwise the
69
- * reaper's `Scope.close` would silently drop the just-enqueued
70
- * request, leaving the server-side `Deferred.await` to time out.
71
- */
72
- export type IdleSince = {
73
- readonly _tag: "Idle";
74
- readonly sinceMs: number;
75
- } | {
76
- readonly _tag: "Busy";
77
- } | {
78
- readonly _tag: "Retiring";
79
- };
80
- /**
81
- * One partition's runtime state. Constructed by `makePartitionWorker`;
82
- * owned by the dispatcher's partition map.
83
- */
84
- export interface PartitionWorker {
85
- /** Identity. Read-only after construction. */
86
- readonly key: PartitionKey;
87
- /**
88
- * Non-blocking offer. Fails with `PartitionQueueFullError` if the
89
- * bounded queue is at capacity; never suspends. Idempotent under
90
- * shutdown — post-shutdown offers fail with the same tag (queue is
91
- * shut down, equivalent to "no capacity").
92
- */
93
- readonly offer: (request: PartitionableRequest) => Effect.Effect<void, PartitionQueueFullError>;
94
- /**
95
- * Worker fiber draining the queue via `Stream.runForEach(handle)`.
96
- * Surfaced for observability + tests; the worker's `Scope.close` is
97
- * the canonical teardown path (it triggers the queue.shutdown
98
- * finalizer and interrupts the fiber via the scope cascade).
99
- */
100
- readonly fiber: Fiber.RuntimeFiber<void, never>;
101
- /**
102
- * Idle-since tracker. `Busy` while the worker has a request queued
103
- * or a handler running; `Idle{sinceMs}` once the queue drains and
104
- * the handler returns. Stored in a `MutableRef` so the dispatcher's
105
- * reaper can read it synchronously inside its `Ref.modify` lambda
106
- * over the partition map (the atomic check that prevents a reaped
107
- * worker from receiving a concurrent offer between snapshot and
108
- * removal).
109
- */
110
- readonly idleSince: MutableRef.MutableRef<IdleSince>;
111
- /**
112
- * Live queue size for `DispatcherStats`. Reaper does not consult
113
- * this — it gates strictly on `idleSince._tag === "Idle"` to avoid
114
- * races where the queue snapshot misses an in-flight item.
115
- */
116
- readonly queueSize: Effect.Effect<number>;
117
- /**
118
- * The closeable scope owning this worker's finalizers (queue
119
- * shutdown, drain fiber). Forked from the dispatcher's scope by
120
- * `getOrCreatePartitionWorker`; closing it cleanly retires the
121
- * worker without orphaning finalizers on the parent dispatcher
122
- * scope.
123
- */
124
- readonly scope: Scope.CloseableScope;
125
- }
126
- /**
127
- * Construction parameters for one worker. Capacity and the handler are
128
- * dispatcher-level config; `scope` is the per-worker closeable scope
129
- * the dispatcher allocated via `Scope.fork`; logger is optional and
130
- * matches the existing `WsClientLogger` shape used elsewhere in the
131
- * client package.
132
- */
133
- export interface PartitionWorkerConfig {
134
- readonly key: PartitionKey;
135
- readonly capacity: number;
136
- readonly handle: PartitionHandler;
137
- /**
138
- * Per-worker scope owning every finalizer this worker registers
139
- * (queue shutdown + drain fiber). The dispatcher's reaper closes
140
- * this scope to retire the worker. Closing the parent dispatcher
141
- * scope cascades through it via `Scope.fork`.
142
- */
143
- readonly scope: Scope.CloseableScope;
144
- readonly logger?: WsClientLogger;
145
- /** Monotonic clock for tests; defaults to `Date.now`. */
146
- readonly clock?: () => number;
147
- }
148
- /**
149
- * Build one worker. Allocates a bounded queue, forks a draining fiber
150
- * via `Stream.fromQueue` + `Stream.runForEach`, and registers the
151
- * scope finalizer that shuts the queue. Defects in `handle` are
152
- * caught + logged; the fiber never fails.
153
- *
154
- * Returns `Effect<PartitionWorker, never>` — no `Scope.Scope`
155
- * requirement. All scoped allocations (queue finalizer, forked drain
156
- * fiber) are bound to the caller-provided `config.scope` via
157
- * `Scope.extend`.
158
- */
159
- export declare function makePartitionWorker(config: PartitionWorkerConfig): Effect.Effect<PartitionWorker, never>;
160
- //# sourceMappingURL=app-callback-partition-worker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-partition-worker.d.ts","sourceRoot":"","sources":["../../src/internal/app-callback-partition-worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,OAAO,EAAS,MAAM,EAAE,KAAK,EAAE,UAAU,EAAS,KAAK,EAAU,MAAM,QAAQ,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAC9E,OAAO,KAAK,EACV,YAAY,EACZ,oBAAoB,EACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,oBAAoB,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,SAAS,GACjB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAElC;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,CACd,OAAO,EAAE,oBAAoB,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC;IAClD;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAChD;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACrD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1C;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC;CACtC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,qBAAqB,GAC5B,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,CAmIvC"}
@@ -1,141 +0,0 @@
1
- /**
2
- * Per-partition worker — bounded queue + forked fiber, owning ordering
3
- * within one `(taskId, conversationId, hookKind)` tuple.
4
- *
5
- * Spec: moltzap#356.
6
- *
7
- * Lifecycle is anchored in a per-worker `Scope.CloseableScope` provided
8
- * by the dispatcher (`app-callback-partitioned-dispatcher.ts`'s
9
- * `getOrCreatePartitionWorker` calls `Scope.fork(dispatcherScope, …)`
10
- * for each new worker and hands the child scope in via the config).
11
- * Closing the worker's scope shuts the queue, runs registered
12
- * finalizers, and interrupts the drain fiber — cascading finalization
13
- * with no orphan references on the dispatcher scope. Closing the
14
- * dispatcher scope still cascades to every worker via the parent-child
15
- * scope link from `Scope.fork`.
16
- *
17
- * Backpressure: the queue is `Queue.bounded(capacity)`. `offer` is
18
- * **non-blocking** — if the queue is full it fails with
19
- * `PartitionQueueFullError` rather than suspending the caller. The
20
- * reader fiber must NOT block; per-partition fullness is surfaced as
21
- * a typed error and translated to a wire-level error response.
22
- *
23
- * Single-producer guarantee: only the dispatcher's reader-fiber path
24
- * calls `offer` for any given partition. That makes the
25
- * `Queue.size + offer` pre-check race-free for "full" detection: if
26
- * size < capacity at the check, the subsequent `offer` cannot suspend
27
- * because no other producer can fill the queue between check and
28
- * offer. The drain fiber only consumes; it cannot push us past
29
- * capacity.
30
- *
31
- * Why `idleSince` is a `MutableRef` (synchronous cell) rather than a
32
- * `Ref`: the dispatcher's reaper inspects `idleSince._tag === "Idle"`
33
- * inside a `Ref.modify` lambda over the partition map (synchronous
34
- * lambda body — no `yield`). A `Ref.get` would require an Effect step
35
- * we cannot take inside the lambda; `MutableRef.get` is synchronous.
36
- * Producer (offer) and drain-loop sites use `MutableRef.set`. JS's
37
- * single-threaded execution model makes the `Idle ↔ Busy` transitions
38
- * race-free between fibers, and the dispatcher's atomic
39
- * `Ref.modify(partitionsRef)` is the synchronization point that
40
- * ensures snapshot+remove on idle partitions runs without an offer
41
- * landing in between.
42
- */
43
- import { Cause, Effect, Fiber, MutableRef, Queue, Scope, Stream } from "effect";
44
- import { PartitionQueueFullError } from "./app-callback-dispatcher-errors.js";
45
- /**
46
- * Build one worker. Allocates a bounded queue, forks a draining fiber
47
- * via `Stream.fromQueue` + `Stream.runForEach`, and registers the
48
- * scope finalizer that shuts the queue. Defects in `handle` are
49
- * caught + logged; the fiber never fails.
50
- *
51
- * Returns `Effect<PartitionWorker, never>` — no `Scope.Scope`
52
- * requirement. All scoped allocations (queue finalizer, forked drain
53
- * fiber) are bound to the caller-provided `config.scope` via
54
- * `Scope.extend`.
55
- */
56
- export function makePartitionWorker(config) {
57
- const clock = config.clock ?? Date.now;
58
- return Scope.extend(Effect.gen(function* () {
59
- const queue = yield* Queue.bounded(config.capacity);
60
- const idleSince = MutableRef.make({
61
- _tag: "Idle",
62
- sinceMs: clock(),
63
- });
64
- // Drain loop: take → mark Busy → run handler (catching defects)
65
- // → mark Idle if queue is now empty. `Stream.fromQueue` ends
66
- // naturally when the queue is shut down (Scope finalizer below),
67
- // so the fiber returns cleanly without an explicit interrupt for
68
- // the happy path.
69
- const drainEffect = Stream.fromQueue(queue).pipe(Stream.runForEach((request) => Effect.gen(function* () {
70
- MutableRef.set(idleSince, { _tag: "Busy" });
71
- yield* config.handle(request).pipe(Effect.catchAllCause((cause) => Effect.sync(() => {
72
- config.logger?.warn(`appCallback partition worker handler defected (key=${config.key})`, Cause.pretty(cause));
73
- })));
74
- const remaining = yield* Queue.size(queue).pipe(Effect.catchAllCause(() => Effect.succeed(0)));
75
- if (remaining === 0) {
76
- MutableRef.set(idleSince, {
77
- _tag: "Idle",
78
- sinceMs: clock(),
79
- });
80
- }
81
- })), Effect.catchAllCause((cause) => Effect.sync(() => {
82
- config.logger?.warn(`appCallback partition worker fiber exited (key=${config.key})`, Cause.pretty(cause));
83
- })), Effect.asVoid);
84
- const fiber = yield* Effect.forkScoped(drainEffect);
85
- // Shut the queue when the worker scope closes. `Effect.forkScoped`
86
- // already covers fiber interruption; queue.shutdown is the
87
- // belt-and-braces guarantee that the running stream observes the
88
- // shutdown and returns even if the fiber is mid-handler.
89
- yield* Effect.addFinalizer(() => Queue.shutdown(queue).pipe(Effect.catchAllCause(() => Effect.void)));
90
- const offer = (request) => Effect.gen(function* () {
91
- const size = yield* Queue.size(queue).pipe(
92
- // `Queue.size` on a shut-down queue surfaces interrupt; treat
93
- // every failure as "queue unavailable" → full-error tag.
94
- Effect.catchAllCause(() => Effect.succeed(config.capacity)));
95
- if (size >= config.capacity) {
96
- return yield* Effect.fail(new PartitionQueueFullError({
97
- key: config.key,
98
- capacity: config.capacity,
99
- requestId: request.id,
100
- }));
101
- }
102
- // Atomic CAS: transition Idle/Busy → Busy, but refuse if the
103
- // dispatcher's reaper has already claimed this worker for
104
- // teardown (`Retiring`). The reaper sets `Retiring`
105
- // synchronously inside its `Ref.modify(partitionsRef, …)`
106
- // lambda, so once we observe `Retiring` here, the worker has
107
- // already been removed from the partition map and its scope
108
- // is being closed. Failing without enqueueing is the correct
109
- // and only safe move — a queued item would be silently
110
- // dropped when `Scope.close` fires `Queue.shutdown`.
111
- const claim = MutableRef.updateAndGet(idleSince, (current) => current._tag === "Retiring" ? current : { _tag: "Busy" });
112
- if (claim._tag === "Retiring") {
113
- return yield* Effect.fail(new PartitionQueueFullError({
114
- key: config.key,
115
- capacity: config.capacity,
116
- requestId: request.id,
117
- }));
118
- }
119
- const accepted = yield* Queue.offer(queue, request).pipe(Effect.catchAllCause(() => Effect.succeed(false)));
120
- if (!accepted) {
121
- // Queue shut down between size-check and offer — surface as
122
- // `PartitionQueueFullError` so the reader's tag-discrimination
123
- // path treats it identically to "queue full".
124
- return yield* Effect.fail(new PartitionQueueFullError({
125
- key: config.key,
126
- capacity: config.capacity,
127
- requestId: request.id,
128
- }));
129
- }
130
- });
131
- return {
132
- key: config.key,
133
- offer,
134
- fiber,
135
- idleSince,
136
- queueSize: Queue.size(queue).pipe(Effect.catchAllCause(() => Effect.succeed(0))),
137
- scope: config.scope,
138
- };
139
- }), config.scope);
140
- }
141
- //# sourceMappingURL=app-callback-partition-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-callback-partition-worker.js","sourceRoot":"","sources":["../../src/internal/app-callback-partition-worker.ts"],"names":[],"mappings":"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"}