@adhd/apigen-gateway 0.1.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/index.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const __apigen_pkg = "@adhd/apigen-gateway";
2
+ export { createGateway, type Gateway, type GatewayOptions, type GatewayHealth, type HostHealth, type HostStatus, type HostAdapter, type HostRequest, type InProcessRuntime, createInProcessHostAdapter, type BackoffPolicy, defaultBackoff, GATEWAY_ERROR_CODES, type GatewayErrorCode, GATEWAY_HTTP_STATUS, GATEWAY_GRPC_CODE, type GatewayErrorDetail, makeUnavailableError, makeDeadlineExceededError, isGatewayError, } from './lib/gateway';
package/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class m extends Error{constructor(r,i,o){super(i),this.name="ApiError",this.code=r,this.details=o,Object.setPrototypeOf(this,new.target.prototype)}toJSON(){const r={code:this.code,message:this.message};return this.details!==void 0&&(r.details=this.details),r}}const S=["unavailable","deadline_exceeded"],v={unavailable:503,deadline_exceeded:504},g={unavailable:"UNAVAILABLE",deadline_exceeded:"DEADLINE_EXCEEDED"};function l(s,r,i){const o={gatewayCode:"unavailable",host:s,operationId:r,httpStatus:v.unavailable,grpcCode:g.unavailable};return new m("internal",`host '${s}' unavailable: ${i}`,o)}function T(s,r,i){const o={gatewayCode:"deadline_exceeded",host:s,operationId:r,httpStatus:v.deadline_exceeded,grpcCode:g.deadline_exceeded};return new m("internal",`op '${r}' on host '${s}' exceeded ${i}ms deadline`,o)}function C(s){if(!(s instanceof m)||s.details==null||typeof s.details!="object")return!1;const r=s.details;return r.gatewayCode==="unavailable"||r.gatewayCode==="deadline_exceeded"}function D(s,r,i={}){let o=!1;const n=i.readyWhenStarted??!0;return{host:s,hopCost:0,async start(){o=!0},async ready(){return o&&n},async invoke(c,w){if(!o)throw l(s,c.operation.id,"not started");return r.invoke(c,w)},onExit(){return()=>{}},async stop(){o=!1}}}const k={delayMs(s){return Math.min(50*2**(s-1),5e3)},maxRestarts:0};function P(s){const r=s.defaultDeadlineMs??3e4,i=s.backoff??k,o=s.timers??{setTimeout:(e,a)=>setTimeout(e,a),clearTimeout:e=>clearTimeout(e)},n=new Map;for(const e of s.adapters){if(n.has(e.host))throw new Error(`apigen-gateway: duplicate host adapter '${e.host}'`);n.set(e.host,{adapter:e,status:"down",restarts:0,unsubscribe:()=>{},retired:!1})}function c(e){return new Promise(a=>{if(e<=0){a();return}o.setTimeout(a,e)})}async function w(e){if(e.retired)return;e.unsubscribe(),e.unsubscribe=e.adapter.onExit(t=>{x(e)}),await e.adapter.start();let a=!1;try{a=await e.adapter.ready()}catch{a=!1}e.retired||(e.status=a?"ready":"degraded")}async function x(e,a){if(e.retired)return;e.status="down";let t=0;for(;!e.retired;){if(t+=1,e.restarts+=1,i.maxRestarts>0&&t>i.maxRestarts){e.status="down";return}if(await c(i.delayMs(t)),e.retired)return;try{await e.adapter.start();const d=await e.adapter.ready();if(e.retired)return;if(d){e.status="ready";return}e.status="degraded"}catch{e.status="down"}}}async function _(e){if(e.retired||e.status==="down")return;let a=!1;try{a=await e.adapter.ready()}catch{a=!1}e.status=a?"ready":"degraded"}function y(e,a,t,d){return new Promise((E,b)=>{let u=!1;const f=new AbortController,p=()=>f.abort(t.reason);t.aborted?f.abort(t.reason):t.addEventListener("abort",p,{once:!0});const A=o.setTimeout(()=>{u||(u=!0,t.removeEventListener("abort",p),f.abort("deadline"),b(T(e.adapter.host,a.operation.id,d)))},d);e.adapter.invoke(a,f.signal).then(h=>{u||(u=!0,o.clearTimeout(A),t.removeEventListener("abort",p),E(h))},h=>{u||(u=!0,o.clearTimeout(A),t.removeEventListener("abort",p),e.status!=="ready"&&!C(h)?b(l(e.adapter.host,a.operation.id,"host not serving")):b(h))})})}return{async start(){await Promise.all([...n.values()].map(e=>w(e)))},async route(e,a){const t=e.operation.host,d=n.get(t);if(d==null)throw l(t,e.operation.id,"no adapter registered for host");if(d.status==="degraded"&&await _(d),d.status!=="ready")throw l(t,e.operation.id,`host status '${d.status}'`);const E=a??new AbortController().signal;return y(d,e,E,r)},async health(){const e={};let a=!1;return await Promise.all([...n.values()].map(async t=>{t.status==="degraded"&&await _(t),t.status==="ready"&&(a=!0),e[t.adapter.host]={host:t.adapter.host,status:t.status,hopCost:t.adapter.hopCost,restarts:t.restarts}})),{hosts:e,serving:a}},topologyCost(){let e=0;for(const a of n.values())e+=a.adapter.hopCost;return e},async stop(){await Promise.all([...n.values()].map(async e=>{e.retired=!0,e.unsubscribe(),e.status="down",await e.adapter.stop()}))}}}const R="@adhd/apigen-gateway";exports.GATEWAY_ERROR_CODES=S;exports.GATEWAY_GRPC_CODE=g;exports.GATEWAY_HTTP_STATUS=v;exports.__apigen_pkg=R;exports.createGateway=P;exports.createInProcessHostAdapter=D;exports.defaultBackoff=k;exports.isGatewayError=C;exports.makeDeadlineExceededError=T;exports.makeUnavailableError=l;
package/index.mjs ADDED
@@ -0,0 +1,229 @@
1
+ class v extends Error {
2
+ constructor(r, i, o) {
3
+ super(i), this.name = "ApiError", this.code = r, this.details = o, Object.setPrototypeOf(this, new.target.prototype);
4
+ }
5
+ /** Serialise to a plain object suitable for JSON transport. */
6
+ toJSON() {
7
+ const r = {
8
+ code: this.code,
9
+ message: this.message
10
+ };
11
+ return this.details !== void 0 && (r.details = this.details), r;
12
+ }
13
+ }
14
+ const D = ["unavailable", "deadline_exceeded"], _ = {
15
+ unavailable: 503,
16
+ deadline_exceeded: 504
17
+ }, A = {
18
+ unavailable: "UNAVAILABLE",
19
+ deadline_exceeded: "DEADLINE_EXCEEDED"
20
+ };
21
+ function h(s, r, i) {
22
+ const o = {
23
+ gatewayCode: "unavailable",
24
+ host: s,
25
+ operationId: r,
26
+ httpStatus: _.unavailable,
27
+ grpcCode: A.unavailable
28
+ };
29
+ return new v("internal", `host '${s}' unavailable: ${i}`, o);
30
+ }
31
+ function T(s, r, i) {
32
+ const o = {
33
+ gatewayCode: "deadline_exceeded",
34
+ host: s,
35
+ operationId: r,
36
+ httpStatus: _.deadline_exceeded,
37
+ grpcCode: A.deadline_exceeded
38
+ };
39
+ return new v("internal", `op '${r}' on host '${s}' exceeded ${i}ms deadline`, o);
40
+ }
41
+ function k(s) {
42
+ if (!(s instanceof v) || s.details == null || typeof s.details != "object")
43
+ return !1;
44
+ const r = s.details;
45
+ return r.gatewayCode === "unavailable" || r.gatewayCode === "deadline_exceeded";
46
+ }
47
+ function P(s, r, i = {}) {
48
+ let o = !1;
49
+ const n = i.readyWhenStarted ?? !0;
50
+ return {
51
+ host: s,
52
+ hopCost: 0,
53
+ async start() {
54
+ o = !0;
55
+ },
56
+ async ready() {
57
+ return o && n;
58
+ },
59
+ async invoke(l, w) {
60
+ if (!o)
61
+ throw h(s, l.operation.id, "not started");
62
+ return r.invoke(l, w);
63
+ },
64
+ onExit() {
65
+ return () => {
66
+ };
67
+ },
68
+ async stop() {
69
+ o = !1;
70
+ }
71
+ };
72
+ }
73
+ const y = {
74
+ delayMs(s) {
75
+ return Math.min(50 * 2 ** (s - 1), 5e3);
76
+ },
77
+ maxRestarts: 0
78
+ };
79
+ function R(s) {
80
+ const r = s.defaultDeadlineMs ?? 3e4, i = s.backoff ?? y, o = s.timers ?? {
81
+ setTimeout: (e, a) => setTimeout(e, a),
82
+ clearTimeout: (e) => clearTimeout(e)
83
+ }, n = /* @__PURE__ */ new Map();
84
+ for (const e of s.adapters) {
85
+ if (n.has(e.host))
86
+ throw new Error(`apigen-gateway: duplicate host adapter '${e.host}'`);
87
+ n.set(e.host, {
88
+ adapter: e,
89
+ status: "down",
90
+ restarts: 0,
91
+ unsubscribe: () => {
92
+ },
93
+ retired: !1
94
+ });
95
+ }
96
+ function l(e) {
97
+ return new Promise((a) => {
98
+ if (e <= 0) {
99
+ a();
100
+ return;
101
+ }
102
+ o.setTimeout(a, e);
103
+ });
104
+ }
105
+ async function w(e) {
106
+ if (e.retired)
107
+ return;
108
+ e.unsubscribe(), e.unsubscribe = e.adapter.onExit((t) => {
109
+ C(e);
110
+ }), await e.adapter.start();
111
+ let a = !1;
112
+ try {
113
+ a = await e.adapter.ready();
114
+ } catch {
115
+ a = !1;
116
+ }
117
+ e.retired || (e.status = a ? "ready" : "degraded");
118
+ }
119
+ async function C(e, a) {
120
+ if (e.retired)
121
+ return;
122
+ e.status = "down";
123
+ let t = 0;
124
+ for (; !e.retired; ) {
125
+ if (t += 1, e.restarts += 1, i.maxRestarts > 0 && t > i.maxRestarts) {
126
+ e.status = "down";
127
+ return;
128
+ }
129
+ if (await l(i.delayMs(t)), e.retired)
130
+ return;
131
+ try {
132
+ await e.adapter.start();
133
+ const d = await e.adapter.ready();
134
+ if (e.retired)
135
+ return;
136
+ if (d) {
137
+ e.status = "ready";
138
+ return;
139
+ }
140
+ e.status = "degraded";
141
+ } catch {
142
+ e.status = "down";
143
+ }
144
+ }
145
+ }
146
+ async function E(e) {
147
+ if (e.retired || e.status === "down")
148
+ return;
149
+ let a = !1;
150
+ try {
151
+ a = await e.adapter.ready();
152
+ } catch {
153
+ a = !1;
154
+ }
155
+ e.status = a ? "ready" : "degraded";
156
+ }
157
+ function x(e, a, t, d) {
158
+ return new Promise((b, m) => {
159
+ let u = !1;
160
+ const c = new AbortController(), f = () => c.abort(t.reason);
161
+ t.aborted ? c.abort(t.reason) : t.addEventListener("abort", f, { once: !0 });
162
+ const g = o.setTimeout(() => {
163
+ u || (u = !0, t.removeEventListener("abort", f), c.abort("deadline"), m(T(e.adapter.host, a.operation.id, d)));
164
+ }, d);
165
+ e.adapter.invoke(a, c.signal).then(
166
+ (p) => {
167
+ u || (u = !0, o.clearTimeout(g), t.removeEventListener("abort", f), b(p));
168
+ },
169
+ (p) => {
170
+ u || (u = !0, o.clearTimeout(g), t.removeEventListener("abort", f), e.status !== "ready" && !k(p) ? m(h(e.adapter.host, a.operation.id, "host not serving")) : m(p));
171
+ }
172
+ );
173
+ });
174
+ }
175
+ return {
176
+ async start() {
177
+ await Promise.all([...n.values()].map((e) => w(e)));
178
+ },
179
+ async route(e, a) {
180
+ const t = e.operation.host, d = n.get(t);
181
+ if (d == null)
182
+ throw h(t, e.operation.id, "no adapter registered for host");
183
+ if (d.status === "degraded" && await E(d), d.status !== "ready")
184
+ throw h(t, e.operation.id, `host status '${d.status}'`);
185
+ const b = a ?? new AbortController().signal;
186
+ return x(d, e, b, r);
187
+ },
188
+ async health() {
189
+ const e = {};
190
+ let a = !1;
191
+ return await Promise.all(
192
+ [...n.values()].map(async (t) => {
193
+ t.status === "degraded" && await E(t), t.status === "ready" && (a = !0), e[t.adapter.host] = {
194
+ host: t.adapter.host,
195
+ status: t.status,
196
+ hopCost: t.adapter.hopCost,
197
+ restarts: t.restarts
198
+ };
199
+ })
200
+ ), { hosts: e, serving: a };
201
+ },
202
+ topologyCost() {
203
+ let e = 0;
204
+ for (const a of n.values())
205
+ e += a.adapter.hopCost;
206
+ return e;
207
+ },
208
+ async stop() {
209
+ await Promise.all(
210
+ [...n.values()].map(async (e) => {
211
+ e.retired = !0, e.unsubscribe(), e.status = "down", await e.adapter.stop();
212
+ })
213
+ );
214
+ }
215
+ };
216
+ }
217
+ const S = "@adhd/apigen-gateway";
218
+ export {
219
+ D as GATEWAY_ERROR_CODES,
220
+ A as GATEWAY_GRPC_CODE,
221
+ _ as GATEWAY_HTTP_STATUS,
222
+ S as __apigen_pkg,
223
+ R as createGateway,
224
+ P as createInProcessHostAdapter,
225
+ y as defaultBackoff,
226
+ k as isGatewayError,
227
+ T as makeDeadlineExceededError,
228
+ h as makeUnavailableError
229
+ };
@@ -0,0 +1,219 @@
1
+ import { Operation, Transport } from '@adhd/apigen-core';
2
+ import { ApiError } from '@adhd/apigen-errors';
3
+
4
+ /** The distributed-system error codes the gateway raises (SPEC §13.1). */
5
+ export declare const GATEWAY_ERROR_CODES: readonly ["unavailable", "deadline_exceeded"];
6
+ /** String-union type for the gateway error codes. */
7
+ export type GatewayErrorCode = (typeof GATEWAY_ERROR_CODES)[number];
8
+ /** code → HTTP status (SPEC §9.1: unavailable = 503, deadline_exceeded = 504). */
9
+ export declare const GATEWAY_HTTP_STATUS: Record<GatewayErrorCode, number>;
10
+ /** code → gRPC status name (SPEC §9.1). */
11
+ export declare const GATEWAY_GRPC_CODE: Record<GatewayErrorCode, string>;
12
+ /**
13
+ * Structured detail attached to a gateway `ApiError` so transport adapters can read
14
+ * the distributed-system code and host without string-matching the message.
15
+ */
16
+ export interface GatewayErrorDetail {
17
+ /** The §13.1 code: `unavailable` or `deadline_exceeded`. */
18
+ gatewayCode: GatewayErrorCode;
19
+ /** The host whose op failed. */
20
+ host: string;
21
+ /** The operation id that was being routed. */
22
+ operationId: string;
23
+ /** Suggested HTTP status for the transport adapter. */
24
+ httpStatus: number;
25
+ /** Suggested gRPC status name for the transport adapter. */
26
+ grpcCode: string;
27
+ }
28
+ /**
29
+ * Build the `ApiError` raised when a host is not serving (down / not-ready / restarting).
30
+ * Maps to the core `internal` taxonomy slot but carries the real §13.1 `unavailable`
31
+ * code in `details.gatewayCode` (and a 503 hint) — the shared `ApiError` type has no
32
+ * `unavailable` member, so the gateway code travels in `details`.
33
+ */
34
+ export declare function makeUnavailableError(host: string, operationId: string, reason: string): ApiError;
35
+ /**
36
+ * Build the `ApiError` raised when a cross-host op exceeds its deadline (SPEC §13.1).
37
+ * Carries the real `deadline_exceeded` code + a 504 hint in `details`.
38
+ */
39
+ export declare function makeDeadlineExceededError(host: string, operationId: string, deadlineMs: number): ApiError;
40
+ /** Type guard: was this `ApiError` raised by the gateway failure model? */
41
+ export declare function isGatewayError(err: unknown): err is ApiError & {
42
+ details: GatewayErrorDetail;
43
+ };
44
+ /**
45
+ * A host's status as reported by the gateway's aggregate `_meta/health` (SPEC §13.1):
46
+ *
47
+ * - `ready` — the sidecar reported ready via its `_meta/health` mount; ops route.
48
+ * - `degraded` — the host is up but its readiness probe is failing (ops do NOT route).
49
+ * - `down` — the host crashed / is not spawned / is restarting; ops do NOT route.
50
+ */
51
+ export type HostStatus = 'ready' | 'degraded' | 'down';
52
+ /**
53
+ * The single request the gateway forwards to a host runtime over its IPC.
54
+ *
55
+ * Mirrors the cross-host-portable subset of a core `Call`: the operation, the bare domain
56
+ * `data`, the metadata `envelope`, and the transport tag. `signal` and the deadline are
57
+ * handled by the gateway around the adapter call — they do not cross the IPC boundary as
58
+ * part of this struct (the adapter receives `signal` as a separate argument so it can
59
+ * propagate cancellation natively).
60
+ */
61
+ export interface HostRequest {
62
+ /** The operation being invoked (carries `operation.host` → routing key). */
63
+ operation: Operation;
64
+ /** Bare domain params (the `data`-wrapper dissolved; ctx excluded). */
65
+ data: Record<string, unknown>;
66
+ /** Transport-native side-channel metadata (session, auth, …). */
67
+ envelope: Record<string, unknown>;
68
+ /** Which transport delivered the original call. */
69
+ transport: Transport;
70
+ }
71
+ /**
72
+ * A host runtime as seen by the gateway (SPEC §14.4 "gateway adapter").
73
+ *
74
+ * This is the ONE seam every host language plugs into. The in-process TS adapter
75
+ * ({@link createInProcessHostAdapter}) calls the runtime directly (zero hop); the
76
+ * out-of-process Python adapter (the `python-host` state) spawns a subprocess and speaks
77
+ * the IPC (one round-trip); the in-memory fake (tests) closes over a map. The gateway is
78
+ * written ONLY against this interface — it never knows which kind it routes to.
79
+ */
80
+ export interface HostAdapter {
81
+ /** The host tag this adapter serves (matches `operation.host`). */
82
+ readonly host: string;
83
+ /**
84
+ * Routing cost of this host, in IPC round-trips per op (SPEC §13.1 cost function):
85
+ * - `0` — in-process (zero hop; the TS fast path).
86
+ * - `1` — out-of-process sidecar (serialize → IPC → deserialize).
87
+ * The CLI's "simplest viable topology" selector minimises the sum of these.
88
+ */
89
+ readonly hopCost: 0 | 1;
90
+ /**
91
+ * Bring the host up (spawn the sidecar / initialise the in-process runtime). Resolves
92
+ * when the process exists — NOT when it is ready. Readiness is reported separately via
93
+ * {@link ready}. Idempotent: calling `start()` on an already-started adapter is a no-op.
94
+ */
95
+ start(): Promise<void>;
96
+ /**
97
+ * Readiness probe — the gateway calls this to learn whether the host's `_meta/health`
98
+ * mount reports ready (SPEC §13.1). The gateway routes the host's ops ONLY when this
99
+ * resolves `true`. Must not throw; a failed probe resolves `false`.
100
+ */
101
+ ready(): Promise<boolean>;
102
+ /**
103
+ * Forward a single operation to the host runtime and return its result.
104
+ *
105
+ * The gateway wraps this in the deadline timer and cancellation wiring — the adapter
106
+ * receives `signal` so it can abort native work (HTTP abort / kill the in-flight IPC).
107
+ * The adapter MUST reject if the host process has died mid-call (the gateway maps that
108
+ * to `unavailable` and triggers supervision/restart).
109
+ */
110
+ invoke(req: HostRequest, signal: AbortSignal): Promise<unknown>;
111
+ /**
112
+ * Register a one-shot listener fired when the host process exits unexpectedly (crash).
113
+ * The gateway uses this to drive supervision/restart (SPEC §13.1). In-process adapters
114
+ * that cannot crash may install a no-op. Returns an unsubscribe function.
115
+ */
116
+ onExit(listener: (reason: unknown) => void): () => void;
117
+ /** Tear the host down gracefully (kill the sidecar / release resources). */
118
+ stop(): Promise<void>;
119
+ }
120
+ /**
121
+ * The runtime contract the in-process adapter calls — the TS harness's `invoke`, reduced
122
+ * to the cross-host-portable shape. The real `@adhd/apigen-ts-runtime` harness satisfies
123
+ * this; tests pass a plain function.
124
+ */
125
+ export interface InProcessRuntime {
126
+ invoke(req: HostRequest, signal: AbortSignal): Promise<unknown>;
127
+ }
128
+ /**
129
+ * Create the in-process host adapter (SPEC §13.1 single-host fast path / zero hop).
130
+ *
131
+ * Wraps a same-process runtime so the gateway can treat the local TS host identically to
132
+ * a remote sidecar — but every call is a direct function invocation (`hopCost: 0`). An
133
+ * in-process runtime cannot "crash" the way a subprocess can, so `onExit` is a no-op and
134
+ * `ready()` returns the supplied readiness flag (default: ready once started).
135
+ */
136
+ export declare function createInProcessHostAdapter(host: string, runtime: InProcessRuntime, opts?: {
137
+ readyWhenStarted?: boolean;
138
+ }): HostAdapter;
139
+ /**
140
+ * Backoff policy for restarting a crashed sidecar (SPEC §13.1 supervision).
141
+ * Exponential with a cap; the gateway sleeps `delayMs(attempt)` between restart attempts.
142
+ */
143
+ export interface BackoffPolicy {
144
+ /** Delay before restart attempt `n` (1-based). */
145
+ delayMs(attempt: number): number;
146
+ /** Max consecutive restart attempts before the host is parked `down` (0 = unlimited). */
147
+ maxRestarts: number;
148
+ }
149
+ /** Default exponential backoff: 50ms · 2^(n-1), capped at 5s, unlimited restarts. */
150
+ export declare const defaultBackoff: BackoffPolicy;
151
+ /** Construction options for {@link createGateway}. */
152
+ export interface GatewayOptions {
153
+ /** The host adapters to route to, keyed by `operation.host`. */
154
+ adapters: HostAdapter[];
155
+ /** Default per-call deadline in ms when the call carries no signal-derived deadline. */
156
+ defaultDeadlineMs?: number;
157
+ /** Restart-with-backoff policy for crashed sidecars (default: {@link defaultBackoff}). */
158
+ backoff?: BackoffPolicy;
159
+ /**
160
+ * Injectable timer hooks — let tests drive deadlines deterministically without wall
161
+ * clock. Defaults to real `setTimeout`/`clearTimeout`. (CLAUDE.md §6: deterministic,
162
+ * no `sleep`.)
163
+ */
164
+ timers?: {
165
+ setTimeout: (fn: () => void, ms: number) => unknown;
166
+ clearTimeout: (handle: unknown) => void;
167
+ };
168
+ }
169
+ /** Per-host snapshot in the aggregate `_meta/health` report (SPEC §13.1). */
170
+ export interface HostHealth {
171
+ host: string;
172
+ status: HostStatus;
173
+ /** Routing cost in IPC hops (SPEC §13.1 cost function). */
174
+ hopCost: 0 | 1;
175
+ /** Consecutive crash-restart attempts since last healthy (supervision telemetry). */
176
+ restarts: number;
177
+ }
178
+ /** The gateway's aggregate `_meta/health` payload (SPEC §13.1: per-host status). */
179
+ export interface GatewayHealth {
180
+ /** Per-host status, keyed by host tag. */
181
+ hosts: Record<string, HostHealth>;
182
+ /** True when at least one host is `ready` (the surface can serve something). */
183
+ serving: boolean;
184
+ }
185
+ /**
186
+ * The gateway surface presented to a transport adapter. The transport calls `route()`
187
+ * once per inbound request; the gateway picks the owning host and applies the full §13.1
188
+ * failure model around the adapter call.
189
+ */
190
+ export interface Gateway {
191
+ /** Spawn + begin supervising every host. Resolves once all `start()` calls settle. */
192
+ start(): Promise<void>;
193
+ /**
194
+ * Route one operation to its owning host and return the result (SPEC §13).
195
+ * Applies readiness gating, deadline, cancellation and partial-availability mapping.
196
+ * @throws ApiError(`unavailable`) when the host is not serving.
197
+ * @throws ApiError(`deadline_exceeded`) when the deadline elapses.
198
+ */
199
+ route(req: HostRequest, signal?: AbortSignal): Promise<unknown>;
200
+ /** The aggregate `_meta/health` report (SPEC §13.1 per-host status). */
201
+ health(): Promise<GatewayHealth>;
202
+ /** The total routing cost (sum of hop costs) — drives topology selection (SPEC §13.1). */
203
+ topologyCost(): number;
204
+ /** Shut every host down and stop supervision. */
205
+ stop(): Promise<void>;
206
+ }
207
+ /**
208
+ * Create the sidecar gateway (SPEC §13 / §13.1).
209
+ *
210
+ * The gateway routes every operation to the host named by `operation.host`, applying the
211
+ * normative failure model:
212
+ * - **partial availability** — an op for a `down`/`degraded` host throws `unavailable`;
213
+ * other hosts keep serving (one host's failure never affects another's route).
214
+ * - **readiness gating** — an op routes only when its host's `ready()` probe passed.
215
+ * - **deadline** — each route is raced against its deadline → `deadline_exceeded`.
216
+ * - **supervision** — a host's `onExit` triggers a restart-with-backoff loop; the
217
+ * gateway process stays up and the host returns to `ready` after a successful restart.
218
+ */
219
+ export declare function createGateway(opts: GatewayOptions): Gateway;
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@adhd/apigen-gateway",
3
+ "version": "0.1.0",
4
+ "main": "./index.js",
5
+ "module": "./index.mjs",
6
+ "typings": "./index.d.ts",
7
+ "dependencies": {
8
+ "@adhd/apigen-core": "^0.1.0",
9
+ "@adhd/apigen-errors": "^0.1.0"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ }
14
+ }