dialogue-ts 0.0.2 → 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.
@@ -1,6 +1,6 @@
1
1
  import { Hono } from 'hono';
2
- import { Result } from 'slang-ts';
3
2
  import { Socket, Server } from 'socket.io';
3
+ import { Result } from 'slang-ts';
4
4
  import { z } from 'zod';
5
5
 
6
6
  /**
@@ -247,6 +247,14 @@ interface DialogueConfig {
247
247
  logger?: Logger;
248
248
  /** CORS configuration. Defaults to allowing all origins in development. */
249
249
  cors?: CorsConfig | boolean;
250
+ /**
251
+ * Runtime to use for the HTTP server and Socket.IO engine.
252
+ * - "bun": Uses Bun.serve() with @socket.io/bun-engine
253
+ * - "node": Uses Node.js http.createServer() with Socket.IO's built-in engine
254
+ *
255
+ * Auto-detected if not specified (checks for Bun globals, falls back to Node).
256
+ */
257
+ runtime?: "bun" | "node";
250
258
  }
251
259
  /**
252
260
  * Message envelope - the shape of all messages sent between clients and server.
@@ -436,6 +444,56 @@ interface RoomManager {
436
444
  removeParticipant(roomId: string, clientId: string): void;
437
445
  }
438
446
 
447
+ /** Supported runtime environments */
448
+ type Runtime = "bun" | "node";
449
+ /**
450
+ * Runtime adapter interface that abstracts the HTTP server and Socket.IO engine
451
+ * binding for different JavaScript runtimes (Bun, Node.js).
452
+ *
453
+ * Each adapter handles:
454
+ * - Binding the Socket.IO engine to the IO server
455
+ * - Starting/stopping the HTTP server with Hono routing and Socket.IO transport
456
+ */
457
+ interface RuntimeAdapter {
458
+ /** The runtime this adapter targets */
459
+ readonly runtime: Runtime;
460
+ /**
461
+ * Bind the Socket.IO server to the runtime-specific engine.
462
+ * For Bun: uses @socket.io/bun-engine
463
+ * For Node: uses Socket.IO's built-in engine.io (attached to http.Server)
464
+ */
465
+ bind(io: Server): void;
466
+ /**
467
+ * Start the HTTP server on the given port, routing both Hono app
468
+ * requests and Socket.IO transport (polling + websocket).
469
+ */
470
+ start(options: RuntimeStartOptions): Promise<void>;
471
+ /** Stop the HTTP server and clean up resources */
472
+ stop(): Promise<void>;
473
+ }
474
+ /** Options passed to RuntimeAdapter.start() */
475
+ interface RuntimeStartOptions {
476
+ port: number;
477
+ app: Hono;
478
+ io: Server;
479
+ corsConfig: CorsConfig | boolean | undefined;
480
+ logger: Logger;
481
+ }
482
+
483
+ /**
484
+ * Detects the current JavaScript runtime by checking for Bun globals.
485
+ * Falls back to "node" if Bun is not detected.
486
+ */
487
+ declare function detectRuntime(): Runtime;
488
+ /**
489
+ * Creates the appropriate runtime adapter based on the specified runtime.
490
+ * Uses auto-detection if no runtime is specified.
491
+ *
492
+ * @param runtime - Explicit runtime choice, or auto-detected if omitted
493
+ * @returns A RuntimeAdapter for the target runtime
494
+ */
495
+ declare function createRuntimeAdapter(runtime?: Runtime): RuntimeAdapter;
496
+
439
497
  /**
440
498
  * Creates a Dialogue instance from configuration.
441
499
  * This is the main entry point for the library.
@@ -661,4 +719,4 @@ interface RateLimiter {
661
719
  */
662
720
  declare function createRateLimiter(config: RateLimiterConfig): RateLimiter;
663
721
 
