@rotorsoft/act-http 1.5.0 → 1.7.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/README.md +40 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/api/index.d.ts +1 -0
- package/dist/@types/api/index.d.ts.map +1 -1
- package/dist/@types/api/sse-wiring.d.ts +166 -0
- package/dist/@types/api/sse-wiring.d.ts.map +1 -0
- package/dist/@types/hono/index.d.ts +39 -3
- package/dist/@types/hono/index.d.ts.map +1 -1
- package/dist/@types/openapi/index.d.ts +19 -61
- package/dist/@types/openapi/index.d.ts.map +1 -1
- package/dist/@types/trpc/index.d.ts +60 -4
- package/dist/@types/trpc/index.d.ts.map +1 -1
- package/dist/api/index.cjs +104 -0
- package/dist/api/index.cjs.map +1 -1
- package/dist/api/index.js +13 -1
- package/dist/chunk-QOHCL5GN.js +161 -0
- package/dist/chunk-QOHCL5GN.js.map +1 -0
- package/dist/chunk-WGFX4G27.js +14522 -0
- package/dist/chunk-WGFX4G27.js.map +1 -0
- package/dist/hono/index.cjs +149 -1
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.js +62 -2
- package/dist/hono/index.js.map +1 -1
- package/dist/openapi/index.cjs.map +1 -1
- package/dist/openapi/index.js +3 -14516
- package/dist/openapi/index.js.map +1 -1
- package/dist/trpc/index.cjs +14634 -8
- package/dist/trpc/index.cjs.map +1 -1
- package/dist/trpc/index.js +42 -10
- package/dist/trpc/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-Z6FVXDDU.js +0 -63
- package/dist/chunk-Z6FVXDDU.js.map +0 -1
|
@@ -36,4 +36,5 @@
|
|
|
36
36
|
export type { ActorExtractor } from "./actor.js";
|
|
37
37
|
export { type ApiError, ERROR_MAP, type ErrorMapEntry, toApiError, } from "./errors.js";
|
|
38
38
|
export { type IdempotencyResult, withIdempotency } from "./idempotency.js";
|
|
39
|
+
export { DEFAULT_SSE_HEARTBEAT_MS, DEFAULT_SSE_MAX_CONNECTIONS, fireAndForget, resolveSseConfig, runSseSubscription, type SseAccounting, type SseConfig, SseConnectionCounter, type SseOptions, type SseSubscriptionFrame, } from "./sse-wiring.js";
|
|
39
40
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EACL,KAAK,QAAQ,EACb,SAAS,EACT,KAAK,aAAa,EAClB,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EACL,KAAK,QAAQ,EACb,SAAS,EACT,KAAK,aAAa,EAClB,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,KAAK,iBAAiB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,oBAAoB,GAC1B,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { BroadcastChannel } from "../sse/broadcast.js";
|
|
2
|
+
import type { BroadcastState } from "../sse/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* SSE wiring options shared by the auto-generated `trpc` and `hono`
|
|
5
|
+
* transports. When supplied, each transport emits one streaming
|
|
6
|
+
* subscription per registered state name, all reading from the same
|
|
7
|
+
* host-supplied {@link BroadcastChannel}.
|
|
8
|
+
*
|
|
9
|
+
* The host owns publication: every `app.do(...)` call site must
|
|
10
|
+
* forward the resulting snapshots to `channel.publish(...)` for
|
|
11
|
+
* subscribers to see updates. The framework deliberately doesn't
|
|
12
|
+
* auto-publish because the derived state usually needs app-specific
|
|
13
|
+
* overlays (presence, computed fields) that only the host can supply.
|
|
14
|
+
*
|
|
15
|
+
* Defaults are sized for typical business-app dashboards (dozens to
|
|
16
|
+
* hundreds of human viewers per process). Both numeric knobs are
|
|
17
|
+
* validated at transport-function construction; out-of-range values
|
|
18
|
+
* throw `RangeError` immediately so misconfiguration surfaces at
|
|
19
|
+
* startup instead of at first connection.
|
|
20
|
+
*/
|
|
21
|
+
export type SseOptions = {
|
|
22
|
+
/**
|
|
23
|
+
* The {@link BroadcastChannel} the generator's subscriptions hook
|
|
24
|
+
* into. Host owns its lifecycle; the generator does not construct
|
|
25
|
+
* one for you because the channel's `<S>` shape is app-specific and
|
|
26
|
+
* passing it in keeps the type chain typed end-to-end at the host.
|
|
27
|
+
*/
|
|
28
|
+
readonly channel: BroadcastChannel<any>;
|
|
29
|
+
/**
|
|
30
|
+
* Hard cap on concurrent open subscriptions per generator
|
|
31
|
+
* instance. The 501st simultaneous open returns `503` (Hono) or
|
|
32
|
+
* throws `TOO_MANY_REQUESTS` (tRPC) — never a silent stall.
|
|
33
|
+
*
|
|
34
|
+
* Default `500`. Validated range `[1, 10_000]`. Above 10k the
|
|
35
|
+
* framework refuses to construct: at that point the FD ceiling
|
|
36
|
+
* and per-connection memory force horizontal scaling regardless
|
|
37
|
+
* of tuning, so the right move is sticky LB + per-process cap,
|
|
38
|
+
* not a higher single-process limit.
|
|
39
|
+
*/
|
|
40
|
+
readonly maxConnections?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Keep-alive cadence in milliseconds. Most reverse proxies idle
|
|
43
|
+
* connections out after ~60 seconds; the default `30_000` stays
|
|
44
|
+
* comfortably under.
|
|
45
|
+
*
|
|
46
|
+
* Validated range `[15_000, 300_000]`. Under 15 s wastes
|
|
47
|
+
* bandwidth on a business workload; over 5 min risks proxy drops.
|
|
48
|
+
*/
|
|
49
|
+
readonly heartbeatMs?: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Resolved SSE configuration after validation and default expansion.
|
|
53
|
+
* Internal — the resolved values are what every transport actually
|
|
54
|
+
* runs against.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export type SseConfig = {
|
|
59
|
+
readonly channel: BroadcastChannel<BroadcastState>;
|
|
60
|
+
readonly maxConnections: number;
|
|
61
|
+
readonly heartbeatMs: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Default cap on concurrent open subscriptions per generator
|
|
65
|
+
* instance. Sized for typical business-app dashboards (dozens to a
|
|
66
|
+
* few hundred viewers); high enough not to surprise small
|
|
67
|
+
* deployments, low enough not to mask a missing horizontal-scale
|
|
68
|
+
* strategy on big ones.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_SSE_MAX_CONNECTIONS = 500;
|
|
71
|
+
/**
|
|
72
|
+
* Default keep-alive cadence (30 s). Sits comfortably under the
|
|
73
|
+
* 60 s idle timeout most reverse proxies impose.
|
|
74
|
+
*/
|
|
75
|
+
export declare const DEFAULT_SSE_HEARTBEAT_MS = 30000;
|
|
76
|
+
/**
|
|
77
|
+
* Validate and apply defaults to host-supplied {@link SseOptions}.
|
|
78
|
+
*
|
|
79
|
+
* Throws `RangeError` when either numeric knob is outside its
|
|
80
|
+
* documented range. Pure: same input → same output, no side
|
|
81
|
+
* effects, no I/O. Each transport calls this once at the start of
|
|
82
|
+
* `hono(...)` / `trpc(...)`.
|
|
83
|
+
*/
|
|
84
|
+
export declare function resolveSseConfig(options: SseOptions): SseConfig;
|
|
85
|
+
/**
|
|
86
|
+
* Per-generator concurrent-open counter. Constructed once inside
|
|
87
|
+
* `hono(...)` / `trpc(...)` and consulted at every subscription
|
|
88
|
+
* open: failure to acquire means the cap has been hit and the
|
|
89
|
+
* transport reports `503` / `TOO_MANY_REQUESTS` to the caller.
|
|
90
|
+
*
|
|
91
|
+
* The counter is intentionally process-local. SSE doesn't multiplex
|
|
92
|
+
* across processes; operators wanting more than `maxConnections`
|
|
93
|
+
* viewers run multiple processes behind a sticky load balancer.
|
|
94
|
+
*/
|
|
95
|
+
export declare class SseConnectionCounter {
|
|
96
|
+
readonly limit: number;
|
|
97
|
+
private _open;
|
|
98
|
+
constructor(limit: number);
|
|
99
|
+
/**
|
|
100
|
+
* Reserve one slot. Returns `true` when accepted (caller must
|
|
101
|
+
* eventually {@link release} the slot, even on error paths) or
|
|
102
|
+
* `false` when the cap is already reached.
|
|
103
|
+
*/
|
|
104
|
+
acquire(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Free a previously-{@link acquire}d slot. Always runs from a
|
|
107
|
+
* `finally` block in the transport's subscription handler so
|
|
108
|
+
* crashed handlers don't leak count.
|
|
109
|
+
*/
|
|
110
|
+
release(): void;
|
|
111
|
+
/** Currently-open count. Test/observability hook. */
|
|
112
|
+
get open(): number;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Wrap a fire-and-forget Promise-returning operation so its
|
|
116
|
+
* rejection is swallowed in place of leaking an unhandled
|
|
117
|
+
* rejection. Used by the Hono heartbeat tick (the interval callback
|
|
118
|
+
* isn't `await`ed, so its rejection has nowhere to go) and by any
|
|
119
|
+
* other "best-effort write" the transports run.
|
|
120
|
+
*
|
|
121
|
+
* Pure: same input → same output, no side effects on the caller.
|
|
122
|
+
* Returns the chained Promise so the caller can also `await`
|
|
123
|
+
* completion if they want to (the Hono heartbeat doesn't).
|
|
124
|
+
*/
|
|
125
|
+
export declare function fireAndForget<T>(op: () => Promise<T>): Promise<T | undefined>;
|
|
126
|
+
/**
|
|
127
|
+
* Frame yielded by the per-state SSE subscription generator. The
|
|
128
|
+
* initial cached state (when present) arrives as `kind: "state"`;
|
|
129
|
+
* every subsequent broadcast publication arrives as `kind: "patch"`
|
|
130
|
+
* with the channel's `PatchMessage` payload.
|
|
131
|
+
*/
|
|
132
|
+
export type SseSubscriptionFrame<S> = {
|
|
133
|
+
readonly kind: "state";
|
|
134
|
+
readonly data: S;
|
|
135
|
+
} | {
|
|
136
|
+
readonly kind: "patch";
|
|
137
|
+
readonly data: unknown;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Subscription accounting hook. Implementations have a chance to
|
|
141
|
+
* acquire a slot before the loop starts and to release it once the
|
|
142
|
+
* loop's `finally` block runs — used by the tRPC side to enforce
|
|
143
|
+
* `maxConnections` from inside the generator, where it can throw
|
|
144
|
+
* `TRPCError` cleanly. Hono enforces the cap outside the loop (so a
|
|
145
|
+
* full counter returns `503` *before* streamSSE writes headers), then
|
|
146
|
+
* passes `undefined` here.
|
|
147
|
+
*/
|
|
148
|
+
export type SseAccounting = {
|
|
149
|
+
acquire(): boolean;
|
|
150
|
+
release(): void;
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* The shared subscription loop both transports run. Optionally
|
|
154
|
+
* acquires one slot on the supplied {@link SseAccounting}, yields the
|
|
155
|
+
* cached state (when present), then forwards every channel
|
|
156
|
+
* publication as a `kind: "patch"` frame until either the consumer
|
|
157
|
+
* breaks (calling `iter.return()`) or the supplied abort signal fires.
|
|
158
|
+
*
|
|
159
|
+
* Exported as the testable seam — the Hono and tRPC sibling
|
|
160
|
+
* generators wrap this so the signal-driven cleanup and counter
|
|
161
|
+
* accounting live in one place.
|
|
162
|
+
*/
|
|
163
|
+
export declare function runSseSubscription<S extends {
|
|
164
|
+
_v: number;
|
|
165
|
+
} = any>(channel: BroadcastChannel<S>, stream_id: string, accounting: SseAccounting | undefined, signal: AbortSignal | undefined, on_cap_exceeded?: () => never): AsyncGenerator<SseSubscriptionFrame<S>>;
|
|
166
|
+
//# sourceMappingURL=sse-wiring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-wiring.d.ts","sourceRoot":"","sources":["../../../src/api/sse-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB;;;;;OAKG;IAEH,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACxC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAC;IACnD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAO/C;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CA0B/D;AAED;;;;;;;;;GASG;AACH,qBAAa,oBAAoB;IAC/B,SAAgB,KAAK,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,KAAK,CAAK;gBAEN,KAAK,EAAE,MAAM;IAIzB;;;;OAIG;IACH,OAAO,IAAI,OAAO;IAMlB;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAIf,qDAAqD;IACrD,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAE7E;AAED;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAC9B;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC5C;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,IAAI,OAAO,CAAC;IACnB,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF;;;;;;;;;;GAUG;AAEH,wBAAuB,kBAAkB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,EACtE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,aAAa,GAAG,SAAS,EACrC,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,eAAe,CAAC,EAAE,MAAM,KAAK,GAC5B,cAAc,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAgCzC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Actor, Target } from "@rotorsoft/act";
|
|
2
2
|
import type { IdempotencyStore } from "@rotorsoft/act-ops/idempotency";
|
|
3
3
|
import type { Context, MiddlewareHandler } from "hono";
|
|
4
4
|
import { Hono } from "hono";
|
|
5
|
-
import { type ActorExtractor } from "../api/index.js";
|
|
5
|
+
import { type ActorExtractor, type SseOptions } from "../api/index.js";
|
|
6
6
|
/**
|
|
7
7
|
* Hono context variables this subpath contributes. The generic on
|
|
8
8
|
* the returned middleware threads it through so routes downstream
|
|
@@ -46,6 +46,23 @@ export type HonoOptions = {
|
|
|
46
46
|
readonly store: IdempotencyStore;
|
|
47
47
|
readonly keyFrom?: (c: Context) => string | undefined;
|
|
48
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Optional SSE wiring. When set, the generator emits one
|
|
51
|
+
* `GET <basePath>/sse/<stateName>?stream=<streamId>` per unique
|
|
52
|
+
* state name in the registry. The endpoint runs the host
|
|
53
|
+
* {@link actor} extractor, looks up the streamId from
|
|
54
|
+
* `?stream=...`, opens a `text/event-stream`, yields the cached
|
|
55
|
+
* state (if any), and forwards every patch published to the
|
|
56
|
+
* shared {@link SseOptions.channel}. A heartbeat keeps proxies
|
|
57
|
+
* from idling the connection out; the per-process connection cap
|
|
58
|
+
* returns `503 Service Unavailable` (with `Retry-After: 1`) when
|
|
59
|
+
* full so operators never see silent stalls.
|
|
60
|
+
*
|
|
61
|
+
* Off by default — most APIs don't expose live state to every
|
|
62
|
+
* client, and opening one is widening both the auth and cost
|
|
63
|
+
* surface.
|
|
64
|
+
*/
|
|
65
|
+
readonly sse?: SseOptions;
|
|
49
66
|
readonly basePath?: string;
|
|
50
67
|
};
|
|
51
68
|
/**
|
|
@@ -103,7 +120,26 @@ export declare function authenticated(extractor: ActorExtractor): MiddlewareHand
|
|
|
103
120
|
* @returns A Hono app covering every registered action under
|
|
104
121
|
* `<basePath>/actions/<name>`.
|
|
105
122
|
*/
|
|
106
|
-
|
|
123
|
+
/**
|
|
124
|
+
* Structural shape of the Act surface this generator walks at
|
|
125
|
+
* runtime — the registry's action-name → owning-state map plus the
|
|
126
|
+
* `do(...)` dispatch. Letting TApp infer to the caller's concrete
|
|
127
|
+
* `Act<...>` against this structural bound keeps nested variance
|
|
128
|
+
* out of the signature and avoids `any` in either direction.
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
type ActSurface = {
|
|
133
|
+
readonly registry: {
|
|
134
|
+
actions: Record<string, {
|
|
135
|
+
readonly name: string;
|
|
136
|
+
readonly actions: Record<string, unknown>;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
do(action: string, target: Target, payload: unknown): Promise<unknown>;
|
|
140
|
+
};
|
|
141
|
+
export declare function hono<TApp extends ActSurface = ActSurface>(app: TApp, options: HonoOptions): Hono<{
|
|
107
142
|
Variables: ActMiddlewareVariables;
|
|
108
143
|
}>;
|
|
144
|
+
export {};
|
|
109
145
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hono/index.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hono/index.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EACL,KAAK,cAAc,EAMnB,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,CACf,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,CAAC,EAAE,OAAO,KACP,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,CAAC,EAAE,OAAO,KACP,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;QACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;KACvD,CAAC;IACF;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,cAAc,GACxB,iBAAiB,CAAC;IAAE,SAAS,EAAE,sBAAsB,CAAA;CAAE,CAAC,CAe1D;AAKD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH;;;;;;;;GAQG;AACH,KAAK,UAAU,GAAG;IAChB,QAAQ,CAAC,QAAQ,EAAE;QACjB,OAAO,EAAE,MAAM,CACb,MAAM,EACN;YACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SAC3C,CACF,CAAC;KACH,CAAC;IACF,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE,CAAC;AAEF,wBAAgB,IAAI,CAAC,IAAI,SAAS,UAAU,GAAG,UAAU,EACvD,GAAG,EAAE,IAAI,EACT,OAAO,EAAE,WAAW,GACnB,IAAI,CAAC;IAAE,SAAS,EAAE,sBAAsB,CAAA;CAAE,CAAC,CA0I7C"}
|
|
@@ -1,63 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @packageDocumentation
|
|
3
|
-
* @module act-http/openapi
|
|
4
|
-
*
|
|
5
|
-
* OpenAPI subpath for the auto-generated API epic (#835). Walks the
|
|
6
|
-
* registry of a built `Act` once at function call and returns a
|
|
7
|
-
* valid OpenAPI 3.1 document describing the same action surface the
|
|
8
|
-
* Hono sibling at `@rotorsoft/act-http/hono` serves. The doc is
|
|
9
|
-
* suitable for cross-language client codegen (Go, Python, Swift,
|
|
10
|
-
* Kotlin, .NET), for serving over `/openapi.json` alongside a live
|
|
11
|
-
* API, and for the broader OpenAPI tooling ecosystem (Postman,
|
|
12
|
-
* Swagger UI, Redoc, schemathesis, API gateways).
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { openapi } from "@rotorsoft/act-http/openapi";
|
|
18
|
-
*
|
|
19
|
-
* const doc = openapi(app, {
|
|
20
|
-
* info: { title: "Wolfdesk API", version: "1.0.0" },
|
|
21
|
-
* servers: [{ url: "https://api.example.com" }],
|
|
22
|
-
* });
|
|
23
|
-
* // doc: OpenAPIDocument — valid OpenAPI 3.1 object
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* **Pure data emit.** No runtime dep on Hono or tRPC. The subpath is
|
|
27
|
-
* consumable standalone for hosts that serve their API some other
|
|
28
|
-
* way (Fastify, Lambda, edge) or that only want the doc for client
|
|
29
|
-
* codegen. The emit walks the registry once at function call; cost
|
|
30
|
-
* is linear in `N actions × M Zod fields each`, single-digit
|
|
31
|
-
* milliseconds for a 100-endpoint registry.
|
|
32
|
-
*
|
|
33
|
-
* **Determinism.** Output is stable across runs given the same
|
|
34
|
-
* registry — entries land in `Object.entries(app.registry.actions)`
|
|
35
|
-
* iteration order, schemas come from the action's Zod definition,
|
|
36
|
-
* and the rest of the doc is fixed content. CI can snapshot the
|
|
37
|
-
* result to catch unintended API surface changes.
|
|
38
|
-
*
|
|
39
|
-
* The same shared utilities at `@rotorsoft/act-http/api` underwrite
|
|
40
|
-
* cross-transport consistency: the `ApiError` envelope is referenced
|
|
41
|
-
* once from `components.schemas`, every error response points at it,
|
|
42
|
-
* and the same shape ships from the tRPC and Hono siblings at
|
|
43
|
-
* runtime. A client speaking the doc and the live REST API sees one
|
|
44
|
-
* envelope per framework error.
|
|
45
|
-
*
|
|
46
|
-
* **The doc describes the Hono surface, not tRPC.** tRPC's URL
|
|
47
|
-
* shape (`POST /trpc/<procedure>`, JSON-RPC-style body framing,
|
|
48
|
-
* batching) doesn't model cleanly as OpenAPI operations — and tRPC
|
|
49
|
-
* consumers already share types directly via `typeof router`, so
|
|
50
|
-
* OpenAPI buys them nothing. The path shape this emitter produces
|
|
51
|
-
* (`POST <basePath>/actions/<name>`) matches the
|
|
52
|
-
* `@rotorsoft/act-http/hono` sibling exactly: same default
|
|
53
|
-
* `basePath`, same request/response shapes, same envelope. If the
|
|
54
|
-
* operator overrides `basePath` on the Hono adapter, they pass the
|
|
55
|
-
* same override here so the doc keeps matching the live routes by
|
|
56
|
-
* construction. tRPC and Hono can run side-by-side at different
|
|
57
|
-
* mount points on the same Act instance; the OpenAPI doc covers
|
|
58
|
-
* the REST half only.
|
|
59
|
-
*/
|
|
60
|
-
import type { Act, Schema, Schemas } from "@rotorsoft/act";
|
|
61
1
|
/**
|
|
62
2
|
* Minimal OpenAPI 3.1 document type. The emitter targets the 3.1
|
|
63
3
|
* spec; the type is intentionally a structural subset rather than
|
|
@@ -187,5 +127,23 @@ export type OpenAPIComponents = {
|
|
|
187
127
|
* empty, or if any `servers[].url` fails URL parsing after
|
|
188
128
|
* `{variable}` substitution.
|
|
189
129
|
*/
|
|
190
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Structural shape of the Act surface this emitter walks. Letting
|
|
132
|
+
* TApp infer to the caller's concrete `Act<TSchemaReg, ...>` instead
|
|
133
|
+
* of forcing it to fit a narrow framework-typed upper bound keeps
|
|
134
|
+
* the caller's variance from leaking — and avoids `any` in the
|
|
135
|
+
* signature.
|
|
136
|
+
*
|
|
137
|
+
* @internal
|
|
138
|
+
*/
|
|
139
|
+
type ActRegistryView = {
|
|
140
|
+
readonly registry: {
|
|
141
|
+
actions: Record<string, {
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly actions: Record<string, unknown>;
|
|
144
|
+
}>;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
export declare function openapi<TApp extends ActRegistryView>(app: TApp, options: OpenAPIOptions): OpenAPIDocument;
|
|
148
|
+
export {};
|
|
191
149
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/openapi/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/openapi/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -50,9 +50,10 @@
|
|
|
50
50
|
* transports never sees two error shapes for the same framework
|
|
51
51
|
* error.
|
|
52
52
|
*/
|
|
53
|
-
import type {
|
|
53
|
+
import type { Target } from "@rotorsoft/act";
|
|
54
54
|
import type { IdempotencyStore } from "@rotorsoft/act-ops/idempotency";
|
|
55
|
-
import { type
|
|
55
|
+
import { type AnyTRPCMiddlewareFunction, type AnyTRPCMutationProcedure, type AnyTRPCRootTypes, type TRPCBuiltRouter } from "@trpc/server";
|
|
56
|
+
import { type ActorExtractor, type SseOptions } from "../api/index.js";
|
|
56
57
|
/**
|
|
57
58
|
* Per-call options for {@link trpc}. The host supplies the three
|
|
58
59
|
* seams the package can't make decisions on:
|
|
@@ -94,6 +95,22 @@ export type TrpcOptions<Ctx> = {
|
|
|
94
95
|
readonly store: IdempotencyStore;
|
|
95
96
|
readonly keyFrom: (ctx: Ctx) => string | undefined;
|
|
96
97
|
};
|
|
98
|
+
/**
|
|
99
|
+
* Optional SSE wiring. When set, the generator emits one
|
|
100
|
+
* subscription per unique state name in the registry, grouped on
|
|
101
|
+
* the returned router as
|
|
102
|
+
* `router.subscribe.<stateName>.useSubscription({ stream })`.
|
|
103
|
+
* Each subscription yields `{ kind: "state", data }` once with
|
|
104
|
+
* the cached state (when present) and then `{ kind: "patch",
|
|
105
|
+
* data }` for every patch the shared
|
|
106
|
+
* {@link SseOptions.channel} publishes. The per-process
|
|
107
|
+
* connection cap surfaces as a `TOO_MANY_REQUESTS` `TRPCError`.
|
|
108
|
+
*
|
|
109
|
+
* Off by default — most APIs don't expose live state to every
|
|
110
|
+
* client, and opening one is widening both the auth and cost
|
|
111
|
+
* surface.
|
|
112
|
+
*/
|
|
113
|
+
readonly sse?: SseOptions;
|
|
97
114
|
};
|
|
98
115
|
/**
|
|
99
116
|
* Build a tRPC middleware that runs an {@link ActorExtractor} once
|
|
@@ -126,7 +143,7 @@ export type TrpcOptions<Ctx> = {
|
|
|
126
143
|
* (typically wrap them as `TRPCError({ code: "UNAUTHORIZED" })`
|
|
127
144
|
* in the extractor body).
|
|
128
145
|
*/
|
|
129
|
-
export declare function authenticated(extractor: ActorExtractor):
|
|
146
|
+
export declare function authenticated(extractor: ActorExtractor): AnyTRPCMiddlewareFunction;
|
|
130
147
|
/**
|
|
131
148
|
* Build a typed tRPC router from a built `Act` instance.
|
|
132
149
|
*
|
|
@@ -164,5 +181,44 @@ export declare function authenticated(extractor: ActorExtractor): any;
|
|
|
164
181
|
* @returns A tRPC router covering every registered action, grouped
|
|
165
182
|
* by owning state.
|
|
166
183
|
*/
|
|
167
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Structural shape of the Act surface this generator walks at
|
|
186
|
+
* runtime — the registry's action-name → owning-state map plus the
|
|
187
|
+
* `do(...)` dispatch. Letting TApp infer to the caller's concrete
|
|
188
|
+
* `Act<...>` against this structural bound (instead of forcing it
|
|
189
|
+
* to fit a narrow framework-typed upper bound) keeps the variance
|
|
190
|
+
* of nested types like `PatchHandler` from leaking — and avoids
|
|
191
|
+
* `any` in the signature.
|
|
192
|
+
*
|
|
193
|
+
* @internal
|
|
194
|
+
*/
|
|
195
|
+
type ActSurface = {
|
|
196
|
+
readonly registry: {
|
|
197
|
+
actions: Record<string, {
|
|
198
|
+
readonly name: string;
|
|
199
|
+
readonly actions: Record<string, unknown>;
|
|
200
|
+
}>;
|
|
201
|
+
};
|
|
202
|
+
do(action: string, target: Target, payload: unknown): Promise<unknown>;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Generated router type — one mutation procedure per action name in
|
|
206
|
+
* `TApp`'s registry. Mapped over the concrete action keys so
|
|
207
|
+
* `createTRPCReact<typeof router>()` sees specific procedure names
|
|
208
|
+
* (`PressKey`, `Clear`, …) instead of a wide `Record<string, ...>`
|
|
209
|
+
* index signature — which is what trips tRPC React's
|
|
210
|
+
* "useContext / Provider / useUtils collides" check on widely-typed
|
|
211
|
+
* routers.
|
|
212
|
+
*
|
|
213
|
+
* @internal
|
|
214
|
+
*/
|
|
215
|
+
type GeneratedRouter<TApp> = TRPCBuiltRouter<AnyTRPCRootTypes, TApp extends {
|
|
216
|
+
registry: {
|
|
217
|
+
actions: infer TActions;
|
|
218
|
+
};
|
|
219
|
+
} ? {
|
|
220
|
+
[K in keyof TActions]: AnyTRPCMutationProcedure;
|
|
221
|
+
} : Record<string, AnyTRPCMutationProcedure>>;
|
|
222
|
+
export declare function trpc<Ctx extends object = object, TApp extends ActSurface = ActSurface>(app: TApp, options: TrpcOptions<Ctx>): GeneratedRouter<TApp>;
|
|
223
|
+
export {};
|
|
168
224
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/trpc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/trpc/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,OAAO,KAAK,EAAS,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EACL,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EAErB,KAAK,eAAe,EAErB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,KAAK,cAAc,EAInB,KAAK,UAAU,EAGhB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,MAAM,WAAW,CAAC,GAAG,IAAI;IAC7B,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,CACf,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,GAAG,KACL,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,GAAG,KACL,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;QACjC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC;KACpD,CAAC;IACF;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,cAAc,GACxB,yBAAyB,CAmB3B;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH;;;;;;;;;;GAUG;AACH,KAAK,UAAU,GAAG;IAChB,QAAQ,CAAC,QAAQ,EAAE;QACjB,OAAO,EAAE,MAAM,CACb,MAAM,EACN;YACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SAC3C,CACF,CAAC;KACH,CAAC;IACF,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,KAAK,eAAe,CAAC,IAAI,IAAI,eAAe,CAC1C,gBAAgB,EAChB,IAAI,SAAS;IAAE,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,QAAQ,CAAA;KAAE,CAAA;CAAE,GAClD;KAAG,CAAC,IAAI,MAAM,QAAQ,GAAG,wBAAwB;CAAE,GACnD,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAC7C,CAAC;AAEF,wBAAgB,IAAI,CAClB,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3B,IAAI,SAAS,UAAU,GAAG,UAAU,EACpC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,CAsG7D"}
|
package/dist/api/index.cjs
CHANGED
|
@@ -20,7 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/api/index.ts
|
|
21
21
|
var api_exports = {};
|
|
22
22
|
__export(api_exports, {
|
|
23
|
+
DEFAULT_SSE_HEARTBEAT_MS: () => DEFAULT_SSE_HEARTBEAT_MS,
|
|
24
|
+
DEFAULT_SSE_MAX_CONNECTIONS: () => DEFAULT_SSE_MAX_CONNECTIONS,
|
|
23
25
|
ERROR_MAP: () => ERROR_MAP,
|
|
26
|
+
SseConnectionCounter: () => SseConnectionCounter,
|
|
27
|
+
fireAndForget: () => fireAndForget,
|
|
28
|
+
resolveSseConfig: () => resolveSseConfig,
|
|
29
|
+
runSseSubscription: () => runSseSubscription,
|
|
24
30
|
toApiError: () => toApiError,
|
|
25
31
|
withIdempotency: () => withIdempotency
|
|
26
32
|
});
|
|
@@ -76,9 +82,107 @@ async function withIdempotency(store, key, handler) {
|
|
|
76
82
|
}
|
|
77
83
|
return { deduped: false, result: await handler() };
|
|
78
84
|
}
|
|
85
|
+
|
|
86
|
+
// src/api/sse-wiring.ts
|
|
87
|
+
var DEFAULT_SSE_MAX_CONNECTIONS = 500;
|
|
88
|
+
var DEFAULT_SSE_HEARTBEAT_MS = 3e4;
|
|
89
|
+
var MAX_CONNECTIONS_MIN = 1;
|
|
90
|
+
var MAX_CONNECTIONS_MAX = 1e4;
|
|
91
|
+
var HEARTBEAT_MS_MIN = 15e3;
|
|
92
|
+
var HEARTBEAT_MS_MAX = 3e5;
|
|
93
|
+
function resolveSseConfig(options) {
|
|
94
|
+
const max_connections = options.maxConnections ?? DEFAULT_SSE_MAX_CONNECTIONS;
|
|
95
|
+
const heartbeat_ms = options.heartbeatMs ?? DEFAULT_SSE_HEARTBEAT_MS;
|
|
96
|
+
if (!Number.isFinite(max_connections) || max_connections < MAX_CONNECTIONS_MIN || max_connections > MAX_CONNECTIONS_MAX) {
|
|
97
|
+
throw new RangeError(
|
|
98
|
+
`sse.maxConnections must be in [${MAX_CONNECTIONS_MIN}, ${MAX_CONNECTIONS_MAX}], got ${max_connections}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (!Number.isFinite(heartbeat_ms) || heartbeat_ms < HEARTBEAT_MS_MIN || heartbeat_ms > HEARTBEAT_MS_MAX) {
|
|
102
|
+
throw new RangeError(
|
|
103
|
+
`sse.heartbeatMs must be in [${HEARTBEAT_MS_MIN}, ${HEARTBEAT_MS_MAX}], got ${heartbeat_ms}`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
channel: options.channel,
|
|
108
|
+
maxConnections: max_connections,
|
|
109
|
+
heartbeatMs: heartbeat_ms
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
var SseConnectionCounter = class {
|
|
113
|
+
limit;
|
|
114
|
+
_open = 0;
|
|
115
|
+
constructor(limit) {
|
|
116
|
+
this.limit = limit;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Reserve one slot. Returns `true` when accepted (caller must
|
|
120
|
+
* eventually {@link release} the slot, even on error paths) or
|
|
121
|
+
* `false` when the cap is already reached.
|
|
122
|
+
*/
|
|
123
|
+
acquire() {
|
|
124
|
+
if (this._open >= this.limit) return false;
|
|
125
|
+
this._open++;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Free a previously-{@link acquire}d slot. Always runs from a
|
|
130
|
+
* `finally` block in the transport's subscription handler so
|
|
131
|
+
* crashed handlers don't leak count.
|
|
132
|
+
*/
|
|
133
|
+
release() {
|
|
134
|
+
if (this._open > 0) this._open--;
|
|
135
|
+
}
|
|
136
|
+
/** Currently-open count. Test/observability hook. */
|
|
137
|
+
get open() {
|
|
138
|
+
return this._open;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function fireAndForget(op) {
|
|
142
|
+
return op().catch(() => void 0);
|
|
143
|
+
}
|
|
144
|
+
async function* runSseSubscription(channel, stream_id, accounting, signal, on_cap_exceeded) {
|
|
145
|
+
if (accounting && !accounting.acquire() && on_cap_exceeded) {
|
|
146
|
+
on_cap_exceeded();
|
|
147
|
+
}
|
|
148
|
+
const pending = [];
|
|
149
|
+
let resolve_wait = null;
|
|
150
|
+
const unsubscribe = channel.subscribe(stream_id, (msg) => {
|
|
151
|
+
pending.push(msg);
|
|
152
|
+
resolve_wait?.();
|
|
153
|
+
});
|
|
154
|
+
const on_abort = () => resolve_wait?.();
|
|
155
|
+
signal?.addEventListener("abort", on_abort);
|
|
156
|
+
try {
|
|
157
|
+
const cached = channel.get_state(stream_id);
|
|
158
|
+
if (cached !== void 0) {
|
|
159
|
+
yield { kind: "state", data: cached };
|
|
160
|
+
}
|
|
161
|
+
while (!signal?.aborted) {
|
|
162
|
+
if (pending.length === 0) {
|
|
163
|
+
await new Promise((resolve) => {
|
|
164
|
+
resolve_wait = resolve;
|
|
165
|
+
});
|
|
166
|
+
if (signal?.aborted) break;
|
|
167
|
+
}
|
|
168
|
+
const msg = pending.shift();
|
|
169
|
+
yield { kind: "patch", data: msg };
|
|
170
|
+
}
|
|
171
|
+
} finally {
|
|
172
|
+
signal?.removeEventListener("abort", on_abort);
|
|
173
|
+
unsubscribe();
|
|
174
|
+
accounting?.release();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
79
177
|
// Annotate the CommonJS export names for ESM import in node:
|
|
80
178
|
0 && (module.exports = {
|
|
179
|
+
DEFAULT_SSE_HEARTBEAT_MS,
|
|
180
|
+
DEFAULT_SSE_MAX_CONNECTIONS,
|
|
81
181
|
ERROR_MAP,
|
|
182
|
+
SseConnectionCounter,
|
|
183
|
+
fireAndForget,
|
|
184
|
+
resolveSseConfig,
|
|
185
|
+
runSseSubscription,
|
|
82
186
|
toApiError,
|
|
83
187
|
withIdempotency
|
|
84
188
|
});
|