@evolu/nodejs 3.0.0-next.2 → 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.
- 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.d.ts.map +1 -1
- package/dist/src/Sqlite.js +125 -35
- 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 +3 -1
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +93 -1
- package/dist/src/index.d.ts +13 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +9 -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 +42 -48
- package/package.json +30 -6
- package/src/Cli.ts +78 -0
- package/src/Platform.ts +20 -0
- package/src/Sqlite.ts +30 -11
- package/src/Task.ts +172 -63
- package/src/TestBundle.ts +674 -0
- package/src/Time.ts +55 -0
- package/src/Worker.ts +44 -0
- package/src/index.ts +14 -5
- package/src/local-first/Relay.ts +54 -58
package/src/Task.ts
CHANGED
|
@@ -5,89 +5,198 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
createConsole,
|
|
9
|
+
createRun,
|
|
10
|
+
isDisposable,
|
|
11
|
+
ok,
|
|
12
|
+
waitForAbort,
|
|
13
|
+
type ConsoleDep,
|
|
14
|
+
type ReportDefectDep,
|
|
15
|
+
type Resource,
|
|
11
16
|
type Run,
|
|
12
|
-
type
|
|
17
|
+
type RunCustomDeps,
|
|
18
|
+
type Task,
|
|
19
|
+
type Typed,
|
|
13
20
|
} from "@evolu/common";
|
|
14
21
|
|
|
15
22
|
/**
|
|
16
|
-
*
|
|
23
|
+
* An abort requested by a Node.js termination signal.
|
|
17
24
|
*
|
|
18
|
-
*
|
|
19
|
-
* `SIGHUP` (console close/terminal disconnect), or `SIGBREAK` (Windows
|
|
20
|
-
* Ctrl-Break).
|
|
21
|
-
*
|
|
22
|
-
* @group Node.js Run
|
|
25
|
+
* @group Node.js Task
|
|
23
26
|
*/
|
|
24
|
-
export
|
|
27
|
+
export interface NodeSignalAbortReason extends Typed<"NodeSignalAbortReason"> {
|
|
28
|
+
readonly signal: NodeSignal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A Node.js termination signal handled by {@link runMain}. */
|
|
32
|
+
export type NodeSignal = "SIGINT" | "SIGTERM" | "SIGBREAK";
|
|
33
|
+
|
|
34
|
+
/** Process lifecycle behavior for {@link runMain}. */
|
|
35
|
+
export type RunMainMode = "service" | "command";
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
/** Options for {@link runMain}. */
|
|
38
|
+
export interface RunMainOptions {
|
|
39
|
+
/**
|
|
40
|
+
* How termination signals affect the process exit status.
|
|
41
|
+
*
|
|
42
|
+
* Services treat a gracefully handled signal as a successful shutdown.
|
|
43
|
+
* Commands use the conventional `128 + signal number` exit status unless a
|
|
44
|
+
* reported defect has already set a failure status.
|
|
45
|
+
*
|
|
46
|
+
* @default "service"
|
|
47
|
+
*/
|
|
48
|
+
readonly mode?: RunMainMode;
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
52
|
+
* Runs the main Task as the Node.js program lifecycle.
|
|
53
|
+
*
|
|
54
|
+
* Creates one root {@link Run} and aborts it on:
|
|
55
|
+
*
|
|
56
|
+
* - `SIGINT`: Ctrl-C on all platforms.
|
|
57
|
+
* - `SIGTERM`: OS, service, Docker, or Kubernetes termination on Unix.
|
|
58
|
+
* - `SIGBREAK`: Ctrl-Break on Windows.
|
|
59
|
+
*
|
|
60
|
+
* The first signal logs shutdown progress, aborts the root Run, and waits for
|
|
61
|
+
* the main Task and structured cleanup to finish. A subsequent signal exits
|
|
62
|
+
* immediately with its conventional signal status, abandoning cleanup. A signal
|
|
63
|
+
* received during final cleanup still applies signal shutdown behavior.
|
|
33
64
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
65
|
+
* A main Task returning {@link Resource} keeps the program running until a
|
|
66
|
+
* termination signal and is disposed during shutdown. A main Task returning
|
|
67
|
+
* `void` completes the program immediately. A Resource result transfers
|
|
68
|
+
* ownership of a live resource that must remain valid after its creating Task
|
|
69
|
+
* settles.
|
|
38
70
|
*
|
|
39
|
-
*
|
|
71
|
+
* Service mode treats graceful signal shutdown as successful. Command mode
|
|
72
|
+
* preserves conventional signal exit statuses. Every defect reported through
|
|
73
|
+
* `reportDefect`, including an observer defect that does not abort the Run,
|
|
74
|
+
* sets `process.exitCode` to 1. The default reporter logs to the configured
|
|
75
|
+
* Evolu console.
|
|
76
|
+
*
|
|
77
|
+
* Escaped uncaught exceptions and unhandled rejections remain under Node.js
|
|
78
|
+
* native reporting and termination.
|
|
79
|
+
*
|
|
80
|
+
* ### Service Example
|
|
40
81
|
*
|
|
41
82
|
* ```ts
|
|
42
|
-
* const deps = { ...createRelayDeps(), console };
|
|
83
|
+
* const deps = { ...createRelayDeps(), console: createConsole() };
|
|
43
84
|
*
|
|
44
|
-
* await
|
|
45
|
-
*
|
|
85
|
+
* await runMain(deps)(createRelay({ port: 4000 }));
|
|
86
|
+
* ```
|
|
46
87
|
*
|
|
47
|
-
*
|
|
88
|
+
* A Task returning `void` can keep a service alive explicitly when no Resource
|
|
89
|
+
* owns its lifetime:
|
|
48
90
|
*
|
|
49
|
-
*
|
|
91
|
+
* ```ts
|
|
92
|
+
* await runMain(deps)(async (run) => {
|
|
93
|
+
* void run(processMessages);
|
|
94
|
+
* return await run(waitForAbort);
|
|
95
|
+
* });
|
|
50
96
|
* ```
|
|
51
97
|
*
|
|
52
|
-
*
|
|
98
|
+
* ### Command Example
|
|
99
|
+
*
|
|
100
|
+
* ```ts
|
|
101
|
+
* await runMain(command, { mode: "command" });
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @group Node.js Task
|
|
53
105
|
*/
|
|
54
|
-
export
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
export function runMain<T extends void | Resource>(
|
|
107
|
+
main: Task<T>,
|
|
108
|
+
options?: RunMainOptions,
|
|
109
|
+
): Promise<void>;
|
|
110
|
+
/** With custom dependencies. */
|
|
111
|
+
export function runMain<D extends object>(
|
|
112
|
+
deps: RunCustomDeps<D>,
|
|
113
|
+
options?: RunMainOptions,
|
|
114
|
+
): <T extends void | Resource>(main: Task<T, never, D>) => Promise<void>;
|
|
115
|
+
export function runMain<T extends void | Resource, D extends object>(
|
|
116
|
+
mainOrDeps: Task<T> | RunCustomDeps<D>,
|
|
117
|
+
{ mode = "service" }: RunMainOptions = {},
|
|
118
|
+
):
|
|
119
|
+
| Promise<void>
|
|
120
|
+
| (<R extends void | Resource>(main: Task<R, never, D>) => Promise<void>) {
|
|
121
|
+
return typeof mainOrDeps === "function"
|
|
122
|
+
? runMainInternal(mainOrDeps, {}, mode)
|
|
123
|
+
: (main) => runMainInternal(main, mainOrDeps, mode);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const commandExitCodeBySignal: Readonly<Record<NodeSignal, number>> = {
|
|
127
|
+
SIGINT: 130,
|
|
128
|
+
SIGTERM: 143,
|
|
129
|
+
SIGBREAK: 149,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const runMainInternal = async <T extends void | Resource, D extends object>(
|
|
133
|
+
main: Task<T, never, D>,
|
|
134
|
+
deps: RunCustomDeps<D> & Partial<ConsoleDep & ReportDefectDep>,
|
|
135
|
+
mode: RunMainMode,
|
|
136
|
+
): Promise<void> => {
|
|
137
|
+
const console = deps.console ?? createConsole();
|
|
138
|
+
const mainConsole = console.child("main");
|
|
139
|
+
|
|
140
|
+
let defectReported = false as boolean;
|
|
141
|
+
let receivedSignal = null as NodeSignal | null;
|
|
142
|
+
|
|
143
|
+
await using disposer = new AsyncDisposableStack();
|
|
144
|
+
const run = disposer.use(
|
|
145
|
+
createRun<D>({
|
|
146
|
+
...deps,
|
|
147
|
+
console,
|
|
148
|
+
reportDefect: (reported) => {
|
|
149
|
+
defectReported = true;
|
|
150
|
+
process.exitCode = 1;
|
|
151
|
+
if (deps.reportDefect) deps.reportDefect(reported);
|
|
152
|
+
else console.error(reported);
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
(["SIGINT", "SIGTERM", "SIGBREAK"] as const).forEach((signal) => {
|
|
158
|
+
const handleSignal = (): void => {
|
|
159
|
+
if (receivedSignal !== null) {
|
|
160
|
+
mainConsole.warn("Forcing shutdown...");
|
|
161
|
+
process.exit(commandExitCodeBySignal[signal]);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
receivedSignal = signal;
|
|
166
|
+
mainConsole.info("Shutting down...");
|
|
167
|
+
run.abort({ type: "NodeSignalAbortReason", signal });
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
process.on(signal, handleSignal);
|
|
171
|
+
run.defer(() => {
|
|
172
|
+
process.off(signal, handleSignal);
|
|
173
|
+
});
|
|
90
174
|
});
|
|
91
175
|
|
|
92
|
-
|
|
176
|
+
try {
|
|
177
|
+
await run(async (run) => {
|
|
178
|
+
const resource = await run.ok(main);
|
|
179
|
+
if (!isDisposable(resource)) return ok();
|
|
180
|
+
|
|
181
|
+
await using _resource = resource;
|
|
182
|
+
return await run(waitForAbort);
|
|
183
|
+
});
|
|
184
|
+
} catch {
|
|
185
|
+
// Aborts are control flow; defects are already handled by reportDefect.
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Move ownership out of the await-using setup safety net so an already
|
|
189
|
+
// reported finalizer defect can be suppressed during explicit disposal.
|
|
190
|
+
try {
|
|
191
|
+
await disposer.move().disposeAsync();
|
|
192
|
+
} catch {
|
|
193
|
+
// Finalizer defects are already handled by reportDefect.
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (receivedSignal !== null) {
|
|
197
|
+
if (defectReported) mainConsole.warn("Shutdown finished with errors");
|
|
198
|
+
else mainConsole.info("Shutdown complete");
|
|
199
|
+
if (mode === "command")
|
|
200
|
+
process.exitCode ??= commandExitCodeBySignal[receivedSignal];
|
|
201
|
+
}
|
|
93
202
|
};
|