stitchkit 0.52.0 → 0.53.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 CHANGED
@@ -221,7 +221,7 @@ const socket = await createSocketIOServer<ServerToClientEvents, ClientToServerEv
221
221
  cors: { origin: 'https://app.example.com' },
222
222
  })
223
223
 
224
- socket.io.on('connection', (s) => { /* rooms, handshake auth — your domain logic */ })
224
+ socket.io.on('connection', (s) => { /* rooms — your domain logic; typed handshake auth via `handshake` */ })
225
225
 
226
226
  createServer({
227
227
  services: [service],
@@ -61,6 +61,13 @@ export interface HttpClientConfig {
61
61
  * Default `false`.
62
62
  */
63
63
  trace?: boolean;
64
+ /**
65
+ * Dial a unix domain socket instead of TCP (Bun runtime only — other
66
+ * runtimes ignore the option and dial `baseUrl` over TCP). `baseUrl` stays
67
+ * required as the path/prefix source; its host is ignored by the socket
68
+ * transport, so `baseUrl: 'http://localhost'` is the idiomatic pairing.
69
+ */
70
+ unix?: string;
64
71
  }
65
72
  type ParamValue = string | number | boolean | undefined;
66
73
  type ParamArrayValue = Array<string | number>;
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
@@ -91,6 +91,34 @@ export interface SocketIOClientConfig<TServerEvents extends SocketEventMap = Soc
91
91
  * cycle. Only the first emitted argument of an event is retained.
92
92
  */
93
93
  retain?: Array<keyof TServerEvents & string>;
94
+ /**
95
+ * Observe handshake/connection failures (`connect_error`). `terminal: true`
96
+ * means socket.io will NOT retry on its own — that is how a server-side
97
+ * handshake gate rejection (`io.use` middleware / the stitchkit `handshake`
98
+ * option, `data.code === 'handshake_rejected'`) arrives, unlike a
99
+ * transport-level failure which keeps retrying. On a terminal error the
100
+ * client resets its connection intent, so a later `connect()` starts a fresh
101
+ * attempt and re-reads a function-form `auth` — the recovery path after
102
+ * rotating a rejected token. For transport-level failures (server down) the
103
+ * hook fires once **per retry attempt** — debounce before wiring it to
104
+ * user-facing alerts.
105
+ */
106
+ onConnectError?: (error: {
107
+ message: string;
108
+ data?: unknown;
109
+ terminal: boolean;
110
+ }) => void;
111
+ /**
112
+ * Observe every emit dropped because the socket was disconnected (the
113
+ * central alternative to guarding each call with `connected`). Receives the
114
+ * **wire** arguments — for a validated `RealtimeClient` that is the
115
+ * Zod-parsed values plus the wrapped ack callback, not the caller's original
116
+ * arguments. The drop itself is also reported by `emit` returning `false`.
117
+ */
118
+ onDroppedEmit?: (dropped: {
119
+ event: string;
120
+ args: unknown[];
121
+ }) => void;
94
122
  }
95
123
  export interface SocketIOClient<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap> {
96
124
  /** Create the socket (if absent) and connect. Idempotent. */
@@ -105,8 +133,16 @@ export interface SocketIOClient<TServerEvents extends SocketEventMap, TClientEve
105
133
  * survives a `disconnect()` / `connect()` cycle.
106
134
  */
107
135
  on<E extends keyof TServerEvents & string>(event: E, handler: TServerEvents[E]): () => void;
108
- /** Emit a client → server event. No-op while disconnected. */
109
- emit<E extends keyof TClientEvents & string>(event: E, ...args: Parameters<TClientEvents[E]>): void;
136
+ /**
137
+ * Emit a client server event. Returns `true` when the event was handed to
138
+ * the transport (not a delivery guarantee) and `false` when it was dropped
139
+ * because the socket is disconnected — including the window while the lazy
140
+ * peer is still loading right after `connect()`. A drop also fires the
141
+ * `onDroppedEmit` hook. The default stays a drop (no buffering): after a
142
+ * reconnect the durable subscriptions replay state deterministically instead
143
+ * of an unordered backlog of stale emits.
144
+ */
145
+ emit<E extends keyof TClientEvents & string>(event: E, ...args: Parameters<TClientEvents[E]>): boolean;
110
146
  /**
111
147
  * Subscribe to connection up/down changes. Returns an unsubscribe. On a
112
148
  * disconnect the listener also receives the Socket.IO **reason** (e.g.
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/browser/socket-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AA0D5B;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,SAAS,CAAC;AAEvC,MAAM,WAAW,oBAAoB,CAAC,aAAa,SAAS,cAAc,GAAG,cAAc;IACzF,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,IAAI,CAAC,EACD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACvE,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IAClD;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5C,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,uDAAuD;IACvD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,2BAA2B,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7C;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,cAAc,CAC7B,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc;IAEpC,6DAA6D;IAC7D,OAAO,IAAI,IAAI,CAAC;IAChB,+CAA+C;IAC/C,UAAU,IAAI,IAAI,CAAC;IACnB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;OAIG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,aAAa,GAAG,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC;IAC5F,8DAA8D;IAC9D,IAAI,CAAC,CAAC,SAAS,MAAM,aAAa,GAAG,MAAM,EACzC,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GACpC,IAAI,CAAC;IACR;;;;;;OAMG;IACH,kBAAkB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACzF;AAED,MAAM,WAAW,cAAc,CAC7B,eAAe,SAAS,qBAAqB,EAC7C,eAAe,SAAS,qBAAqB,CAC7C,SAAQ,uBAAuB,CAAC,eAAe,EAAE,eAAe,CAAC;IACjE,OAAO,IAAI,IAAI,CAAC;IAChB,UAAU,IAAI,IAAI,CAAC;IACnB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,kBAAkB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACzF;AAED,MAAM,WAAW,qBAAqB,CAAC,eAAe,SAAS,qBAAqB,CAClF,SAAQ,oBAAoB,CAAC;KAC1B,MAAM,IAAI,MAAM,eAAe,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI;CAChE,CAAC;IACF,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,wBAAgB,oBAAoB,CAClC,KAAK,CAAC,eAAe,SAAS,qBAAqB,EACnD,KAAK,CAAC,eAAe,SAAS,qBAAqB,EAEnD,QAAQ,EAAE,gBAAgB,CAAC,eAAe,EAAE,eAAe,CAAC,EAC5D,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,qBAAqB,CAAC,eAAe,CAAC,GACxE,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC,CAiClD;AAED,wBAAgB,oBAAoB,CAClC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,MAAM,EAAE,oBAAoB,CAAC,aAAa,CAAC,GAAG,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAqK3F"}
1
+ {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/browser/socket-io.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,8 @@ import {
27
27
  __require,
28
28
  isRecord,
29
29
  parseTrailingWildcard,
30
- resolveTraceContext
30
+ resolveTraceContext,
31
+ transportResult
31
32
  } from "./index-bmnhqtya.js";
32
33
 
33
34
  // src/server/multipart.ts
@@ -1913,7 +1914,7 @@ function createValidatedRealtimeSocket({
1913
1914
  acknowledgement(ack.data);
1914
1915
  });
1915
1916
  }
1916
- emitTarget(event, ...wireArgs);
1917
+ return emitTarget(event, ...wireArgs) !== false;
1917
1918
  }
1918
1919
  };
1919
1920
  }
@@ -2035,6 +2036,43 @@ async function importPeer(load, pkg) {
2035
2036
  throw err;
2036
2037
  }
2037
2038
  }
2039
+ function handshakeRejection(message) {
2040
+ const error = new Error(message);
2041
+ error.data = { code: "handshake_rejected" };
2042
+ return error;
2043
+ }
2044
+ function handshakeMiddleware(handshake) {
2045
+ return (socket, next) => {
2046
+ let parsed;
2047
+ try {
2048
+ parsed = handshake.schema.safeParse(socket.handshake.auth);
2049
+ } catch (error) {
2050
+ console.error("[stitchkit] handshake schema threw — use a synchronous schema", error);
2051
+ next(handshakeRejection("handshake auth failed validation"));
2052
+ return;
2053
+ }
2054
+ if (!parsed.success) {
2055
+ next(handshakeRejection("handshake auth failed validation"));
2056
+ return;
2057
+ }
2058
+ const verify = handshake.verify;
2059
+ Promise.resolve().then(() => {
2060
+ if (verify)
2061
+ return verify(parsed.data, { handshake: socket.handshake });
2062
+ return transportResult(parsed.data);
2063
+ }).then((identity) => {
2064
+ if (identity === null || identity === undefined) {
2065
+ next(handshakeRejection("handshake rejected"));
2066
+ return;
2067
+ }
2068
+ socket.data = identity;
2069
+ next();
2070
+ }, (error) => {
2071
+ console.error("[stitchkit] handshake verify rejected", error);
2072
+ next(handshakeRejection("handshake rejected"));
2073
+ });
2074
+ };
2075
+ }
2038
2076
  async function createSocketIOServer(config) {
2039
2077
  const path = config.path ?? "/socket.io/";
2040
2078
  const onBun = "Bun" in globalThis;
@@ -2084,6 +2122,8 @@ async function createSocketIOServer(config) {
2084
2122
  pingInterval,
2085
2123
  allowRequest
2086
2124
  });
2125
+ if (config.handshake)
2126
+ io.use(handshakeMiddleware(config.handshake));
2087
2127
  const lifecycle = {
2088
2128
  beginShutdown() {
2089
2129
  accepting = false;
package/dist/index.js CHANGED
@@ -397,19 +397,21 @@ function parseApiErrorBody(body) {
397
397
  hint: typeof error.hint === "string" ? error.hint : undefined
398
398
  };
399
399
  }
400
- function createRetryAwareFetch() {
400
+ function createRetryAwareFetch(unix) {
401
401
  const runtimeFetch = globalThis.fetch.bind(globalThis);
402
402
  let attempt = 0;
403
403
  return (input, init) => {
404
404
  attempt += 1;
405
- if (attempt === 1) {
405
+ if (unix === undefined && attempt === 1) {
406
406
  return runtimeFetch(input, init);
407
407
  }
408
- if (!(input instanceof Request))
409
- return runtimeFetch(input, init);
408
+ if (!(input instanceof Request)) {
409
+ return runtimeFetch(input, unix === undefined ? init : { ...init, unix });
410
+ }
410
411
  const streamedBody = input.body ? { body: input.body, duplex: "half" } : {};
411
412
  return runtimeFetch(input.url, {
412
413
  ...init,
414
+ ...unix !== undefined && { unix },
413
415
  method: input.method,
414
416
  headers: input.headers,
415
417
  ...streamedBody,
@@ -490,7 +492,7 @@ function createHttpClient(config) {
490
492
  async function request(method, url, data, options = {}) {
491
493
  const cancellation = createRequestCancellation(options.signal, options.timeout ?? config.timeout ?? 30000);
492
494
  const kyOptions = {
493
- fetch: createRetryAwareFetch(),
495
+ fetch: createRetryAwareFetch(config.unix),
494
496
  timeout: false,
495
497
  signal: cancellation.signal
496
498
  };
@@ -991,7 +993,7 @@ function createValidatedRealtimeSocket({
991
993
  acknowledgement(ack.data);
992
994
  });
993
995
  }
994
- emitTarget(event, ...wireArgs);
996
+ return emitTarget(event, ...wireArgs) !== false;
995
997
  }
996
998
  };
997
999
  }
@@ -1128,6 +1130,16 @@ function createSocketIOClient(config) {
1128
1130
  socket.io.on("reconnect_failed", () => {
1129
1131
  desiredConnected = false;
1130
1132
  });
1133
+ socket.on("connect_error", (error) => {
1134
+ const terminal = socket !== null && !socket.active;
1135
+ if (terminal)
1136
+ desiredConnected = false;
1137
+ config.onConnectError?.({
1138
+ message: error.message,
1139
+ data: Reflect.get(error, "data"),
1140
+ terminal
1141
+ });
1142
+ });
1131
1143
  if (retained) {
1132
1144
  for (const name of retainNames) {
1133
1145
  socket.on(name, (payload) => retained.record(name, payload));
@@ -1181,10 +1193,13 @@ function createSocketIOClient(config) {
1181
1193
  };
1182
1194
  },
1183
1195
  emit(event, ...args) {
1184
- if (!socket?.connected)
1185
- return;
1186
1196
  const name = event;
1197
+ if (!socket?.connected) {
1198
+ config.onDroppedEmit?.({ event: name, args });
1199
+ return false;
1200
+ }
1187
1201
  socket.emit(name, ...args);
1202
+ return true;
1188
1203
  },
1189
1204
  onConnectionChange(listener) {
1190
1205
  connectionListeners.add(listener);
package/dist/node.d.ts CHANGED
@@ -12,6 +12,6 @@ export { type NodeRuntimeServer, type NodeServerConfig, type NodeServerHandle, t
12
12
  export { bindProcessSignals, type ProcessSignalName, type ProcessSignalsBinding, type ProcessSignalsErrorPhase, type ProcessSignalsOptions, type ShutdownTarget, type SignalSource, } from './server/process-signals';
13
13
  export { bindRealtimeServer, type RealtimeServer, type RealtimeServerConnection, type RealtimeServerHandle, } from './server/realtime';
14
14
  export { type ManagedServerHandle, type ShutdownOptions, ShutdownOptionsSchema, type ShutdownResult, ShutdownResultSchema, type ShutdownState, ShutdownStateSchema, type ShutdownStatus, ShutdownStatusSchema, } from './server/shutdown';
15
- export { createNodeSocketIOServer as createSocketIOServer, type NodeSocketIOServerHandle as SocketIOServerHandle, type SocketIORequestPolicy, type SocketIOServerConfig, } from './server/socket-io-node';
15
+ export { createNodeSocketIOServer as createSocketIOServer, type NodeSocketIOServerHandle as SocketIOServerHandle, type SocketIOHandshakeConfig, type SocketIORequestPolicy, type SocketIOServerConfig, } from './server/socket-io-node';
16
16
  export type { EffectiveScope, FetchComposition, FetchHandler, HandlerConfig, LoggingConfig, LogOutcome, RawRoute, RawRouteContext, ScopeContexts, ScopedHandlers, ServiceDef, } from './server/types';
17
17
  //# sourceMappingURL=node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,6BAA6B,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,SAAS,EACT,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,cAAc,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,IAAI,oBAAoB,EAChD,KAAK,wBAAwB,IAAI,oBAAoB,EACrD,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,UAAU,EACV,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,EACd,UAAU,GACX,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,6BAA6B,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,SAAS,EACT,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,4BAA4B,EACjC,KAAK,sBAAsB,EAC3B,KAAK,WAAW,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,kBAAkB,EAClB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,YAAY,GAClB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,kBAAkB,EAClB,KAAK,cAAc,EACnB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,cAAc,EACnB,oBAAoB,EACpB,KAAK,aAAa,EAClB,mBAAmB,EACnB,KAAK,cAAc,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,wBAAwB,IAAI,oBAAoB,EAChD,KAAK,wBAAwB,IAAI,oBAAoB,EACrD,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,aAAa,EACb,UAAU,EACV,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,EACd,UAAU,GACX,MAAM,gBAAgB,CAAC"}
package/dist/node.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  createHandler,
9
9
  createServerLifecycle,
10
10
  createSocketIOServer
11
- } from "./index-d6jj3cp5.js";
11
+ } from "./index-6jngw7hw.js";
12
12
  import {
13
13
  createImplement,
14
14
  createImplementRegistry,
@@ -23,7 +23,14 @@ export declare const REALTIME_TARGET_FORMS: ({
23
23
  })[];
24
24
  export interface ValidatedRealtimeSocket<TInbound extends RealtimeEventRegistry, TOutbound extends RealtimeEventRegistry> {
25
25
  on<TEvent extends keyof TInbound & string>(event: TEvent, handler: RealtimeEventHandler<TInbound[TEvent]>): () => void;
26
- emit<TEvent extends keyof TOutbound & string>(event: TEvent, ...args: RealtimeEmitArguments<TOutbound[TEvent]>): void;
26
+ /**
27
+ * Emit a validated event. Three outcomes, in order: a local contract
28
+ * violation **throws** (validation runs even while disconnected); an emit a
29
+ * disconnected browser transport drops returns `false`; otherwise `true` —
30
+ * accepted by the transport, which is not a delivery guarantee. Server
31
+ * targets always return `true` (an empty room is not a drop).
32
+ */
33
+ emit<TEvent extends keyof TOutbound & string>(event: TEvent, ...args: RealtimeEmitArguments<TOutbound[TEvent]>): boolean;
27
34
  }
28
35
  export declare function createValidatedRealtimeSocket<const TInbound extends RealtimeEventRegistry, const TOutbound extends RealtimeEventRegistry>({ target, inbound, outbound, inboundDirection, outboundDirection, onRejected, logger, subscribe, }: ValidatedRealtimeSocketOptions<TInbound, TOutbound>): ValidatedRealtimeSocket<TInbound, TOutbound>;
29
36
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/realtime/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,qBAAqB,EAErB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EAEvB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAGpB,UAAU,8BAA8B,CACtC,QAAQ,SAAS,qBAAqB,EACtC,SAAS,SAAS,qBAAqB;IAEvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,QAAQ,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;IACpB,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CAClF;AAED,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB;;;;;;;;;IAOhC,CAAC;AAEH,MAAM,WAAW,uBAAuB,CACtC,QAAQ,SAAS,qBAAqB,EACtC,SAAS,SAAS,qBAAqB;IAEvC,EAAE,CAAC,MAAM,SAAS,MAAM,QAAQ,GAAG,MAAM,EACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAC9C,MAAM,IAAI,CAAC;IACd,IAAI,CAAC,MAAM,SAAS,MAAM,SAAS,GAAG,MAAM,EAC1C,KAAK,EAAE,MAAM,EACb,GAAG,IAAI,EAAE,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAChD,IAAI,CAAC;CACT;AA+DD,wBAAgB,6BAA6B,CAC3C,KAAK,CAAC,QAAQ,SAAS,qBAAqB,EAC5C,KAAK,CAAC,SAAS,SAAS,qBAAqB,EAC7C,EACA,MAAM,EACN,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,SAAS,GACV,EAAE,8BAA8B,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,uBAAuB,CAC9E,QAAQ,EACR,SAAS,CACV,CA0HA"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/realtime/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,qBAAqB,EAErB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EAEvB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAGpB,UAAU,8BAA8B,CACtC,QAAQ,SAAS,qBAAqB,EACtC,SAAS,SAAS,qBAAqB;IAEvC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,QAAQ,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;IACpB,gBAAgB,EAAE,uBAAuB,CAAC;IAC1C,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CAClF;AAED,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB;;;;;;;;;IAOhC,CAAC;AAEH,MAAM,WAAW,uBAAuB,CACtC,QAAQ,SAAS,qBAAqB,EACtC,SAAS,SAAS,qBAAqB;IAEvC,EAAE,CAAC,MAAM,SAAS,MAAM,QAAQ,GAAG,MAAM,EACvC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAC9C,MAAM,IAAI,CAAC;IACd;;;;;;OAMG;IACH,IAAI,CAAC,MAAM,SAAS,MAAM,SAAS,GAAG,MAAM,EAC1C,KAAK,EAAE,MAAM,EACb,GAAG,IAAI,EAAE,qBAAqB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAChD,OAAO,CAAC;CACZ;AA+DD,wBAAgB,6BAA6B,CAC3C,KAAK,CAAC,QAAQ,SAAS,qBAAqB,EAC5C,KAAK,CAAC,SAAS,SAAS,qBAAqB,EAC7C,EACA,MAAM,EACN,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,MAAM,EACN,SAAS,GACV,EAAE,8BAA8B,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,uBAAuB,CAC9E,QAAQ,EACR,SAAS,CACV,CA+HA"}
@@ -14,10 +14,29 @@ type BunDevelopmentOptions = BunServeOptions extends {
14
14
  development?: infer T;
15
15
  } ? T : never;
