@evolu/nodejs 1.0.1-preview.8 → 1.0.1-preview.9

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,7 +1,22 @@
1
- import { ConsoleDep } from "@evolu/common";
1
+ import { ConsoleDep, CreateSqliteDriverDep, Result, SqliteError } from "@evolu/common";
2
2
  import { Relay, RelayConfig } from "@evolu/common/evolu";
3
3
  export interface NodeJsRelayConfig extends RelayConfig {
4
+ /** The port number for the HTTP server. */
4
5
  readonly port?: number;
5
6
  }
6
- export declare const createNodeJsRelay: (deps: ConsoleDep) => (config: NodeJsRelayConfig) => Promise<Relay>;
7
+ /**
8
+ * Creates an Evolu relay server.
9
+ *
10
+ * This implementation uses Node.js and better-sqlite3. Additional relay
11
+ * implementations will be provided for other platforms (Bun, Deno, Cloudflare
12
+ * Workers, Vercel Edge, etc.).
13
+ */
14
+ export declare const createNodeJsRelay: (deps: ConsoleDep) => (config: NodeJsRelayConfig) => Promise<Result<Relay, SqliteError>>;
15
+ /**
16
+ * Creates an Evolu relay server with a custom SQLite driver.
17
+ *
18
+ * Use this when you need to provide a different SQLite driver implementation
19
+ * (e.g., using alternative SQLite libraries).
20
+ */
21
+ export declare const createNodeJsRelayWithSqliteDriver: (deps: ConsoleDep & CreateSqliteDriverDep) => (config: NodeJsRelayConfig) => Promise<Result<Relay, SqliteError>>;
7
22
  //# sourceMappingURL=Relay.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../src/Evolu/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAQX,MAAM,eAAe,CAAC;AACvB,OAAO,EAKL,KAAK,EACL,WAAW,EAEZ,MAAM,qBAAqB,CAAC;AAK7B,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,iBAAiB,GAC3B,MAAM,UAAU,MACV,QAAQ,iBAAiB,KAAG,OAAO,CAAC,KAAK,CA+I/C,CAAC"}
