@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.js CHANGED
@@ -1,21 +1,27 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
1
3
  // src/index.ts
2
4
  import { randomUUID } from "crypto";
3
5
  import { WebSocketServer } from "ws";
4
6
  import {
5
7
  jsonSerializer,
6
8
  validate,
7
- SocketError
9
+ SuperLineError,
10
+ INSPECTOR_SUBPROTOCOL,
11
+ INSPECTOR_ROLE,
12
+ classifyContract
8
13
  } from "@super-line/core";
9
14
 
10
15
  // src/conn.ts
11
16
  var Conn = class {
12
- constructor(ws, id, role, ctx, serializer, backpressure) {
17
+ constructor(ws, id, role, ctx, serializer, backpressure, onEmit) {
13
18
  this.ws = ws;
14
19
  this.id = id;
15
20
  this.role = role;
16
21
  this.ctx = ctx;
17
22
  this.serializer = serializer;
18
23
  this.backpressure = backpressure;
24
+ this.onEmit = onEmit;
19
25
  }
20
26
  ws;
21
27
  id;
@@ -23,6 +29,7 @@ var Conn = class {
23
29
  ctx;
24
30
  serializer;
25
31
  backpressure;
32
+ onEmit;
26
33
  /** Namespaced channels (rooms + topics) this connection belongs to. */
27
34
  channels = /* @__PURE__ */ new Set();
28
35
  /** Mutable per-connection scratch state, typed per role by the contract's `data` schema. */
@@ -58,6 +65,7 @@ var Conn = class {
58
65
  }
59
66
  /** Push an event to THIS connection (node-local). Scoped to the role's events. */
60
67
  emit(event, data) {
68
+ this.onEmit?.(String(event), data);
61
69
  this.send({ t: "evt", e: String(event), d: data });
62
70
  }
63
71
  /** Close the underlying socket. */
@@ -139,6 +147,7 @@ var MemoryPresence = class {
139
147
  }
140
148
  return [...byNode.entries()].map(([nodeId, ds]) => ({
141
149
  nodeId,
150
+ nodeName: ds[0]?.nodeName ?? nodeId,
142
151
  connections: ds.length,
143
152
  rooms: new Set(ds.flatMap((d) => d.rooms)).size,
144
153
  alive: true
@@ -179,16 +188,25 @@ var TOPIC = "t:";
179
188
  var CONN = "c:";
180
189
  var USER = "u:";
181
190
  var REPLY = "reply:";
182
- var S2S = "s2s";
183
- function createSocketServer(contract, opts) {
191
+ var INSPECT = "i:";
192
+ function createSuperLineServer(contract, opts) {
184
193
  const c = contract;
185
194
  const serializer = opts.serializer ?? jsonSerializer;
186
195
  const adapter = opts.adapter ?? createInMemoryAdapter();
187
- const wss = new WebSocketServer({ noServer: true });
196
+ const inspectorEnabled = !!opts.inspector;
197
+ const inspectorRedact = new Set(
198
+ opts.inspector && typeof opts.inspector === "object" ? opts.inspector.redact ?? [] : []
199
+ );
200
+ const wss = new WebSocketServer({
201
+ noServer: true,
202
+ handleProtocols: (protocols) => inspectorEnabled && protocols.has(INSPECTOR_SUBPROTOCOL) ? INSPECTOR_SUBPROTOCOL : false
203
+ });
188
204
  const conns = /* @__PURE__ */ new Set();
205
+ const inspectorConns = /* @__PURE__ */ new Set();
189
206
  const members = /* @__PURE__ */ new Map();
190
- const serverListeners = /* @__PURE__ */ new Map();
207
+ const busListeners = /* @__PURE__ */ new Map();
191
208
  const instanceId = randomUUID();
209
+ const nodeName = opts.nodeName ?? process.env.SUPER_LINE_NODE_NAME ?? instanceId.slice(0, 8);
192
210
  const replyChannel = REPLY + instanceId;
193
211
  let impl = {};
194
212
  let closing = false;
@@ -203,6 +221,7 @@ function createSocketServer(contract, opts) {
203
221
  id: conn.id,
204
222
  role: conn.role,
205
223
  nodeId: instanceId,
224
+ nodeName,
206
225
  connectedAt: conn.connectedAt,
207
226
  ...userId !== void 0 ? { userId } : {},
208
227
  rooms,
@@ -214,10 +233,6 @@ function createSocketServer(contract, opts) {
214
233
  return adapter.presence;
215
234
  }
216
235
  adapter.onMessage((channel, payload) => {
217
- if (channel === S2S) {
218
- handleServerMessage(payload);
219
- return;
220
- }
221
236
  if (channel === replyChannel) {
222
237
  handleReply(payload);
223
238
  return;
@@ -227,8 +242,9 @@ function createSocketServer(contract, opts) {
227
242
  return;
228
243
  }
229
244
  const set = members.get(channel);
230
- if (!set) return;
231
- for (const conn of set) conn.sendRaw(payload);
245
+ if (set) for (const conn of set) conn.sendRaw(payload);
246
+ const busSet = busListeners.get(channel);
247
+ if (busSet) deliverBus(payload, busSet);
232
248
  });
233
249
  void adapter.subscribe(replyChannel);
234
250
  function handlePersonal(channel, payload) {
@@ -265,7 +281,7 @@ function createSocketServer(contract, opts) {
265
281
  originWaiters.delete(env.i);
266
282
  if (w.timer) clearTimeout(w.timer);
267
283
  if (env.ok) w.resolve(env.d);
268
- else w.reject(new SocketError(env.code, env.m, env.d));
284
+ else w.reject(new SuperLineError(env.code, env.m, env.d));
269
285
  }
270
286
  async function handleClientReply(conn, localId, result) {
271
287
  const r = ownerRouting.get(localId);
@@ -278,15 +294,24 @@ function createSocketServer(contract, opts) {
278
294
  const out = def && "output" in def ? await validate(def.output, result.d) : result.d;
279
295
  env = { i: r.corrId, ok: true, d: out };
280
296
  } catch (err) {
281
- const e = err instanceof SocketError ? err : new SocketError("INTERNAL", "Internal server error");
297
+ const e = err instanceof SuperLineError ? err : new SuperLineError("INTERNAL", "Internal server error");
282
298
  env = { i: r.corrId, ok: false, code: e.code, m: e.message, d: e.data };
283
299
  }
284
300
  } else {
285
301
  env = { i: r.corrId, ok: false, code: result.code, m: result.m, d: result.d };
286
302
  }
303
+ if (inspectorEnabled)
304
+ emitInspectorEvent(
305
+ env.ok ? { type: "msg.serverReply", target: conn.id, name: r.name, ok: true, output: safeSnapshot(env.d) } : {
306
+ type: "msg.serverReply",
307
+ target: conn.id,
308
+ name: r.name,
309
+ ok: false,
310
+ error: { code: env.code, message: env.m }
311
+ }
312
+ );
287
313
  void adapter.publish(REPLY + r.origin, serializer.encode(env));
288
314
  }
289
- if (c.serverToServer) void adapter.subscribe(S2S);
290
315
  const hb = opts.heartbeat === false ? null : opts.heartbeat ?? {};
291
316
  let hbTimer;
292
317
  if (hb) {
@@ -305,37 +330,43 @@ function createSocketServer(contract, opts) {
305
330
  }, hb.interval ?? 3e4);
306
331
  hbTimer.unref?.();
307
332
  }
308
- function handleServerMessage(payload) {
309
- let msg;
310
- try {
311
- msg = serializer.decode(payload);
312
- } catch {
313
- return;
314
- }
315
- if (msg.from === instanceId) return;
316
- const set = serverListeners.get(msg.e);
317
- if (set) for (const cb of set) cb(msg.d);
333
+ function emitInspectorEvent(event) {
334
+ if (!inspectorEnabled) return;
335
+ void adapter.publish(INSPECT + "events", serializer.encode({ t: "pub", c: "events", d: event }));
318
336
  }
319
337
  function joinChannel(conn, channel) {
320
338
  conn.channels.add(channel);
321
- if (channel.startsWith(ROOM)) void adapter.presence?.addRoom(conn.id, channel.slice(ROOM.length));
339
+ if (channel.startsWith(ROOM)) {
340
+ const room2 = channel.slice(ROOM.length);
341
+ void adapter.presence?.addRoom(conn.id, room2);
342
+ emitInspectorEvent({ type: "room.add", connId: conn.id, room: room2 });
343
+ } else if (channel.startsWith(TOPIC)) {
344
+ emitInspectorEvent({ type: "topic.sub", connId: conn.id, topic: channel.slice(channel.indexOf(":", TOPIC.length) + 1) });
345
+ }
322
346
  const set = members.get(channel);
323
347
  if (set) {
324
348
  set.add(conn);
325
349
  return;
326
350
  }
351
+ const alreadySubscribed = busListeners.has(channel);
327
352
  members.set(channel, /* @__PURE__ */ new Set([conn]));
328
- return adapter.subscribe(channel);
353
+ if (!alreadySubscribed) return adapter.subscribe(channel);
329
354
  }
330
355
  function leaveChannel(conn, channel) {
331
356
  const set = members.get(channel);
332
357
  if (!set) return;
333
358
  set.delete(conn);
334
359
  conn.channels.delete(channel);
335
- if (channel.startsWith(ROOM)) void adapter.presence?.removeRoom(conn.id, channel.slice(ROOM.length));
360
+ if (channel.startsWith(ROOM)) {
361
+ const room2 = channel.slice(ROOM.length);
362
+ void adapter.presence?.removeRoom(conn.id, room2);
363
+ emitInspectorEvent({ type: "room.remove", connId: conn.id, room: room2 });
364
+ } else if (channel.startsWith(TOPIC)) {
365
+ emitInspectorEvent({ type: "topic.unsub", connId: conn.id, topic: channel.slice(channel.indexOf(":", TOPIC.length) + 1) });
366
+ }
336
367
  if (set.size === 0) {
337
368
  members.delete(channel);
338
- void adapter.unsubscribe(channel);
369
+ if (!busListeners.has(channel)) void adapter.unsubscribe(channel);
339
370
  }
340
371
  }
341
372
  function isTopic(name, block) {
@@ -347,42 +378,200 @@ function createSocketServer(contract, opts) {
347
378
  if (isTopic(name, c.shared)) return "shared";
348
379
  return void 0;
349
380
  }
381
+ function localRooms() {
382
+ const out = [];
383
+ for (const channel of members.keys()) if (channel.startsWith(ROOM)) out.push(channel.slice(ROOM.length));
384
+ return out;
385
+ }
386
+ function localTopics() {
387
+ const out = [];
388
+ for (const channel of members.keys()) {
389
+ if (!channel.startsWith(TOPIC)) continue;
390
+ out.push(channel.slice(channel.indexOf(":", TOPIC.length) + 1));
391
+ }
392
+ return out;
393
+ }
394
+ async function onInspectorFrame(conn, frame) {
395
+ if (frame.t === "req") await handleInspectorReq(conn, frame);
396
+ else if (frame.t === "sub") await handleInspectorSub(conn, frame);
397
+ else if (frame.t === "unsub") leaveChannel(conn, INSPECT + "events");
398
+ }
399
+ async function handleInspectorSub(conn, frame) {
400
+ if (frame.c !== "events") {
401
+ conn.send({ t: "err", i: frame.i, code: "NOT_FOUND", m: `Unknown topic: ${frame.c}` });
402
+ return;
403
+ }
404
+ await joinChannel(conn, INSPECT + "events");
405
+ conn.send({ t: "res", i: frame.i, d: null });
406
+ }
407
+ async function handleInspectorReq(conn, frame) {
408
+ const handler = inspectorHandlers[frame.m];
409
+ if (!handler) {
410
+ conn.send({ t: "err", i: frame.i, code: "NOT_FOUND", m: `Unknown message: ${frame.m}` });
411
+ return;
412
+ }
413
+ try {
414
+ const output = await handler(frame.d, conn);
415
+ conn.send({ t: "res", i: frame.i, d: output });
416
+ } catch (err) {
417
+ const e = err instanceof SuperLineError ? err : new SuperLineError("INTERNAL", "Internal server error");
418
+ conn.send({ t: "err", i: frame.i, code: e.code, m: e.message, d: e.data });
419
+ }
420
+ }
421
+ async function buildInspectedContract() {
422
+ let toJsonSchema;
423
+ try {
424
+ const mod = await import("./dist-S566F7HE.js");
425
+ toJsonSchema = mod.toJsonSchema;
426
+ } catch {
427
+ return classifyContract(c);
428
+ }
429
+ const schemas = /* @__PURE__ */ new Set();
430
+ classifyContract(c, (s) => {
431
+ schemas.add(s);
432
+ return void 0;
433
+ });
434
+ const converted = /* @__PURE__ */ new Map();
435
+ await Promise.all(
436
+ [...schemas].map(
437
+ (s) => toJsonSchema(s).then(
438
+ (j) => {
439
+ converted.set(s, j);
440
+ },
441
+ () => {
442
+ }
443
+ // unsupported vendor / missing per-vendor converter -> structure-only for this entry
444
+ )
445
+ )
446
+ );
447
+ return classifyContract(c, (s) => converted.get(s));
448
+ }
449
+ function safeSnapshot(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
450
+ if (value === null) return null;
451
+ const t = typeof value;
452
+ if (t === "bigint") return `${value.toString()}n`;
453
+ if (t === "function") return "[Function]";
454
+ if (t === "symbol") return value.toString();
455
+ if (t !== "object") return value;
456
+ const obj = value;
457
+ if (obj instanceof Date) return obj.toISOString();
458
+ if (seen.has(obj)) return "[Circular]";
459
+ if (depth >= 6) return "[MaxDepth]";
460
+ seen.add(obj);
461
+ try {
462
+ if (Array.isArray(obj)) return obj.slice(0, 1e3).map((v) => safeSnapshot(v, depth + 1, seen));
463
+ const ctor = Object.getPrototypeOf(obj)?.constructor?.name;
464
+ const out = {};
465
+ if (ctor && ctor !== "Object") out["#type"] = ctor;
466
+ for (const [k, v] of Object.entries(obj)) {
467
+ out[k] = inspectorRedact.has(k) ? "[Redacted]" : safeSnapshot(v, depth + 1, seen);
468
+ }
469
+ return out;
470
+ } finally {
471
+ seen.delete(obj);
472
+ }
473
+ }
474
+ const inspectorHandlers = {
475
+ getContract: () => buildInspectedContract(),
476
+ getTopology: async () => presenceOrThrow().topology(),
477
+ listConnections: async () => presenceOrThrow().list(),
478
+ getNode: async () => ({ nodeId: instanceId, nodeName, rooms: localRooms(), topics: localTopics() }),
479
+ getConn: async (input) => {
480
+ const id = input?.id;
481
+ if (!id) throw new SuperLineError("BAD_REQUEST", "getConn requires an id");
482
+ const local = [...conns].find((cn) => cn.id === id);
483
+ if (local) {
484
+ return {
485
+ descriptor: buildDescriptor(local),
486
+ ctx: safeSnapshot(local.ctx),
487
+ data: safeSnapshot(local.data),
488
+ ctxAvailable: true
489
+ };
490
+ }
491
+ const remote = await presenceOrThrow().get(id);
492
+ if (!remote) throw new SuperLineError("NOT_FOUND", `Unknown connection: ${id}`);
493
+ return { descriptor: remote, ctxAvailable: false };
494
+ }
495
+ };
350
496
  if (opts.server) {
351
497
  opts.server.on("upgrade", (req, socket, head) => {
352
498
  void handleUpgrade(req, socket, head);
353
499
  });
354
500
  }
501
+ function isInspectorRequest(req) {
502
+ if (!inspectorEnabled) return false;
503
+ const raw = req.headers["sec-websocket-protocol"];
504
+ if (!raw) return false;
505
+ const offered = (Array.isArray(raw) ? raw.join(",") : raw).split(",").map((p) => p.trim());
506
+ return offered.includes(INSPECTOR_SUBPROTOCOL);
507
+ }
355
508
  async function handleUpgrade(req, socket, head) {
356
509
  if (opts.path) {
357
510
  const { pathname } = new URL(req.url ?? "/", "http://localhost");
358
511
  if (pathname !== opts.path) return;
359
512
  }
360
- let auth;
361
- try {
362
- auth = await opts.authenticate(req);
363
- } catch {
364
- socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
365
- socket.destroy();
366
- return;
513
+ const inspector = isInspectorRequest(req);
514
+ let role;
515
+ let ctx;
516
+ if (inspector) {
517
+ role = INSPECTOR_ROLE;
518
+ ctx = {};
519
+ } else {
520
+ let auth;
521
+ try {
522
+ auth = await opts.authenticate(req);
523
+ } catch {
524
+ socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
525
+ socket.destroy();
526
+ return;
527
+ }
528
+ role = auth.role;
529
+ ctx = auth.ctx;
367
530
  }
368
531
  wss.handleUpgrade(req, socket, head, (ws) => {
369
- const conn = new Conn(ws, randomUUID(), auth.role, auth.ctx, serializer, opts.backpressure);
532
+ const connId = randomUUID();
533
+ const conn = new Conn(
534
+ ws,
535
+ connId,
536
+ role,
537
+ ctx,
538
+ serializer,
539
+ opts.backpressure,
540
+ inspectorEnabled ? (event, data) => emitInspectorEvent({ type: "msg.event", target: connId, name: event, data: safeSnapshot(data) }) : void 0
541
+ );
542
+ ws.on("message", (data, isBinary) => {
543
+ void onMessage(conn, data, isBinary);
544
+ });
545
+ if (inspector) {
546
+ inspectorConns.add(conn);
547
+ ws.on("close", () => {
548
+ inspectorConns.delete(conn);
549
+ for (const channel of conn.channels) leaveChannel(conn, channel);
550
+ });
551
+ return;
552
+ }
370
553
  conns.add(conn);
371
554
  ws.on("pong", () => {
372
555
  conn.lastPongAt = Date.now();
373
556
  conn.missedPongs = 0;
374
557
  });
375
- ws.on("message", (data, isBinary) => {
376
- void onMessage(conn, data, isBinary);
377
- });
378
558
  ws.on("close", (code) => {
379
559
  conns.delete(conn);
380
560
  for (const channel of conn.channels) leaveChannel(conn, channel);
381
561
  void adapter.presence?.del(conn.id);
382
- opts.onDisconnect?.(conn, auth.ctx, code);
562
+ const goneUserId = opts.identify?.(conn);
563
+ emitInspectorEvent({
564
+ type: "disconnect",
565
+ connId: conn.id,
566
+ nodeId: instanceId,
567
+ ...goneUserId !== void 0 ? { userId: goneUserId } : {}
568
+ });
569
+ opts.onDisconnect?.(conn, ctx, code);
383
570
  });
384
- opts.onConnection?.(conn, auth.ctx);
385
- void adapter.presence?.set(buildDescriptor(conn));
571
+ opts.onConnection?.(conn, ctx);
572
+ const descriptor = buildDescriptor(conn);
573
+ void adapter.presence?.set(descriptor);
574
+ emitInspectorEvent({ type: "connect", descriptor });
386
575
  void joinChannel(conn, CONN + conn.id);
387
576
  const uid = opts.identify?.(conn);
388
577
  if (uid !== void 0) void joinChannel(conn, USER + uid);
@@ -395,6 +584,10 @@ function createSocketServer(contract, opts) {
395
584
  } catch {
396
585
  return;
397
586
  }
587
+ if (inspectorConns.has(conn)) {
588
+ await onInspectorFrame(conn, frame);
589
+ return;
590
+ }
398
591
  if (frame.t === "req") await handleReq(conn, frame);
399
592
  else if (frame.t === "sub") await handleSub(conn, frame);
400
593
  else if (frame.t === "unsub") {
@@ -427,10 +620,16 @@ function createSocketServer(contract, opts) {
427
620
  });
428
621
  } catch (err) {
429
622
  opts.onError?.(err, info);
430
- if (!responded) {
431
- const e = err instanceof SocketError ? err : new SocketError("INTERNAL", "Internal server error");
432
- conn.send({ t: "err", i: id, code: e.code, m: e.message, d: e.data });
433
- }
623
+ const e = err instanceof SuperLineError ? err : new SuperLineError("INTERNAL", "Internal server error");
624
+ if (!responded) conn.send({ t: "err", i: id, code: e.code, m: e.message, d: e.data });
625
+ if (inspectorEnabled && info.kind === "request")
626
+ emitInspectorEvent({
627
+ type: "msg.response",
628
+ connId: conn.id,
629
+ name: info.name,
630
+ ok: false,
631
+ error: { code: e.code, message: e.message }
632
+ });
434
633
  }
435
634
  }
436
635
  async function handleReq(conn, frame) {
@@ -442,7 +641,23 @@ function createSocketServer(contract, opts) {
442
641
  }
443
642
  await dispatchOp(conn, frame.i, { kind: "request", name: frame.m, conn }, async () => {
444
643
  const input = await validate(def.input, frame.d);
644
+ if (inspectorEnabled)
645
+ emitInspectorEvent({
646
+ type: "msg.request",
647
+ connId: conn.id,
648
+ role: conn.role,
649
+ name: frame.m,
650
+ input: safeSnapshot(input)
651
+ });
445
652
  const output = await handler(input, conn.ctx, conn);
653
+ if (inspectorEnabled)
654
+ emitInspectorEvent({
655
+ type: "msg.response",
656
+ connId: conn.id,
657
+ name: frame.m,
658
+ ok: true,
659
+ output: safeSnapshot(output)
660
+ });
446
661
  conn.send({ t: "res", i: frame.i, d: output });
447
662
  });
448
663
  }
@@ -455,7 +670,7 @@ function createSocketServer(contract, opts) {
455
670
  await dispatchOp(conn, frame.i, { kind: "subscribe", name: frame.c, conn }, async () => {
456
671
  if (opts.authorizeSubscribe) {
457
672
  const ok = await opts.authorizeSubscribe(frame.c, conn.ctx, conn);
458
- if (ok === false) throw new SocketError("FORBIDDEN", `Subscribe denied: ${frame.c}`);
673
+ if (ok === false) throw new SuperLineError("FORBIDDEN", `Subscribe denied: ${frame.c}`);
459
674
  }
460
675
  await joinChannel(conn, TOPIC + ns + ":" + frame.c);
461
676
  conn.send({ t: "res", i: frame.i, d: null });
@@ -471,6 +686,8 @@ function createSocketServer(contract, opts) {
471
686
  leaveChannel(conn, channel);
472
687
  },
473
688
  broadcast(event, data) {
689
+ if (inspectorEnabled)
690
+ emitInspectorEvent({ type: "msg.broadcast", room: name, name: String(event), data: safeSnapshot(data) });
474
691
  void adapter.publish(channel, serializer.encode({ t: "evt", e: String(event), d: data }));
475
692
  },
476
693
  get size() {
@@ -482,11 +699,54 @@ function createSocketServer(contract, opts) {
482
699
  };
483
700
  }
484
701
  function publishTo(ns, name, data) {
485
- void adapter.publish(TOPIC + ns + ":" + name, serializer.encode({ t: "pub", c: name, d: data }));
702
+ const channel = TOPIC + ns + ":" + name;
703
+ if (inspectorEnabled) emitInspectorEvent({ type: "msg.publish", topic: name, data: safeSnapshot(data) });
704
+ const busSet = busListeners.get(channel);
705
+ if (busSet) for (const cb of busSet) callBus(cb, data, instanceId, name);
706
+ void adapter.publish(channel, serializer.encode({ t: "pub", c: name, d: data, i: instanceId }));
707
+ }
708
+ function callBus(cb, data, from, name) {
709
+ try {
710
+ cb(data, { from });
711
+ } catch (err) {
712
+ opts.onError?.(err, { kind: "event", name });
713
+ }
714
+ }
715
+ function deliverBus(payload, set) {
716
+ let frame;
717
+ try {
718
+ frame = serializer.decode(payload);
719
+ } catch {
720
+ return;
721
+ }
722
+ if (frame.i === instanceId) return;
723
+ const from = frame.i ?? "";
724
+ const name = frame.c;
725
+ const def = c.shared?.serverToClient?.[name];
726
+ const schema = def && typeof def === "object" && "payload" in def ? def.payload : void 0;
727
+ void (async () => {
728
+ let data = frame.d;
729
+ if (schema) {
730
+ try {
731
+ data = await validate(schema, frame.d);
732
+ } catch (err) {
733
+ opts.onError?.(err, { kind: "event", name });
734
+ return;
735
+ }
736
+ }
737
+ for (const cb of set) callBus(cb, data, from, name);
738
+ })();
486
739
  }
487
740
  function personalTarget(channel) {
488
741
  return {
489
742
  emit(event, data) {
743
+ if (inspectorEnabled)
744
+ emitInspectorEvent({
745
+ type: "msg.event",
746
+ target: channel.slice(channel.indexOf(":") + 1),
747
+ name: event,
748
+ data: safeSnapshot(data)
749
+ });
490
750
  const env = { p: "emit", f: { t: "evt", e: event, d: data } };
491
751
  void adapter.publish(channel, serializer.encode(env));
492
752
  },
@@ -501,7 +761,7 @@ function createSocketServer(contract, opts) {
501
761
  const ms = opts2?.timeout ?? 3e4;
502
762
  const timer = ms > 0 ? setTimeout(() => {
503
763
  originWaiters.delete(reqId);
504
- reject(new SocketError("TIMEOUT", `Request '${name}' timed out`));
764
+ reject(new SuperLineError("TIMEOUT", `Request '${name}' timed out`));
505
765
  }, ms) : void 0;
506
766
  originWaiters.set(reqId, { resolve, reject, timer });
507
767
  opts2?.signal?.addEventListener(
@@ -509,32 +769,28 @@ function createSocketServer(contract, opts) {
509
769
  () => {
510
770
  if (originWaiters.delete(reqId)) {
511
771
  if (timer) clearTimeout(timer);
512
- reject(new SocketError("BAD_REQUEST", "Aborted"));
772
+ reject(new SuperLineError("BAD_REQUEST", "Aborted"));
513
773
  }
514
774
  },
515
775
  { once: true }
516
776
  );
777
+ if (inspectorEnabled)
778
+ emitInspectorEvent({ type: "msg.serverRequest", target: id, name, input: safeSnapshot(input) });
517
779
  const env = { p: "req", o: instanceId, i: reqId, m: name, d: input };
518
780
  void adapter.publish(CONN + id, serializer.encode(env));
519
781
  });
520
782
  }
521
783
  const api = {
522
784
  nodeId: instanceId,
785
+ nodeName,
523
786
  get local() {
524
787
  return {
525
788
  connections: [...conns],
526
789
  get rooms() {
527
- const out = [];
528
- for (const channel of members.keys()) if (channel.startsWith(ROOM)) out.push(channel.slice(ROOM.length));
529
- return out;
790
+ return localRooms();
530
791
  },
531
792
  get topics() {
532
- const out = [];
533
- for (const channel of members.keys()) {
534
- if (!channel.startsWith(TOPIC)) continue;
535
- out.push(channel.slice(channel.indexOf(":", TOPIC.length) + 1));
536
- }
537
- return out;
793
+ return localTopics();
538
794
  }
539
795
  };
540
796
  },
@@ -578,29 +834,31 @@ function createSocketServer(contract, opts) {
578
834
  publish(topic, data) {
579
835
  publishTo("shared", String(topic), data);
580
836
  },
581
- forRole(role) {
582
- return {
583
- publish(topic, data) {
584
- publishTo(role, String(topic), data);
585
- }
586
- };
587
- },
588
- emitServer(event, data) {
589
- void adapter.publish(S2S, serializer.encode({ from: instanceId, e: String(event), d: data }));
590
- },
591
- onServer(event, handler) {
592
- const name = String(event);
593
- let set = serverListeners.get(name);
837
+ subscribe(topic, handler) {
838
+ const channel = TOPIC + "shared:" + String(topic);
839
+ let set = busListeners.get(channel);
594
840
  if (!set) {
595
841
  set = /* @__PURE__ */ new Set();
596
- serverListeners.set(name, set);
842
+ busListeners.set(channel, set);
843
+ if (!members.has(channel)) void adapter.subscribe(channel);
597
844
  }
598
- set.add(handler);
845
+ const cb = handler;
846
+ set.add(cb);
599
847
  return () => {
600
- const current = serverListeners.get(name);
848
+ const current = busListeners.get(channel);
601
849
  if (!current) return;
602
- current.delete(handler);
603
- if (current.size === 0) serverListeners.delete(name);
850
+ current.delete(cb);
851
+ if (current.size === 0) {
852
+ busListeners.delete(channel);
853
+ if (!members.has(channel)) void adapter.unsubscribe(channel);
854
+ }
855
+ };
856
+ },
857
+ forRole(role) {
858
+ return {
859
+ publish(topic, data) {
860
+ publishTo(role, String(topic), data);
861
+ }
604
862
  };
605
863
  },
606
864
  async close() {
@@ -608,6 +866,7 @@ function createSocketServer(contract, opts) {
608
866
  closing = true;
609
867
  if (hbTimer) clearInterval(hbTimer);
610
868
  for (const conn of conns) conn.close();
869
+ for (const conn of inspectorConns) conn.close();
611
870
  await adapter.presence?.clearNode(instanceId);
612
871
  await adapter.close?.();
613
872
  await new Promise((resolve) => {
@@ -626,5 +885,5 @@ export {
626
885
  Conn,
627
886
  MemoryBus,
628
887
  createInMemoryAdapter,
629
- createSocketServer
888
+ createSuperLineServer
630
889
  };
@@ -0,0 +1,17 @@
1
+ import {
2
+ MissingDependencyError
3
+ } from "./chunk-BXHVPS35.js";
4
+ import "./chunk-MLKGABMK.js";
5
+
6
+ // ../../node_modules/.pnpm/@standard-community+standard-json@0.3.5_@standard-schema+spec@1.1.0_@types+json-schema@_c5121037ae681f5d570421df77d4831b/node_modules/@standard-community/standard-json/dist/sury-CWZTCd75.js
7
+ async function getToJsonSchemaFn() {
8
+ try {
9
+ const { toJSONSchema } = await import("sury");
10
+ return toJSONSchema;
11
+ } catch {
12
+ throw new MissingDependencyError("sury");
13
+ }
14
+ }
15
+ export {
16
+ getToJsonSchemaFn as default
17
+ };
@@ -0,0 +1,9 @@
1
+ import "./chunk-MLKGABMK.js";
2
+
3
+ // ../../node_modules/.pnpm/@standard-community+standard-json@0.3.5_@standard-schema+spec@1.1.0_@types+json-schema@_c5121037ae681f5d570421df77d4831b/node_modules/@standard-community/standard-json/dist/typebox-Dei93FPO.js
4
+ function getToJsonSchemaFn() {
5
+ return (schema) => JSON.parse(JSON.stringify(schema.Type()));
6
+ }
7
+ export {
8
+ getToJsonSchemaFn as default
9
+ };