@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.
- package/README.md +4 -2
- package/dist/src/Crypto.d.ts.map +1 -0
- package/dist/src/Sqlite.d.ts +3 -0
- package/dist/src/Sqlite.d.ts.map +1 -0
- package/dist/src/Sqlite.js +38 -0
- package/dist/src/Task.d.ts +45 -0
- package/dist/src/Task.d.ts.map +1 -0
- package/dist/src/Task.js +59 -0
- package/dist/src/WebSocket.d.ts +19 -0
- package/dist/src/WebSocket.d.ts.map +1 -0
- package/dist/src/WebSocket.js +46 -0
- package/dist/src/Worker.d.ts +2 -0
- package/dist/src/Worker.d.ts.map +1 -0
- package/dist/src/Worker.js +12 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +5 -0
- package/dist/src/local-first/Relay.d.ts +55 -0
- package/dist/src/local-first/Relay.d.ts.map +1 -0
- package/dist/src/local-first/Relay.js +270 -0
- package/package.json +21 -17
- package/src/Crypto.ts +1 -1
- package/src/Sqlite.ts +60 -0
- package/src/Task.ts +93 -0
- package/src/WebSocket.ts +69 -0
- package/src/Worker.ts +11 -0
- package/src/index.ts +4 -1
- package/src/local-first/Relay.ts +201 -137
- package/dist/BetterSqliteDriver.d.ts +0 -3
- package/dist/BetterSqliteDriver.d.ts.map +0 -1
- package/dist/BetterSqliteDriver.js +0 -31
- package/dist/Crypto.d.ts.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/local-first/Relay.d.ts +0 -22
- package/dist/local-first/Relay.d.ts.map +0 -1
- package/dist/local-first/Relay.js +0 -165
- package/src/BetterSqliteDriver.ts +0 -47
- /package/dist/{Crypto.d.ts → src/Crypto.d.ts} +0 -0
- /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": "
|
|
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":
|
|
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
|
-
".":
|
|
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.
|
|
24
|
-
"ws": "^8.
|
|
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": "^
|
|
35
|
+
"@types/node": "^24.10.9",
|
|
29
36
|
"@types/ws": "^8.18.1",
|
|
30
|
-
"typescript": "^
|
|
31
|
-
"
|
|
32
|
-
"@evolu/
|
|
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": "^
|
|
42
|
+
"@evolu/common": "^8.0.0-next.0"
|
|
37
43
|
},
|
|
38
44
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
45
|
+
"node": ">=24.0.0"
|
|
40
46
|
},
|
|
41
47
|
"publishConfig": {
|
|
42
48
|
"access": "public"
|
|
43
49
|
},
|
|
44
50
|
"scripts": {
|
|
45
|
-
"
|
|
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
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
|
+
};
|
package/src/WebSocket.ts
ADDED
|
@@ -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