1
+ {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../src/Evolu/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,qBAAqB,EAIrB,MAAM,EAEN,WAAW,EAGZ,MAAM,eAAe,CAAC;AACvB,OAAO,EAOL,KAAK,EACL,WAAW,EACZ,MAAM,qBAAqB,CAAC;AAM7B,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAC3B,MAAM,UAAU,MAChB,QAAQ,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAInD,CAAC;AAEf;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,GAC3C,MAAM,UAAU,GAAG,qBAAqB,MACxC,QAAQ,iBAAiB,KAAG,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAKnD,CAAC"}
@@ -1,54 +1,98 @@
1
- import { createManyToManyMap, createRandom, createSqlite, getOrThrow, SimpleName, Uint8Array, } from "@evolu/common";
2
- import { applyProtocolMessageAsRelay, createRelayStorage, maxProtocolMessageSize, } from "@evolu/common/evolu";
1
+ import { createManyToManyMap, createRandom, createSqlite, ok, SimpleName, Uint8Array, } from "@evolu/common";
2
+ import { applyProtocolMessageAsRelay, createRelayLogger, createRelaySqliteStorage, defaultProtocolMessageMaxSize, parseOwnerIdFromUrl, } from "@evolu/common/evolu";
3
+ import { createServer } from "http";
3
4
  import { WebSocket, WebSocketServer } from "ws";
4
5
  import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
5
6
  import { createTimingSafeEqual } from "../Crypto.js";
6
- export const createNodeJsRelay = (deps) => async (config) => {
7
- const { port = 443, name = SimpleName.orThrow("evolu-relay"), enableLogging = false, } = config;
8
- deps.console.enabled = true;
9
- deps.console.log(`Evolu Relay started on port ${port}`);
10
- deps.console.enabled = enableLogging;
11
- const sqlite = getOrThrow(await createSqlite({
12
- ...deps,
13
- createSqliteDriver: createBetterSqliteDriver,
14
- })(name));
15
- const relaySqliteStorageDeps = {
16
- sqlite,
17
- random: createRandom(),
18
- timingSafeEqual: createTimingSafeEqual(),
19
- };
20
- const storage = getOrThrow(createRelayStorage(relaySqliteStorageDeps)({
21
- onStorageError: (error) => {
22
- deps.console.error("[relay]", "storage", error);
23
- },
24
- }));
7
+ /**
8
+ * Creates an Evolu relay server.
9
+ *
10
+ * This implementation uses Node.js and better-sqlite3. Additional relay
11
+ * implementations will be provided for other platforms (Bun, Deno, Cloudflare
12
+ * Workers, Vercel Edge, etc.).
13
+ */
14
+ export const createNodeJsRelay = (deps) => (config) => createNodeJsRelayWithSqliteDriver({
15
+ ...deps,
16
+ createSqliteDriver: createBetterSqliteDriver,
17
+ })(config);
18
+ /**
19
+ * Creates an Evolu relay server with a custom SQLite driver.
20
+ *
21
+ * Use this when you need to provide a different SQLite driver implementation
22
+ * (e.g., using alternative SQLite libraries).
23
+ */
24
+ export const createNodeJsRelayWithSqliteDriver = (deps) => (config) => createNodeJsRelayWithDeps({
25
+ ...deps,
26
+ random: createRandom(),
27
+ timingSafeEqual: createTimingSafeEqual(),
28
+ })(config);
29
+ const createNodeJsRelayWithDeps = (deps) => async ({ port = 443, name = SimpleName.orThrow("evolu-relay"), enableLogging = false, authenticateOwner, }) => {
30
+ const log = createRelayLogger(deps);
31
+ log.started(enableLogging, port);
32
+ const sqliteResult = await createSqlite(deps)(name);
33
+ if (!sqliteResult.ok)
34
+ return sqliteResult;
35
+ const sqlite = sqliteResult.value;
36
+ const storageResult = createRelaySqliteStorage({ ...deps, sqlite })({
37
+ onStorageError: log.storageError,
38
+ });
39
+ if (!storageResult.ok)
40
+ return storageResult;
41
+ const storage = storageResult.value;
42
+ const server = createServer();
25
43
  const wss = new WebSocketServer({
26
- maxPayload: maxProtocolMessageSize,
27
- port,
44
+ maxPayload: defaultProtocolMessageMaxSize,
45
+ noServer: true,
28
46
  });
29
47
  const ownerSocketsMap = createManyToManyMap();
30
- wss.on("connection", (ws) => {
31
- deps.console.log("[relay]", "connection", {
32
- clientCount: wss.clients.size,
48
+ server.on("upgrade", (request, socket, head) => {
49
+ socket.on("error", log.upgradeSocketError);
50
+ const completeUpgrade = () => {
51
+ socket.removeListener("error", log.upgradeSocketError);
52
+ wss.handleUpgrade(request, socket, head, (ws) => {
53
+ wss.emit("connection", ws, request);
54
+ });
55
+ };
56
+ if (!authenticateOwner) {
57
+ completeUpgrade();
58
+ return;
59
+ }
60
+ const ownerId = parseOwnerIdFromUrl(request.url);
61
+ if (!ownerId) {
62
+ log.invalidOrMissingOwnerIdInUrl(request.url);
63
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
64
+ socket.destroy();
65
+ return;
66
+ }
67
+ authenticateOwner(ownerId)
68
+ .then((isAuthenticated) => {
69
+ if (!isAuthenticated) {
70
+ log.unauthorizedOwner(ownerId);
71
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
72
+ socket.destroy();
73
+ return;
74
+ }
75
+ completeUpgrade();
76
+ })
77
+ .catch((error) => {
78
+ log.authenticateOwnerError(error);
79
+ socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
80
+ socket.destroy();
33
81
  });
82
+ });
83
+ wss.on("connection", (ws) => {
84
+ log.connectionEstablished(wss.clients.size);
34
85
  ws.on("error", (error) => {
35
- deps.console.warn("[relay]", "error", { error });
36
- deps.console.error(error);
86
+ log.connectionWebSocketError(error);
37
87
  });
38
88
  const options = {
39
89
  subscribe: (ownerId) => {
40
90
  ownerSocketsMap.add(ownerId, ws);
41
- deps.console.log("[relay]", "subscribe", {
42
- ownerId,
43
- subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
44
- });
91
+ log.relayOptionSubscribe(ownerId, () => ownerSocketsMap.getValues(ownerId)?.size ?? 0);
45
92
  },
46
93
  unsubscribe: (ownerId) => {
47
94
  ownerSocketsMap.remove(ownerId, ws);
48
- deps.console.log("[relay]", "unsubscribe", {
49
- ownerId,
50
- subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
51
- });
95
+ log.relayOptionUnsubscribe(ownerId, () => ownerSocketsMap.getValues(ownerId)?.size ?? 0);
52
96
  },
53
97
  broadcast: (ownerId, message) => {
54
98
  const sockets = ownerSocketsMap.getValues(ownerId);
@@ -61,61 +105,53 @@ export const createNodeJsRelay = (deps) => async (config) => {
61
105
  broadcastCount++;
62
106
  }
63
107
  }
64
- deps.console.log("[relay]", "broadcast", {
65
- ownerId,
66
- broadcastCount,
67
- totalSubscribers: sockets.size,
68
- });
108
+ log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
69
109
  },
70
110
  };
71
111
  ws.on("message", (message) => {
72
112
  if (!Uint8Array.is(message))
73
113
  return;
74
- deps.console.log("[relay]", "on message", {
75
- messageSize: message.length,
76
- });
114
+ log.messageLength(message.length);
77
115
  applyProtocolMessageAsRelay({ storage })(message, options)
78
116
  .then((response) => {
79
117
  if (!response.ok) {
80
- deps.console.error("[relay]", "protocol", response.error);
81
- deps.console.error(response.error);
118
+ log.applyProtocolMessageAsRelayError(response.error);
82
119
  return;
83
120
  }
84
121
  ws.send(response.value.message, { binary: true });
85
- deps.console.log("[relay]", "response", {
86
- responseSize: response.value.message.length,
87
- });
122
+ log.responseLength(response.value.message.length);
88
123
  })
89
- .catch((error) => {
90
- deps.console.error("[relay]", "applyProtocolMessageAsRelay", error);
91
- });
124
+ .catch(log.applyProtocolMessageAsRelayUnknownError);
92
125
  });
93
126
  ws.on("close", () => {
94
127
  ownerSocketsMap.deleteValue(ws);
95
- deps.console.log("[relay]", "close", {
96
- clientCount: wss.clients.size,
97
- });
128
+ log.connectionClosed(wss.clients.size);
98
129
  });
99
130
  });
100
- const disposeWss = () => {
101
- deps.console.log("Shutting down Evolu Relay...");
131
+ server.listen(port);
132
+ const dispose = () => {
133
+ log.shuttingDown();
102
134
  wss.clients.forEach((client) => {
103
135
  if (client.readyState === WebSocket.OPEN) {
104
136
  client.close(1000, "Evolu Relay shutting down");
105
137
  }
106
138
  });
107
139
  wss.close(() => {
108
- deps.console.log("Evolu Relay disposed");
140
+ log.webSocketServerDisposed();
141
+ });
142
+ server.close(() => {
143
+ log.httpServerDisposed();
109
144
  });
110
145
  };
111
146
  let isDisposed = false;
112
- return {
147
+ const relay = {
113
148
  [Symbol.dispose]: () => {
114
149
  if (isDisposed)
115
150
  return;
116
151
  isDisposed = true;
117
152
  sqlite[Symbol.dispose]();
118
- disposeWss();
153
+ dispose();
119
154
  },
120
155
  };
156
+ return ok(relay);
121
157
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/nodejs",
3
- "version": "1.0.1-preview.8",
3
+ "version": "1.0.1-preview.9",
4
4
  "description": "Evolu for Node.js",
5
5
  "author": "Daniel Steigerwald <daniel@steigerwald.cz>",
6
6
  "license": "MIT",
@@ -30,11 +30,11 @@
30
30
  "shx": "^0.4.0",
31
31
  "typescript": "^5.9.2",
32
32
  "vitest": "^4.0.4",
33
- "@evolu/common": "6.0.1-preview.20",
33
+ "@evolu/common": "6.0.1-preview.21",
34
34
  "@evolu/tsconfig": "0.0.2"
35
35
  },
36
36
  "peerDependencies": {
37
- "@evolu/common": "^6.0.1-preview.20"
37
+ "@evolu/common": "^6.0.1-preview.21"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22.0.0"
@@ -3,94 +3,158 @@ import {
3
3
  createManyToManyMap,
4
4
  createRandom,
5
5
  createSqlite,
6
- getOrThrow,
6
+ CreateSqliteDriverDep,
7
+ ok,
7
8
  OwnerId,
9
+ RandomDep,
10
+ Result,
8
11
  SimpleName,
12
+ SqliteError,
13
+ TimingSafeEqualDep,
9
14
  Uint8Array,
10
15
  } from "@evolu/common";
11
16
  import {
12
17
  applyProtocolMessageAsRelay,
13
18
  ApplyProtocolMessageAsRelayOptions,
14
- createRelayStorage,
15
- maxProtocolMessageSize,
19
+ createRelayLogger,
20
+ createRelaySqliteStorage,
21
+ defaultProtocolMessageMaxSize,
22
+ parseOwnerIdFromUrl,
16
23
  Relay,
17
24
  RelayConfig,
18
- RelaySqliteStorageDeps,
19
25
  } from "@evolu/common/evolu";
26
+ import { createServer } from "http";
20
27
  import { WebSocket, WebSocketServer } from "ws";
21
28
  import { createBetterSqliteDriver } from "../BetterSqliteDriver.js";
22
29
  import { createTimingSafeEqual } from "../Crypto.js";
23
30
 
24
31
  export interface NodeJsRelayConfig extends RelayConfig {
32
+ /** The port number for the HTTP server. */
25
33
  readonly port?: number;
26
34
  }
27
35
 
36
+ /**
37
+ * Creates an Evolu relay server.
38
+ *
39
+ * This implementation uses Node.js and better-sqlite3. Additional relay
40
+ * implementations will be provided for other platforms (Bun, Deno, Cloudflare
41
+ * Workers, Vercel Edge, etc.).
42
+ */
28
43
  export const createNodeJsRelay =
29
44
  (deps: ConsoleDep) =>
30
- async (config: NodeJsRelayConfig): Promise<Relay> => {
31
- const {
32
- port = 443,
33
- name = SimpleName.orThrow("evolu-relay"),
34
- enableLogging = false,
35
- } = config;
36
-
37
- deps.console.enabled = true;
38
- deps.console.log(`Evolu Relay started on port ${port}`);
39
- deps.console.enabled = enableLogging;
40
-
41
- const sqlite = getOrThrow(
42
- await createSqlite({
43
- ...deps,
44
- createSqliteDriver: createBetterSqliteDriver,
45
- })(name),
46
- );
47
-
48
- const relaySqliteStorageDeps: RelaySqliteStorageDeps = {
49
- sqlite,
45
+ (config: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> =>
46
+ createNodeJsRelayWithSqliteDriver({
47
+ ...deps,
48
+ createSqliteDriver: createBetterSqliteDriver,
49
+ })(config);
50
+
51
+ /**
52
+ * Creates an Evolu relay server with a custom SQLite driver.
53
+ *
54
+ * Use this when you need to provide a different SQLite driver implementation
55
+ * (e.g., using alternative SQLite libraries).
56
+ */
57
+ export const createNodeJsRelayWithSqliteDriver =
58
+ (deps: ConsoleDep & CreateSqliteDriverDep) =>
59
+ (config: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> =>
60
+ createNodeJsRelayWithDeps({
61
+ ...deps,
50
62
  random: createRandom(),
51
63
  timingSafeEqual: createTimingSafeEqual(),
52
- };
64
+ })(config);
65
+
66
+ const createNodeJsRelayWithDeps =
67
+ (deps: ConsoleDep & CreateSqliteDriverDep & RandomDep & TimingSafeEqualDep) =>
68
+ async ({
69
+ port = 443,
70
+ name = SimpleName.orThrow("evolu-relay"),
71
+ enableLogging = false,
72
+ authenticateOwner,
73
+ }: NodeJsRelayConfig): Promise<Result<Relay, SqliteError>> => {
74
+ const log = createRelayLogger(deps);
75
+ log.started(enableLogging, port);
76
+
77
+ const sqliteResult = await createSqlite(deps)(name);
78
+ if (!sqliteResult.ok) return sqliteResult;
79
+ const sqlite = sqliteResult.value;
80
+
81
+ const storageResult = createRelaySqliteStorage({ ...deps, sqlite })({
82
+ onStorageError: log.storageError,
83
+ });
53
84
 
54
- const storage = getOrThrow(
55
- createRelayStorage(relaySqliteStorageDeps)({
56
- onStorageError: (error) => {
57
- deps.console.error("[relay]", "storage", error);
58
- },
59
- }),
60
- );
85
+ if (!storageResult.ok) return storageResult;
86
+ const storage = storageResult.value;
61
87
 
88
+ const server = createServer();
62
89
  const wss = new WebSocketServer({
63
- maxPayload: maxProtocolMessageSize,
64
- port,
90
+ maxPayload: defaultProtocolMessageMaxSize,
91
+ noServer: true,
65
92
  });
66
93
 
67
94
  const ownerSocketsMap = createManyToManyMap<OwnerId, WebSocket>();
68
95
 
96
+ server.on("upgrade", (request, socket, head) => {
97
+ socket.on("error", log.upgradeSocketError);
98
+
99
+ const completeUpgrade = () => {
100
+ socket.removeListener("error", log.upgradeSocketError);
101
+ wss.handleUpgrade(request, socket, head, (ws) => {
102
+ wss.emit("connection", ws, request);
103
+ });
104
+ };
105
+
106
+ if (!authenticateOwner) {
107
+ completeUpgrade();
108
+ return;
109
+ }
110
+
111
+ const ownerId = parseOwnerIdFromUrl(request.url);
112
+ if (!ownerId) {
113
+ log.invalidOrMissingOwnerIdInUrl(request.url);
114
+ socket.write("HTTP/1.1 400 Bad Request\r\n\r\n");
115
+ socket.destroy();
116
+ return;
117
+ }
118
+
119
+ authenticateOwner(ownerId)
120
+ .then((isAuthenticated) => {
121
+ if (!isAuthenticated) {
122
+ log.unauthorizedOwner(ownerId);
123
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
124
+ socket.destroy();
125
+ return;
126
+ }
127
+ completeUpgrade();
128
+ })
129
+ .catch((error: unknown) => {
130
+ log.authenticateOwnerError(error);
131
+ socket.write("HTTP/1.1 500 Internal Server Error\r\n\r\n");
132
+ socket.destroy();
133
+ });
134
+ });
135
+
69
136
  wss.on("connection", (ws) => {
70
- deps.console.log("[relay]", "connection", {
71
- clientCount: wss.clients.size,
72
- });
137
+ log.connectionEstablished(wss.clients.size);
73
138
 
74
139
  ws.on("error", (error) => {
75
- deps.console.warn("[relay]", "error", { error });
76
- deps.console.error(error);
140
+ log.connectionWebSocketError(error);
77
141
  });
78
142
 
79
143
  const options: ApplyProtocolMessageAsRelayOptions = {
80
144
  subscribe: (ownerId) => {
81
145
  ownerSocketsMap.add(ownerId, ws);
82
- deps.console.log("[relay]", "subscribe", {
146
+ log.relayOptionSubscribe(
83
147
  ownerId,
84
- subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
85
- });
148
+ () => ownerSocketsMap.getValues(ownerId)?.size ?? 0,
149
+ );
86
150
  },
87
151
 
88
152
  unsubscribe: (ownerId) => {
89
153
  ownerSocketsMap.remove(ownerId, ws);
90
- deps.console.log("[relay]", "unsubscribe", {
154
+ log.relayOptionUnsubscribe(
91
155
  ownerId,
92
- subscriberCount: ownerSocketsMap.getValues(ownerId)?.size ?? 0,
93
- });
156
+ () => ownerSocketsMap.getValues(ownerId)?.size ?? 0,
157
+ );
94
158
  },
95
159
 
96
160
  broadcast: (ownerId, message) => {
@@ -105,49 +169,36 @@ export const createNodeJsRelay =
105
169
  }
106
170
  }
107
171
 
108
- deps.console.log("[relay]", "broadcast", {
109
- ownerId,
110
- broadcastCount,
111
- totalSubscribers: sockets.size,
112
- });
172
+ log.relayOptionBroadcast(ownerId, broadcastCount, sockets.size);
113
173
  },
114
174
  };
115
175
 
116
176
  ws.on("message", (message) => {
117
177
  if (!Uint8Array.is(message)) return;
118
-
119
- deps.console.log("[relay]", "on message", {
120
- messageSize: message.length,
121
- });
178
+ log.messageLength(message.length);
122
179
 
123
180
  applyProtocolMessageAsRelay({ storage })(message, options)
124
181
  .then((response) => {
125
182
  if (!response.ok) {
126
- deps.console.error("[relay]", "protocol", response.error);
127
- deps.console.error(response.error);
183
+ log.applyProtocolMessageAsRelayError(response.error);
128
184
  return;
129
185
  }
130
-
131
186
  ws.send(response.value.message, { binary: true });
132
- deps.console.log("[relay]", "response", {
133
- responseSize: response.value.message.length,
134
- });
187
+ log.responseLength(response.value.message.length);
135
188
  })
136
- .catch((error: unknown) => {
137
- deps.console.error("[relay]", "applyProtocolMessageAsRelay", error);
138
- });
189
+ .catch(log.applyProtocolMessageAsRelayUnknownError);
139
190
  });
140
191
 
141
192
  ws.on("close", () => {
142
193
  ownerSocketsMap.deleteValue(ws);
143
- deps.console.log("[relay]", "close", {
144
- clientCount: wss.clients.size,
145
- });
194
+ log.connectionClosed(wss.clients.size);
146
195
  });
147
196
  });
148
197
 
149
- const disposeWss = () => {
150
- deps.console.log("Shutting down Evolu Relay...");
198
+ server.listen(port);
199
+
200
+ const dispose = () => {
201
+ log.shuttingDown();
151
202
 
152
203
  wss.clients.forEach((client) => {
153
204
  if (client.readyState === WebSocket.OPEN) {
@@ -156,18 +207,24 @@ export const createNodeJsRelay =
156
207
  });
157
208
 
158
209
  wss.close(() => {
159
- deps.console.log("Evolu Relay disposed");
210
+ log.webSocketServerDisposed();
211
+ });
212
+
213
+ server.close(() => {
214
+ log.httpServerDisposed();
160
215
  });
161
216
  };
162
217
 
163
218
  let isDisposed = false;
164
219
 
165
- return {
220
+ const relay: Relay = {
166
221
  [Symbol.dispose]: () => {
167
222
  if (isDisposed) return;
168
223
  isDisposed = true;
169
224
  sqlite[Symbol.dispose]();
170
- disposeWss();
225
+ dispose();
171
226
  },
172
227
  };
228
+
229
+ return ok(relay);
173
230
  };