@super-line/server 0.2.0 → 0.4.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 +7 -6
- package/dist/arktype-aI7TBD0R-QUZZIMBF.js +9 -0
- package/dist/chunk-BXHVPS35.js +164 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/core-DXJRFNRR.js +9858 -0
- package/dist/dist-S566F7HE.js +9 -0
- package/dist/effect-QlVUlMFu-XUZCEH2A.js +17 -0
- package/dist/esm-F5WYXMGY.js +5222 -0
- package/dist/index.cjs +16600 -138
- package/dist/index.d.cts +67 -51
- package/dist/index.d.ts +67 -51
- package/dist/index.js +349 -132
- package/dist/sury-CWZTCd75-XRKNS4FU.js +17 -0
- package/dist/typebox-Dei93FPO-EURKZGC4.js +9 -0
- package/dist/valibot--1zFm7rT-CYY6WSKB.js +17 -0
- package/dist/zod-Bwrt9trS-3RR2G3Z5.js +31 -0
- package/package.json +27 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
3
|
-
import { WebSocket } from 'ws';
|
|
1
|
+
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData, ServerTransport, Handshake } from '@super-line/core';
|
|
4
2
|
|
|
5
|
-
/** Backpressure policy: what to do when a connection's send buffer grows too large. */
|
|
6
|
-
interface Backpressure {
|
|
7
|
-
/** Buffer size (bytes) above which {@link Backpressure.onExceed} kicks in. */
|
|
8
|
-
maxBufferedBytes: number;
|
|
9
|
-
/** `'close'` (default) drops the connection with code 1013; `'drop'` skips the frame. */
|
|
10
|
-
onExceed?: 'close' | 'drop';
|
|
11
|
-
}
|
|
12
3
|
/**
|
|
13
4
|
* A single client connection, passed to handlers as the third argument.
|
|
14
5
|
*
|
|
15
|
-
* Node-local: `conn` objects live on the node that accepted the
|
|
6
|
+
* Node-local: `conn` objects live on the node that accepted the connection, so don't
|
|
16
7
|
* stash one to reach a user later — cross-node delivery goes through the Adapter
|
|
17
8
|
* (use a per-user room instead). Generic over the events it may emit (scoped by
|
|
18
9
|
* role), its `ctx`, and its `role`.
|
|
19
10
|
*/
|
|
20
11
|
declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role extends string = string, Data = unknown> {
|
|
21
|
-
/** The underlying
|
|
22
|
-
readonly
|
|
12
|
+
/** The underlying transport connection. `conn.terminate()` simulates a drop in tests. */
|
|
13
|
+
readonly raw: RawConn;
|
|
23
14
|
/** Server-assigned unique id for this connection (stable for its lifetime). */
|
|
24
15
|
readonly id: string;
|
|
25
16
|
/** This connection's role (the literal resolved by `authenticate`). */
|
|
@@ -27,12 +18,15 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
27
18
|
/** The context `authenticate` returned for this connection. */
|
|
28
19
|
readonly ctx: Ctx;
|
|
29
20
|
private readonly serializer;
|
|
30
|
-
|
|
21
|
+
/** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
|
|
22
|
+
private readonly onEmit?;
|
|
31
23
|
/** Namespaced channels (rooms + topics) this connection belongs to. */
|
|
32
24
|
readonly channels: Set<string>;
|
|
33
25
|
/** Mutable per-connection scratch state, typed per role by the contract's `data` schema. */
|
|
34
26
|
data: Data;
|
|
35
|
-
/**
|
|
27
|
+
/** The client↔server transport (wire) this connection was accepted on (set by the server at accept). */
|
|
28
|
+
transport?: string;
|
|
29
|
+
/** When this connection was accepted (`Date.now()`). */
|
|
36
30
|
readonly connectedAt: number;
|
|
37
31
|
/** When the server last sent a heartbeat ping to this connection (managed by the server). */
|
|
38
32
|
lastPingAt?: number;
|
|
@@ -41,23 +35,26 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
41
35
|
/** Pings sent since the last pong; drives reaping (managed by the server). */
|
|
42
36
|
missedPongs: number;
|
|
43
37
|
constructor(
|
|
44
|
-
/** The underlying
|
|
45
|
-
|
|
38
|
+
/** The underlying transport connection. `conn.terminate()` simulates a drop in tests. */
|
|
39
|
+
raw: RawConn,
|
|
46
40
|
/** Server-assigned unique id for this connection (stable for its lifetime). */
|
|
47
41
|
id: string,
|
|
48
42
|
/** This connection's role (the literal resolved by `authenticate`). */
|
|
49
43
|
role: Role,
|
|
50
44
|
/** The context `authenticate` returned for this connection. */
|
|
51
|
-
ctx: Ctx, serializer: Serializer,
|
|
52
|
-
|
|
45
|
+
ctx: Ctx, serializer: Serializer,
|
|
46
|
+
/** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
|
|
47
|
+
onEmit?: ((event: string, data: unknown) => void) | undefined);
|
|
53
48
|
/** Encode and send a frame (unicast, e.g. req/res). */
|
|
54
49
|
send(frame: ServerFrame): void;
|
|
55
50
|
/** Forward an already-encoded frame (fan-out path; encoded once at the source). */
|
|
56
51
|
sendRaw(payload: string | Uint8Array): void;
|
|
57
52
|
/** Push an event to THIS connection (node-local). Scoped to the role's events. */
|
|
58
53
|
emit<E extends keyof Ev>(event: E, data: EmitData<Ev[E]>): void;
|
|
59
|
-
/**
|
|
54
|
+
/** Graceful close of the underlying transport connection. */
|
|
60
55
|
close(): void;
|
|
56
|
+
/** Hard close with no handshake — used by heartbeat reaping. */
|
|
57
|
+
terminate(): void;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/**
|
|
@@ -127,12 +124,17 @@ type Handlers<C extends Contract, A> = ([keyof SharedRequests<C>] extends [never
|
|
|
127
124
|
};
|
|
128
125
|
/** Context passed to middleware and lifecycle hooks about the current operation. */
|
|
129
126
|
interface MiddlewareInfo {
|
|
130
|
-
/** Whether this is a request or a
|
|
131
|
-
kind: 'request' | 'subscribe';
|
|
132
|
-
/** The request/topic name. */
|
|
127
|
+
/** Whether this is a request, a topic subscribe, or a bus event delivery. */
|
|
128
|
+
kind: 'request' | 'subscribe' | 'event';
|
|
129
|
+
/** The request/topic/event name. */
|
|
133
130
|
name: string;
|
|
134
|
-
/** The connection the operation is on (`conn.role` available). */
|
|
135
|
-
conn
|
|
131
|
+
/** The connection the operation is on, if any (`conn.role` available). Absent for bus events. */
|
|
132
|
+
conn?: Conn;
|
|
133
|
+
}
|
|
134
|
+
/** Metadata passed to a {@link SuperLineServer.subscribe} callback alongside the event payload. */
|
|
135
|
+
interface BusMeta {
|
|
136
|
+
/** The node that published the event. Equals `srv.nodeId` for a same-node publish (local echo). */
|
|
137
|
+
from: string;
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Flat middleware run before request/subscribe handlers. Call `next()` to proceed,
|
|
@@ -186,7 +188,7 @@ interface ConnTarget<C extends Contract> {
|
|
|
186
188
|
emit<E extends keyof SharedEvents<C>>(event: E, data: EmitData<SharedEvents<C>[E]>): void;
|
|
187
189
|
/**
|
|
188
190
|
* Send a shared server→client request and await the client's typed reply
|
|
189
|
-
* (cross-node). Rejects with a `TIMEOUT` `
|
|
191
|
+
* (cross-node). Rejects with a `TIMEOUT` `SuperLineError` if no live node owns
|
|
190
192
|
* the connection or the client doesn't answer in time.
|
|
191
193
|
*/
|
|
192
194
|
request<M extends keyof SharedServerRequests<C>>(name: M, input: ClientInput<SharedServerRequests<C>[M]>, opts?: {
|
|
@@ -208,18 +210,21 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
|
|
|
208
210
|
/** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
|
|
209
211
|
publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
|
|
210
212
|
}
|
|
211
|
-
/** Options for {@link
|
|
212
|
-
interface
|
|
213
|
-
/**
|
|
214
|
-
|
|
213
|
+
/** Options for {@link createSuperLineServer}. */
|
|
214
|
+
interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
215
|
+
/** Client↔server transports to accept connections on (e.g. `webSocketServerTransport({ server })`). */
|
|
216
|
+
transports: ServerTransport[];
|
|
215
217
|
/** Wire serializer; MUST match the client. Defaults to `jsonSerializer`. */
|
|
216
218
|
serializer?: Serializer;
|
|
217
219
|
/** Cross-node fan-out adapter. Defaults to a per-server in-memory adapter. */
|
|
218
220
|
adapter?: Adapter;
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Friendly name for this node, surfaced in `srv.nodeName`, the cluster descriptor, and the
|
|
223
|
+
* Control Center topology. Defaults to `SUPER_LINE_NODE_NAME` or a short slice of `nodeId`.
|
|
224
|
+
*/
|
|
225
|
+
nodeName?: string;
|
|
226
|
+
/** Authenticate a connection from its normalized {@link Handshake}. Return { role, ctx }, or throw to reject. */
|
|
227
|
+
authenticate: (handshake: Handshake) => Awaitable<A>;
|
|
223
228
|
/** Stable user key for a connection (powers `cluster.byUser`, `isOnline`, and `toUser`). */
|
|
224
229
|
identify?: (conn: Conn) => string | undefined;
|
|
225
230
|
/** Extra fields merged into the connection's cluster descriptor (e.g. `{ plan }`). `ctx` is never auto-serialized. */
|
|
@@ -238,8 +243,14 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
|
238
243
|
interval?: number;
|
|
239
244
|
maxMissed?: number;
|
|
240
245
|
} | false;
|
|
241
|
-
/**
|
|
242
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Enable the read-only Control Center inspector: emit `msg.*` telemetry and accept inspector
|
|
248
|
+
* clients. The WS transport must also be created with `inspector: true` to negotiate the
|
|
249
|
+
* `superline.inspector.v1` subprotocol. **Default off; dev / trusted-network only.**
|
|
250
|
+
*/
|
|
251
|
+
inspector?: boolean | {
|
|
252
|
+
redact?: string[];
|
|
253
|
+
};
|
|
243
254
|
/** Called once per accepted connection. */
|
|
244
255
|
onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
|
|
245
256
|
/** Called when a connection closes, with the WebSocket close `code`. */
|
|
@@ -247,10 +258,12 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
|
247
258
|
/** Called for any error thrown in middleware/handlers (after the client is replied to). */
|
|
248
259
|
onError?: (error: unknown, info: MiddlewareInfo) => void;
|
|
249
260
|
}
|
|
250
|
-
/** A running super-line server, returned by {@link
|
|
251
|
-
interface
|
|
261
|
+
/** A running super-line server, returned by {@link createSuperLineServer}. */
|
|
262
|
+
interface SuperLineServer<C extends Contract, A extends AuthResult<C>> {
|
|
252
263
|
/** This node's stable id (unique per server process). */
|
|
253
264
|
readonly nodeId: string;
|
|
265
|
+
/** This node's friendly name (from `nodeName`/`SUPER_LINE_NODE_NAME`, else a short `nodeId` slice). */
|
|
266
|
+
readonly nodeName: string;
|
|
254
267
|
/** Synchronous, node-local introspection (connections, rooms, topics on this process). */
|
|
255
268
|
readonly local: LocalView;
|
|
256
269
|
/** Asynchronous, cluster-wide introspection backed by the adapter's presence directory. */
|
|
@@ -262,34 +275,37 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
|
|
|
262
275
|
/** Target all of a user's connections (by `identify` key) across nodes (emit/disconnect). */
|
|
263
276
|
toUser(userId: string): UserTarget<C>;
|
|
264
277
|
/** Register handlers for shared + per-role requests (chainable). */
|
|
265
|
-
implement(handlers: Handlers<C, A>):
|
|
278
|
+
implement(handlers: Handlers<C, A>): SuperLineServer<C, A>;
|
|
266
279
|
/** Mixed-role connection group; broadcast() sends a shared contract event to members. */
|
|
267
280
|
room(name: string): Room<C>;
|
|
268
281
|
/** Publish a SHARED topic to all subscribers (server-only publish). */
|
|
269
282
|
publish<T extends keyof SharedTopics<C>>(topic: T, data: EmitData<SharedTopics<C>[T]>): void;
|
|
283
|
+
/**
|
|
284
|
+
* Subscribe SERVER-side to a shared topic, cluster-wide. The callback fires for a
|
|
285
|
+
* publish from any node — including this one (local echo, delivered in-process with
|
|
286
|
+
* no round-trip). `meta.from` is the publishing node; self-exclude with
|
|
287
|
+
* `if (meta.from === srv.nodeId) return`. Returns an unsubscribe fn.
|
|
288
|
+
*/
|
|
289
|
+
subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
|
|
270
290
|
/** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
|
|
271
291
|
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
292
|
close(): Promise<void>;
|
|
277
293
|
}
|
|
278
294
|
/**
|
|
279
295
|
* Create a server bound to a contract. Attach it to an `http.Server`, then call
|
|
280
|
-
* {@link
|
|
296
|
+
* {@link SuperLineServer.implement} with your handlers. `authenticate` resolves each
|
|
281
297
|
* connection's `{ role, ctx }` at the upgrade.
|
|
282
298
|
*
|
|
283
299
|
* @param contract - the shared contract.
|
|
284
300
|
* @param opts - server options; `authenticate` is required.
|
|
285
|
-
* @returns the {@link
|
|
286
|
-
* @throws nothing directly; handler throws become a typed `
|
|
301
|
+
* @returns the {@link SuperLineServer}.
|
|
302
|
+
* @throws nothing directly; handler throws become a typed `SuperLineError` to the client.
|
|
287
303
|
*
|
|
288
304
|
* @example
|
|
289
305
|
* ```ts
|
|
290
|
-
* const srv =
|
|
291
|
-
* server,
|
|
292
|
-
* authenticate: (
|
|
306
|
+
* const srv = createSuperLineServer(api, {
|
|
307
|
+
* transports: [webSocketServerTransport({ server })],
|
|
308
|
+
* authenticate: (h) => ({ role: 'user' as const, ctx: { id: '1' } }),
|
|
293
309
|
* })
|
|
294
310
|
* srv.implement({
|
|
295
311
|
* shared: { join: async ({ room }, _ctx, conn) => { srv.room(room).add(conn); return { ok: true } } },
|
|
@@ -297,6 +313,6 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
|
|
|
297
313
|
* })
|
|
298
314
|
* ```
|
|
299
315
|
*/
|
|
300
|
-
declare function
|
|
316
|
+
declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: SuperLineServerOptions<C, A>): SuperLineServer<C, A>;
|
|
301
317
|
|
|
302
|
-
export { type AuthResult, type
|
|
318
|
+
export { type AuthResult, 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,25 +1,16 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
3
|
-
import { WebSocket } from 'ws';
|
|
1
|
+
import { ServerMessageDef, RawConn, Serializer, ServerFrame, EmitData, ConnDescriptor, Adapter, PresenceStore, Contract, RoleOf, NodeStat, SharedEvents, SharedServerRequests, ClientInput, Output, SharedRequests, ServerInput, AnyData, RoleRequests, Events, DataOf, RoleTopics, SharedTopics, EventData, ServerTransport, Handshake } from '@super-line/core';
|
|
4
2
|
|
|
5
|
-
/** Backpressure policy: what to do when a connection's send buffer grows too large. */
|
|
6
|
-
interface Backpressure {
|
|
7
|
-
/** Buffer size (bytes) above which {@link Backpressure.onExceed} kicks in. */
|
|
8
|
-
maxBufferedBytes: number;
|
|
9
|
-
/** `'close'` (default) drops the connection with code 1013; `'drop'` skips the frame. */
|
|
10
|
-
onExceed?: 'close' | 'drop';
|
|
11
|
-
}
|
|
12
3
|
/**
|
|
13
4
|
* A single client connection, passed to handlers as the third argument.
|
|
14
5
|
*
|
|
15
|
-
* Node-local: `conn` objects live on the node that accepted the
|
|
6
|
+
* Node-local: `conn` objects live on the node that accepted the connection, so don't
|
|
16
7
|
* stash one to reach a user later — cross-node delivery goes through the Adapter
|
|
17
8
|
* (use a per-user room instead). Generic over the events it may emit (scoped by
|
|
18
9
|
* role), its `ctx`, and its `role`.
|
|
19
10
|
*/
|
|
20
11
|
declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role extends string = string, Data = unknown> {
|
|
21
|
-
/** The underlying
|
|
22
|
-
readonly
|
|
12
|
+
/** The underlying transport connection. `conn.terminate()` simulates a drop in tests. */
|
|
13
|
+
readonly raw: RawConn;
|
|
23
14
|
/** Server-assigned unique id for this connection (stable for its lifetime). */
|
|
24
15
|
readonly id: string;
|
|
25
16
|
/** This connection's role (the literal resolved by `authenticate`). */
|
|
@@ -27,12 +18,15 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
27
18
|
/** The context `authenticate` returned for this connection. */
|
|
28
19
|
readonly ctx: Ctx;
|
|
29
20
|
private readonly serializer;
|
|
30
|
-
|
|
21
|
+
/** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
|
|
22
|
+
private readonly onEmit?;
|
|
31
23
|
/** Namespaced channels (rooms + topics) this connection belongs to. */
|
|
32
24
|
readonly channels: Set<string>;
|
|
33
25
|
/** Mutable per-connection scratch state, typed per role by the contract's `data` schema. */
|
|
34
26
|
data: Data;
|
|
35
|
-
/**
|
|
27
|
+
/** The client↔server transport (wire) this connection was accepted on (set by the server at accept). */
|
|
28
|
+
transport?: string;
|
|
29
|
+
/** When this connection was accepted (`Date.now()`). */
|
|
36
30
|
readonly connectedAt: number;
|
|
37
31
|
/** When the server last sent a heartbeat ping to this connection (managed by the server). */
|
|
38
32
|
lastPingAt?: number;
|
|
@@ -41,23 +35,26 @@ declare class Conn<Ev = Record<string, ServerMessageDef>, Ctx = unknown, Role ex
|
|
|
41
35
|
/** Pings sent since the last pong; drives reaping (managed by the server). */
|
|
42
36
|
missedPongs: number;
|
|
43
37
|
constructor(
|
|
44
|
-
/** The underlying
|
|
45
|
-
|
|
38
|
+
/** The underlying transport connection. `conn.terminate()` simulates a drop in tests. */
|
|
39
|
+
raw: RawConn,
|
|
46
40
|
/** Server-assigned unique id for this connection (stable for its lifetime). */
|
|
47
41
|
id: string,
|
|
48
42
|
/** This connection's role (the literal resolved by `authenticate`). */
|
|
49
43
|
role: Role,
|
|
50
44
|
/** The context `authenticate` returned for this connection. */
|
|
51
|
-
ctx: Ctx, serializer: Serializer,
|
|
52
|
-
|
|
45
|
+
ctx: Ctx, serializer: Serializer,
|
|
46
|
+
/** Optional inspector tap: called with each `emit` so the server can mirror it to inspectors. */
|
|
47
|
+
onEmit?: ((event: string, data: unknown) => void) | undefined);
|
|
53
48
|
/** Encode and send a frame (unicast, e.g. req/res). */
|
|
54
49
|
send(frame: ServerFrame): void;
|
|
55
50
|
/** Forward an already-encoded frame (fan-out path; encoded once at the source). */
|
|
56
51
|
sendRaw(payload: string | Uint8Array): void;
|
|
57
52
|
/** Push an event to THIS connection (node-local). Scoped to the role's events. */
|
|
58
53
|
emit<E extends keyof Ev>(event: E, data: EmitData<Ev[E]>): void;
|
|
59
|
-
/**
|
|
54
|
+
/** Graceful close of the underlying transport connection. */
|
|
60
55
|
close(): void;
|
|
56
|
+
/** Hard close with no handshake — used by heartbeat reaping. */
|
|
57
|
+
terminate(): void;
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/**
|
|
@@ -127,12 +124,17 @@ type Handlers<C extends Contract, A> = ([keyof SharedRequests<C>] extends [never
|
|
|
127
124
|
};
|
|
128
125
|
/** Context passed to middleware and lifecycle hooks about the current operation. */
|
|
129
126
|
interface MiddlewareInfo {
|
|
130
|
-
/** Whether this is a request or a
|
|
131
|
-
kind: 'request' | 'subscribe';
|
|
132
|
-
/** The request/topic name. */
|
|
127
|
+
/** Whether this is a request, a topic subscribe, or a bus event delivery. */
|
|
128
|
+
kind: 'request' | 'subscribe' | 'event';
|
|
129
|
+
/** The request/topic/event name. */
|
|
133
130
|
name: string;
|
|
134
|
-
/** The connection the operation is on (`conn.role` available). */
|
|
135
|
-
conn
|
|
131
|
+
/** The connection the operation is on, if any (`conn.role` available). Absent for bus events. */
|
|
132
|
+
conn?: Conn;
|
|
133
|
+
}
|
|
134
|
+
/** Metadata passed to a {@link SuperLineServer.subscribe} callback alongside the event payload. */
|
|
135
|
+
interface BusMeta {
|
|
136
|
+
/** The node that published the event. Equals `srv.nodeId` for a same-node publish (local echo). */
|
|
137
|
+
from: string;
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Flat middleware run before request/subscribe handlers. Call `next()` to proceed,
|
|
@@ -186,7 +188,7 @@ interface ConnTarget<C extends Contract> {
|
|
|
186
188
|
emit<E extends keyof SharedEvents<C>>(event: E, data: EmitData<SharedEvents<C>[E]>): void;
|
|
187
189
|
/**
|
|
188
190
|
* Send a shared server→client request and await the client's typed reply
|
|
189
|
-
* (cross-node). Rejects with a `TIMEOUT` `
|
|
191
|
+
* (cross-node). Rejects with a `TIMEOUT` `SuperLineError` if no live node owns
|
|
190
192
|
* the connection or the client doesn't answer in time.
|
|
191
193
|
*/
|
|
192
194
|
request<M extends keyof SharedServerRequests<C>>(name: M, input: ClientInput<SharedServerRequests<C>[M]>, opts?: {
|
|
@@ -208,18 +210,21 @@ interface RoleLens<C extends Contract, R extends RoleOf<C>> {
|
|
|
208
210
|
/** Publish to a topic in role `R`'s surface (reaches that role's subscribers). */
|
|
209
211
|
publish<T extends keyof RoleTopics<C, R>>(topic: T, data: EmitData<RoleTopics<C, R>[T]>): void;
|
|
210
212
|
}
|
|
211
|
-
/** Options for {@link
|
|
212
|
-
interface
|
|
213
|
-
/**
|
|
214
|
-
|
|
213
|
+
/** Options for {@link createSuperLineServer}. */
|
|
214
|
+
interface SuperLineServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
215
|
+
/** Client↔server transports to accept connections on (e.g. `webSocketServerTransport({ server })`). */
|
|
216
|
+
transports: ServerTransport[];
|
|
215
217
|
/** Wire serializer; MUST match the client. Defaults to `jsonSerializer`. */
|
|
216
218
|
serializer?: Serializer;
|
|
217
219
|
/** Cross-node fan-out adapter. Defaults to a per-server in-memory adapter. */
|
|
218
220
|
adapter?: Adapter;
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Friendly name for this node, surfaced in `srv.nodeName`, the cluster descriptor, and the
|
|
223
|
+
* Control Center topology. Defaults to `SUPER_LINE_NODE_NAME` or a short slice of `nodeId`.
|
|
224
|
+
*/
|
|
225
|
+
nodeName?: string;
|
|
226
|
+
/** Authenticate a connection from its normalized {@link Handshake}. Return { role, ctx }, or throw to reject. */
|
|
227
|
+
authenticate: (handshake: Handshake) => Awaitable<A>;
|
|
223
228
|
/** Stable user key for a connection (powers `cluster.byUser`, `isOnline`, and `toUser`). */
|
|
224
229
|
identify?: (conn: Conn) => string | undefined;
|
|
225
230
|
/** Extra fields merged into the connection's cluster descriptor (e.g. `{ plan }`). `ctx` is never auto-serialized. */
|
|
@@ -238,8 +243,14 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
|
238
243
|
interval?: number;
|
|
239
244
|
maxMissed?: number;
|
|
240
245
|
} | false;
|
|
241
|
-
/**
|
|
242
|
-
|
|
246
|
+
/**
|
|
247
|
+
* Enable the read-only Control Center inspector: emit `msg.*` telemetry and accept inspector
|
|
248
|
+
* clients. The WS transport must also be created with `inspector: true` to negotiate the
|
|
249
|
+
* `superline.inspector.v1` subprotocol. **Default off; dev / trusted-network only.**
|
|
250
|
+
*/
|
|
251
|
+
inspector?: boolean | {
|
|
252
|
+
redact?: string[];
|
|
253
|
+
};
|
|
243
254
|
/** Called once per accepted connection. */
|
|
244
255
|
onConnection?: (conn: Conn, ctx: CtxUnion<A>) => void;
|
|
245
256
|
/** Called when a connection closes, with the WebSocket close `code`. */
|
|
@@ -247,10 +258,12 @@ interface ServerOptions<C extends Contract, A extends AuthResult<C>> {
|
|
|
247
258
|
/** Called for any error thrown in middleware/handlers (after the client is replied to). */
|
|
248
259
|
onError?: (error: unknown, info: MiddlewareInfo) => void;
|
|
249
260
|
}
|
|
250
|
-
/** A running super-line server, returned by {@link
|
|
251
|
-
interface
|
|
261
|
+
/** A running super-line server, returned by {@link createSuperLineServer}. */
|
|
262
|
+
interface SuperLineServer<C extends Contract, A extends AuthResult<C>> {
|
|
252
263
|
/** This node's stable id (unique per server process). */
|
|
253
264
|
readonly nodeId: string;
|
|
265
|
+
/** This node's friendly name (from `nodeName`/`SUPER_LINE_NODE_NAME`, else a short `nodeId` slice). */
|
|
266
|
+
readonly nodeName: string;
|
|
254
267
|
/** Synchronous, node-local introspection (connections, rooms, topics on this process). */
|
|
255
268
|
readonly local: LocalView;
|
|
256
269
|
/** Asynchronous, cluster-wide introspection backed by the adapter's presence directory. */
|
|
@@ -262,34 +275,37 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
|
|
|
262
275
|
/** Target all of a user's connections (by `identify` key) across nodes (emit/disconnect). */
|
|
263
276
|
toUser(userId: string): UserTarget<C>;
|
|
264
277
|
/** Register handlers for shared + per-role requests (chainable). */
|
|
265
|
-
implement(handlers: Handlers<C, A>):
|
|
278
|
+
implement(handlers: Handlers<C, A>): SuperLineServer<C, A>;
|
|
266
279
|
/** Mixed-role connection group; broadcast() sends a shared contract event to members. */
|
|
267
280
|
room(name: string): Room<C>;
|
|
268
281
|
/** Publish a SHARED topic to all subscribers (server-only publish). */
|
|
269
282
|
publish<T extends keyof SharedTopics<C>>(topic: T, data: EmitData<SharedTopics<C>[T]>): void;
|
|
283
|
+
/**
|
|
284
|
+
* Subscribe SERVER-side to a shared topic, cluster-wide. The callback fires for a
|
|
285
|
+
* publish from any node — including this one (local echo, delivered in-process with
|
|
286
|
+
* no round-trip). `meta.from` is the publishing node; self-exclude with
|
|
287
|
+
* `if (meta.from === srv.nodeId) return`. Returns an unsubscribe fn.
|
|
288
|
+
*/
|
|
289
|
+
subscribe<T extends keyof SharedTopics<C>>(topic: T, handler: (data: EventData<SharedTopics<C>[T]>, meta: BusMeta) => void): () => void;
|
|
270
290
|
/** Lens for role-scoped sends, e.g. forRole('user').publish('feed', data). */
|
|
271
291
|
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
292
|
close(): Promise<void>;
|
|
277
293
|
}
|
|
278
294
|
/**
|
|
279
295
|
* Create a server bound to a contract. Attach it to an `http.Server`, then call
|
|
280
|
-
* {@link
|
|
296
|
+
* {@link SuperLineServer.implement} with your handlers. `authenticate` resolves each
|
|
281
297
|
* connection's `{ role, ctx }` at the upgrade.
|
|
282
298
|
*
|
|
283
299
|
* @param contract - the shared contract.
|
|
284
300
|
* @param opts - server options; `authenticate` is required.
|
|
285
|
-
* @returns the {@link
|
|
286
|
-
* @throws nothing directly; handler throws become a typed `
|
|
301
|
+
* @returns the {@link SuperLineServer}.
|
|
302
|
+
* @throws nothing directly; handler throws become a typed `SuperLineError` to the client.
|
|
287
303
|
*
|
|
288
304
|
* @example
|
|
289
305
|
* ```ts
|
|
290
|
-
* const srv =
|
|
291
|
-
* server,
|
|
292
|
-
* authenticate: (
|
|
306
|
+
* const srv = createSuperLineServer(api, {
|
|
307
|
+
* transports: [webSocketServerTransport({ server })],
|
|
308
|
+
* authenticate: (h) => ({ role: 'user' as const, ctx: { id: '1' } }),
|
|
293
309
|
* })
|
|
294
310
|
* srv.implement({
|
|
295
311
|
* shared: { join: async ({ room }, _ctx, conn) => { srv.room(room).add(conn); return { ok: true } } },
|
|
@@ -297,6 +313,6 @@ interface SocketServer<C extends Contract, A extends AuthResult<C>> {
|
|
|
297
313
|
* })
|
|
298
314
|
* ```
|
|
299
315
|
*/
|
|
300
|
-
declare function
|
|
316
|
+
declare function createSuperLineServer<C extends Contract, A extends AuthResult<C>>(contract: C, opts: SuperLineServerOptions<C, A>): SuperLineServer<C, A>;
|
|
301
317
|
|
|
302
|
-
export { type AuthResult, type
|
|
318
|
+
export { type AuthResult, 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 };
|