664
- export { type AuthData, type AuthenticateResult, type ConnectedClient, type CorsConfig, type Dialogue, type DialogueConfig, type DialogueContext, type EventDefinition, type EventHistoryConfig, type EventMessage, type HistoryManager, type HistoryManagerConfig, type HooksConfig, type JwtClaims, type LogEntry, type Logger, type RateLimiter, type RateLimiterConfig, type Room, type RoomConfig, type RoomManager, createDefaultLogger, createDialogue, createHistoryManager, createRateLimiter, createSilentLogger, defineEvent, getEventByName, isEventAllowed, validateEventData };
722
+ export { type AuthData, type AuthenticateResult, type ConnectedClient, type CorsConfig, type Dialogue, type DialogueConfig, type DialogueContext, type EventDefinition, type EventHistoryConfig, type EventMessage, type HistoryManager, type HistoryManagerConfig, type HooksConfig, type JwtClaims, type LogEntry, type Logger, type RateLimiter, type RateLimiterConfig, type Room, type RoomConfig, type RoomManager, type Runtime, type RuntimeAdapter, type RuntimeStartOptions, createDefaultLogger, createDialogue, createHistoryManager, createRateLimiter, createRuntimeAdapter, createSilentLogger, defineEvent, detectRuntime, getEventByName, isEventAllowed, validateEventData };
package/dist/src/index.js CHANGED
@@ -1,3 +1,149 @@
1
+ // src/adapters/bun-adapter.ts
2
+ import { Server as BunEngine } from "@socket.io/bun-engine";
3
+ function addCorsHeaders(response, request, corsConfig) {
4
+ const origin = request.headers.get("Origin");
5
+ if (!origin) {
6
+ return response;
7
+ }
8
+ const headers = new Headers(response.headers);
9
+ if (corsConfig === void 0 || corsConfig === true) {
10
+ headers.set("Access-Control-Allow-Origin", origin);
11
+ headers.set("Access-Control-Allow-Credentials", "true");
12
+ } else if (corsConfig === false) {
13
+ return response;
14
+ } else {
15
+ const allowedOrigin = corsConfig.origin;
16
+ if (allowedOrigin === true) {
17
+ headers.set("Access-Control-Allow-Origin", origin);
18
+ } else if (typeof allowedOrigin === "string" && allowedOrigin === origin) {
19
+ headers.set("Access-Control-Allow-Origin", origin);
20
+ } else if (Array.isArray(allowedOrigin) && allowedOrigin.includes(origin)) {
21
+ headers.set("Access-Control-Allow-Origin", origin);
22
+ }
23
+ if (corsConfig.credentials !== false) {
24
+ headers.set("Access-Control-Allow-Credentials", "true");
25
+ }
26
+ }
27
+ headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
28
+ headers.set("Access-Control-Allow-Headers", "Content-Type");
29
+ return new Response(response.body, {
30
+ status: response.status,
31
+ statusText: response.statusText,
32
+ headers
33
+ });
34
+ }
35
+ function createBunAdapter() {
36
+ const engine = new BunEngine();
37
+ let bunServer = null;
38
+ return {
39
+ runtime: "bun",
40
+ bind(io) {
41
+ io.bind(engine);
42
+ },
43
+ start({
44
+ port,
45
+ app,
46
+ corsConfig,
47
+ logger
48
+ }) {
49
+ const { websocket } = engine.handler();
50
+ bunServer = Bun.serve({
51
+ port,
52
+ idleTimeout: 30,
53
+ async fetch(req, server) {
54
+ const url = new URL(req.url);
55
+ if (url.pathname.startsWith("/socket.io")) {
56
+ if (req.method === "OPTIONS") {
57
+ return addCorsHeaders(
58
+ new Response(null, { status: 204 }),
59
+ req,
60
+ corsConfig
61
+ );
62
+ }
63
+ const response = await engine.handleRequest(req, server);
64
+ return addCorsHeaders(response, req, corsConfig);
65
+ }
66
+ return app.fetch(req);
67
+ },
68
+ websocket
69
+ });
70
+ logger.info({
71
+ message: `Server running on http://localhost:${port} (bun)`,
72
+ atFunction: "bunAdapter.start",
73
+ data: { port, runtime: "bun" }
74
+ });
75
+ return Promise.resolve();
76
+ },
77
+ stop() {
78
+ if (bunServer) {
79
+ bunServer.stop();
80
+ bunServer = null;
81
+ }
82
+ return Promise.resolve();
83
+ }
84
+ };
85
+ }
86
+
87
+ // src/adapters/node-adapter.ts
88
+ import { createServer } from "http";
89
+ import { getRequestListener } from "@hono/node-server";
90
+ function createNodeAdapter() {
91
+ let httpServer = null;
92
+ let socketIoServer = null;
93
+ return {
94
+ runtime: "node",
95
+ bind(io) {
96
+ socketIoServer = io;
97
+ },
98
+ start({ port, app, io, logger }) {
99
+ const requestListener = getRequestListener(app.fetch);
100
+ httpServer = createServer(requestListener);
101
+ io.attach(httpServer);
102
+ return new Promise((resolve) => {
103
+ httpServer?.listen(port, () => {
104
+ logger.info({
105
+ message: `Server running on http://localhost:${port} (node)`,
106
+ atFunction: "nodeAdapter.start",
107
+ data: { port, runtime: "node" }
108
+ });
109
+ resolve();
110
+ });
111
+ });
112
+ },
113
+ stop() {
114
+ return new Promise((resolve) => {
115
+ if (socketIoServer) {
116
+ socketIoServer = null;
117
+ }
118
+ if (httpServer) {
119
+ httpServer.close(() => {
120
+ httpServer = null;
121
+ resolve();
122
+ });
123
+ } else {
124
+ resolve();
125
+ }
126
+ });
127
+ }
128
+ };
129
+ }
130
+
131
+ // src/adapters/index.ts
132
+ function detectRuntime() {
133
+ if (typeof globalThis.Bun !== "undefined") {
134
+ return "bun";
135
+ }
136
+ return "node";
137
+ }
138
+ function createRuntimeAdapter(runtime) {
139
+ const resolved = runtime ?? detectRuntime();
140
+ const adapters = {
141
+ bun: createBunAdapter,
142
+ node: createNodeAdapter
143
+ };
144
+ return adapters[resolved]();
145
+ }
146
+
1
147
  // src/create-dialogue.ts
