@emeryld/rrroutes-server 2.0.4 → 2.0.6

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.cjs CHANGED
@@ -419,10 +419,10 @@ function warnMissingControllers(router, registry, logger) {
419
419
  }
420
420
  }
421
421
 
422
- // src/sockets/socket.server.index.ts
423
- function createSocketConnections(io, events, opts) {
424
- const debug = opts?.debug ?? {};
425
- const dbg = (maybeEvent, e) => {
422
+ // src/sockets/socket.server.debug.ts
423
+ function createDebugger(options) {
424
+ const debug = options ?? {};
425
+ return (maybeEvent, e) => {
426
426
  if (!debug.logger) return;
427
427
  if (!debug[e.type]) return;
428
428
  if (debug.only && maybeEvent && (e.type === "register" || e.type === "handler" || e.type === "emit") && !debug.only.includes(maybeEvent)) {
@@ -430,40 +430,39 @@ function createSocketConnections(io, events, opts) {
430
430
  }
431
431
  debug.logger(e);
432
432
  };
433
- const getSchema = (k) => events[k].message;
434
- const toArray = (rooms) => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
435
- const registrations = /* @__PURE__ */ new Map();
436
- const addRegistration = (eventName, reg) => {
437
- let set = registrations.get(eventName);
438
- if (!set) {
439
- set = /* @__PURE__ */ new Set();
440
- registrations.set(eventName, set);
441
- }
442
- set.add(reg);
443
- };
444
- const removeAllForEvent = (eventName) => {
445
- const set = registrations.get(eventName);
446
- if (!set) return;
447
- for (const reg of set) {
448
- io.off("connection", reg.connectionListener);
449
- for (const socket of io.sockets.sockets.values()) {
450
- const wrapped = reg.socketListeners.get(socket);
451
- if (wrapped) socket.off(String(eventName), wrapped);
452
- }
453
- }
454
- registrations.delete(eventName);
455
- dbg(eventName, { type: "register", action: "unregister", event: eventName });
456
- };
457
- const roomJoinEvent = opts?.rooms?.roomJoinEvent ?? "room:join";
458
- const roomLeaveEvent = opts?.rooms?.roomLeaveEvent ?? "room:leave";
459
- const hb = opts?.heartbeat;
460
- if (!hb) throw new Error("createSocketConnections: heartbeat config is required");
461
- const pingEvent = hb.pingEvent ?? "sys:ping";
462
- const pongEvent = hb.pongEvent ?? "sys:pong";
463
- const sysEvents = opts?.sys ?? {};
433
+ }
434
+
435
+ // src/sockets/socket.server.sys.ts
436
+ var import_zod = require("zod");
437
+ var normalizeError = (error) => {
438
+ if (error instanceof Error) {
439
+ return { name: error.name, message: error.message };
440
+ }
441
+ if (typeof error === "string") return { message: error };
442
+ return { message: "Unknown error" };
443
+ };
444
+ var roomValueSchema = import_zod.z.union([import_zod.z.string(), import_zod.z.array(import_zod.z.string())]);
445
+ var buildRoomPayloadSchema = (metaSchema) => import_zod.z.object({
446
+ rooms: roomValueSchema,
447
+ meta: metaSchema
448
+ });
449
+ var toArray = (rooms) => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
450
+ function createBuiltInConnectionHandlers(opts) {
451
+ const { heartbeat, sys, dbg, config, createCtx } = opts;
452
+ const roomJoinEvent = "sys:room_join";
453
+ const roomLeaveEvent = "sys:room_leave";
454
+ const pingEvent = "sys:ping";
455
+ const pongEvent = "sys:pong";
456
+ const heartbeatEnabled = heartbeat?.enabled !== false;
457
+ const joinPayloadSchema = buildRoomPayloadSchema(config.joinMetaMessage);
458
+ const leavePayloadSchema = buildRoomPayloadSchema(config.leaveMetaMessage);
459
+ const pingPayloadSchema = config.pingPayload;
460
+ const pongPayloadSchema = config.pongPayload;
461
+ const sysEvents = sys;
464
462
  const getSysEvent = (name) => sysEvents[name];
465
463
  const socketTeardowns = /* @__PURE__ */ new WeakMap();
466
464
  const builtInConnectionListener = (socket) => {
465
+ const typedSocket = socket;
467
466
  const defaultConnect = () => {
468
467
  if (socket.data?.user) {
469
468
  dbg(null, {
@@ -473,140 +472,160 @@ function createSocketConnections(io, events, opts) {
473
472
  sub: socket.data.user?.sub
474
473
  });
475
474
  }
476
- const joinHandler = async ({ rooms }) => {
477
- const list = toArray(rooms);
478
- const defaultJoin = async () => {
479
- for (const r of list) {
480
- const allowed = !opts?.rooms?.onRoomJoin || await opts.rooms.onRoomJoin({ room: r, socket }) === true;
481
- if (allowed) {
482
- await socket.join(r);
483
- dbg(null, { type: "rooms", action: "join", rooms: [r], socketId: socket.id });
484
- }
485
- }
475
+ const joinHandler = async (msg) => {
476
+ const parsed = joinPayloadSchema.safeParse(msg);
477
+ if (!parsed.success) {
478
+ socket.emit(`${roomJoinEvent}:error`, {
479
+ eventName: roomJoinEvent,
480
+ issues: parsed.error.issues
481
+ });
482
+ dbg("sys:room_join", {
483
+ type: "handler",
484
+ phase: "validation_error",
485
+ event: roomJoinEvent,
486
+ issues: parsed.error.issues
487
+ });
488
+ return;
489
+ }
490
+ const list = toArray(parsed.data.rooms);
491
+ const join = async (room) => {
492
+ await socket.join(room);
493
+ dbg(null, { type: "rooms", action: "join", room, socketId: socket.id });
486
494
  };
487
- const sysRoomJoin = getSysEvent("sys:room_join");
488
- if (sysRoomJoin?.sysHandler) {
489
- await sysRoomJoin.sysHandler({
490
- name: "sys:room_join",
491
- socket,
495
+ const run = async () => {
496
+ await getSysEvent("sys:room_join")({
497
+ socket: typedSocket,
492
498
  rooms: list,
493
- next: () => {
494
- void defaultJoin();
495
- }
499
+ meta: parsed.data.meta,
500
+ join
501
+ });
502
+ };
503
+ try {
504
+ await run();
505
+ } catch (error) {
506
+ socket.emit(`${roomJoinEvent}:error`, { error: normalizeError(error) });
507
+ dbg("sys:room_join", {
508
+ type: "handler",
509
+ phase: "handler_error",
510
+ event: roomJoinEvent,
511
+ error
496
512
  });
497
- } else {
498
- await defaultJoin();
499
513
  }
500
514
  };
501
- const leaveHandler = async ({ rooms }) => {
502
- const list = toArray(rooms);
503
- const defaultLeave = async () => {
504
- for (const r of list) {
505
- const allowed = !opts?.rooms?.onRoomLeave || await opts.rooms.onRoomLeave({ room: r, socket }) === true;
506
- if (allowed) {
507
- dbg(null, { type: "rooms", action: "leave", rooms: [r], socketId: socket.id });
508
- await socket.leave(r);
509
- }
510
- }
515
+ const leaveHandler = async (msg) => {
516
+ const parsed = leavePayloadSchema.safeParse(msg);
517
+ if (!parsed.success) {
518
+ socket.emit(`${roomLeaveEvent}:error`, {
519
+ eventName: roomLeaveEvent,
520
+ issues: parsed.error.issues
521
+ });
522
+ dbg("sys:room_leave", {
523
+ type: "handler",
524
+ phase: "validation_error",
525
+ event: roomLeaveEvent,
526
+ issues: parsed.error.issues
527
+ });
528
+ return;
529
+ }
530
+ const list = toArray(parsed.data.rooms);
531
+ const leave = async (room) => {
532
+ dbg(null, { type: "rooms", action: "leave", room, socketId: socket.id });
533
+ await socket.leave(room);
511
534
  };
512
- const sysRoomLeave = getSysEvent("sys:room_leave");
513
- if (sysRoomLeave?.sysHandler) {
514
- await sysRoomLeave.sysHandler({
515
- name: "sys:room_leave",
516
- socket,
535
+ const run = async () => {
536
+ await getSysEvent("sys:room_leave")({
537
+ socket: typedSocket,
517
538
  rooms: list,
518
- next: () => {
519
- void defaultLeave();
520
- }
539
+ meta: parsed.data.meta,
540
+ leave
541
+ });
542
+ };
543
+ try {
544
+ await run();
545
+ } catch (error) {
546
+ socket.emit(`${roomLeaveEvent}:error`, { error: normalizeError(error) });
547
+ dbg("sys:room_leave", {
548
+ type: "handler",
549
+ phase: "handler_error",
550
+ event: roomLeaveEvent,
551
+ error
521
552
  });
522
- } else {
523
- await defaultLeave();
524
553
  }
525
554
  };
526
- const pingHandler = (msg = {}, ack) => {
527
- const emitPong = (pong) => {
528
- const sysPong = getSysEvent("sys:pong");
529
- const sendPong = () => {
530
- if (hb.pongSchema) {
531
- const check = hb.pongSchema.safeParse(pong);
532
- if (!check.success) {
533
- socket.emit(`${pongEvent}:error`, { issues: check.error.issues });
534
- dbg(null, {
535
- type: "heartbeat",
536
- phase: "pong",
537
- socketId: socket.id,
538
- issues: check.error.issues,
539
- error: true
540
- });
541
- return;
542
- }
543
- }
544
- socket.emit(pongEvent, pong);
545
- dbg(null, {
546
- type: "heartbeat",
547
- phase: "pong",
548
- socketId: socket.id,
549
- payload: pong,
550
- error: false
551
- });
552
- };
553
- if (sysPong?.sysHandler) {
554
- void sysPong.sysHandler({
555
- name: "sys:pong",
556
- socket,
557
- payload: pong,
558
- next: sendPong
559
- });
560
- } else {
561
- sendPong();
562
- }
563
- };
564
- const defaultPing = () => {
565
- const parsed = hb.pingSchema.safeParse(msg?.payload);
566
- if (!parsed.success) {
567
- socket.emit(`${pingEvent}:error`, { issues: parsed.error.issues });
568
- dbg(null, {
569
- type: "heartbeat",
570
- phase: "ping",
571
- socketId: socket.id,
572
- payload: msg?.payload,
573
- issues: parsed.error.issues,
574
- error: true
575
- });
576
- return;
577
- }
555
+ const pingHandler = async (msg) => {
556
+ if (!heartbeatEnabled) return;
557
+ const parsedPing = pingPayloadSchema.safeParse(msg);
558
+ if (!parsedPing.success) {
559
+ socket.emit(`${pingEvent}:error`, {
560
+ eventName: pingEvent,
561
+ issues: parsedPing.error.issues
562
+ });
578
563
  dbg(null, {
579
564
  type: "heartbeat",
580
565
  phase: "ping",
581
566
  socketId: socket.id,
582
- payload: parsed.data
567
+ issues: parsedPing.error.issues,
568
+ error: true
583
569
  });
584
- const pong = hb.makePongPayload({ socket, ping: parsed.data });
585
- emitPong(pong);
586
- if (typeof ack === "function") {
587
- ack({ serverNow: (/* @__PURE__ */ new Date()).toISOString() });
588
- }
589
- };
590
- const sysPing = getSysEvent("sys:ping");
591
- if (sysPing?.sysHandler) {
592
- void sysPing.sysHandler({
593
- name: "sys:ping",
594
- socket,
595
- raw: msg,
596
- ack,
597
- next: defaultPing
570
+ return;
571
+ }
572
+ const ctx = createCtx(socket);
573
+ dbg(null, {
574
+ type: "heartbeat",
575
+ phase: "ping",
576
+ socketId: socket.id,
577
+ payload: parsedPing.data,
578
+ error: false
579
+ });
580
+ let pongPayload;
581
+ try {
582
+ pongPayload = await getSysEvent("sys:ping")(parsedPing.data, ctx);
583
+ } catch (error) {
584
+ socket.emit(`${pingEvent}:error`, { error: normalizeError(error) });
585
+ dbg(null, {
586
+ type: "heartbeat",
587
+ phase: "ping",
588
+ socketId: socket.id,
589
+ payload: parsedPing.data,
590
+ error: true
598
591
  });
599
- } else {
600
- defaultPing();
592
+ return;
593
+ }
594
+ const parsedPong = pongPayloadSchema.safeParse(pongPayload);
595
+ if (!parsedPong.success) {
596
+ socket.emit(`${pongEvent}:error`, {
597
+ eventName: pongEvent,
598
+ issues: parsedPong.error.issues
599
+ });
600
+ dbg(null, {
601
+ type: "heartbeat",
602
+ phase: "pong",
603
+ socketId: socket.id,
604
+ issues: parsedPong.error.issues,
605
+ error: true
606
+ });
607
+ return;
601
608
  }
609
+ socket.emit(pongEvent, parsedPong.data);
610
+ dbg(null, {
611
+ type: "heartbeat",
612
+ phase: "pong",
613
+ socketId: socket.id,
614
+ payload: parsedPong.data,
615
+ error: false
616
+ });
602
617
  };
603
618
  socket.on(roomJoinEvent, joinHandler);
604
619
  socket.on(roomLeaveEvent, leaveHandler);
605
- socket.on(pingEvent, pingHandler);
620
+ if (heartbeatEnabled) {
621
+ socket.on(pingEvent, pingHandler);
622
+ }
606
623
  const cleanup = () => {
607
624
  socket.off(roomJoinEvent, joinHandler);
608
625
  socket.off(roomLeaveEvent, leaveHandler);
609
- socket.off(pingEvent, pingHandler);
626
+ if (heartbeatEnabled) {
627
+ socket.off(pingEvent, pingHandler);
628
+ }
610
629
  };
611
630
  socketTeardowns.set(socket, cleanup);
612
631
  socket.once("disconnect", (reason) => {
@@ -614,30 +633,91 @@ function createSocketConnections(io, events, opts) {
614
633
  cleanup();
615
634
  socketTeardowns.delete(socket);
616
635
  };
617
- const sysDisconnect = getSysEvent("sys:disconnect");
618
- if (sysDisconnect?.sysHandler) {
619
- void sysDisconnect.sysHandler({
620
- name: "sys:disconnect",
621
- socket,
622
- reason: String(reason),
623
- next: defaultDisconnect
624
- });
625
- } else {
626
- defaultDisconnect();
627
- }
636
+ getSysEvent("sys:disconnect")({
637
+ socket: typedSocket,
638
+ reason: String(reason),
639
+ cleanup: defaultDisconnect
640
+ });
628
641
  });
629
642
  };
630
- const sysConnect = getSysEvent("sys:connect");
631
- if (sysConnect?.sysHandler) {
632
- void sysConnect.sysHandler({
633
- name: "sys:connect",
634
- socket,
635
- next: defaultConnect
636
- });
637
- } else {
638
- defaultConnect();
643
+ getSysEvent("sys:connect")({
644
+ socket: typedSocket,
645
+ initConnection: defaultConnect
646
+ });
647
+ };
648
+ return {
649
+ builtInConnectionListener,
650
+ socketTeardowns,
651
+ roomJoinEvent,
652
+ roomLeaveEvent,
653
+ pingEvent
654
+ };
655
+ }
656
+
657
+ // src/sockets/socket.server.index.ts
658
+ var createBaseHandlerCtx = (socket) => ({
659
+ sentAt: /* @__PURE__ */ new Date(),
660
+ socket,
661
+ socketId: socket.id,
662
+ nsp: socket.nsp.name,
663
+ rooms: Array.from(socket.rooms),
664
+ user: socket.data?.user,
665
+ scopes: socket.data?.scopes
666
+ });
667
+ var normalizeError2 = (error, verbose) => {
668
+ if (error instanceof Error) {
669
+ return {
670
+ name: error.name,
671
+ message: error.message,
672
+ stack: verbose ? error.stack : void 0
673
+ };
674
+ }
675
+ if (typeof error === "string") return { message: error };
676
+ if (typeof error === "object" && error !== null) {
677
+ return verbose ? error : { message: "Unknown error" };
678
+ }
679
+ return { message: String(error) };
680
+ };
681
+ function createSocketConnections(io, events, opts) {
682
+ const dbg = createDebugger(opts?.debug);
683
+ const socketConfig = opts.config;
684
+ const getSchema = (k) => events[k].message;
685
+ const toArray2 = (rooms) => rooms == null ? [] : Array.isArray(rooms) ? rooms : [rooms];
686
+ const registrations = /* @__PURE__ */ new Map();
687
+ const addRegistration = (eventName, reg) => {
688
+ let set = registrations.get(eventName);
689
+ if (!set) {
690
+ set = /* @__PURE__ */ new Set();
691
+ registrations.set(eventName, set);
639
692
  }
693
+ set.add(reg);
640
694
  };
695
+ const removeAllForEvent = (eventName) => {
696
+ const set = registrations.get(eventName);
697
+ if (!set) return;
698
+ for (const reg of set) {
699
+ io.off("connection", reg.connectionListener);
700
+ for (const socket of io.sockets.sockets.values()) {
701
+ const wrapped = reg.socketListeners.get(socket);
702
+ if (wrapped) socket.off(String(eventName), wrapped);
703
+ }
704
+ }
705
+ registrations.delete(eventName);
706
+ dbg(eventName, { type: "register", action: "unregister", event: eventName });
707
+ };
708
+ const {
709
+ builtInConnectionListener,
710
+ socketTeardowns,
711
+ roomJoinEvent,
712
+ roomLeaveEvent,
713
+ pingEvent
714
+ } = createBuiltInConnectionHandlers({
715
+ heartbeat: opts?.heartbeat,
716
+ sys: opts.sys,
717
+ dbg,
718
+ config: socketConfig,
719
+ createCtx: createBaseHandlerCtx
720
+ });
641
721
  io.on("connection", builtInConnectionListener);
642
722
  const conn = {
643
723
  on(eventName, handler) {
@@ -646,20 +726,10 @@ function createSocketConnections(io, events, opts) {
646
726
  const wrapped = async (raw, maybeAck) => {
647
727
  const schema = getSchema(eventName);
648
728
  const parsed = schema.safeParse(raw);
729
+ const baseCtx = createBaseHandlerCtx(socket);
649
730
  const ctx = {
650
- sentAt: /* @__PURE__ */ new Date(),
651
- socket,
652
- socketId: socket.id,
653
- nsp: socket.nsp.name,
654
- rooms: Array.from(socket.rooms),
655
- user: socket.data?.user,
656
- scopes: socket.data?.scopes,
657
- ack: typeof maybeAck === "function" ? (d) => {
658
- try {
659
- maybeAck(d);
660
- } catch {
661
- }
662
- } : void 0
731
+ ...baseCtx,
732
+ ack: maybeAck
663
733
  };
664
734
  dbg(String(eventName), {
665
735
  type: "handler",
@@ -668,7 +738,7 @@ function createSocketConnections(io, events, opts) {
668
738
  socketId: ctx.socketId,
669
739
  nsp: ctx.nsp,
670
740
  rooms: ctx.rooms,
671
- raw: debug.verbose ? raw : void 0
741
+ raw: opts?.debug?.verbose ? raw : void 0
672
742
  });
673
743
  if (!parsed.success) {
674
744
  socket.emit(`${String(eventName)}:error`, {
@@ -698,7 +768,11 @@ function createSocketConnections(io, events, opts) {
698
768
  event: String(eventName),
699
769
  error
700
770
  });
701
- throw error;
771
+ socket.emit(`${String(eventName)}:error`, {
772
+ eventName,
773
+ sentAt: ctx.sentAt,
774
+ error: normalizeError2(error, opts?.debug?.verbose)
775
+ });
702
776
  }
703
777
  };
704
778
  socketListeners.set(socket, wrapped);
@@ -745,7 +819,7 @@ function createSocketConnections(io, events, opts) {
745
819
  const check = schema.safeParse(payload);
746
820
  if (!check.success)
747
821
  throw new Error(`Invalid payload for "${String(eventName)}": ${check.error.message}`);
748
- const targets = toArray(rooms);
822
+ const targets = toArray2(rooms);
749
823
  const envelope = {
750
824
  eventName,
751
825
  sentAt: /* @__PURE__ */ new Date(),
@@ -771,7 +845,7 @@ function createSocketConnections(io, events, opts) {
771
845
  type: "emit",
772
846
  event: String(eventName),
773
847
  rooms: targets,
774
- envelope: debug.verbose ? envelope : void 0
848
+ envelope: opts?.debug?.verbose ? envelope : void 0
775
849
  });
776
850
  return;
777
851
  }
@@ -782,7 +856,7 @@ function createSocketConnections(io, events, opts) {
782
856
  type: "emit",
783
857
  event: String(eventName),
784
858
  rooms: targets,
785
- envelope: debug.verbose ? envelope : void 0
859
+ envelope: opts?.debug?.verbose ? envelope : void 0
786
860
  });
787
861
  },
788
862
  destroy() {