dialogue-ts 0.0.1 → 0.0.4

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.
@@ -24,14 +24,162 @@ __export(src_exports, {
24
24
  createDialogue: () => createDialogue,
25
25
  createHistoryManager: () => createHistoryManager,
26
26
  createRateLimiter: () => createRateLimiter,
27
+ createRuntimeAdapter: () => createRuntimeAdapter,
27
28
  createSilentLogger: () => createSilentLogger,
28
29
  defineEvent: () => defineEvent,
30
+ detectRuntime: () => detectRuntime,
29
31
  getEventByName: () => getEventByName,
30
32
  isEventAllowed: () => isEventAllowed,
31
33
  validateEventData: () => validateEventData
32
34
  });
33
35
  module.exports = __toCommonJS(src_exports);
34
36
 
37
+ // src/adapters/bun-adapter.ts
38
+ var import_bun_engine = require("@socket.io/bun-engine");
39
+ function addCorsHeaders(response, request, corsConfig) {
40
+ const origin = request.headers.get("Origin");
41
+ if (!origin) {
42
+ return response;
43
+ }
44
+ const headers = new Headers(response.headers);
45
+ if (corsConfig === void 0 || corsConfig === true) {
46
+ headers.set("Access-Control-Allow-Origin", origin);
47
+ headers.set("Access-Control-Allow-Credentials", "true");
48
+ } else if (corsConfig === false) {
49
+ return response;
50
+ } else {
51
+ const allowedOrigin = corsConfig.origin;
52
+ if (allowedOrigin === true) {
53
+ headers.set("Access-Control-Allow-Origin", origin);
54
+ } else if (typeof allowedOrigin === "string" && allowedOrigin === origin) {
55
+ headers.set("Access-Control-Allow-Origin", origin);
56
+ } else if (Array.isArray(allowedOrigin) && allowedOrigin.includes(origin)) {
57
+ headers.set("Access-Control-Allow-Origin", origin);
58
+ }
59
+ if (corsConfig.credentials !== false) {
60
+ headers.set("Access-Control-Allow-Credentials", "true");
61
+ }
62
+ }
63
+ headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
64
+ headers.set("Access-Control-Allow-Headers", "Content-Type");
65
+ return new Response(response.body, {
66
+ status: response.status,
67
+ statusText: response.statusText,
68
+ headers
69
+ });
70
+ }
71
+ function createBunAdapter() {
72
+ const engine = new import_bun_engine.Server();
73
+ let bunServer = null;
74
+ return {
75
+ runtime: "bun",
76
+ bind(io) {
77
+ io.bind(engine);
78
+ },
79
+ start({
80
+ port,
81
+ app,
82
+ corsConfig,
83
+ logger
84
+ }) {
85
+ const { websocket } = engine.handler();
86
+ bunServer = Bun.serve({
87
+ port,
88
+ idleTimeout: 30,
89
+ async fetch(req, server) {
90
+ const url = new URL(req.url);
91
+ if (url.pathname.startsWith("/socket.io")) {
92
+ if (req.method === "OPTIONS") {
93
+ return addCorsHeaders(
94
+ new Response(null, { status: 204 }),
95
+ req,
96
+ corsConfig
97
+ );
98
+ }
99
+ const response = await engine.handleRequest(req, server);
100
+ return addCorsHeaders(response, req, corsConfig);
101
+ }
102
+ return app.fetch(req);
103
+ },
104
+ websocket
105
+ });
106
+ logger.info({
107
+ message: `Server running on http://localhost:${port} (bun)`,
108
+ atFunction: "bunAdapter.start",
109
+ data: { port, runtime: "bun" }
110
+ });
111
+ return Promise.resolve();
112
+ },
113
+ stop() {
114
+ if (bunServer) {
115
+ bunServer.stop();
116
+ bunServer = null;
117
+ }
118
+ return Promise.resolve();
119
+ }
120
+ };
121
+ }
122
+
123
+ // src/adapters/node-adapter.ts
124
+ var import_node_http = require("http");
125
+ var import_node_server = require("@hono/node-server");
126
+ function createNodeAdapter() {
127
+ let httpServer = null;
128
+ let socketIoServer = null;
129
+ return {
130
+ runtime: "node",
131
+ bind(io) {
132
+ socketIoServer = io;
133
+ },
134
+ start({ port, app, io, logger }) {
135
+ const requestListener = (0, import_node_server.getRequestListener)(app.fetch);
136
+ httpServer = (0, import_node_http.createServer)(requestListener);
137
+ io.attach(httpServer);
138
+ return new Promise((resolve) => {
139
+ httpServer?.listen(port, () => {
140
+ logger.info({
141
+ message: `Server running on http://localhost:${port} (node)`,
142
+ atFunction: "nodeAdapter.start",
143
+ data: { port, runtime: "node" }
144
+ });
145
+ resolve();
146
+ });
147
+ });
148
+ },
149
+ stop() {
150
+ return new Promise((resolve) => {
151
+ if (socketIoServer) {
152
+ socketIoServer = null;
153
+ }
154
+ if (httpServer) {
155
+ httpServer.close(() => {
156
+ httpServer = null;
157
+ resolve();
158
+ });
159
+ } else {
160
+ resolve();
161
+ }
162
+ });
163
+ }
164
+ };
165
+ }
166
+
167
+ // src/adapters/index.ts
168
+ function detectRuntime() {
169
+ if (typeof globalThis.Bun !== "undefined") {
170
+ return "bun";
171
+ }
172
+ return "node";
173
+ }
174
+ function createRuntimeAdapter(runtime) {
175
+ const resolved = runtime ?? detectRuntime();
176
+ const adapters = {
177
+ bun: createBunAdapter,
178
+ node: createNodeAdapter
179
+ };
180
+ return adapters[resolved]();
181
+ }
182
+
35
183
  // src/create-dialogue.ts