2
148
  import { Hono } from "hono";
3
149
  import { Err as Err3 } from "slang-ts";
@@ -125,7 +271,6 @@ function createSilentLogger() {
125
271
  }
126
272
 
127
273
  // src/server.ts
128
- import { Server as BunEngine } from "@socket.io/bun-engine";
129
274
  import { cors as honoCors } from "hono/cors";
130
275
  import { Server } from "socket.io";
131
276
 
@@ -717,38 +862,6 @@ function buildHonoCorsOptions(cors) {
717
862
  credentials: cors.credentials ?? true
718
863
  };
719
864
  }
720
- function addCorsHeaders(response, request, corsConfig) {
721
- const origin = request.headers.get("Origin");
722
- if (!origin) {
723
- return response;
724
- }
725
- const headers = new Headers(response.headers);
726
- if (corsConfig === void 0 || corsConfig === true) {
727
- headers.set("Access-Control-Allow-Origin", origin);
728
- headers.set("Access-Control-Allow-Credentials", "true");
729
- } else if (corsConfig === false) {
730
- return response;
731
- } else {
732
- const allowedOrigin = corsConfig.origin;
733
- if (allowedOrigin === true) {
734
- headers.set("Access-Control-Allow-Origin", origin);
735
- } else if (typeof allowedOrigin === "string" && allowedOrigin === origin) {
736
- headers.set("Access-Control-Allow-Origin", origin);
737
- } else if (Array.isArray(allowedOrigin) && allowedOrigin.includes(origin)) {
738
- headers.set("Access-Control-Allow-Origin", origin);
739
- }
740
- if (corsConfig.credentials !== false) {
741
- headers.set("Access-Control-Allow-Credentials", "true");
742
- }
743
- }
744
- headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
745
- headers.set("Access-Control-Allow-Headers", "Content-Type");
746
- return new Response(response.body, {
747
- status: response.status,
748
- statusText: response.statusText,
749
- headers
750
- });
751
- }
752
865
  function sendHistoryOnJoin(socket, roomId, roomConfig, historyManager) {
753
866
  if (!(roomConfig?.syncHistoryOnJoin && historyManager)) {
754
867
  return;
@@ -777,7 +890,7 @@ function createDialogueContext(io, connectedClients, roomManager) {
777
890
  rooms: roomsRecord
778
891
  };
779
892
  }
780
- function setupServer(app, config, historyManager) {
893
+ function setupServer(app, config, adapter, historyManager) {
781
894
  const logger = config.logger ?? createDefaultLogger();
782
895
  const hooks = config.hooks;
783
896
  const connectedClients = /* @__PURE__ */ new Map();
@@ -786,8 +899,7 @@ function setupServer(app, config, historyManager) {
786
899
  const honoCorsOptions = buildHonoCorsOptions(config.cors);
787
900
  app.use("*", honoCors(honoCorsOptions));
788
901
  const io = new Server({ cors: corsOptions });
789
- const engine = new BunEngine();
790
- io.bind(engine);
902
+ adapter.bind(io);
791
903
  const getContextForHooks = () => {
792
904
  return createDialogueContext(io, connectedClients, roomManager);
793
905
  };
@@ -1168,46 +1280,20 @@ function setupServer(app, config, historyManager) {
1168
1280
  }
1169
1281
  });
1170
1282
  });