16
16
  export type ServerPassthrough = Omit<BunServeOptions, 'fetch' | 'port' | 'hostname' | 'unix' | 'routes' | 'websocket' | 'development'>;
17
+ /**
18
+ * Unix domain socket listener. The plain-string form binds the path as-is;
19
+ * the object form additionally tightens the socket file mode after listen —
20
+ * `mode: 0o600` is the right choice when access to the socket IS the
21
+ * credential (a local daemon whose only auth is filesystem permission).
22
+ */
23
+ export type UnixListenConfig = string | {
24
+ path: string;
25
+ mode?: number;
26
+ };
17
27
  /** Bun-specific server config layered over the Fetch-clean handler config. */
18
28
  export interface BunServerConfig extends BunHandlerConfig, BunFetchComposition {
19
29
  port?: number;
20
30
  hostname?: string;
31
+ /**
32
+ * Listen on a unix domain socket instead of a TCP port. Mutually exclusive
33
+ * with `port`/`hostname`. A stale socket file left by a killed process is
34
+ * reclaimed (probe-then-unlink, best-effort against concurrent starts); a
35
+ * path answered by a live listener — or one this process may not probe —
36
+ * fails loudly. Not compatible with the Socket.IO lifecycle (`socket`):
37
+ * socket.io clients cannot dial a unix socket.
38
+ */
39
+ unix?: UnixListenConfig;
21
40
  websocket?: BunWebSocketHandlers;
22
41
  /** Full Stitchkit Socket.IO lifecycle; route and default websocket are mounted automatically. */
23
42
  socket?: SocketIOServerLifecycle;
@@ -1 +1 @@
1
- {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/server/bun.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AACtD,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,oBAAoB,GAAG,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC1D,KAAK,qBAAqB,GAAG,eAAe,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3F,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,eAAe,EACf,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAChF,CAAC;AAEF,8EAA8E;AAC9E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,mBAAmB;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,iGAAiG;IACjG,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AAE7D,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAyIrE"}
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../../src/server/bun.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,eAAe,EAChB,MAAM,SAAS,CAAC;AAEjB,yEAAyE;AACzE,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC9C,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AACtD,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;AAExD,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,oBAAoB,GAAG,GAAG,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAC1D,KAAK,qBAAqB,GAAG,eAAe,SAAS;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,KAAK,CAAC;AAE3F,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,eAAe,EACf,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAChF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExE,8EAA8E;AAC9E,MAAM,WAAW,eAAgB,SAAQ,gBAAgB,EAAE,mBAAmB;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,iGAAiG;IACjG,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,GAAG,CAAC,EAAE,iBAAiB,CAAC;CACzB;AAED,MAAM,MAAM,eAAe,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;AAyD7D,qDAAqD;AACrD,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CA6LrE"}
@@ -1,7 +1,7 @@
1
1
  export { AppError, appError, badRequest, conflict, forbidden, isStitchErrorCode, notFound, rateLimited, STITCH_ERROR_STATUS, type StitchErrorCode, unauthorized, } from '../contract';
2
2
  export { errorCode, formatZodError, normalizeError, type ZodIssueSummary, zodIssues, } from '../internal/errors';
3
3
  export { isWithinDir } from '../internal/within-dir';
4
- export { type BunFetchComposition as FetchComposition, type BunFetchHandler as FetchHandler, type BunHandlerConfig as HandlerConfig, type BunRawRoute as RawRoute, type BunRawRouteContext as RawRouteContext, type BunServer, type BunServerConfig, type BunServerHandle, createServer, type ServerPassthrough, } from './bun';
4
+ export { type BunFetchComposition as FetchComposition, type BunFetchHandler as FetchHandler, type BunHandlerConfig as HandlerConfig, type BunRawRoute as RawRoute, type BunRawRouteContext as RawRouteContext, type BunServer, type BunServerConfig, type BunServerHandle, createServer, type ServerPassthrough, type UnixListenConfig, } from './bun';
5
5
  export { type CacheOptions, cacheHeaders, createCache } from './cache';
6
6
  export { createHandler } from './create';
7
7
  export { createErrorHook, type ErrorHookConfig, type ResolvedError, } from './error-hook';
@@ -21,7 +21,7 @@ export { errorResponse, parseBody, respondJson } from './raw';
21
21
  export { bindRealtimeServer, type RealtimeServer, type RealtimeServerConnection, type RealtimeServerHandle, } from './realtime';
22
22
  export { type ClientIpOptions, extractIp, generateTraceId, getClientInfo, resolveSocketIp, resolveTraceId, } from './request';
23
23
  export { type ManagedServerHandle, type ShutdownOptions, ShutdownOptionsSchema, type ShutdownResult, ShutdownResultSchema, type ShutdownState, ShutdownStateSchema, type ShutdownStatus, ShutdownStatusSchema, } from './shutdown';
24
- export type { SocketIORequestPolicy, SocketIOServerConfig, SocketIOServerHandle, SocketIOServerLifecycle, } from './socket-io';
24
+ export type { SocketIOHandshakeConfig, SocketIORequestPolicy, SocketIOServerConfig, SocketIOServerHandle, SocketIOServerLifecycle, } from './socket-io';
25
25
  export { createSocketIOServer, socketIoLane } from './socket-io';
26
26
  export { type ParseSSEOptions, parseSSE, streamSSE } from './stream';
27
27
  export type { AuthorizationContext, EffectiveScope, EndpointHandlerContext, Handlers, LifecycleHooks, LoggingConfig, LogOutcome, MethodDef, MultipartFileMetadata, MultipartReceiver, MultipartReceiverResult, OperationIdentity, RouteGroup, ScopeContexts, ScopedHandlers, ServiceDef, StitchLogger, StreamingMultipartImplementation, } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
@@ -12,7 +12,7 @@ import {
12
12
  parseMultipart,
13
13
  socketIoLane,
14
14
  webSocketLane
15
- } from "../index-d6jj3cp5.js";
15
+ } from "../index-6jngw7hw.js";
16
16
  import {
17
17
  createAuthHook,
18
18
  createBearerResolver,
@@ -75,6 +75,39 @@ import {
75
75
  parseTrailingWildcard
76
76
  } from "../index-bmnhqtya.js";
77
77
  // src/server/bun.ts
78
+ import { chmodSync, statSync, unlinkSync } from "node:fs";
79
+ function reclaimStaleUnixSocket(path) {
80
+ const stats = statSync(path, { throwIfNoEntry: false });
81
+ if (stats === undefined)
82
+ return;
83
+ if (!stats.isSocket()) {
84
+ throw new Error(`[stitchkit] createServer: "${path}" exists and is not a socket — refusing to remove it`);
85
+ }
86
+ if (typeof process.getuid === "function" && stats.uid !== process.getuid()) {
87
+ throw new Error(`[stitchkit] createServer: unix socket "${path}" is owned by another user — refusing to reclaim it`);
88
+ }
89
+ const standalone = Bun.main.startsWith("/$bunfs/");
90
+ const interpreter = standalone ? Bun.which("bun") : process.execPath;
91
+ if (!interpreter) {
92
+ throw new Error(`[stitchkit] createServer: unix socket "${path}" already exists and no \`bun\` binary is on PATH to probe it from this compiled executable — remove the file manually if the previous process is dead`);
93
+ }
94
+ const probe = Bun.spawnSync({
95
+ cmd: [
96
+ interpreter,
97
+ "-e",
98
+ 'try { const socket = await Bun.connect({ unix: Bun.env.STITCHKIT_UNIX_PROBE ?? "", socket: { data() { return undefined; } } }); socket.end(); process.exit(0); } catch (error) { process.exit(error && typeof error === "object" && "code" in error && error.code === "EACCES" ? 2 : 1); }'
99
+ ],
100
+ env: { ...process.env, STITCHKIT_UNIX_PROBE: path },
101
+ timeout: 2000
102
+ });
103
+ if (probe.exitedDueToTimeout) {
104
+ throw new Error(`[stitchkit] createServer: liveness probe for unix socket "${path}" timed out — refusing to reclaim; remove the file manually if the previous process is dead`);
105
+ }
106
+ if (probe.exitCode === 0 || probe.exitCode === 2) {
107
+ throw new Error(`[stitchkit] createServer: unix socket "${path}" is already in use${probe.exitCode === 2 ? " (connect permission denied)" : " by a live listener"}`);
108
+ }
109
+ unlinkSync(path);
110
+ }
78
111
  function createServer(config) {
79
112
  const {
80
113
  websocket: configuredWebSocket,
@@ -84,6 +117,19 @@ function createServer(config) {
84
117
  port = 3000,
85
118
  hostname
86
119
  } = config;
120
+ const unixPath = typeof config.unix === "string" ? config.unix : config.unix?.path;
121
+ const unixMode = typeof config.unix === "string" ? undefined : config.unix?.mode;
122
+ if (unixPath !== undefined) {
123
+ if (unixPath.length === 0) {
124
+ throw new Error("[stitchkit] createServer: `unix` must be a non-empty socket path");
125
+ }
126
+ if (config.port !== undefined || config.hostname !== undefined) {
127
+ throw new Error("[stitchkit] createServer: `unix` is mutually exclusive with `port`/`hostname`");
128
+ }
129
+ if (socket) {
130
+ throw new Error("[stitchkit] createServer: the Socket.IO lifecycle cannot listen on a unix socket — socket.io clients dial TCP only");
131
+ }
132
+ }
87
133
  const socketRoutePrefix = socket?.route.path.replace(/\*socketPath$/, "");
88
134
  const websocket = configuredWebSocket ?? socket?.websocket;
89
135
  const openSockets = new Set;
@@ -176,24 +222,43 @@ function createServer(config) {
176
222
  }
177
223
  return admittedFetch(request, server2);
178
224
  };
179
- runtime = trackedWebSocket ? Bun.serve({
180
- ...bunExtra,
181
- ...development && { development },
182
- port,
183
- hostname,
184
- websocket: trackedWebSocket,
185
- fetch
186
- }) : Bun.serve({
187
- ...bunExtra,
188
- ...development && { development },
189
- port,
190
- hostname,
191
- fetch
192
- });
225
+ if (unixPath !== undefined) {
226
+ reclaimStaleUnixSocket(unixPath);
227
+ const { reusePort, ipv6Only, http3, http1, idleTimeout, ...unixExtra } = bunExtra ?? {};
228
+ runtime = trackedWebSocket ? Bun.serve({
229
+ ...unixExtra,
230
+ ...development && { development },
231
+ unix: unixPath,
232
+ websocket: trackedWebSocket,
233
+ fetch
234
+ }) : Bun.serve({
235
+ ...unixExtra,
236
+ ...development && { development },
237
+ unix: unixPath,
238
+ fetch
239
+ });
240
+ if (unixMode !== undefined)
241
+ chmodSync(unixPath, unixMode);
242
+ } else {
243
+ runtime = trackedWebSocket ? Bun.serve({
244
+ ...bunExtra,
245
+ ...development && { development },
246
+ port,
247
+ hostname,
248
+ websocket: trackedWebSocket,
249
+ fetch
250
+ }) : Bun.serve({
251
+ ...bunExtra,
252
+ ...development && { development },
253
+ port,
254
+ hostname,
255
+ fetch
256
+ });
257
+ }
193
258
  const server = runtime;
194
259
  return {
195
260
  url: server.url.toString().replace(/\/$/, ""),
196
- port: server.port ?? port,
261
+ port: server.port ?? (unixPath !== undefined ? 0 : port),
197
262
  runtime: server,
198
263
  get status() {
199
264
  return lifecycle.status;
@@ -1,24 +1,28 @@
1
- import type { Socket, Server as SocketIOServer } from 'socket.io';
1
+ import type { DefaultEventsMap, Socket, Server as SocketIOServer } from 'socket.io';
2
2
  import type { SocketEventMap } from '../browser/socket-io';
3
3
  import type { StitchLogger } from '../logger';
4
4
  import type { RealtimeContract, RealtimeEventRegistry, RealtimeRejectedEventHook } from '../realtime/contract';
5
5
  import { type ValidatedRealtimeSocket } from '../realtime/socket';
6
- export interface RealtimeServerConnection<TServerToClient extends RealtimeEventRegistry, TClientToServer extends RealtimeEventRegistry> {
7
- /** Raw Socket.IO socket for handshake auth, rooms and application-owned delivery policy. */
8
- raw: Socket;
6
+ export interface RealtimeServerConnection<TServerToClient extends RealtimeEventRegistry, TClientToServer extends RealtimeEventRegistry, TData = any> {
7
+ /**
8
+ * Raw Socket.IO socket for rooms and application-owned delivery policy.
9
+ * `raw.data` carries the typed handshake identity when the server was
10
+ * created with a `handshake` gate.
11
+ */
12
+ raw: Socket<SocketEventMap, SocketEventMap, DefaultEventsMap, TData>;
9
13
  events: ValidatedRealtimeSocket<TClientToServer, TServerToClient>;
10
14
  to(room: string): Pick<ValidatedRealtimeSocket<RealtimeEventRegistry, TServerToClient>, 'emit'>;
11
15
  }
12
- export interface RealtimeServer<TServerToClient extends RealtimeEventRegistry, TClientToServer extends RealtimeEventRegistry> {
13
- onConnection(handler: (connection: RealtimeServerConnection<TServerToClient, TClientToServer>) => void | Promise<void>): () => void;
16
+ export interface RealtimeServer<TServerToClient extends RealtimeEventRegistry, TClientToServer extends RealtimeEventRegistry, TData = any> {
17
+ onConnection(handler: (connection: RealtimeServerConnection<TServerToClient, TClientToServer, TData>) => void | Promise<void>): () => void;
14
18
  emit: ValidatedRealtimeSocket<RealtimeEventRegistry, TServerToClient>['emit'];
15
19
  to(room: string): Pick<ValidatedRealtimeSocket<RealtimeEventRegistry, TServerToClient>, 'emit'>;
16
20
  }
17
- export interface RealtimeServerHandle {
18
- io: SocketIOServer<SocketEventMap, SocketEventMap>;
21
+ export interface RealtimeServerHandle<TData = any> {
22
+ io: SocketIOServer<SocketEventMap, SocketEventMap, DefaultEventsMap, TData>;
19
23
  }
20
- export declare function bindRealtimeServer<const TServerToClient extends RealtimeEventRegistry, const TClientToServer extends RealtimeEventRegistry>(contract: RealtimeContract<TServerToClient, TClientToServer>, handle: RealtimeServerHandle, options?: {
24
+ export declare function bindRealtimeServer<const TServerToClient extends RealtimeEventRegistry, const TClientToServer extends RealtimeEventRegistry, TData = any>(contract: RealtimeContract<TServerToClient, TClientToServer>, handle: RealtimeServerHandle<TData>, options?: {
21
25
  onRejected?: RealtimeRejectedEventHook;
22
26
  logger?: StitchLogger;
23
- }): RealtimeServer<TServerToClient, TClientToServer>;
27
+ }): RealtimeServer<TServerToClient, TClientToServer, TData>;
24
28
  //# sourceMappingURL=realtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/server/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,wBAAwB,CACvC,eAAe,SAAS,qBAAqB,EAC7C,eAAe,SAAS,qBAAqB;IAE7C,4FAA4F;IAC5F,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,uBAAuB,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAClE,EAAE,CACA,IAAI,EAAE,MAAM,GACX,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,cAAc,CAC7B,eAAe,SAAS,qBAAqB,EAC7C,eAAe,SAAS,qBAAqB;IAE7C,YAAY,CACV,OAAO,EAAE,CACP,UAAU,EAAE,wBAAwB,CAAC,eAAe,EAAE,eAAe,CAAC,KACnE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACxB,MAAM,IAAI,CAAC;IACd,IAAI,EAAE,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9E,EAAE,CACA,IAAI,EAAE,MAAM,GACX,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,cAAc,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;CACpD;AAED,wBAAgB,kBAAkB,CAChC,KAAK,CAAC,eAAe,SAAS,qBAAqB,EACnD,KAAK,CAAC,eAAe,SAAS,qBAAqB,EAEnD,QAAQ,EAAE,gBAAgB,CAAC,eAAe,EAAE,eAAe,CAAC,EAC5D,MAAM,EAAE,oBAAoB,EAC5B,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,yBAAyB,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,GAC9E,cAAc,CAAC,eAAe,EAAE,eAAe,CAAC,CAsClD"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/server/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,gBAAgB,EAChB,qBAAqB,EACrB,yBAAyB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,wBAAwB,CACvC,eAAe,SAAS,qBAAqB,EAC7C,eAAe,SAAS,qBAAqB,EAC7C,KAAK,GAAG,GAAG;IAEX;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;IACrE,MAAM,EAAE,uBAAuB,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;IAClE,EAAE,CACA,IAAI,EAAE,MAAM,GACX,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,cAAc,CAC7B,eAAe,SAAS,qBAAqB,EAC7C,eAAe,SAAS,qBAAqB,EAC7C,KAAK,GAAG,GAAG;IAEX,YAAY,CACV,OAAO,EAAE,CACP,UAAU,EAAE,wBAAwB,CAAC,eAAe,EAAE,eAAe,EAAE,KAAK,CAAC,KAC1E,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACxB,MAAM,IAAI,CAAC;IACd,IAAI,EAAE,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9E,EAAE,CACA,IAAI,EAAE,MAAM,GACX,IAAI,CAAC,uBAAuB,CAAC,qBAAqB,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,oBAAoB,CAAC,KAAK,GAAG,GAAG;IAC/C,EAAE,EAAE,cAAc,CAAC,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;CAC7E;AAED,wBAAgB,kBAAkB,CAChC,KAAK,CAAC,eAAe,SAAS,qBAAqB,EACnD,KAAK,CAAC,eAAe,SAAS,qBAAqB,EACnD,KAAK,GAAG,GAAG,EAEX,QAAQ,EAAE,gBAAgB,CAAC,eAAe,EAAE,eAAe,CAAC,EAC5D,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC,EACnC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,yBAAyB,CAAC;IAAC,MAAM,CAAC,EAAE,YAAY,CAAA;CAAO,GAC9E,cAAc,CAAC,eAAe,EAAE,eAAe,EAAE,KAAK,CAAC,CAwCzD"}
@@ -1,8 +1,42 @@
1
1
  /** Runtime-neutral Socket.IO configuration shared by Bun and Node adapters. */
2
- import type { ServerOptions } from 'socket.io';
2
+ import type { ServerOptions, Socket } from 'socket.io';
3
+ import type { z } from 'zod';
3
4
  /** Runtime-neutral Engine.IO handshake policy, evaluated before shutdown admission. */
4
5
  export type SocketIORequestPolicy = (request: Request) => boolean | Promise<boolean>;
5
- export interface SocketIOServerConfig {
6
+ /**
7
+ * Typed identity gate for the Socket.IO handshake. `schema` validates
8
+ * `socket.handshake.auth` (the structured channel — `query` values are strings
9
+ * on the wire); an optional `verify` turns the parsed payload into the
10
+ * connection identity. Returning `null` or throwing rejects the handshake
11
+ * **before** the connection handler and before any event validation; the
12
+ * accepted identity is written to `socket.data`, typed end-to-end through
13
+ * `createSocketIOServer` and `bindRealtimeServer`.
14
+ *
15
+ * A rejected handshake is terminal for socket.io-client — it does **not**
16
+ * retry (unlike a transport-level denial). The stitchkit client surfaces it
17
+ * via `onConnectError` with `terminal: true`; recovery is an explicit
18
+ * `connect()`, which re-reads a function-form `auth`.
19
+ */
20
+ export interface SocketIOHandshakeConfig<TParsed, TData = TParsed> {
21
+ /** Zod schema for `socket.handshake.auth`. */
22
+ schema: z.ZodType<TParsed>;
23
+ /**
24
+ * Turn the schema-validated payload into the connection identity (may be
25
+ * async — the wrapper awaits it safely; socket.io itself would leak an
26
+ * unhandled rejection from a raw async middleware). Return `null` or throw
27
+ * to reject; a thrown error is logged server-side and the peer sees only
28
+ * the generic `handshake rejected` — raw error text never crosses to an
29
+ * unauthenticated client. When omitted, the parsed payload itself is the
30
+ * identity (when passing `TData` explicitly with `TData ≠ TParsed`,
31
+ * `verify` is therefore mandatory — without it the runtime stores the
32
+ * schema output). The `schema` must be synchronous (no async
33
+ * refine/transform).
34
+ */
35
+ verify?: (parsed: TParsed, context: {
36
+ handshake: Socket['handshake'];
37
+ }) => TData | null | Promise<TData | null>;
38
+ }
39
+ export interface SocketIOServerConfig<TParsed = any, TData = TParsed> {
6
40
  /** CORS — the browser origin(s) allowed to open a socket. */
7
41
  cors: {
8
42
  origin: string | string[];
@@ -18,6 +52,15 @@ export interface SocketIOServerConfig {
18
52
  pingInterval?: number;
19
53
  /** Runtime-neutral policy for accepting a new Engine.IO handshake. */
20
54
  allowRequest?: SocketIORequestPolicy;
55
+ /**
56
+ * Typed identity gate — Zod-validate the handshake and put the result into
57
+ * `socket.data`. Registered as the **first** `io.use()` middleware, so app
58
+ * middlewares added later see the typed identity already in place. Type
59
+ * inference works on calls without explicit event generics (the
60
+ * `bindRealtimeServer` lane); with explicit generics, pass the identity
61
+ * types explicitly: `createSocketIOServer<S, C, Parsed, Data>`.
62
+ */
63
+ handshake?: SocketIOHandshakeConfig<TParsed, TData>;
21
64
  /** Typed Socket.IO options not owned by this wrapper. */
22
65
  serverOptions?: Omit<Partial<ServerOptions>, 'allowRequest'>;
23
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io-config.d.ts","sourceRoot":"","sources":["../../src/server/socket-io-config.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,uFAAuF;AACvF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5C,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,yDAAyD;IACzD,aAAa,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC,CAAC;CAC9D"}
1
+ {"version":3,"file":"socket-io-config.d.ts","sourceRoot":"","sources":["../../src/server/socket-io-config.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,uFAAuF;AACvF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAErF;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,uBAAuB,CAAC,OAAO,EAAE,KAAK,GAAG,OAAO;IAC/D,8CAA8C;IAC9C,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,CACP,MAAM,EAAE,OAAO,EACf,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;KAAE,KACxC,KAAK,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB,CAAC,OAAO,GAAG,GAAG,EAAE,KAAK,GAAG,OAAO;IAClE,6DAA6D;IAC7D,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,sDAAsD;IACtD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,UAAU,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;IAC5C,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,uBAAuB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpD,yDAAyD;IACzD,aAAa,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,cAAc,CAAC,CAAC;CAC9D"}
@@ -1,17 +1,17 @@
1
1
  /** Node-facing Socket.IO surface without Bun engine declarations. */
2
2
  import type { Server as HttpServer } from 'node:http';
3
- import type { Server as SocketIOServer } from 'socket.io';
3
+ import type { DefaultEventsMap, Server as SocketIOServer } from 'socket.io';
4
4
  import type { SocketEventMap } from '../browser/socket-io';
5
5
  import type { SocketIOServerConfig } from './socket-io-config';
6
- export type { SocketIORequestPolicy, SocketIOServerConfig } from './socket-io-config';
6
+ export type { SocketIOHandshakeConfig, SocketIORequestPolicy, SocketIOServerConfig, } from './socket-io-config';
7
7
  /** The runtime-neutral part of a Socket.IO handle used by `serveNode`. */
8
- export interface NodeSocketIOServerHandle<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap> {
9
- io: SocketIOServer<TClientEvents, TServerEvents>;
8
+ export interface NodeSocketIOServerHandle<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap, TData = any> {
9
+ io: SocketIOServer<TClientEvents, TServerEvents, DefaultEventsMap, TData>;
10
10
  attach(server: HttpServer): void;
11
11
  beginShutdown(): void;
12
12
  close(): Promise<void>;
13
13
  connections(): number;
14
14
  }
15
15
  /** Create a Socket.IO handle typed only for the Node capabilities it exposes. */
16
- export declare function createNodeSocketIOServer<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap>(config: SocketIOServerConfig): Promise<NodeSocketIOServerHandle<TServerEvents, TClientEvents>>;
16
+ export declare function createNodeSocketIOServer<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap, TParsed = any, TData = TParsed>(config: SocketIOServerConfig<TParsed, TData>): Promise<NodeSocketIOServerHandle<TServerEvents, TClientEvents, TData>>;
17
17
  //# sourceMappingURL=socket-io-node.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io-node.d.ts","sourceRoot":"","sources":["../../src/server/socket-io-node.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEtF,0EAA0E;AAC1E,MAAM,WAAW,wBAAwB,CACvC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc;IAEpC,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACjD,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,aAAa,IAAI,IAAI,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,iFAAiF;AACjF,wBAAsB,wBAAwB,CAC5C,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EAEpC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,wBAAwB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAEjE"}
1
+ {"version":3,"file":"socket-io-node.d.ts","sourceRoot":"","sources":["../../src/server/socket-io-node.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,IAAI,cAAc,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,0EAA0E;AAC1E,MAAM,WAAW,wBAAwB,CACvC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,KAAK,GAAG,GAAG;IAEX,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;IAC1E,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,aAAa,IAAI,IAAI,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,iFAAiF;AACjF,wBAAsB,wBAAwB,CAC5C,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EAGpC,OAAO,GAAG,GAAG,EACb,KAAK,GAAG,OAAO,EAEf,MAAM,EAAE,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,OAAO,CAAC,wBAAwB,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAExE"}
@@ -17,13 +17,13 @@
17
17
  */
18
18
  import type { Server as HttpServer } from 'node:http';
19
19
  import type { Server as BunEngine } from '@socket.io/bun-engine';
20
- import type { Server as SocketIOServer } from 'socket.io';
20
+ import type { DefaultEventsMap, Server as SocketIOServer } from 'socket.io';
21
21
  import type { SocketEventMap } from '../browser/socket-io';
22
22
  import type { BunServer } from './bun';
23
23
  import type { SocketIOServerConfig } from './socket-io-config';
24
24
  import type { RawRoute } from './types';
25
25
  import { type ComposedLane } from './websocket';
26
- export type { SocketIORequestPolicy, SocketIOServerConfig } from './socket-io-config';
26
+ export type { SocketIOHandshakeConfig, SocketIORequestPolicy, SocketIOServerConfig, } from './socket-io-config';
27
27
  export interface SocketIOServerLifecycle {
28
28
  /**
29
29
  * WebSocket handler for `Bun.serve({ websocket })`. Real on Bun; on Node it is
@@ -52,11 +52,11 @@ export interface SocketIOServerLifecycle {
52
52
  /** Current Engine.IO client count, when the engine has been bound. */
53
53
  connections(): number;
54
54
  }
55
- export interface SocketIOServerHandle<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap> extends SocketIOServerLifecycle {
55
+ export interface SocketIOServerHandle<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap, TData = any> extends SocketIOServerLifecycle {
56
56
  /** The typed Socket.IO server — attach `io.on('connection', ...)` handlers. */
57
- io: SocketIOServer<TClientEvents, TServerEvents>;
57
+ io: SocketIOServer<TClientEvents, TServerEvents, DefaultEventsMap, TData>;
58
58
  }
59
- export declare function createSocketIOServer<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap>(config: SocketIOServerConfig): Promise<SocketIOServerHandle<TServerEvents, TClientEvents>>;
59
+ export declare function createSocketIOServer<TServerEvents extends SocketEventMap, TClientEvents extends SocketEventMap, TParsed = any, TData = TParsed>(config: SocketIOServerConfig<TParsed, TData>): Promise<SocketIOServerHandle<TServerEvents, TClientEvents, TData>>;
60
60
  /**
61
61
  * Wrap a Socket.IO handle's `websocket` as a catch-all {@link ComposedLane} for
62
62
  * {@link composeWebSocketHandlers} — when a second, raw WebSocket lane shares
@@ -1 +1 @@
1
- {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/server/socket-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EACV,MAAM,IAAI,SAAS,EAEpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EACV,MAAM,IAAI,cAAc,EAEzB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,aAAa,CAAC;AAE/D,YAAY,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEtF,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACzD;;;;;OAKG;IACH,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,6EAA6E;IAC7E,aAAa,IAAI,IAAI,CAAC;IACtB,8EAA8E;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CACnC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,CACpC,SAAQ,uBAAuB;IAC/B,+EAA+E;IAC/E,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;CAClD;AAuCD,wBAAsB,oBAAoB,CACxC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CAyI3F;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,oBAAoB,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,WAAW,CAAC,GAC3E,YAAY,CAMd"}
1
+ {"version":3,"file":"socket-io.d.ts","sourceRoot":"","sources":["../../src/server/socket-io.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EACV,MAAM,IAAI,SAAS,EAEpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EACV,gBAAgB,EAChB,MAAM,IAAI,cAAc,EAGzB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAA2B,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AACxF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,KAAK,YAAY,EAAiB,MAAM,aAAa,CAAC;AAE/D,YAAY,EACV,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACzD;;;;;OAKG;IACH,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IAC3B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACjC,6EAA6E;IAC7E,aAAa,IAAI,IAAI,CAAC;IACtB,8EAA8E;IAC9E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,sEAAsE;IACtE,WAAW,IAAI,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB,CACnC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EACpC,KAAK,GAAG,GAAG,CACX,SAAQ,uBAAuB;IAC/B,+EAA+E;IAC/E,EAAE,EAAE,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,CAAC,CAAC;CAC3E;AA8GD,wBAAsB,oBAAoB,CACxC,aAAa,SAAS,cAAc,EACpC,aAAa,SAAS,cAAc,EAKpC,OAAO,GAAG,GAAG,EACb,KAAK,GAAG,OAAO,EAEf,MAAM,EAAE,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,GAC3C,OAAO,CAAC,oBAAoB,CAAC,aAAa,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CA6IpE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,oBAAoB,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC,WAAW,CAAC,GAC3E,YAAY,CAMd"}
package/llms-full.txt CHANGED
@@ -851,6 +851,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
851
851
  | `rawRoutes` | non-contract routes (see below) |
852
852
  | `maxJsonBodyBytes` | optional JSON body cap (bytes); per-route value overrides; unset preserves existing behaviour |
853
853
  | `port` / `hostname` | listen address — port defaults to `3000` |
854
+ | `unix` | listen on a unix domain socket instead of TCP — `'/run/app.sock'` or `{ path, mode }` (see below) |
854
855
  | `cors` | CORS policy — `{ origin, credentials, methods, headers, exposeHeaders }`. `origin` is **required** when `cors` is present: pass an explicit origin (or list), or `'*'` to deliberately allow every origin — an origin-less config is a construction error, never a silent wildcard. Omit `cors` entirely to emit no CORS headers. |
855
856
  | `hooks` | lifecycle hooks (see below) |
856
857
  | `logging` | `true` for built-in request logs, or a `LoggingConfig` (see below) |
@@ -864,6 +865,41 @@ Native Bun `routes` are intentionally not accepted: Bun matches them before
864
865
  `fetch`, so they could bypass shutdown admission. Use `rawRoutes`; they retain
865
866
  the Fetch `Request → Response` model and participate in lifecycle tracking.
866
867
 
868
+ ### Local daemon over a unix socket
869
+
870
+ A local daemon whose door is a socket file needs no TCP port at all:
871
+
872
+ ```ts
873
+ const server = createServer({
874
+ services,
875
+ unix: { path: '/run/my-daemon.sock', mode: 0o600 },
876
+ })
877
+ ```
878
+
879
+ `unix` is mutually exclusive with `port`/`hostname` (construction error). The
880
+ handle stays honest: `server.url` is `unix:///run/my-daemon.sock` — an
881
+ identifier, **not** a fetchable address (dial the path with
882
+ `createHttpClient({ unix })`, [client guide](client.md#unix-domain-sockets)) —
883
+ and `server.port` is `0`.
884
+
885
+ Socket-file hygiene is built in. A stale file left by a killed process is
886
+ reclaimed automatically: the path is probed, and only a socket that is owned by
887
+ the current user **and** answers no live listener is removed before binding
888
+ (best-effort — two processes racing the same path resolve through the loud
889
+ bind error of the loser). A regular file at the path, another user's socket, or
890
+ a live listener each fail with a specific error instead of being unlinked. A
891
+ clean `server.shutdown()` removes the file.
892
+
893
+ `mode` tightens the socket file permissions after listen. Bun creates the file
894
+ `0755` under a normal umask — since `connect(2)` requires *write* permission
895
+ that is already owner-only in practice — but when access to the socket **is**
896
+ the credential, set `mode: 0o600` explicitly and let the filesystem be the
897
+ policy. The Socket.IO lifecycle (`socket`) cannot mount on a unix listener
898
+ (socket.io clients dial TCP only — construction error); Bun's own WebSocket
899
+ client cannot dial a unix path either. Unix listeners are Bun-only: `srvx`
900
+ resolves a numeric port unconditionally, so `stitchkit/node` does not offer
901
+ `unix` (no half-support).
902
+
867
903
  ### Managed shutdown
868
904
 
869
905
  ```ts
@@ -1678,6 +1714,7 @@ without repeating transport configuration.
1678
1714
  | `suppressUnauthorizedFor` | `[]` | exact contract-derived operation matchers whose expected 401 does not emit `unauthorized` |
1679
1715
  | `parseError` | built-in | map an error body to `{ code, message, details, hint }` |
1680
1716
  | `trace` | `false` | emit a W3C `traceparent` header on every request |
1717
+ | `unix` | — | dial a unix domain socket instead of TCP (Bun only, see below) |
1681
1718
 
1682
1719
  `headers` as a function is the hook for runtime tokens — a bearer token or any
1683
1720
  short-lived credential — re-evaluated on every request.
@@ -1691,6 +1728,26 @@ compile the prefix structure without a concrete tenant id. Matching is exact by
1691
1728
  path segments, including params and trailing wildcards — a shared prefix never
1692
1729
  suppresses a neighbouring protected endpoint.
1693
1730
 
1731
+ ### Unix domain sockets
1732
+
1733
+ The same typed client dials a local daemon's socket file
1734
+ ([server side](server.md#local-daemon-over-a-unix-socket)):
1735
+
1736
+ ```ts
1737
+ const http = createHttpClient({
1738
+ baseUrl: 'http://localhost', // required prefix source; its host is ignored
1739
+ unix: '/run/my-daemon.sock',
1740
+ })
1741
+ const daemon = createClient(daemonContract, http)
1742
+ ```
1743
+
1744
+ `baseUrl` stays required — it supplies the path prefix and the `Host` header,
1745
+ while the connection itself goes through the socket file. Bun runtime only:
1746
+ other runtimes ignore the option and dial `baseUrl` over TCP (Node's fetch
1747
+ would need an undici dispatcher — out of scope). A missing socket file
1748
+ surfaces as a normal `ApiError` and is not retried (transport retry stays
1749
+ connection-refused-only).
1750
+
1694
1751
  `trace: true` mints a fresh root trace per request. The stitchkit server
1695
1752
  [continues an inbound `traceparent`](./observability.md#trace-context), so the
1696
1753
  browser call, the HTTP handler and every nested tool call share one trace id
@@ -3286,8 +3343,11 @@ export function publishExampleNote(realtime: ExampleRealtimePublisher): void {
3286
3343
  | `beginShutdown()` / `connections()` | lifecycle surface consumed by the managed server |
3287
3344
 
3288
3345
  `SocketIOServerConfig` also takes `path`, `transports`, `pingTimeout`,
3289
- `pingInterval` and a runtime-neutral `allowRequest(Request)` handshake policy.
3290
- The policy is composed with managed-shutdown admission on both Bun and Node.
3346
+ `pingInterval`, a runtime-neutral `allowRequest(Request)` handshake policy and
3347
+ the typed **`handshake`** identity gate (schema + verify
3348
+ [typed `socket.data`](#handshake-auth--cookie-or-token)). `allowRequest` is the
3349
+ transport gate (composed with managed-shutdown admission on both Bun and Node);
3350
+ `handshake` is the identity gate that runs after it.
3291
3351
  For anything else socket.io's `ServerOptions` exposes, use the
3292
3352
  typed **`serverOptions`** passthrough — most often `maxHttpBufferSize` to lift the
3293
3353
  1 MB default for large emits:
@@ -3337,7 +3397,40 @@ subscribe once; reconnection is the wrapper's problem, not yours.
3337
3397
 
3338
3398
  `SocketIOClientConfig` takes `url`, `path`, `withCredentials` (cookies on the
3339
3399
  handshake — default `true`), `auth`, `query`, `extraHeaders`, `transports`,
3340
- `reconnectionAttempts`, `reconnectionDelay` and `retain` (below).
3400
+ `reconnectionAttempts`, `reconnectionDelay`, `retain` (below), `onConnectError`
3401
+ (handshake/connection failures — see the auth section) and `onDroppedEmit`
3402
+ (observability for emits dropped while disconnected — see below).
3403
+
3404
+ ### Honest emit — what happens while disconnected
3405
+
3406
+ `emit` has exactly three outcomes, in order:
3407
+
3408
+ 1. **A local contract violation throws** (validated `RealtimeClient` only) —
3409
+ validation runs before the connection guard, even while disconnected.
3410
+ 2. **Disconnected → the emit is dropped**: `emit` returns `false` and the
3411
+ `onDroppedEmit` hook fires with the event name and wire arguments. This
3412
+ includes the short window right after `connect()` while the socket.io peer
3413
+ is still loading.
3414
+ 3. **Otherwise `emit` returns `true`** — handed to the transport, which is
3415
+ *not* a delivery guarantee.
3416
+
3417
+ The default is deliberately a drop, not a buffer: after a reconnect, durable
3418
+ subscriptions and [sticky events](#sticky-events) replay current state
3419
+ deterministically, instead of the server receiving an unordered backlog of
3420
+ stale emits. What changed is that the drop is now observable — assert it
3421
+ per-call (`if (!client.emit(...)) …`) or centrally:
3422
+
3423
+ ```ts
3424
+ const client = createSocketIOClient({
3425
+ url,
3426
+ onDroppedEmit: ({ event }) => metrics.count('realtime.dropped_emit', { event }),
3427
+ })
3428
+ ```
3429
+
3430
+ Server-side emits (`realtime.emit`, `to(room).emit`, `connection.events.emit`)
3431
+ always return `true` — a broadcast into an empty room is not a drop, and the
3432
+ server has no disconnected state of this kind. For state that must survive
3433
+ gaps, see [durability](#durability--idempotent--replay).
3341
3434
 
3342
3435
  ### Sticky events
3343
3436
 
@@ -3392,32 +3485,71 @@ const socket = createRealtimeClient(realtimeContract, {
3392
3485
  ```
3393
3486
 
3394
3487
  ```ts
3395
- // server — the gate is your logic, on socket.handshake.auth
3488
+ // server — the typed identity gate: Zod-validate handshake.auth, verify,
3489
+ // and the result lands in socket.data — typed all the way to onConnection.
3396
3490
  import { verifyJwt } from 'stitchkit/server'
3397
3491
 
3398
- socket.io.use(async (s, next) => {
3399
- const token = s.handshake.auth.token
3400
- if (typeof token !== 'string') return next(new Error('unauthorized'))
3401
- try {
3402
- s.data.user = await verifyJwt(token, secret)
3403
- next()
3404
- } catch {
3405
- next(new Error('unauthorized'))
3406
- }
3492
+ const socket = await createSocketIOServer({
3493
+ cors: { origin: 'https://app.example.com' },
3494
+ handshake: {
3495
+ schema: z.object({ token: z.string() }),
3496
+ verify: async ({ token }) => ({ user: await verifyJwt(token, secret) }),
3497
+ },
3498
+ })
3499
+
3500
+ const realtime = bindRealtimeServer(realtimeContract, socket)
3501
+ realtime.onConnection(({ raw }) => {
3502
+ raw.data.user // typed — no String(...) coercions, no casts
3407
3503
  })
3408
3504
  ```
3409
3505
 
3506
+ `verify` may be async (the wrapper runs it inside a settled promise chain —
3507
+ raw async `io.use` middleware would leak an unhandled rejection); throwing or
3508
+ returning `null` rejects the handshake **before** the connection handler and
3509
+ before any event validation. A thrown error's raw message is logged
3510
+ server-side and never reaches the unauthenticated peer — the wire always
3511
+ carries the generic `handshake rejected` (the same never-leak policy as the
3512
+ HTTP error normalizer). The schema itself must be synchronous (no async
3513
+ refine/transform). Omit `verify` and the schema output itself is the
3514
+ identity. The gate registers as the **first** middleware, so `socket.io.use(…)`
3515
+ middlewares you add afterwards see the typed `socket.data` already in place.
3516
+ Raw `io.use` remains available for anything beyond identity.
3517
+
3518
+ Type inference works on calls without explicit event generics (the
3519
+ `bindRealtimeServer` lane above). With explicit generics TypeScript cannot
3520
+ partially infer — pass the identity types too:
3521
+ `createSocketIOServer<S, C, z.infer<typeof schema>, Identity>`.
3522
+
3410
3523
  A static object (`auth: { token }`) works too, but only the function form
3411
3524
  re-reads on reconnect — prefer it for rotating tokens. `query` adds handshake
3412
- URL params (`socket.handshake.query`); `extraHeaders` adds handshake headers,
3413
- but in a browser those apply to the **polling** transport only (a WebSocket
3414
- upgrade cannot set request headers) for browser WebSocket auth use `auth`.
3415
- If an auth producer throws or rejects, the wrapper sends an empty auth object so
3416
- the server gate can reject it instead of leaving the handshake waiting forever.
3525
+ URL params (`socket.handshake.query`) note its values are **strings** on the
3526
+ wire, which is why the schema gate reads `auth`; `extraHeaders` adds handshake
3527
+ headers, but in a browser those apply to the **polling** transport only (a
3528
+ WebSocket upgrade cannot set request headers) for browser WebSocket auth use
3529
+ `auth`.
3530
+
3531
+ **A gate rejection is terminal for the client.** Unlike a transport-level
3532
+ failure (which socket.io retries indefinitely), a middleware/handshake-gate
3533
+ rejection stops the client: socket.io destroys its retry path and will not
3534
+ reconnect on its own. The stitchkit client surfaces this through
3535
+ **`onConnectError`** with `terminal: true` (and `data.code ===
3536
+ 'handshake_rejected'` for the built-in gate) and resets its connection intent —
3537
+ so recovery is explicit: rotate the credential, call `connect()`, and the
3538
+ function-form `auth` is re-read:
3539
+
3540
+ ```ts
3541
+ const client = createRealtimeClient(realtimeContract, {
3542
+ url: 'https://api.example.com',
3543
+ auth: () => ({ token: getAccessToken() }),
3544
+ onConnectError: ({ terminal }) => {
3545
+ if (terminal) refreshTokenThen(() => client.connect())
3546
+ },
3547
+ })
3548
+ ```
3417
3549
 
3418
- On a hard auth failure Socket.IO emits `connect_error` and keeps retrying
3419
- (`reconnectionAttempts` defaults to `Infinity`) set a finite value if a
3420
- rejected token should stop hammering the server.
3550
+ If an auth producer throws or rejects, the wrapper sends an empty auth object —
3551
+ the server gate then rejects it visibly (via `onConnectError`) instead of
3552
+ leaving the handshake waiting forever.
3421
3553
 
3422
3554
  ## Cache bridge
3423
3555
 
@@ -5277,6 +5409,28 @@ current one *up to* your target, and apply each snippet.
5277
5409
  runtime): bootstrap the server, one HTTP request, and any feature you rely on
5278
5410
  (Socket.IO connect, an MCP tool call, a multipart upload, …).
5279
5411
 
5412
+ ## Released migration: 0.53.0
5413
+
5414
+ ### Realtime `emit` returns `boolean` instead of `void`
5415
+
5416
+ Every **call site** compiles and behaves exactly as before — the return value
5417
+ is new information (`true` = accepted by the transport, `false` = dropped
5418
+ while the browser client was disconnected), not a behavior change. What breaks
5419
+ is **implementing** the interfaces: a test mock or app-side adapter of
5420
+ `SocketIOClient` / `RealtimeClient` / `ValidatedRealtimeSocket` /
5421
+ `RealtimeServer` whose `emit` returns `void` no longer typechecks.
5422
+
5423
+ ```ts
5424
+ // before — a void mock satisfied the interface
5425
+ const mock: Pick<RealtimeClient<S, C>, 'emit'> = { emit: () => {} }
5426
+ // after — report acceptance (true is what a live server-side emit reports)
5427
+ const mock: Pick<RealtimeClient<S, C>, 'emit'> = { emit: () => true }
5428
+ ```
5429
+
5430
+ While migrating, consider replacing hand-rolled `if (client.connected)` guards
5431
+ with the new honest surface: check `client.emit(...) === false`, or observe
5432
+ drops centrally with `onDroppedEmit`.
5433
+
5280
5434
  ## Released migration: 0.50.0
5281
5435
 
5282
5436
  ### The factory's scope union now covers per-endpoint overrides
@@ -6208,7 +6362,7 @@ The browser-and-server entrypoint. Re-exports everything from
6208
6362
  | `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` and optional readonly `traceId` from `x-request-id` |
6209
6363
  | `HttpClient` | _type_ | the transport interface `createClient` builds on |
6210
6364
  | `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
6211
- | `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default — [details](../guide/client.md#createhttpclient) |
6365
+ | `HttpClientConfig` | _type_ | config for `createHttpClient`; retry `limit` counts retries after the initial attempt (default 2 = at most 3 GET attempts), with `statusCodes: []` by default; `unix` dials a unix domain socket (Bun only) — [details](../guide/client.md#createhttpclient) |
6212
6366
  | `UnauthorizedMatcher` | _type_ | exact `(pathname) => boolean` policy accepted by `suppressUnauthorizedFor` |
6213
6367
  | `RequestOptions` | _type_ | per-call options — params, timeout, response type |
6214
6368
  | `HeaderProvider` | _type_ | static or per-request headers |
@@ -6224,8 +6378,8 @@ The browser-and-server entrypoint. Re-exports everything from
6224
6378
  | `createRealtimeClient` | function | inferred, runtime-validated Socket.IO client — [guide](../guide/realtime.md#client--createrealtimeclient) |
6225
6379
  | `createRetainedTopics` | function | retained last-value store for sticky events — [guide](../guide/realtime.md#sticky-events) |
6226
6380
  | `parseSSE` | function | parse an SSE `Response` into an async generator — [guide](../guide/client.md#sse) |
6227
- | `SocketIOClient` | _type_ | the client handle |
6228
- | `SocketIOClientConfig` | _type_ | config for `createSocketIOClient` (incl. `retain`) |
6381
+ | `SocketIOClient` | _type_ | the client handle; `emit` returns `false` for an emit dropped while disconnected |
6382
+ | `SocketIOClientConfig` | _type_ | config for `createSocketIOClient` (incl. `retain`, `onConnectError`, `onDroppedEmit`) |
6229
6383
  | `SocketEventMap` | _type_ | the shape of an event map |
6230
6384
  | `RealtimeClient` | _type_ | validated client inferred from a realtime contract |
6231
6385
  | `RealtimeClientOptions` | _type_ | transport options and the rejected-event hook for `createRealtimeClient` |
@@ -6239,7 +6393,7 @@ The browser-and-server entrypoint. Re-exports everything from
6239
6393
  | `RealtimeRejectDirection` | _type_ | server/client inbound/outbound rejection direction |
6240
6394
  | `RealtimeRejectedEvent` | _type_ | structured rejected event with event, direction, phase, reason and fault |
6241
6395
  | `RealtimeRejectedEventHook` | _type_ | sync/async observer for structured realtime rejections |
6242
- | `ValidatedRealtimeSocket` | _type_ | runtime-validating `on`/`emit` surface inferred from registries |
6396
+ | `ValidatedRealtimeSocket` | _type_ | runtime-validating `on`/`emit` surface inferred from registries; `emit` returns "accepted by the transport" (`false` only for a client-side disconnected drop) |
6243
6397
  | `RetainedTopics` | _type_ | the `createRetainedTopics` handle |
6244
6398
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
6245
6399
 
@@ -6393,7 +6547,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
6393
6547
  | `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) |
6394
6548
  | `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
6395
6549
  | `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
6396
- | `BunServerConfig` | _type_ | config for `createServer` (Bun) |
6550
+ | `BunServerConfig` | _type_ | config for `createServer` (Bun); `unix` listens on a unix domain socket (mutually exclusive with `port`/`hostname`) — [details](../guide/server.md#local-daemon-over-a-unix-socket) |
6551
+ | `UnixListenConfig` | _type_ | unix listener — a socket path, or `{ path, mode }` to tighten the file mode after listen |
6397
6552
  | `BunServerHandle` | _type_ | managed Bun handle (`url`, `port`, `runtime`, `status`, `shutdown`) |
6398
6553
  | `ManagedServerHandle` | _type_ | shared lifecycle shape generic over the runtime escape hatch |
6399
6554
  | `ShutdownOptionsSchema` / `ShutdownOptions` | schema / _type_ | one graceful budget, bounded forced-completion timeout, retry hint and optional external abort signal |
@@ -6469,9 +6624,10 @@ Also re-exports the error helpers from `stitchkit/contract`.
6469
6624
  | `bindRealtimeServer` | function | inferred, runtime-validated connection and broadcast boundary |
6470
6625
  | `RealtimeServer` | _type_ | validated broadcast and connection API inferred from a realtime contract |
6471
6626
  | `RealtimeServerConnection` | _type_ | one validated connection with raw socket access for auth and rooms |
6472
- | `RealtimeServerHandle` | _type_ | minimal Socket.IO server handle accepted by `bindRealtimeServer` |
6627
+ | `RealtimeServerHandle` | _type_ | minimal Socket.IO server handle accepted by `bindRealtimeServer`; carries the handshake identity type through to `connection.raw.data` |
6473
6628
  | `SocketIORequestPolicy` | _type_ | runtime-neutral async-capable Web `Request` handshake admission policy |
6474
- | `SocketIOServerConfig` | _type_ | config for `createSocketIOServer` |
6629
+ | `SocketIOServerConfig` | _type_ | config for `createSocketIOServer`; `handshake` is the typed identity gate — [guide](../guide/realtime.md#handshake-auth--cookie-or-token) |
6630
+ | `SocketIOHandshakeConfig` | _type_ | the `handshake` gate — Zod `schema` over `handshake.auth` plus optional async `verify`; the result lands typed in `socket.data` |
6475
6631
  | `SocketIOServerHandle` | _type_ | typed Socket.IO server plus Bun mount fields and idempotent lifecycle |
6476
6632
  | `SocketIOServerLifecycle` | _type_ | non-generic Bun mount/shutdown portion accepted by `createServer` |
6477
6633
  | `composeWebSocketHandlers` | function | compose one Bun `websocket` from N lanes — a raw binary lane beside Socket.IO ([guide](../guide/realtime.md#raw-binary-lane-bun)) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.52.0",
3
+ "version": "0.53.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",