@super-line/server 0.2.0 → 0.3.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Server, IncomingMessage } from 'node:http';
2
- import { ServerMessageDef, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, ServerEvents, ServerEmit, ServerData } from '@super-line/core';
2
+ import { ServerMessageDef, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData } from '@super-line/core';
3
3
  import { WebSocket } from 'ws';
4
4
 
5
5
  /** Backpressure policy: what to do when a connection's send buffer grows too large. */
@@ -28,6 +28,8 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
28
28
  readonly ctx: Ctx;
29
29
  private readonly serializer;
30
30
  private readonly backpressure?;
31
+ /** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
32
+ private readonly onEmit?;
31
33
  /** Namespaced channels (rooms + topics) this connection belongs to. */
32
34
  readonly channels: Set<string>;
33
35
  /** Mutable per-connection scratch state, typed per role by the contract's `data` schema. */
@@ -48,7 +50,9 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
48
50
  /** This connection's role (the literal resolved by `authenticate`). */
49
51
  role: Role,
50
52
  /** The context `authenticate` returned for this connection. */
51
- ctx: Ctx, serializer: Serializer, backpressure?: Backpressure | undefined);
53
+ ctx: Ctx, serializer: Serializer, backpressure?: Backpressure | undefined,
54
+ /** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
55
+ onEmit?: ((event: string, data: unknown) => void) | undefined);
52
56
  private overBackpressure;
53
57
  /** Encode and send a frame (unicast, e.g. req/res). */
54
58
  send(frame: ServerFrame): void;