1171
- const { websocket } = engine.handler();
1172
1283
  const port = config.port ?? 3e3;
1173
- let bunServer = null;
1174
1284
  return {
1175
1285
  io,
1176
- engine,
1177
1286
  roomManager,
1178
1287
  start() {
1179
- bunServer = Bun.serve({
1288
+ return adapter.start({
1180
1289
  port,
1181
- idleTimeout: 30,
1182
- async fetch(req, server) {
1183
- const url = new URL(req.url);
1184
- if (url.pathname.startsWith("/socket.io")) {
1185
- if (req.method === "OPTIONS") {
1186
- return addCorsHeaders(
1187
- new Response(null, { status: 204 }),
1188
- req,
1189
- config.cors
1190
- );
1191
- }
1192
- const response = await engine.handleRequest(req, server);
1193
- return addCorsHeaders(response, req, config.cors);
1194
- }
1195
- return app.fetch(req);
1196
- },
1197
- websocket
1198
- });
1199
- logger.info({
1200
- message: `Server running on http://localhost:${port}`,
1201
- atFunction: "setupServer.start",
1202
- data: { port }
1290
+ app,
1291
+ io,
1292
+ corsConfig: config.cors,
1293
+ logger
1203
1294
  });
1204
- return Promise.resolve();
1205
1295
  },
1206
1296
  stop() {
1207
- if (bunServer) {
1208
- bunServer.stop();
1209
- bunServer = null;
1210
- }
1211
1297
  for (const client of connectedClients.values()) {
1212
1298
  client.disconnect();
1213
1299
  }
@@ -1219,7 +1305,7 @@ function setupServer(app, config, historyManager) {
1219
1305
  atFunction: "setupServer.stop",
1220
1306
  data: null
1221
1307
  });
1222
- return Promise.resolve();
1308
+ return adapter.stop();
1223
1309
  },
1224
1310
  /**
1225
1311
  * Gets a connected client by socket ID
@@ -1283,6 +1369,7 @@ function createDialogue(config) {
1283
1369
  onCleanup: config.hooks?.events?.onCleanup,
1284
1370
  onLoad: config.hooks?.events?.onLoad
1285
1371
  });
1372
+ const adapter = createRuntimeAdapter(config.runtime);
1286
1373
  const {
1287
1374
  io,
1288
1375
  roomManager,
@@ -1293,7 +1380,7 @@ function createDialogue(config) {
1293
1380
  getClientsByUserId,
1294
1381
  getClientRooms: getClientRoomIds,
1295
1382
  isUserInRoom
1296
- } = setupServer(app, config, historyManager);
1383
+ } = setupServer(app, config, adapter, historyManager);
1297
1384
  const dialogue = {
1298
1385
  app,
1299
1386
  io,
@@ -1402,8 +1489,10 @@ export {
1402
1489
  createDialogue,
1403
1490
  createHistoryManager,
1404
1491
  createRateLimiter,
1492
+ createRuntimeAdapter,
1405
1493
  createSilentLogger,
1406
1494
  defineEvent,
1495
+ detectRuntime,
1407
1496
  getEventByName,
1408
1497
  isEventAllowed,
1409
1498
  validateEventData