36
184
  var import_hono = require("hono");
37
185
  var import_slang_ts3 = require("slang-ts");
@@ -159,7 +307,6 @@ function createSilentLogger() {
159
307
  }
160
308
 
161
309
  // src/server.ts
162
- var import_bun_engine = require("@socket.io/bun-engine");
163
310
  var import_cors = require("hono/cors");
164
311
  var import_socket = require("socket.io");
165
312
 
@@ -369,6 +516,79 @@ function isParticipantSubscribed(participant, roomId, eventName) {
369
516
  function createRoom(id, config, _io, logger, historyManager, hooks, externalParticipants, getContext) {
370
517
  const participants = externalParticipants ?? /* @__PURE__ */ new Map();
371
518
  const eventHandlers = /* @__PURE__ */ new Map();
519
+ function validateEvent(event, data) {
520
+ if (!isEventAllowed(event.name, config.events)) {
521
+ const errorMsg = `Event '${event.name}' is not allowed in room '${id}'`;
522
+ logger.warn({
523
+ message: errorMsg,
524
+ atFunction: "room.trigger",
525
+ data: { eventName: event.name, roomId: id }
526
+ });
527
+ return (0, import_slang_ts2.Err)(errorMsg);
528
+ }
529
+ const eventDef = getEventByName(event.name, config.events) ?? event;
530
+ const validation = validateEventData(eventDef, data);
531
+ if (validation.isErr) {
532
+ logger.warn({
533
+ message: validation.error,
534
+ atFunction: "room.trigger",
535
+ data: {
536
+ eventName: event.name,
537
+ roomId: id,
538
+ validationError: validation.error
539
+ }
540
+ });
541
+ return (0, import_slang_ts2.Err)(validation.error);
542
+ }
543
+ return (0, import_slang_ts2.Ok)(validation.value);
544
+ }
545
+ function runBeforeEachHook(event, message) {
546
+ if (!(hooks?.events?.beforeEach && getContext)) {
547
+ return (0, import_slang_ts2.Ok)(message);
548
+ }
549
+ const context = getContext();
550
+ const hookResult = hooks.events.beforeEach({
551
+ context,
552
+ roomId: id,
553
+ message,
554
+ from: message.from
555
+ });
556
+ if (hookResult.isErr) {
557
+ logger.debug({
558
+ message: `Event '${event.name}' blocked by beforeEach: ${hookResult.error}`,
559
+ atFunction: "room.trigger.beforeEach",
560
+ data: {
561
+ eventName: event.name,
562
+ roomId: id,
563
+ reason: hookResult.error
564
+ }
565
+ });
566
+ return (0, import_slang_ts2.Err)(hookResult.error);
567
+ }
568
+ return (0, import_slang_ts2.Ok)(hookResult.value);
569
+ }
570
+ function broadcastToParticipants(eventName, message) {
571
+ let recipientCount = 0;
572
+ for (const [, participant] of participants) {
573
+ if (isParticipantSubscribed(participant, id, eventName)) {
574
+ participant.socket.emit("dialogue:event", message);
575
+ recipientCount++;
576
+ }
577
+ }
578
+ return recipientCount;
579
+ }
580
+ function runAfterEachHook(message, recipientCount) {
581
+ if (!(hooks?.events?.afterEach && getContext)) {
582
+ return;
583
+ }
584
+ const context = getContext();
585
+ hooks.events.afterEach({
586
+ context,
587
+ roomId: id,
588
+ message,
589
+ recipientCount
590
+ });
591
+ }
372
592
  function handlePostBroadcast(eventName, message) {
373
593
  const eventDef = getEventByName(eventName, config.events);
374
594
  if (eventDef?.history?.enabled && historyManager) {
@@ -405,27 +625,8 @@ function createRoom(id, config, _io, logger, historyManager, hooks, externalPart
405
625
  defaultSubscriptions: config.defaultSubscriptions ?? [],
406
626
  createdById: config.createdById,
407
627
  trigger(event, data, from, meta) {
408
- if (!isEventAllowed(event.name, config.events)) {
409
- const errorMsg = `Event '${event.name}' is not allowed in room '${id}'`;
410
- logger.warn({
411
- message: errorMsg,
412
- atFunction: "room.trigger",
413
- data: { eventName: event.name, roomId: id }
414
- });
415
- return (0, import_slang_ts2.Err)(errorMsg);
416
- }
417
- const eventDef = getEventByName(event.name, config.events) ?? event;
418
- const validation = validateEventData(eventDef, data);
628
+ const validation = validateEvent(event, data);
419
629
  if (validation.isErr) {
420
- logger.warn({
421
- message: validation.error,
422
- atFunction: "room.trigger",
423
- data: {
424
- eventName: event.name,
425
- roomId: id,
426
- validationError: validation.error
427
- }
428
- });
429
630
  return (0, import_slang_ts2.Err)(validation.error);
430
631
  }
431
632
  const message = {
@@ -436,46 +637,14 @@ function createRoom(id, config, _io, logger, historyManager, hooks, externalPart
436
637
  timestamp: Date.now(),
437
638
  ...meta && { meta }
438
639
  };
439
- let finalMessage = message;
440
- if (hooks?.events?.beforeEach && getContext) {
441
- const context = getContext();
442
- const hookResult = hooks.events.beforeEach({
443
- context,
444
- roomId: id,
445
- message,
446
- from: message.from
447
- });
448
- if (hookResult.isErr) {
449
- logger.debug({
450
- message: `Event '${event.name}' blocked by beforeEach: ${hookResult.error}`,
451
- atFunction: "room.trigger.beforeEach",
452
- data: {
453
- eventName: event.name,
454
- roomId: id,
455
- reason: hookResult.error
456
- }
457
- });
458
- return (0, import_slang_ts2.Err)(hookResult.error);
459
- }
460
- finalMessage = hookResult.value;
461
- }
462
- let recipientCount = 0;
463
- for (const [, participant] of participants) {
464
- if (isParticipantSubscribed(participant, id, event.name)) {
465
- participant.socket.emit("dialogue:event", finalMessage);
466
- recipientCount++;
467
- }
640
+ const hookResult = runBeforeEachHook(event, message);
641
+ if (hookResult.isErr) {
642
+ return (0, import_slang_ts2.Err)(hookResult.error);
468
643
  }
644
+ const finalMessage = hookResult.value;
645
+ const recipientCount = broadcastToParticipants(event.name, finalMessage);
469
646
  handlePostBroadcast(event.name, finalMessage);
470
- if (hooks?.events?.afterEach && getContext) {
471
- const context = getContext();
472
- hooks.events.afterEach({
473
- context,
474
- roomId: id,
475
- message: finalMessage,
476
- recipientCount
477
- });
478
- }
647
+ runAfterEachHook(finalMessage, recipientCount);
479
648
  return (0, import_slang_ts2.Ok)(void 0);
480
649
  },
481
650
  on(event, handler) {
@@ -729,38 +898,6 @@ function buildHonoCorsOptions(cors) {
729
898
  credentials: cors.credentials ?? true
730
899
  };
731
900
  }
732
- function addCorsHeaders(response, request, corsConfig) {
733
- const origin = request.headers.get("Origin");
734
- if (!origin) {
735
- return response;
736
- }
737
- const headers = new Headers(response.headers);
738
- if (corsConfig === void 0 || corsConfig === true) {
739
- headers.set("Access-Control-Allow-Origin", origin);
740
- headers.set("Access-Control-Allow-Credentials", "true");
741
- } else if (corsConfig === false) {
742
- return response;
743
- } else {
744
- const allowedOrigin = corsConfig.origin;
745
- if (allowedOrigin === true) {
746
- headers.set("Access-Control-Allow-Origin", origin);
747
- } else if (typeof allowedOrigin === "string" && allowedOrigin === origin) {
748
- headers.set("Access-Control-Allow-Origin", origin);
749
- } else if (Array.isArray(allowedOrigin) && allowedOrigin.includes(origin)) {
750
- headers.set("Access-Control-Allow-Origin", origin);
751
- }
752
- if (corsConfig.credentials !== false) {
753
- headers.set("Access-Control-Allow-Credentials", "true");
754
- }
755
- }
756
- headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
757
- headers.set("Access-Control-Allow-Headers", "Content-Type");
758
- return new Response(response.body, {
759
- status: response.status,
760
- statusText: response.statusText,
761
- headers
762
- });
763
- }
764
901
  function sendHistoryOnJoin(socket, roomId, roomConfig, historyManager) {
765
902
  if (!(roomConfig?.syncHistoryOnJoin && historyManager)) {
766
903
  return;
@@ -789,7 +926,7 @@ function createDialogueContext(io, connectedClients, roomManager) {
789
926
  rooms: roomsRecord
790
927
  };
791
928
  }
792
- function setupServer(app, config, historyManager) {
929
+ function setupServer(app, config, adapter, historyManager) {
793
930
  const logger = config.logger ?? createDefaultLogger();
794
931
  const hooks = config.hooks;
795
932
  const connectedClients = /* @__PURE__ */ new Map();
@@ -798,8 +935,7 @@ function setupServer(app, config, historyManager) {
798
935
  const honoCorsOptions = buildHonoCorsOptions(config.cors);
799
936
  app.use("*", (0, import_cors.cors)(honoCorsOptions));
800
937
  const io = new import_socket.Server({ cors: corsOptions });
801
- const engine = new import_bun_engine.Server();
802
- io.bind(engine);
938
+ adapter.bind(io);
803
939
  const getContextForHooks = () => {
804
940
  return createDialogueContext(io, connectedClients, roomManager);
805
941
  };
@@ -1180,46 +1316,20 @@ function setupServer(app, config, historyManager) {
1180
1316
  }
1181
1317
  });
1182
1318
  });
1183
- const { websocket } = engine.handler();
1184
1319
  const port = config.port ?? 3e3;
1185
- let bunServer = null;
1186
1320
  return {
1187
1321
  io,
1188
- engine,
1189
1322
  roomManager,
1190
1323
  start() {
1191
- bunServer = Bun.serve({
1324
+ return adapter.start({
1192
1325
  port,
1193
- idleTimeout: 30,
1194
- async fetch(req, server) {
1195
- const url = new URL(req.url);
1196
- if (url.pathname.startsWith("/socket.io")) {
1197
- if (req.method === "OPTIONS") {
1198
- return addCorsHeaders(
1199
- new Response(null, { status: 204 }),
1200
- req,
1201
- config.cors
1202
- );
1203
- }
1204
- const response = await engine.handleRequest(req, server);
1205
- return addCorsHeaders(response, req, config.cors);
1206
- }
1207
- return app.fetch(req);
1208
- },
1209
- websocket
1210
- });
1211
- logger.info({
1212
- message: `Server running on http://localhost:${port}`,
1213
- atFunction: "setupServer.start",
1214
- data: { port }
1326
+ app,
1327
+ io,
1328
+ corsConfig: config.cors,
1329
+ logger
1215
1330
  });
1216
- return Promise.resolve();
1217
1331
  },
1218
1332
  stop() {
1219
- if (bunServer) {
1220
- bunServer.stop();
1221
- bunServer = null;
1222
- }
1223
1333
  for (const client of connectedClients.values()) {
1224
1334
  client.disconnect();
1225
1335
  }
@@ -1231,7 +1341,7 @@ function setupServer(app, config, historyManager) {
1231
1341
  atFunction: "setupServer.stop",
1232
1342
  data: null
1233
1343
  });
1234
- return Promise.resolve();
1344
+ return adapter.stop();
1235
1345
  },
1236
1346
  /**
1237
1347
  * Gets a connected client by socket ID
@@ -1295,6 +1405,7 @@ function createDialogue(config) {
1295
1405
  onCleanup: config.hooks?.events?.onCleanup,
1296
1406
  onLoad: config.hooks?.events?.onLoad
1297
1407
  });
1408
+ const adapter = createRuntimeAdapter(config.runtime);
1298
1409
  const {
1299
1410
  io,
1300
1411
  roomManager,
@@ -1305,7 +1416,7 @@ function createDialogue(config) {
1305
1416
  getClientsByUserId,
1306
1417
  getClientRooms: getClientRoomIds,
1307
1418
  isUserInRoom
1308
- } = setupServer(app, config, historyManager);
1419
+ } = setupServer(app, config, adapter, historyManager);
1309
1420
  const dialogue = {
1310
1421
  app,
1311
1422
  io,
@@ -1415,8 +1526,10 @@ function createDialogue(config) {
1415
1526
  createDialogue,
1416
1527
  createHistoryManager,
1417
1528
  createRateLimiter,
1529
+ createRuntimeAdapter,
1418
1530
  createSilentLogger,
1419
1531
  defineEvent,
1532
+ detectRuntime,
1420
1533
  getEventByName,
1421
1534
  isEventAllowed,
1422
1535
  validateEventData