@@ -127,12 +131,17 @@ type Handlers<C extends Contract, A> = ([keyof SharedRequests<C>] extends [never
127
131
  };
128
132
  /** Context passed to middleware and lifecycle hooks about the current operation. */
129
133
  interface MiddlewareInfo {
130
- /** Whether this is a request or a topic subscribe. */
131
- kind: 'request' | 'subscribe';
132
- /** The request/topic name. */
134
+ /** Whether this is a request, a topic subscribe, or a bus event delivery. */
135
+ kind: 'request' | 'subscribe' | 'event';
136
+ /** The request/topic/event name. */
133
137
  name: string;
134
- /** The connection the operation is on (`conn.role` available). */
135
- conn: Conn;
138
+ /** The connection the operation is on, if any (`conn.role` available). Absent for bus events. */
139
+ conn?: Conn;
140
+ }
141
+ /** Metadata passed to a {@link SuperLineServer.subscribe} callback alongside the event payload. */
142
+ interface BusMeta {
143
+ /** The node that published the event. Equals `srv.nodeId` for a same-node publish (local echo). */
144
+ from: string;
136
145
  }
137
146
  /**
138
147
  * Flat middleware run before request/subscribe handlers. Call `next()` to proceed,
@@ -186,7 +195,7 @@ interface ConnTarget<C extends Contract> {
186
195
  emit<E extends keyof SharedEvents<C>>(event: E, data: EmitData<SharedEvents<C>[E]>): void;
187
196
  /**
188
197
  * Send a shared server→client request and await the client's typed reply
189
- * (cross-node). Rejects with a `TIMEOUT` `SocketError` if no live node owns
198
+ * (cross-node). Rejects with a `TIMEOUT` `SuperLineError` if no live node owns
190
199
  * the connection or the client doesn't answer in time.
191
200
  */
192
201
  request<M extends keyof SharedServerRequests<C>>(name: M, input: ClientInput<SharedServerRequests<C>[M]>, opts?: {
@@ -208,8 +217,8 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
208
217
  /** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
209
218
  publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
210
219
  }
211
- /** Options for {@link createSocketServer}. */
212
- interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
220
+ /** Options for {@link createSuperLineServer}. */
221
+ interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>> {
213
222
  /** The `http.Server` to attach to (compose with Express/Fastify/Hono). */
214
223
  server?: Server;
215
224
  /** Wire serializer; MUST match the client. Defaults to `jsonSerializer`. */
@@ -218,6 +227,11 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
218
227
  adapter?: Adapter;
219
228
  /** Only handle upgrades for this pathname; others are left untouched. */
220
229
  path?: string;
230
+ /**
231
+ * Friendly name for this node, surfaced in `srv.nodeName`, the cluster descriptor, and the
232
+ * Control Center topology. Defaults to `SUPER_LINE_NODE_NAME` or a short slice of `nodeId`.
233
+ */
234
+ nodeName?: string;
221
235
  /** Runs at the HTTP upgrade. Return { role, ctx }, or throw to reject with 401. */
222
236
  authenticate: (req: IncomingMessage) => Awaitable<A>;
223
237
  /** Stable user key for a connection (powers `cluster.byUser`, `isOnline`, and `toUser`). */
@@ -240,6 +254,14 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
240
254
  } | false;
241
255
  /** Guard against slow consumers: when a connection's send buffer exceeds the limit, close or drop. */
242
256
  backpressure?: Backpressure;
257
+ /**
258
+ * Read-only Control Center inspector channel, reached via the WS subprotocol
259
+ * `superline.inspector.v1`. Connections on it bypass `authenticate` and are kept out of
260
+ * presence, the heartbeat, and `local`/`cluster` results. **Default off; dev / trusted-network only.**
261
+ */
262
+ inspector?: boolean | {
263
+ redact?: string[];
264
+ };
243
265
  /** Called once per accepted connection. */
244
266
  onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
245
267
  /** Called when a connection closes, with the WebSocket close `code`. */
@@ -247,10 +269,12 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
247
269
  /** Called for any error thrown in middleware/handlers (after the client is replied to). */
248
270
  onError?: (error: unknown, info: MiddlewareInfo) => void;
249
271
  }
250
- /** A running super-line server, returned by {@link createSocketServer}. */
251
- interface SocketServer<C extends Contract, A extends AuthResult<C>> {
272
+ /** A running super-line server, returned by {@link createSuperLineServer}. */
273
+ interface SuperLineServer<C extends Contract, A extends AuthResult<C>> {
252
274
  /** This node's stable id (unique per server process). */
253
275
  readonly nodeId: string;
276
+ /** This node's friendly name (from `nodeName`/`SUPER_LINE_NODE_NAME`, else a short `nodeId` slice). */
277
+ readonly nodeName: string;
254
278
  /** Synchronous, node-local introspection (connections, rooms, topics on this process). */
255
279
  readonly local: LocalView;
256
280
  /** Asynchronous, cluster-wide introspection backed by the adapter's presence directory. */
@@ -262,32 +286,35 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
262
286
  /** Target all of a user's connections (by `identify` key) across nodes (emit/disconnect). */
263
287
  toUser(userId: string): UserTarget<C>;
264
288
  /** Register handlers for shared + per-role requests (chainable). */
265
- implement(handlers: Handlers<C, A>): SocketServer<C, A>;
289
+ implement(handlers: Handlers<C, A>): SuperLineServer<C, A>;
266
290
  /** Mixed-role connection group; broadcast() sends a shared contract event to members. */
267
291
  room(name: string): Room<C>;
268
292
  /** Publish a SHARED topic to all subscribers (server-only publish). */
269
293
  publish<T extends keyof SharedTopics<C>>(topic: T, data: EmitData<SharedTopics<C>[T]>): void;
294
+ /**
295
+ * Subscribe SERVER-side to a shared topic, cluster-wide. The callback fires for a
296
+ * publish from any node — including this one (local echo, delivered in-process with
297
+ * no round-trip). `meta.from` is the publishing node; self-exclude with
298
+ * `if (meta.from === srv.nodeId) return`. Returns an unsubscribe fn.
299
+ */
300
+ subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
270
301
  /** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
271
302
  forRole<R extends RoleOf<C>>(role: R): RoleLens<C, R>;
272
- /** Broadcast a typed event to all OTHER server nodes (at-most-once, excludes self). */
273
- emitServer<E extends keyof ServerEvents<C>>(event: E, data: ServerEmit<ServerEvents<C>[E]>): void;
274
- /** Listen for inter-server events from other nodes. Returns an unsubscribe fn. */
275
- onServer<E extends keyof ServerEvents<C>>(event: E, handler: (data: ServerData<ServerEvents<C>[E]>) => void): () => void;
276
303
  close(): Promise<void>;
277
304
  }
278
305
  /**
279
306
  * Create a server bound to a contract. Attach it to an `http.Server`, then call
280
- * {@link SocketServer.implement} with your handlers. `authenticate` resolves each
307
+ * {@link SuperLineServer.implement} with your handlers. `authenticate` resolves each
281
308
  * connection's `{ role, ctx }` at the upgrade.
282
309
  *
283
310
  * @param contract - the shared contract.
284
311
  * @param opts - server options; `authenticate` is required.
285
- * @returns the {@link SocketServer}.
286
- * @throws nothing directly; handler throws become a typed `SocketError` to the client.
312
+ * @returns the {@link SuperLineServer}.
313
+ * @throws nothing directly; handler throws become a typed `SuperLineError` to the client.
287
314
  *
288
315
  * @example
289
316
  * ```ts
290
- * const srv = createSocketServer(api, {
317
+ * const srv = createSuperLineServer(api, {
291
318
  * server,
292
319
  * authenticate: (req) => ({ role: 'user' as const, ctx: { id: '1' } }),
293
320
  * })
@@ -297,6 +324,6 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
297
324
  * })
298
325
  * ```
299
326
  */
300
- declare function createSocketServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: ServerOptions<C, A>): SocketServer<C, A>;
327
+ declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: SuperLineServerOptions<C, A>): SuperLineServer<C, A>;
301
328
 
302
- export { type AuthResult, type Backpressure, type ClusterView, Conn, type ConnTarget, type Handlers, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type RoleLens, type Room, type ServerOptions, type SocketServer, type UserTarget, createInMemoryAdapter, createSocketServer };
329
+ export { type AuthResult, type Backpressure, type BusMeta, type ClusterView, Conn, type ConnTarget, type Handlers, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type RoleLens, type Room, type SuperLineServer, type SuperLineServerOptions, type UserTarget, createInMemoryAdapter, createSuperLineServer };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Server, IncomingMessage } from 'node:http';
2
- import { ServerMessageDef, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, ServerEvents, ServerEmit, ServerData } from '@super-line/core';
2
+ import { ServerMessageDef, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData } from '@super-line/core';
3
3
  import { WebSocket } from 'ws';
