@evolu/nodejs 3.0.0-next.3 → 3.0.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/dist/src/Cli.d.ts +29 -0
- package/dist/src/Cli.d.ts.map +1 -0
- package/dist/src/Cli.js +49 -0
- package/dist/src/Platform.d.ts +15 -0
- package/dist/src/Platform.d.ts.map +1 -0
- package/dist/src/Platform.js +9 -0
- package/dist/src/Sqlite.js +2 -2
- package/dist/src/Task.d.ts +69 -24
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +148 -52
- package/dist/src/TestBundle.d.ts +113 -0
- package/dist/src/TestBundle.d.ts.map +1 -0
- package/dist/src/TestBundle.js +503 -0
- package/dist/src/Time.d.ts +28 -0
- package/dist/src/Time.d.ts.map +1 -0
- package/dist/src/Time.js +25 -0
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +7 -8
- package/dist/src/index.d.ts +13 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -1
- package/dist/src/local-first/Relay.d.ts +13 -20
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +36 -43
- package/package.json +30 -6
- package/src/Cli.ts +78 -0
- package/src/Platform.ts +20 -0
- package/src/Sqlite.ts +2 -2
- package/src/Task.ts +172 -63
- package/src/TestBundle.ts +674 -0
- package/src/Time.ts +55 -0
- package/src/Worker.ts +22 -20
- package/src/index.ts +14 -6
- package/src/local-first/Relay.ts +48 -53
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort-aware CLI process utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { type Task, type Typed } from "@evolu/common";
|
|
7
|
+
/** Runs a command with inherited stdio. */
|
|
8
|
+
export type Spawn = (file: string, args: ReadonlyArray<string>, options?: {
|
|
9
|
+
readonly cwd?: string | URL;
|
|
10
|
+
}) => Task<void, SpawnError>;
|
|
11
|
+
/** Failure to start a command or an unsuccessful command exit. */
|
|
12
|
+
export interface SpawnError extends Typed<"SpawnError"> {
|
|
13
|
+
readonly command: string;
|
|
14
|
+
readonly exitCode: number | null;
|
|
15
|
+
readonly signal: NodeJS.Signals | null;
|
|
16
|
+
readonly message: string;
|
|
17
|
+
}
|
|
18
|
+
/** Dependency wrapper for {@link spawn}. */
|
|
19
|
+
export interface SpawnDep {
|
|
20
|
+
readonly spawn: Spawn;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Runs a command with inherited stdio and aborts it with the current Run.
|
|
24
|
+
*
|
|
25
|
+
* A zero exit code succeeds. A start failure, non-zero exit code, or signal
|
|
26
|
+
* exit returns {@link SpawnError}.
|
|
27
|
+
*/
|
|
28
|
+
export declare const spawn: Spawn;
|
|
29
|
+
//# sourceMappingURL=Cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Cli.d.ts","sourceRoot":"","sources":["../../src/Cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAqB,KAAK,IAAI,EAAE,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAGzE,2CAA2C;AAC3C,MAAM,MAAM,KAAK,GAAG,CAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;CAC7B,KACE,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAE5B,kEAAkE;AAClE,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,YAAY,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,4CAA4C;AAC5C,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,KAwCnB,CAAC"}
|
package/dist/src/Cli.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort-aware CLI process utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { callback, err, ok } from "@evolu/common";
|
|
7
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
8
|
+
/**
|
|
9
|
+
* Runs a command with inherited stdio and aborts it with the current Run.
|
|
10
|
+
*
|
|
11
|
+
* A zero exit code succeeds. A start failure, non-zero exit code, or signal
|
|
12
|
+
* exit returns {@link SpawnError}.
|
|
13
|
+
*/
|
|
14
|
+
export const spawn = (file, args, { cwd } = {}) => {
|
|
15
|
+
const command = [file, ...args].join(" ");
|
|
16
|
+
return callback(({ run, resolve }) => {
|
|
17
|
+
const child = nodeSpawn(file, args, {
|
|
18
|
+
cwd,
|
|
19
|
+
signal: run.signal,
|
|
20
|
+
stdio: "inherit",
|
|
21
|
+
});
|
|
22
|
+
child.once("error", (error) => {
|
|
23
|
+
if (run.signal.aborted)
|
|
24
|
+
return;
|
|
25
|
+
resolve(err({
|
|
26
|
+
type: "SpawnError",
|
|
27
|
+
command,
|
|
28
|
+
exitCode: null,
|
|
29
|
+
signal: null,
|
|
30
|
+
message: `Failed to start ${command}: ${error.message}`,
|
|
31
|
+
}));
|
|
32
|
+
});
|
|
33
|
+
child.once("close", (exitCode, signal) => {
|
|
34
|
+
if (run.signal.aborted)
|
|
35
|
+
return;
|
|
36
|
+
resolve(exitCode === 0
|
|
37
|
+
? ok()
|
|
38
|
+
: err({
|
|
39
|
+
type: "SpawnError",
|
|
40
|
+
command,
|
|
41
|
+
exitCode,
|
|
42
|
+
signal,
|
|
43
|
+
message: signal == null
|
|
44
|
+
? `${command} exited with code ${exitCode}.`
|
|
45
|
+
: `${command} exited from ${signal}.`,
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js platform utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { PositiveInt } from "@evolu/common";
|
|
7
|
+
/** Returns the recommended amount of parallelism available to this process. */
|
|
8
|
+
export type AvailableParallelism = () => PositiveInt;
|
|
9
|
+
/** Dependency wrapper for {@link availableParallelism}. */
|
|
10
|
+
export interface AvailableParallelismDep {
|
|
11
|
+
readonly availableParallelism: AvailableParallelism;
|
|
12
|
+
}
|
|
13
|
+
/** Returns the recommended amount of parallelism available to this process. */
|
|
14
|
+
export declare const availableParallelism: AvailableParallelism;
|
|
15
|
+
//# sourceMappingURL=Platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Platform.d.ts","sourceRoot":"","sources":["../../src/Platform.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,MAAM,WAAW,CAAC;AAErD,2DAA2D;AAC3D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;CACrD;AAED,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,EAAE,oBACc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js platform utilities.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { PositiveInt } from "@evolu/common";
|
|
7
|
+
import { availableParallelism as nodeAvailableParallelism } from "node:os";
|
|
8
|
+
/** Returns the recommended amount of parallelism available to this process. */
|
|
9
|
+
export const availableParallelism = () => PositiveInt.orThrow(nodeAvailableParallelism());
|
package/dist/src/Sqlite.js
CHANGED
|
@@ -50,7 +50,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import {
|
|
53
|
+
import { constVoid, createPreparedStatementsCache, ok, } from "@evolu/common";
|
|
54
54
|
import BetterSQLite, {} from "better-sqlite3";
|
|
55
55
|
import { rmSync } from "fs";
|
|
56
56
|
export const createBetterSqliteDriver = (name, options) => () => {
|
|
@@ -72,7 +72,7 @@ export const createBetterSqliteDriver = (name, options) => () => {
|
|
|
72
72
|
const cache = disposer.use(createPreparedStatementsCache((sql) => db.prepare(sql),
|
|
73
73
|
// Not needed.
|
|
74
74
|
// https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#class-statement
|
|
75
|
-
|
|
75
|
+
constVoid));
|
|
76
76
|
const disposables = disposer.move();
|
|
77
77
|
return ok({
|
|
78
78
|
exec: (query) => {
|
package/dist/src/Task.d.ts
CHANGED
|
@@ -3,43 +3,88 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import { type
|
|
6
|
+
import { type Resource, type RunCustomDeps, type Task, type Typed } from "@evolu/common";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* An abort requested by a Node.js termination signal.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* `SIGHUP` (console close/terminal disconnect), or `SIGBREAK` (Windows
|
|
12
|
-
* Ctrl-Break).
|
|
13
|
-
*
|
|
14
|
-
* @group Node.js Run
|
|
10
|
+
* @group Node.js Task
|
|
15
11
|
*/
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
export interface NodeSignalAbortReason extends Typed<"NodeSignalAbortReason"> {
|
|
13
|
+
readonly signal: NodeSignal;
|
|
14
|
+
}
|
|
15
|
+
/** A Node.js termination signal handled by {@link runMain}. */
|
|
16
|
+
export type NodeSignal = "SIGINT" | "SIGTERM" | "SIGBREAK";
|
|
17
|
+
/** Process lifecycle behavior for {@link runMain}. */
|
|
18
|
+
export type RunMainMode = "service" | "command";
|
|
19
|
+
/** Options for {@link runMain}. */
|
|
20
|
+
export interface RunMainOptions {
|
|
21
|
+
/**
|
|
22
|
+
* How termination signals affect the process exit status.
|
|
23
|
+
*
|
|
24
|
+
* Services treat a gracefully handled signal as a successful shutdown.
|
|
25
|
+
* Commands use the conventional `128 + signal number` exit status unless a
|
|
26
|
+
* reported defect has already set a failure status.
|
|
27
|
+
*
|
|
28
|
+
* @default "service"
|
|
29
|
+
*/
|
|
30
|
+
readonly mode?: RunMainMode;
|
|
19
31
|
}
|
|
20
32
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
33
|
+
* Runs the main Task as the Node.js program lifecycle.
|
|
34
|
+
*
|
|
35
|
+
* Creates one root {@link Run} and aborts it on:
|
|
36
|
+
*
|
|
37
|
+
* - `SIGINT`: Ctrl-C on all platforms.
|
|
38
|
+
* - `SIGTERM`: OS, service, Docker, or Kubernetes termination on Unix.
|
|
39
|
+
* - `SIGBREAK`: Ctrl-Break on Windows.
|
|
40
|
+
*
|
|
41
|
+
* The first signal logs shutdown progress, aborts the root Run, and waits for
|
|
42
|
+
* the main Task and structured cleanup to finish. A subsequent signal exits
|
|
43
|
+
* immediately with its conventional signal status, abandoning cleanup. A signal
|
|
44
|
+
* received during final cleanup still applies signal shutdown behavior.
|
|
45
|
+
*
|
|
46
|
+
* A main Task returning {@link Resource} keeps the program running until a
|
|
47
|
+
* termination signal and is disposed during shutdown. A main Task returning
|
|
48
|
+
* `void` completes the program immediately. A Resource result transfers
|
|
49
|
+
* ownership of a live resource that must remain valid after its creating Task
|
|
50
|
+
* settles.
|
|
51
|
+
*
|
|
52
|
+
* Service mode treats graceful signal shutdown as successful. Command mode
|
|
53
|
+
* preserves conventional signal exit statuses. Every defect reported through
|
|
54
|
+
* `reportDefect`, including an observer defect that does not abort the Run,
|
|
55
|
+
* sets `process.exitCode` to 1. The default reporter logs to the configured
|
|
56
|
+
* Evolu console.
|
|
23
57
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* resolves on termination signals (`SIGINT`, `SIGTERM`, `SIGHUP`). Handlers are
|
|
27
|
-
* removed when the Run is disposed.
|
|
58
|
+
* Escaped uncaught exceptions and unhandled rejections remain under Node.js
|
|
59
|
+
* native reporting and termination.
|
|
28
60
|
*
|
|
29
|
-
* ### Example
|
|
61
|
+
* ### Service Example
|
|
30
62
|
*
|
|
31
63
|
* ```ts
|
|
32
|
-
* const deps = { ...createRelayDeps(), console };
|
|
64
|
+
* const deps = { ...createRelayDeps(), console: createConsole() };
|
|
33
65
|
*
|
|
34
|
-
* await
|
|
35
|
-
*
|
|
66
|
+
* await runMain(deps)(createRelay({ port: 4000 }));
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* A Task returning `void` can keep a service alive explicitly when no Resource
|
|
70
|
+
* owns its lifetime:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* await runMain(deps)(async (run) => {
|
|
74
|
+
* void run(processMessages);
|
|
75
|
+
* return await run(waitForAbort);
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
36
78
|
*
|
|
37
|
-
*
|
|
79
|
+
* ### Command Example
|
|
38
80
|
*
|
|
39
|
-
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* await runMain(command, { mode: "command" });
|
|
40
83
|
* ```
|
|
41
84
|
*
|
|
42
|
-
* @group Node.js
|
|
85
|
+
* @group Node.js Task
|
|
43
86
|
*/
|
|
44
|
-
export declare
|
|
87
|
+
export declare function runMain<T extends void | Resource>(main: Task<T>, options?: RunMainOptions): Promise<void>;
|
|
88
|
+
/** With custom dependencies. */
|
|
89
|
+
export declare function runMain<D extends object>(deps: RunCustomDeps<D>, options?: RunMainOptions): <T extends void | Resource>(main: Task<T, never, D>) => Promise<void>;
|
|
45
90
|
//# sourceMappingURL=Task.d.ts.map
|
package/dist/src/Task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAQL,KAAK,QAAQ,EAEb,KAAK,aAAa,EAClB,KAAK,IAAI,EACT,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAEvB;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,KAAK,CAAC,uBAAuB,CAAC;IAC3E,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B;AAED,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAE3D,sDAAsD;AACtD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhD,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,IAAI,GAAG,QAAQ,EAC/C,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EACb,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAAC;AACjB,gCAAgC;AAChC,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EACtC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,EACtB,OAAO,CAAC,EAAE,cAAc,GACvB,CAAC,CAAC,SAAS,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
package/dist/src/Task.js
CHANGED
|
@@ -3,57 +3,153 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
6
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
7
|
+
if (value !== null && value !== void 0) {
|
|
8
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
9
|
+
var dispose, inner;
|
|
10
|
+
if (async) {
|
|
11
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.asyncDispose];
|
|
13
|
+
}
|
|
14
|
+
if (dispose === void 0) {
|
|
15
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
16
|
+
dispose = value[Symbol.dispose];
|
|
17
|
+
if (async) inner = dispose;
|
|
18
|
+
}
|
|
19
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
20
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
21
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
22
|
+
}
|
|
23
|
+
else if (async) {
|
|
24
|
+
env.stack.push({ async: true });
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
29
|
+
return function (env) {
|
|
30
|
+
function fail(e) {
|
|
31
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
32
|
+
env.hasError = true;
|
|
33
|
+
}
|
|
34
|
+
var r, s = 0;
|
|
35
|
+
function next() {
|
|
36
|
+
while (r = env.stack.pop()) {
|
|
37
|
+
try {
|
|
38
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
39
|
+
if (r.dispose) {
|
|
40
|
+
var result = r.dispose.call(r.value);
|
|
41
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
42
|
+
}
|
|
43
|
+
else s |= 1;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
fail(e);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
50
|
+
if (env.hasError) throw env.error;
|
|
51
|
+
}
|
|
52
|
+
return next();
|
|
41
53
|
};
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
55
|
+
var e = new Error(message);
|
|
56
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
57
|
+
});
|
|
58
|
+
import { createConsole, createRun, isDisposable, ok, waitForAbort, } from "@evolu/common";
|
|
59
|
+
export function runMain(mainOrDeps, { mode = "service" } = {}) {
|
|
60
|
+
return typeof mainOrDeps === "function"
|
|
61
|
+
? runMainInternal(mainOrDeps, {}, mode)
|
|
62
|
+
: (main) => runMainInternal(main, mainOrDeps, mode);
|
|
63
|
+
}
|
|
64
|
+
const commandExitCodeBySignal = {
|
|
65
|
+
SIGINT: 130,
|
|
66
|
+
SIGTERM: 143,
|
|
67
|
+
SIGBREAK: 149,
|
|
68
|
+
};
|
|
69
|
+
const runMainInternal = async (main, deps, mode) => {
|
|
70
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
71
|
+
try {
|
|
72
|
+
const console = deps.console ?? createConsole();
|
|
73
|
+
const mainConsole = console.child("main");
|
|
74
|
+
let defectReported = false;
|
|
75
|
+
let receivedSignal = null;
|
|
76
|
+
const disposer = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
|
|
77
|
+
const run = disposer.use(createRun({
|
|
78
|
+
...deps,
|
|
79
|
+
console,
|
|
80
|
+
reportDefect: (reported) => {
|
|
81
|
+
defectReported = true;
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
if (deps.reportDefect)
|
|
84
|
+
deps.reportDefect(reported);
|
|
85
|
+
else
|
|
86
|
+
console.error(reported);
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
89
|
+
["SIGINT", "SIGTERM", "SIGBREAK"].forEach((signal) => {
|
|
90
|
+
const handleSignal = () => {
|
|
91
|
+
if (receivedSignal !== null) {
|
|
92
|
+
mainConsole.warn("Forcing shutdown...");
|
|
93
|
+
process.exit(commandExitCodeBySignal[signal]);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
receivedSignal = signal;
|
|
97
|
+
mainConsole.info("Shutting down...");
|
|
98
|
+
run.abort({ type: "NodeSignalAbortReason", signal });
|
|
99
|
+
};
|
|
100
|
+
process.on(signal, handleSignal);
|
|
101
|
+
run.defer(() => {
|
|
102
|
+
process.off(signal, handleSignal);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
await run(async (run) => {
|
|
107
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
108
|
+
try {
|
|
109
|
+
const resource = await run.ok(main);
|
|
110
|
+
if (!isDisposable(resource))
|
|
111
|
+
return ok();
|
|
112
|
+
const _resource = __addDisposableResource(env_2, resource, true);
|
|
113
|
+
return await run(waitForAbort);
|
|
114
|
+
}
|
|
115
|
+
catch (e_2) {
|
|
116
|
+
env_2.error = e_2;
|
|
117
|
+
env_2.hasError = true;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
const result_2 = __disposeResources(env_2);
|
|
121
|
+
if (result_2)
|
|
122
|
+
await result_2;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Aborts are control flow; defects are already handled by reportDefect.
|
|
128
|
+
}
|
|
129
|
+
// Move ownership out of the await-using setup safety net so an already
|
|
130
|
+
// reported finalizer defect can be suppressed during explicit disposal.
|
|
131
|
+
try {
|
|
132
|
+
await disposer.move().disposeAsync();
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Finalizer defects are already handled by reportDefect.
|
|
136
|
+
}
|
|
137
|
+
if (receivedSignal !== null) {
|
|
138
|
+
if (defectReported)
|
|
139
|
+
mainConsole.warn("Shutdown finished with errors");
|
|
140
|
+
else
|
|
141
|
+
mainConsole.info("Shutdown complete");
|
|
142
|
+
if (mode === "command")
|
|
143
|
+
process.exitCode ??= commandExitCodeBySignal[receivedSignal];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e_1) {
|
|
147
|
+
env_1.error = e_1;
|
|
148
|
+
env_1.hasError = true;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
const result_1 = __disposeResources(env_1);
|
|
152
|
+
if (result_1)
|
|
153
|
+
await result_1;
|
|
154
|
+
}
|
|
59
155
|
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js utilities for testing production bundles.
|
|
3
|
+
*
|
|
4
|
+
* These utilities use the dedicated `@evolu/nodejs/TestBundle` entry point so
|
|
5
|
+
* normal `@evolu/nodejs` imports do not evaluate the test toolchain, while
|
|
6
|
+
* bundle tests do not evaluate unrelated Evolu Node.js adapters.
|
|
7
|
+
*
|
|
8
|
+
* The {@link testBundle} function loads its optional Webpack and Vite peer
|
|
9
|
+
* dependencies only when called.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { type PositiveDuration, type ReadonlyRecord } from "@evolu/common";
|
|
14
|
+
/** The measured sizes of one bundle. */
|
|
15
|
+
export interface TestBundleSize {
|
|
16
|
+
readonly rawSizeInBytes: number;
|
|
17
|
+
/** The Brotli-compressed size using quality 11. */
|
|
18
|
+
readonly brotliSizeInBytes: number;
|
|
19
|
+
}
|
|
20
|
+
/** Bundle sizes keyed by bundler name and version. */
|
|
21
|
+
export type TestBundleCaseResult = ReadonlyRecord<string, TestBundleSize>;
|
|
22
|
+
/** Case results keyed by case name for size snapshots. */
|
|
23
|
+
export type TestBundleResult = ReadonlyRecord<string, TestBundleCaseResult>;
|
|
24
|
+
/** The measured output supplied to {@link TestBundleCase.verify}. */
|
|
25
|
+
export interface TestBundle extends TestBundleSize {
|
|
26
|
+
/** The bundler name and version, for example `"vite@8.1.5"`. */
|
|
27
|
+
readonly bundler: string;
|
|
28
|
+
readonly code: string;
|
|
29
|
+
/** The copied artifact, or `null` when no output directory was requested. */
|
|
30
|
+
readonly outputPath: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** One named bundle test case. */
|
|
33
|
+
export interface TestBundleCase {
|
|
34
|
+
/**
|
|
35
|
+
* A JavaScript or erasable-TypeScript entry.
|
|
36
|
+
*
|
|
37
|
+
* TypeScript dependencies are supported. TSX and TypeScript syntax that
|
|
38
|
+
* requires JavaScript generation are not supported by Webpack.
|
|
39
|
+
*/
|
|
40
|
+
readonly entryPath: string;
|
|
41
|
+
/** Verifies the default export returned by each emitted bundle. */
|
|
42
|
+
readonly verify: (value: unknown, bundle: TestBundle) => void | Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/** Options for producing and verifying equivalent bundles. */
|
|
45
|
+
export interface TestBundleOptions {
|
|
46
|
+
/** Named cases bundled with every supported bundler. */
|
|
47
|
+
readonly cases: ReadonlyRecord<string, TestBundleCase>;
|
|
48
|
+
/**
|
|
49
|
+
* Package aliases resolved for every case and bundler. Targets must be
|
|
50
|
+
* absolute paths to modules using JavaScript syntax.
|
|
51
|
+
*/
|
|
52
|
+
readonly aliases?: ReadonlyRecord<string, string>;
|
|
53
|
+
/**
|
|
54
|
+
* Copies emitted bundles into this directory for later inspection. Relative
|
|
55
|
+
* paths are resolved from the current working directory.
|
|
56
|
+
*/
|
|
57
|
+
readonly outputDirectory?: string;
|
|
58
|
+
/** Maximum duration for producing each bundle. Defaults to `"30s"`. */
|
|
59
|
+
readonly bundlingTimeout?: PositiveDuration;
|
|
60
|
+
/** Maximum duration for executing each emitted bundle. Defaults to `"5s"`. */
|
|
61
|
+
readonly timeout?: PositiveDuration;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Bundles, executes, measures, and verifies named cases with every supported
|
|
65
|
+
* bundler.
|
|
66
|
+
*
|
|
67
|
+
* The fixture must default-export either a value, a promise, or a function that
|
|
68
|
+
* returns one. The resolved value must be structured-cloneable so it can cross
|
|
69
|
+
* the worker boundary. All asynchronous work started by the fixture must be
|
|
70
|
+
* awaited by that returned promise; detached work scheduled after it settles is
|
|
71
|
+
* outside the execution contract. Verification runs outside the measured
|
|
72
|
+
* bundle. Each emitted bundle runs in an isolated worker so evaluation errors,
|
|
73
|
+
* rejected promises, uncaught errors and unhandled rejections observed before
|
|
74
|
+
* completion, and timeouts fail the test without affecting the test process.
|
|
75
|
+
* Cases and bundlers form one flattened job list that runs concurrently,
|
|
76
|
+
* bounded by the CPU parallelism available to the process. If multiple jobs
|
|
77
|
+
* fail, all failures are reported together with their case and bundler names.
|
|
78
|
+
* The returned record contains only raw and Brotli byte counts grouped by case
|
|
79
|
+
* and versioned bundler name, so it can be compared directly with an inline
|
|
80
|
+
* snapshot.
|
|
81
|
+
*
|
|
82
|
+
* ### Example
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
86
|
+
* import { tmpdir } from "node:os";
|
|
87
|
+
* import { join } from "node:path";
|
|
88
|
+
* import { testBundle } from "@evolu/nodejs/TestBundle";
|
|
89
|
+
*
|
|
90
|
+
* const directory = await mkdtemp(join(tmpdir(), "evolu-test-bundle-"));
|
|
91
|
+
* try {
|
|
92
|
+
* const entryPath = join(directory, "entry.ts");
|
|
93
|
+
* await writeFile(entryPath, "export default { answer: 42 };\n");
|
|
94
|
+
* const result = await testBundle({
|
|
95
|
+
* cases: {
|
|
96
|
+
* example: {
|
|
97
|
+
* entryPath,
|
|
98
|
+
* verify: (value) => {
|
|
99
|
+
* expect(value).toEqual({ answer: 42 });
|
|
100
|
+
* },
|
|
101
|
+
* },
|
|
102
|
+
* },
|
|
103
|
+
* });
|
|
104
|
+
* for (const size of Object.values(result.example)) {
|
|
105
|
+
* expect(size.brotliSizeInBytes).toBeGreaterThan(0);
|
|
106
|
+
* }
|
|
107
|
+
* } finally {
|
|
108
|
+
* await rm(directory, { recursive: true });
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare const testBundle: ({ cases, aliases, outputDirectory, bundlingTimeout, timeout: executionTimeout, }: TestBundleOptions) => Promise<TestBundleResult>;
|
|
113
|
+
//# sourceMappingURL=TestBundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestBundle.d.ts","sourceRoot":"","sources":["../../src/TestBundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAYL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EAQpB,MAAM,eAAe,CAAC;AA4CvB,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,mDAAmD;IACnD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAED,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE1E,0DAA0D;AAC1D,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAE5E,qEAAqE;AACrE,MAAM,WAAW,UAAW,SAAQ,cAAc;IAChD,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACvD;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,uEAAuE;IACvE,QAAQ,CAAC,eAAe,CAAC,EAAE,gBAAgB,CAAC;IAC5C,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,UAAU,qFAMpB,iBAAiB,KAAG,OAAO,CAAC,gBAAgB,CAgK9C,CAAC"}
|