@evolu/nodejs 2.4.0 → 3.0.0-next.1

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.
Files changed (41) hide show
  1. package/README.md +4 -2
  2. package/dist/src/Crypto.d.ts.map +1 -0
  3. package/dist/src/Sqlite.d.ts +3 -0
  4. package/dist/src/Sqlite.d.ts.map +1 -0
  5. package/dist/src/Sqlite.js +38 -0
  6. package/dist/src/Task.d.ts +45 -0
  7. package/dist/src/Task.d.ts.map +1 -0
  8. package/dist/src/Task.js +59 -0
  9. package/dist/src/WebSocket.d.ts +19 -0
  10. package/dist/src/WebSocket.d.ts.map +1 -0
  11. package/dist/src/WebSocket.js +46 -0
  12. package/dist/src/Worker.d.ts +2 -0
  13. package/dist/src/Worker.d.ts.map +1 -0
  14. package/dist/src/Worker.js +12 -0
  15. package/dist/src/index.d.ts +6 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/index.js +5 -0
  18. package/dist/src/local-first/Relay.d.ts +55 -0
  19. package/dist/src/local-first/Relay.d.ts.map +1 -0
  20. package/dist/src/local-first/Relay.js +270 -0
  21. package/package.json +21 -17
  22. package/src/Crypto.ts +1 -1
  23. package/src/Sqlite.ts +60 -0
  24. package/src/Task.ts +93 -0
  25. package/src/WebSocket.ts +69 -0
  26. package/src/Worker.ts +11 -0
  27. package/src/index.ts +4 -1
  28. package/src/local-first/Relay.ts +201 -137
  29. package/dist/BetterSqliteDriver.d.ts +0 -3
  30. package/dist/BetterSqliteDriver.d.ts.map +0 -1
  31. package/dist/BetterSqliteDriver.js +0 -31
  32. package/dist/Crypto.d.ts.map +0 -1
  33. package/dist/index.d.ts +0 -3
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.js +0 -2
  36. package/dist/local-first/Relay.d.ts +0 -22
  37. package/dist/local-first/Relay.d.ts.map +0 -1
  38. package/dist/local-first/Relay.js +0 -165
  39. package/src/BetterSqliteDriver.ts +0 -47
  40. /package/dist/{Crypto.d.ts → src/Crypto.d.ts} +0 -0
  41. /package/dist/{Crypto.js → src/Crypto.js} +0 -0
package/package.json CHANGED
@@ -1,50 +1,54 @@
1
1
  {
2
2
  "name": "@evolu/nodejs",
3
- "version": "2.4.0",
3
+ "version": "3.0.0-next.1",
4
4
  "description": "Evolu for Node.js",
5
5
  "author": "Daniel Steigerwald <daniel@steigerwald.cz>",
6
6
  "license": "MIT",
7
- "repository": "evoluhq/evolu",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/evoluhq/evolu.git"
10
+ },
8
11
  "bugs": {
9
12
  "url": "https://github.com/evoluhq/evolu/issues"
10
13
  },
11
14
  "homepage": "https://evolu.dev",
12
15
  "type": "module",
13
- "types": "./dist/index.d.ts",
16
+ "types": "./dist/src/index.d.ts",
14
17
  "exports": {
15
- ".": "./dist/index.js"
18
+ ".": {
19
+ "types": "./dist/src/index.d.ts",
20
+ "import": "./dist/src/index.js",
21
+ "default": "./dist/src/index.js"
22
+ }
16
23
  },
17
24
  "files": [
18
- "dist/**",
25
+ "dist/src/**",
19
26
  "src/**",
20
27
  "README.md"
21
28
  ],
22
29
  "dependencies": {
23
- "better-sqlite3": "^12.1.1",
24
- "ws": "^8.18.2"
30
+ "better-sqlite3": "^12.6.2",
31
+ "ws": "^8.19.0"
25
32
  },
26
33
  "devDependencies": {
27
34
  "@types/better-sqlite3": "^7.6.13",
28
- "@types/node": "^22.17.1",
35
+ "@types/node": "^24.10.9",
29
36
  "@types/ws": "^8.18.1",
30
- "typescript": "^5.9.2",
31
- "vitest": "^4.0.4",
32
- "@evolu/common": "7.4.0",
33
- "@evolu/tsconfig": "0.0.2"
37
+ "typescript": "^6",
38
+ "@evolu/common": "8.0.0-next.1",
39
+ "@evolu/typescript-config": "0.0.2"
34
40
  },
35
41
  "peerDependencies": {
36
- "@evolu/common": "^7.4.0"
42
+ "@evolu/common": "^8.0.0-next.0"
37
43
  },
38
44
  "engines": {
39
- "node": ">=22.0.0"
45
+ "node": ">=24.0.0"
40
46
  },
41
47
  "publishConfig": {
42
48
  "access": "public"
43
49
  },