4
4
 
5
5
  /** Backpressure policy: what to do when a connection's send buffer grows too large. */
@@ -28,6 +28,8 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
28
28
  readonly ctx: Ctx;
29
29
  private readonly serializer;
30
30
  private readonly backpressure?;
31
+ /** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
32
+ private readonly onEmit?;
31
33
  /** Namespaced channels (rooms + topics) this connection belongs to. */
32
34
  readonly channels: Set<string>;
33
35
  /** Mutable per-connection scratch state, typed per role by the contract's `data` schema. */
@@ -48,7 +50,9 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
48
50
  /** This connection's role (the literal resolved by `authenticate`). */
49
51
  role: Role,
50
52
  /** The context `authenticate` returned for this connection. */
51
- ctx: Ctx, serializer: Serializer, backpressure?: Backpressure | undefined);
53
+ ctx: Ctx, serializer: Serializer, backpressure?: Backpressure | undefined,
54
+ /** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
55
+ onEmit?: ((event: string, data: unknown) => void) | undefined);
52
56
  private overBackpressure;
53
57
  /** Encode and send a frame (unicast, e.g. req/res). */
54
58
  send(frame: ServerFrame): void;
@@ -127,12 +131,17 @@ type Handlers<C extends Contract, A> = ([keyof SharedRequests<C>] extends [never
127
131
  };
128
132
  /** Context passed to middleware and lifecycle hooks about the current operation. */
129
133
  interface MiddlewareInfo {
130
- /** Whether this is a request or a topic subscribe. */
131
- kind: 'request' | 'subscribe';
132
- /** The request/topic name. */
134
+ /** Whether this is a request, a topic subscribe, or a bus event delivery. */
135
+ kind: 'request' | 'subscribe' | 'event';
136
+ /** The request/topic/event name. */
133
137
  name: string;
134
- /** The connection the operation is on (`conn.role` available). */
135
- conn: Conn;
138
+ /** The connection the operation is on, if any (`conn.role` available). Absent for bus events. */
139
+ conn?: Conn;
140
+ }
141
+ /** Metadata passed to a {@link SuperLineServer.subscribe} callback alongside the event payload. */
142
+ interface BusMeta {
143
+ /** The node that published the event. Equals `srv.nodeId` for a same-node publish (local echo). */
144
+ from: string;
136
145
  }
137
146
  /**
138
147
  * Flat middleware run before request/subscribe handlers. Call `next()` to proceed,
@@ -186,7 +195,7 @@ interface ConnTarget<C extends Contract> {
186
195
  emit<E extends keyof SharedEvents<C>>(event: E, data: EmitData<SharedEvents<C>[E]>): void;
187
196
  /**
188
197
  * Send a shared server→client request and await the client's typed reply
189
- * (cross-node). Rejects with a `TIMEOUT` `SocketError` if no live node owns
198
+ * (cross-node). Rejects with a `TIMEOUT` `SuperLineError` if no live node owns
190
199
  * the connection or the client doesn't answer in time.
191
200
  */
192
201
  request<M extends keyof SharedServerRequests<C>>(name: M, input: ClientInput<SharedServerRequests<C>[M]>, opts?: {
@@ -208,8 +217,8 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
208
217
  /** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
209
218
  publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
210
219
  }
211
- /** Options for {@link createSocketServer}. */
212
- interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
220
+ /** Options for {@link createSuperLineServer}. */
221
+ interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>> {
213
222
  /** The `http.Server` to attach to (compose with Express/Fastify/Hono). */
214
223
  server?: Server;
215
224
  /** Wire serializer; MUST match the client. Defaults to `jsonSerializer`. */
@@ -218,6 +227,11 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
218
227
  adapter?: Adapter;
219
228
  /** Only handle upgrades for this pathname; others are left untouched. */
220
229
  path?: string;
230
+ /**
231
+ * Friendly name for this node, surfaced in `srv.nodeName`, the cluster descriptor, and the
232
+ * Control Center topology. Defaults to `SUPER_LINE_NODE_NAME` or a short slice of `nodeId`.
233
+ */
234
+ nodeName?: string;
221
235
  /** Runs at the HTTP upgrade. Return { role, ctx }, or throw to reject with 401. */
222
236
  authenticate: (req: IncomingMessage) => Awaitable<A>;
223
237
  /** Stable user key for a connection (powers `cluster.byUser`, `isOnline`, and `toUser`). */
@@ -240,6 +254,14 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
240
254
  } | false;
241
255
  /** Guard against slow consumers: when a connection's send buffer exceeds the limit, close or drop. */
242
256
  backpressure?: Backpressure;
257
+ /**
258
+ * Read-only Control Center inspector channel, reached via the WS subprotocol
259
+ * `superline.inspector.v1`. Connections on it bypass `authenticate` and are kept out of
260
+ * presence, the heartbeat, and `local`/`cluster` results. **Default off; dev / trusted-network only.**
261
+ */
262
+ inspector?: boolean | {
263
+ redact?: string[];
264
+ };
243
265
  /** Called once per accepted connection. */
244
266
  onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
245
267
  /** Called when a connection closes, with the WebSocket close `code`. */
@@ -247,10 +269,12 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
247
269
  /** Called for any error thrown in middleware/handlers (after the client is replied to). */
248
270
  onError?: (error: unknown, info: MiddlewareInfo) => void;
249
271
  }
250
- /** A running super-line server, returned by {@link createSocketServer}. */
251
- interface SocketServer<C extends Contract, A extends AuthResult<C>> {
272
+ /** A running super-line server, returned by {@link createSuperLineServer}. */
273
+ interface SuperLineServer<C extends Contract, A extends AuthResult<C>> {
252
274
  /** This node's stable id (unique per server process). */
253
275
  readonly nodeId: string;
276
+ /** This node's friendly name (from `nodeName`/`SUPER_LINE_NODE_NAME`, else a short `nodeId` slice). */
277
+ readonly nodeName: string;
254
278
  /** Synchronous, node-local introspection (connections, rooms, topics on this process). */
255
279
  readonly local: LocalView;
256
280
  /** Asynchronous, cluster-wide introspection backed by the adapter's presence directory. */
@@ -262,32 +286,35 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
262
286
  /** Target all of a user's connections (by `identify` key) across nodes (emit/disconnect). */
263
287
  toUser(userId: string): UserTarget<C>;
264
288
  /** Register handlers for shared + per-role requests (chainable). */
265
- implement(handlers: Handlers<C, A>): SocketServer<C, A>;
289
+ implement(handlers: Handlers<C, A>): SuperLineServer<C, A>;
266
290
  /** Mixed-role connection group; broadcast() sends a shared contract event to members. */
267
291
  room(name: string): Room<C>;
268
292
  /** Publish a SHARED topic to all subscribers (server-only publish). */
269
293
  publish<T extends keyof SharedTopics<C>>(topic: T, data: EmitData<SharedTopics<C>[T]>): void;
294
+ /**
295
+ * Subscribe SERVER-side to a shared topic, cluster-wide. The callback fires for a
296
+ * publish from any node — including this one (local echo, delivered in-process with
297
+ * no round-trip). `meta.from` is the publishing node; self-exclude with
298
+ * `if (meta.from === srv.nodeId) return`. Returns an unsubscribe fn.
299
+ */
300
+ subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
270
301
  /** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
271
302
  forRole<R extends RoleOf<C>>(role: R): RoleLens<C, R>;
272
- /** Broadcast a typed event to all OTHER server nodes (at-most-once, excludes self). */
273
- emitServer<E extends keyof ServerEvents<C>>(event: E, data: ServerEmit<ServerEvents<C>[E]>): void;
274
- /** Listen for inter-server events from other nodes. Returns an unsubscribe fn. */
275
- onServer<E extends keyof ServerEvents<C>>(event: E, handler: (data: ServerData<ServerEvents<C>[E]>) => void): () => void;
276
303
  close(): Promise<void>;
277
304
  }
278
305
  /**
279
306
  * Create a server bound to a contract. Attach it to an `http.Server`, then call
280
- * {@link SocketServer.implement} with your handlers. `authenticate` resolves each
307
+ * {@link SuperLineServer.implement} with your handlers. `authenticate` resolves each
281
308
  * connection's `{ role, ctx }` at the upgrade.
282
309
  *
283
310
  * @param contract - the shared contract.
284
311
  * @param opts - server options; `authenticate` is required.
285
- * @returns the {@link SocketServer}.
286
- * @throws nothing directly; handler throws become a typed `SocketError` to the client.
312
+ * @returns the {@link SuperLineServer}.
313
+ * @throws nothing directly; handler throws become a typed `SuperLineError` to the client.
287
314
  *
288
315
  * @example
289
316
  * ```ts
290
- * const srv = createSocketServer(api, {
317
+ * const srv = createSuperLineServer(api, {
291
318
  * server,
292
319
  * authenticate: (req) => ({ role: 'user' as const, ctx: { id: '1' } }),
293
320
  * })
@@ -297,6 +324,6 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
297
324
  * })
298
325
  * ```
299
326
  */
300
- declare function createSocketServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: ServerOptions<C, A>): SocketServer<C, A>;
327
+ declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: SuperLineServerOptions<C, A>): SuperLineServer<C, A>;
301
328
 
302
- export { type AuthResult, type Backpressure, type ClusterView, Conn, type ConnTarget, type Handlers, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type RoleLens, type Room, type ServerOptions, type SocketServer, type UserTarget, createInMemoryAdapter, createSocketServer };
329
+ export { type AuthResult, type Backpressure, type BusMeta, type ClusterView, Conn, type ConnTarget, type Handlers, type LocalView, MemoryBus, type Middleware, type MiddlewareInfo, type RoleLens, type Room, type SuperLineServer, type SuperLineServerOptions, type UserTarget, createInMemoryAdapter, createSuperLineServer };