@evolu/nodejs 3.0.0-next.3 → 3.0.0

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.
@@ -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"}
@@ -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());
@@ -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 { createPreparedStatementsCache, lazyVoid, ok, } from "@evolu/common";
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
- lazyVoid));
75
+ constVoid));
76
76
  const disposables = disposer.move();
77
77
  return ok({
78
78
  exec: (query) => {
@@ -3,43 +3,88 @@
3
3
  *
4
4
  * @module
5
5
  */
6
- import { type CreateRun, type RunDefaultDeps } from "@evolu/common";
6
+ import { type Resource, type RunCustomDeps, type Task, type Typed } from "@evolu/common";
7
7
  /**
8
- * A promise that resolves when a termination signal is received.
8
+ * An abort requested by a Node.js termination signal.
9
9
  *
10
- * Resolves on `SIGINT` (Ctrl-C), `SIGTERM` (OS/k8s/Docker termination),
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 type Shutdown = Promise<void>;
17
- export interface ShutdownDep {
18
- readonly shutdown: Shutdown;
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
- * Creates {@link Run} for Node.js with global error handling and graceful
22
- * shutdown.
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
- * Registers `uncaughtException` and `unhandledRejection` handlers that log
25
- * errors and initiate graceful shutdown. Adds a `shutdown` promise to deps that
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 using run = createRun(deps);
35
- * await using disposer = new AsyncDisposableStack();
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
- * disposer.use(await run.orThrow(startRelay({ port: 4000 })));
79
+ * ### Command Example
38
80
  *
39
- * await run.deps.shutdown;
81
+ * ```ts
82
+ * await runMain(command, { mode: "command" });
40
83
  * ```
41
84
  *
42
- * @group Node.js Run
85
+ * @group Node.js Task
43
86
  */
44
- export declare const createRun: CreateRun<RunDefaultDeps & ShutdownDep>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;GAQG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,SAAS,EAAE,SAAS,CAAC,cAAc,GAAG,WAAW,CAuC7D,CAAC"}
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
- import { createRun as createCommonRun, createUnknownError, } from "@evolu/common";
7
- /**
8
- * Creates {@link Run} for Node.js with global error handling and graceful
9
- * shutdown.
10
- *
11
- * Registers `uncaughtException` and `unhandledRejection` handlers that log
12
- * errors and initiate graceful shutdown. Adds a `shutdown` promise to deps that
13
- * resolves on termination signals (`SIGINT`, `SIGTERM`, `SIGHUP`). Handlers are
14
- * removed when the Run is disposed.
15
- *
16
- * ### Example
17
- *
18
- * ```ts
19
- * const deps = { ...createRelayDeps(), console };
20
- *
21
- * await using run = createRun(deps);
22
- * await using disposer = new AsyncDisposableStack();
23
- *
24
- * disposer.use(await run.orThrow(startRelay({ port: 4000 })));
25
- *
26
- * await run.deps.shutdown;
27
- * ```
28
- *
29
- * @group Node.js Run
30
- */
31
- export const createRun = (deps) => {
32
- const { promise: shutdown, resolve: resolveShutdown } = Promise.withResolvers();
33
- const run = createCommonRun({ ...deps, shutdown });
34
- const console = run.deps.console.child("global");
35
- const handleError = (source) => (error) => {
36
- console.error(source, createUnknownError(error));
37
- process.exitCode = 1;
38
- // Resolve shutdown so `await run.deps.shutdown` unblocks
39
- // and allows the stack to be disposed.
40
- resolveShutdown();
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
- const handleUncaughtException = handleError("uncaughtException");
43
- const handleUnhandledRejection = handleError("unhandledRejection");
44
- process.on("uncaughtException", handleUncaughtException);
45
- process.on("unhandledRejection", handleUnhandledRejection);
46
- process.on("SIGINT", resolveShutdown); // Ctrl-C (all platforms)
47
- process.on("SIGTERM", resolveShutdown); // OS/k8s/Docker termination (Unix)
48
- process.on("SIGHUP", resolveShutdown); // Console close (Windows), terminal disconnect (Unix)
49
- process.on("SIGBREAK", resolveShutdown); // Ctrl-Break (Windows)
50
- run.onAbort(() => {
51
- process.off("uncaughtException", handleUncaughtException);
52
- process.off("unhandledRejection", handleUnhandledRejection);
53
- process.off("SIGINT", resolveShutdown);
54
- process.off("SIGTERM", resolveShutdown);
55
- process.off("SIGHUP", resolveShutdown);
56
- process.off("SIGBREAK", resolveShutdown);
57
- });
58
- return run;
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"}