44
50
  "scripts": {
45
- "dev": "tsc --watch",
46
- "build": "rimraf dist && tsc",
47
- "clean": "rimraf .turbo node_modules dist",
51
+ "build": "tsc --build tsconfig.build.json",
48
52
  "format": "prettier --write \"src/*.{ts,tsx,md}\""
49
53
  }
50
54
  }
package/src/Crypto.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { timingSafeEqual } from "node:crypto";
2
1
  import type { TimingSafeEqual } from "@evolu/common";
2
+ import { timingSafeEqual } from "node:crypto";
3
3
 
4
4
  export const createTimingSafeEqual = (): TimingSafeEqual => timingSafeEqual;
package/src/Sqlite.ts ADDED
@@ -0,0 +1,60 @@
1
+ import {
2
+ createPreparedStatementsCache,
3
+ lazyVoid,
4
+ ok,
5
+ type CreateSqliteDriver,
6
+ type SqliteDriver,
7
+ type SqliteRow,
8
+ } from "@evolu/common";
9
+ import BetterSQLite, { type Statement } from "better-sqlite3";
10
+
11
+ export const createBetterSqliteDriver: CreateSqliteDriver =
12
+ (name, options) => () => {
13
+ const filename = options?.mode === "memory" ? ":memory:" : `${name}.db`;
14
+ const stack = new globalThis.DisposableStack();
15
+ const db = stack.adopt(new BetterSQLite(filename), (db) => {
16
+ db.close();
17
+ });
18
+
19
+ const cache = stack.use(
20
+ createPreparedStatementsCache<Statement>(
21
+ (sql) => db.prepare(sql),
22
+ // Not needed.
23
+ // https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#class-statement
24
+ lazyVoid,
25
+ ),
26
+ );
27
+
28
+ const driver: SqliteDriver = {
29
+ exec: (query) => {
30
+ // Always prepare is recommended for better-sqlite3
31
+ const prepared = cache.get(query, true);
32
+
33
+ if (prepared.reader) {
34
+ const rows = prepared.all(query.parameters) as Array<SqliteRow>;
35
+ return { rows, changes: 0 };
36
+ }
37
+
38
+ const changes = prepared.run(query.parameters).changes;
39
+ return { rows: [], changes };
40
+ },
41
+
42
+ export: () => {
43
+ const file = db.serialize();
44
+ const { buffer } = file;
45
+
46
+ if (buffer instanceof ArrayBuffer) {
47
+ return new Uint8Array(buffer, file.byteOffset, file.byteLength);
48
+ }
49
+
50
+ // Ensure export uses transferable ArrayBuffer backing.
51
+ return new Uint8Array(file);
52
+ },
53
+
54
+ [Symbol.dispose]: () => {
55
+ stack.dispose();
56
+ },
57
+ };
58
+
59
+ return ok(driver);
60
+ };
package/src/Task.ts ADDED
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Node.js-specific Task utilities.
3
+ *
4
+ * @module
5
+ */
6
+
7
+ import {
8
+ createRun as createCommonRun,
9
+ createUnknownError,
10
+ type CreateRun,
11
+ type Run,
12
+ type RunDeps,
13
+ } from "@evolu/common";
14
+
15
+ /**
16
+ * A promise that resolves when a termination signal is received.
17
+ *
18
+ * Resolves on `SIGINT` (Ctrl-C), `SIGTERM` (OS/k8s/Docker termination),
19
+ * `SIGHUP` (console close/terminal disconnect), or `SIGBREAK` (Windows
20
+ * Ctrl-Break).
21
+ *
22
+ * @group Node.js Run
23
+ */
24
+ export type Shutdown = Promise<void>;
25
+
26
+ export interface ShutdownDep {
27
+ readonly shutdown: Shutdown;
28
+ }
29
+
30
+ /**
31
+ * Creates {@link Run} for Node.js with global error handling and graceful
32
+ * shutdown.
33
+ *
34
+ * Registers `uncaughtException` and `unhandledRejection` handlers that log
35
+ * errors and initiate graceful shutdown. Adds a `shutdown` promise to deps that
36
+ * resolves on termination signals (`SIGINT`, `SIGTERM`, `SIGHUP`). Handlers are
37
+ * removed when the Run is disposed.
38
+ *
39
+ * ### Example
40
+ *
41
+ * ```ts
42
+ * const deps = { ...createRelayDeps(), console };
43
+ *
44
+ * await using run = createRun(deps);
45
+ * await using stack = new AsyncDisposableStack();
46
+ *
47
+ * stack.use(await run.orThrow(startRelay({ port: 4000 })));
48
+ *
49
+ * await run.deps.shutdown;
50
+ * ```
51
+ *
52
+ * @group Node.js Run
53
+ */
54
+ export const createRun: CreateRun<RunDeps & ShutdownDep> = <D>(
55
+ deps?: D,
56
+ ): Run<RunDeps & ShutdownDep & D> => {
57
+ const { promise: shutdown, resolve: resolveShutdown } =
58
+ Promise.withResolvers<void>();
59
+
60
+ const run = createCommonRun({ ...deps, shutdown } as D & ShutdownDep);
61
+
62
+ const console = run.deps.console.child("global");
63
+
64
+ const handleError = (source: string) => (error: unknown) => {
65
+ console.error(source, createUnknownError(error));
66
+ process.exitCode = 1;
67
+
68
+ // Resolve shutdown so `await run.deps.shutdown` unblocks
69
+ // and allows the stack to be disposed.
70
+ resolveShutdown();
71
+ };
72
+
73
+ const handleUncaughtException = handleError("uncaughtException");
74
+ const handleUnhandledRejection = handleError("unhandledRejection");
75
+
76
+ process.on("uncaughtException", handleUncaughtException);
77
+ process.on("unhandledRejection", handleUnhandledRejection);
78
+ process.on("SIGINT", resolveShutdown); // Ctrl-C (all platforms)
79
+ process.on("SIGTERM", resolveShutdown); // OS/k8s/Docker termination (Unix)
80
+ process.on("SIGHUP", resolveShutdown); // Console close (Windows), terminal disconnect (Unix)
81
+ process.on("SIGBREAK", resolveShutdown); // Ctrl-Break (Windows)
82
+
83
+ run.onAbort(() => {
84
+ process.off("uncaughtException", handleUncaughtException);
85
+ process.off("unhandledRejection", handleUnhandledRejection);
86
+ process.off("SIGINT", resolveShutdown);
87
+ process.off("SIGTERM", resolveShutdown);
88
+ process.off("SIGHUP", resolveShutdown);
89
+ process.off("SIGBREAK", resolveShutdown);
90
+ });
91
+
92
+ return run;
93
+ };
@@ -0,0 +1,69 @@
1
+ import { request, type ClientRequest, type IncomingMessage } from "http";
2
+
3
+ /**
4
+ * A raw WebSocket upgrade request prepared for tests by
5
+ * {@link testSetupWebSocketUpgradeRequest}.
6
+ */
7
+ export interface TestSetupWebSocketUpgradeRequest extends AsyncDisposable {
8
+ readonly req: ClientRequest;
9
+ }
10
+
11
+ /**
12
+ * Creates a raw WebSocket upgrade request test setup for tests that need manual
13
+ * request control.
14
+ */
15
+ export const testSetupWebSocketUpgradeRequest = (
16
+ port: number,
17
+ path: string,
18
+ ): TestSetupWebSocketUpgradeRequest => {
19
+ const req = createWebSocketUpgradeRequest(port, path);
20
+ req.on("error", () => undefined);
21
+
22
+ return {
23
+ req,
24
+ [Symbol.asyncDispose]: () => {
25
+ req.destroy();
26
+ return Promise.resolve();
27
+ },
28
+ };
29
+ };
30
+
31
+ /**
32
+ * Sends a raw WebSocket upgrade request and resolves with the rejection
33
+ * response.
34
+ */
35
+ export const testSendWebSocketUpgradeRequest = async (
36
+ port: number,
37
+ path: string,
38
+ ): Promise<IncomingMessage> => {
39
+ const req = createWebSocketUpgradeRequest(port, path);
40
+
41
+ return new Promise((resolve, reject) => {
42
+ req.once("response", (response) => {
43
+ response.resume();
44
+ resolve(response);
45
+ });
46
+ req.once("upgrade", (_response, socket) => {
47
+ socket.destroy();
48
+ reject(new Error("Expected HTTP upgrade rejection"));
49
+ });
50
+ req.once("error", reject);
51
+ req.end();
52
+ });
53
+ };
54
+
55
+ const createWebSocketUpgradeRequest = (
56
+ port: number,
57
+ path: string,
58
+ ): ClientRequest =>
59
+ request({
60
+ host: "127.0.0.1",
61
+ port,
62
+ path,
63
+ headers: {
64
+ Connection: "Upgrade",
65
+ Upgrade: "websocket",
66
+ "Sec-WebSocket-Version": "13",
67
+ "Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
68
+ },
69
+ });
package/src/Worker.ts ADDED
@@ -0,0 +1,11 @@
1
+ // TODO: Implement Node.js Worker API
2
+ //
3
+ // This module should provide Node.js implementations of the common Worker API:
4
+ // - createWorker
5
+ // - createWorkerSelf (with onError hooking process.on('uncaughtException') and
6
+ // process.on('unhandledRejection'))
7
+ // - createMessageChannel
8
+ // - createMessagePort
9
+ //
10
+ // Node.js uses worker_threads module for Worker/MessageChannel/MessagePort.
11
+ // Error handling uses process events instead of globalThis.onerror.
package/src/index.ts CHANGED
@@ -1,2 +1,5 @@
1
- export * from "./BetterSqliteDriver.js";
1
+ export * from "./Sqlite.js";
2
+ export * from "./Crypto.js";
2
3
  export * from "./local-first/Relay.js";
4
+ export * from "./Task.js";
5
+ export * from "./WebSocket.js";