@evolu/nodejs 3.2.0 → 4.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,63 @@
1
+ /**
2
+ * File system operations using Node.js.
3
+ *
4
+ * @module
5
+ */
6
+ import { type Fs } from "@evolu/common";
7
+ /**
8
+ * Creates a {@link Fs} backed by `node:fs/promises`.
9
+ *
10
+ * `readFile` and `writeFile` pass the Run's abort signal to Node and propagate
11
+ * the Run's abort reason if Node rejects after cancellation. Cancellation can
12
+ * leave a write partially completed. Successful operations return their values
13
+ * even if an abort was requested. Other operations run to completion and return
14
+ * their results once started, so callers also receive created temporary
15
+ * directories and can dispose them.
16
+ *
17
+ * Temporary directory parents are resolved through the file system, preserving
18
+ * the meaning of symbolic links followed by `..`. Returned paths are absolute,
19
+ * so cleanup still targets the created directory after a change to the
20
+ * process's working directory.
21
+ *
22
+ * ### Example
23
+ *
24
+ * ```ts
25
+ * import {
26
+ * assertEqual,
27
+ * assertFalse,
28
+ * ok,
29
+ * type FsDep,
30
+ * type FsError,
31
+ * type Task,
32
+ * } from "@evolu/common";
33
+ * import { createNodeFs, runMain } from "@evolu/nodejs";
34
+ * import { join } from "node:path";
35
+ *
36
+ * const main: Task<void, FsError, FsDep> = async (run) => {
37
+ * const { fs } = run.deps;
38
+ * const temp = await run(fs.createTempDirectory({ prefix: "evolu-" }));
39
+ * if (!temp.ok) return temp;
40
+ *
41
+ * {
42
+ * await using directory = temp.value;
43
+ * const path = join(directory.path, "config.json");
44
+ *
45
+ * const result = await run(fs.writeFile(path, '{ "port": 4000 }'));
46
+ * if (!result.ok) return result;
47
+ *
48
+ * const text = await run(fs.readFile(path, "utf8"));
49
+ * if (!text.ok) return text;
50
+ * assertEqual(text.value, '{ "port": 4000 }');
51
+ * }
52
+ *
53
+ * const exists = await run(fs.exists(temp.value.path));
54
+ * if (!exists.ok) return exists;
55
+ * assertFalse(exists.value);
56
+ * return ok();
57
+ * };
58
+ *
59
+ * await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
60
+ * ```
61
+ */
62
+ export declare const createNodeFs: () => Fs;
63
+ //# sourceMappingURL=Fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Fs.d.ts","sourceRoot":"","sources":["../../src/Fs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAOL,KAAK,EAAE,EAQR,MAAM,eAAe,CAAC;AAmBvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,eAAO,MAAM,YAAY,QAAO,EAgI/B,CAAC"}
package/dist/src/Fs.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * File system operations using Node.js.
3
+ *
4
+ * @module
5
+ */
6
+ import { assert, ByteLength, disposable, err, ok, tryAsync, } from "@evolu/common";
7
+ import { constants } from "node:fs";
8
+ import { access, copyFile, cp, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, stat, writeFile, } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join, sep } from "node:path";
11
+ /**
12
+ * Creates a {@link Fs} backed by `node:fs/promises`.
13
+ *
14
+ * `readFile` and `writeFile` pass the Run's abort signal to Node and propagate
15
+ * the Run's abort reason if Node rejects after cancellation. Cancellation can
16
+ * leave a write partially completed. Successful operations return their values
17
+ * even if an abort was requested. Other operations run to completion and return
18
+ * their results once started, so callers also receive created temporary
19
+ * directories and can dispose them.
20
+ *
21
+ * Temporary directory parents are resolved through the file system, preserving
22
+ * the meaning of symbolic links followed by `..`. Returned paths are absolute,
23
+ * so cleanup still targets the created directory after a change to the
24
+ * process's working directory.
25
+ *
26
+ * ### Example
27
+ *
28
+ * ```ts
29
+ * import {
30
+ * assertEqual,
31
+ * assertFalse,
32
+ * ok,
33
+ * type FsDep,
34
+ * type FsError,
35
+ * type Task,
36
+ * } from "@evolu/common";
37
+ * import { createNodeFs, runMain } from "@evolu/nodejs";
38
+ * import { join } from "node:path";
39
+ *
40
+ * const main: Task<void, FsError, FsDep> = async (run) => {
41
+ * const { fs } = run.deps;
42
+ * const temp = await run(fs.createTempDirectory({ prefix: "evolu-" }));
43
+ * if (!temp.ok) return temp;
44
+ *
45
+ * {
46
+ * await using directory = temp.value;
47
+ * const path = join(directory.path, "config.json");
48
+ *
49
+ * const result = await run(fs.writeFile(path, '{ "port": 4000 }'));
50
+ * if (!result.ok) return result;
51
+ *
52
+ * const text = await run(fs.readFile(path, "utf8"));
53
+ * if (!text.ok) return text;
54
+ * assertEqual(text.value, '{ "port": 4000 }');
55
+ * }
56
+ *
57
+ * const exists = await run(fs.exists(temp.value.path));
58
+ * if (!exists.ok) return exists;
59
+ * assertFalse(exists.value);
60
+ * return ok();
61
+ * };
62
+ *
63
+ * await runMain({ fs: createNodeFs() }, { mode: "command" })(main);
64
+ * ```
65
+ */
66
+ export const createNodeFs = () => {
67
+ function readFileTask(path, encoding) {
68
+ return async (run) => {
69
+ const { signal } = run;
70
+ const result = await tryAsync(() => encoding === undefined
71
+ ? readFile(path, { signal })
72
+ : readFile(path, { ...toEncodingOptions(encoding), signal }));
73
+ if (result.ok)
74
+ return result;
75
+ signal.throwIfAborted();
76
+ return err(createFsError("readFile", path, result.error));
77
+ };
78
+ }
79
+ return {
80
+ readFile: readFileTask,
81
+ writeFile: (path, data, options) => async (run) => {
82
+ const { signal } = run;
83
+ const result = await tryAsync(() => writeFile(path, data, { ...options, signal }));
84
+ if (result.ok)
85
+ return result;
86
+ signal.throwIfAborted();
87
+ return err(createFsError("writeFile", path, result.error));
88
+ },
89
+ readDirectory: (path, options) => async () => {
90
+ const result = await tryAsync(() => readdir(path, options));
91
+ return result.ok
92
+ ? result
93
+ : err(createFsError("readDirectory", path, result.error));
94
+ },
95
+ createDirectory: (path, options) => async () => {
96
+ const result = await tryAsync(async () => {
97
+ await mkdir(path, options);
98
+ });
99
+ return result.ok
100
+ ? ok()
101
+ : err(createFsError("createDirectory", path, result.error));
102
+ },
103
+ copy: (source, destination, options) => async () => {
104
+ const result = await tryAsync(() => cp(source, destination, { ...options, recursive: true }));
105
+ return result.ok
106
+ ? ok()
107
+ : err(createFsError("copy", source, result.error, destination));
108
+ },
109
+ copyFile: (source, destination, { overwrite = false } = {}) => async () => {
110
+ const result = await tryAsync(() => copyFile(source, destination, overwrite ? 0 : constants.COPYFILE_EXCL));
111
+ return result.ok
112
+ ? ok()
113
+ : err(createFsError("copyFile", source, result.error, destination));
114
+ },
115
+ rename: (source, destination) => async () => {
116
+ const result = await tryAsync(() => rename(source, destination));
117
+ return result.ok
118
+ ? ok()
119
+ : err(createFsError("rename", source, result.error, destination));
120
+ },
121
+ remove: (path, options) => async () => {
122
+ const result = await tryAsync(() => rm(path, options));
123
+ return result.ok
124
+ ? ok()
125
+ : err(createFsError("remove", path, result.error));
126
+ },
127
+ getMetadata: (path) => async () => {
128
+ const result = await tryAsync(() => stat(path));
129
+ return result.ok
130
+ ? ok(statsToFsMetadata(result.value))
131
+ : err(createFsError("getMetadata", path, result.error));
132
+ },
133
+ exists: (path) => async () => {
134
+ const result = await tryAsync(() => access(path));
135
+ if (result.ok)
136
+ return ok(true);
137
+ const error = createFsError("exists", path, result.error);
138
+ return error.reason === "NotFound" ? ok(false) : err(error);
139
+ },
140
+ createTempDirectory: ({ directory, prefix = "" } = {}) => async () => {
141
+ const parent = directory ?? tmpdir();
142
+ const resolvedParent = await tryAsync(() => realpath(parent || "."), (error) => createFsError("createTempDirectory", parent, error));
143
+ if (!resolvedParent.ok)
144
+ return resolvedParent;
145
+ // Keep the parent separator even when the name prefix is empty.
146
+ const pathPrefix = join(resolvedParent.value, sep) + prefix;
147
+ const result = await tryAsync(() => mkdtemp(pathPrefix), (error) => createFsError("createTempDirectory", pathPrefix, error));
148
+ if (!result.ok)
149
+ return result;
150
+ const path = result.value;
151
+ const disposer = new AsyncDisposableStack();
152
+ disposer.defer(() => rm(path, { recursive: true, force: true }));
153
+ return ok(disposable({ path }, disposer));
154
+ },
155
+ };
156
+ };
157
+ const toEncodingOptions = (encoding) => typeof encoding === "string" ? { encoding } : encoding;
158
+ const fsErrorReasonByCode = {
159
+ ENOENT: "NotFound",
160
+ EEXIST: "AlreadyExists",
161
+ EACCES: "PermissionDenied",
162
+ EPERM: "PermissionDenied",
163
+ EISDIR: "IsDirectory",
164
+ ERR_FS_EISDIR: "IsDirectory",
165
+ ERR_FS_CP_EEXIST: "AlreadyExists",
166
+ ERR_FS_CP_NON_DIR_TO_DIR: "IsDirectory",
167
+ ERR_FS_CP_DIR_TO_NON_DIR: "NotDirectory",
168
+ ENOTDIR: "NotDirectory",
169
+ ENOTEMPTY: "NotEmpty",
170
+ EBUSY: "Busy",
171
+ };
172
+ const createFsError = (method, path, error, destination) => {
173
+ assert(error instanceof Error, "Node fs rejects with an Error.");
174
+ const { code, syscall } = error;
175
+ return {
176
+ type: "FsError",
177
+ reason: fsErrorReasonByCode[String(code)] ?? "Unknown",
178
+ path: typeof path === "string" ? path : path.href,
179
+ ...(destination === undefined
180
+ ? {}
181
+ : {
182
+ destination: typeof destination === "string" ? destination : destination.href,
183
+ }),
184
+ syscall: syscall ?? method,
185
+ message: error.message,
186
+ };
187
+ };
188
+ const fsEntryTypeByMode = {
189
+ [constants.S_IFREG]: "File",
190
+ [constants.S_IFDIR]: "Directory",
191
+ [constants.S_IFLNK]: "SymbolicLink",
192
+ [constants.S_IFBLK]: "BlockDevice",
193
+ [constants.S_IFCHR]: "CharacterDevice",
194
+ [constants.S_IFIFO]: "FIFO",
195
+ [constants.S_IFSOCK]: "Socket",
196
+ };
197
+ const statsToFsMetadata = (stats) => ({
198
+ type: fsEntryTypeByMode[stats.mode & constants.S_IFMT] ?? "Unknown",
199
+ dev: stats.dev,
200
+ ino: stats.ino,
201
+ mode: stats.mode,
202
+ nlink: stats.nlink,
203
+ uid: stats.uid,
204
+ gid: stats.gid,
205
+ rdev: stats.rdev,
206
+ size: ByteLength.orThrow(stats.size),
207
+ blksize: stats.blksize,
208
+ blocks: stats.blocks,
209
+ atimeMs: stats.atimeMs,
210
+ mtimeMs: stats.mtimeMs,
211
+ ctimeMs: stats.ctimeMs,
212
+ birthtimeMs: stats.birthtimeMs,
213
+ atime: stats.atime,
214
+ mtime: stats.mtime,
215
+ ctime: stats.ctime,
216
+ birthtime: stats.birthtime,
217
+ });
@@ -22,69 +22,82 @@ export interface RunMainOptions {
22
22
  * How termination signals affect the process exit status.
23
23
  *
24
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.
25
+ * Commands use the conventional `128 + signal number` exit status unless
26
+ * `process.exitCode` is already set.
27
27
  *
28
28
  * @default "service"
29
29
  */
30
30
  readonly mode?: RunMainMode;
31
31
  }
32
32
  /**
33
- * Runs the main Task as the Node.js program lifecycle.
33
+ * Runs the main {@link Task} of a Node.js command or service.
34
34
  *
35
- * Creates one root {@link Run} and aborts it on:
35
+ * A command does a finite job, such as importing data. A service handles
36
+ * ongoing work, such as accepting connections. Choose a mode for how the
37
+ * program reports an interruption:
36
38
  *
37
- * - `SIGINT`: Ctrl-C on all platforms.
38
- * - `SIGTERM`: OS, service, Docker, or Kubernetes termination on Unix.
39
- * - `SIGBREAK`: Ctrl-Break on Windows.
39
+ * - `"command"`: A termination signal means the job was interrupted. After
40
+ * cleanup, use the conventional signal exit status, such as 130 for Ctrl-C,
41
+ * unless `process.exitCode` is already set.
42
+ * - `"service"` (default): A termination signal is an expected way to stop the
43
+ * service. Graceful shutdown does not set a failure exit status.
40
44
  *
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
+ * Both modes create one root {@link Run} and wait for cleanup. The mode controls
46
+ * signal exit status; the main Task controls how long `runMain` waits.
45
47
  *
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.
48
+ * ### Lifetime
51
49
  *
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
50
+ * When the main Task returns `void`, `runMain` finishes after the Task and root
51
+ * Run cleanup. It does not force the Node.js process to exit.
52
+ *
53
+ * A service can return a live {@link Resource}, such as the relay created by
54
+ * {@link createRelay}. This transfers ownership to `runMain`, which waits for
55
+ * shutdown and then disposes the Resource. It must remain usable after the
56
+ * creating Task finishes; do not dispose it before returning it.
57
+ *
58
+ * Alternatively, the main Task can own its resources with `using` or `await
59
+ * using` while awaiting {@link waitForAbort} with `run(waitForAbort)`. Waiting
60
+ * for abort does not itself keep Node.js running: the service needs active
61
+ * work, such as a listening server.
62
+ *
63
+ * ### Shutdown and errors
64
+ *
65
+ * Handles `SIGINT` (Ctrl-C), `SIGTERM` (termination by the OS or a service
66
+ * host), and `SIGBREAK` (Ctrl-Break on Windows). The first signal logs shutdown
67
+ * progress, aborts the root Run, and waits for cleanup. A second signal exits
68
+ * immediately with its conventional signal status, abandoning cleanup. Signals
69
+ * are still handled during final cleanup.
70
+ *
71
+ * An error returned by the main Task is fatal. {@link getOrThrow} preserves it
72
+ * in `Error.cause`; the Run reports the failure and finishes cleanup. Every
73
+ * reported defect sets `process.exitCode` to 1, including an observer defect
74
+ * that does not abort the Run. The default reporter logs to the configured
56
75
  * Evolu console.
57
76
  *
58
77
  * Escaped uncaught exceptions and unhandled rejections remain under Node.js
59
78
  * native reporting and termination.
60
79
  *
61
- * ### Service Example
80
+ * ### Example
62
81
  *
63
- * ```ts
64
- * const deps = { ...createRelayDeps(), console: createConsole() };
65
- *
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:
82
+ * A command finishes when its main Task completes:
71
83
  *
72
84
  * ```ts
73
- * await runMain(deps)(async (run) => {
74
- * void run(processMessages);
75
- * return await run(waitForAbort);
76
- * });
77
- * ```
85
+ * import { assertTrue, ok, type Task } from "@evolu/common";
86
+ * import { runMain } from "@evolu/nodejs";
78
87
  *
79
- * ### Command Example
88
+ * let completed = false;
89
+ * const command: Task<void> = () => {
90
+ * completed = true;
91
+ * return ok();
92
+ * };
80
93
  *
81
- * ```ts
82
94
  * await runMain(command, { mode: "command" });
95
+ * assertTrue(completed);
83
96
  * ```
84
97
  *
85
98
  * @group Node.js Task
86
99
  */
87
- export declare function runMain<T extends void | Resource>(main: Task<T>, options?: RunMainOptions): Promise<void>;
100
+ export declare function runMain<T extends void | Resource, E = never>(main: Task<T, E>, options?: RunMainOptions): Promise<void>;
88
101
  /** 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>;
102
+ export declare function runMain<D extends object>(deps: RunCustomDeps<D>, options?: RunMainOptions): <T extends void | Resource, E = never>(main: Task<T, E, D>) => Promise<void>;
90
103
  //# 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,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"}
1
+ {"version":3,"file":"Task.d.ts","sourceRoot":"","sources":["../../src/Task.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EASL,KAAK,QAAQ,EAEb,KAAK,aAAa,EAClB,KAAK,IAAI,EACT,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AAGvB;;;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,IAAI,GAAG,QAAQ,EAAE,CAAC,GAAG,KAAK,EAC1D,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAChB,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,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC"}
package/dist/src/Task.js CHANGED
@@ -55,7 +55,7 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
55
55
  var e = new Error(message);
56
56
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
57
57
  });
58
- import { createConsole, createRun, isDisposable, ok, waitForAbort, } from "@evolu/common";
58
+ import { createConsole, createRun, getOrThrow, isDisposable, ok, waitForAbort, } from "@evolu/common";
59
59
  export function runMain(mainOrDeps, { mode = "service" } = {}) {
60
60
  return typeof mainOrDeps === "function"
61
61
  ? runMainInternal(mainOrDeps, {}, mode)
@@ -106,7 +106,7 @@ const runMainInternal = async (main, deps, mode) => {
106
106
  await run(async (run) => {
107
107
  const env_2 = { stack: [], error: void 0, hasError: false };
108
108
  try {
109
- const resource = await run.ok(main);
109
+ const resource = getOrThrow(await run(main));
110
110
  if (!isDisposable(resource))
111
111
  return ok();
112
112
  const _resource = __addDisposableResource(env_2, resource, true);
@@ -14,7 +14,10 @@ import { type ReadonlyRecord } from "@evolu/common";
14
14
  export interface TestJSDocExamplesOptions {
15
15
  /** Source files or glob patterns resolved from `cwd`. */
16
16
  readonly include: string | ReadonlyArray<string>;
17
- /** Package imports redirected to absolute TypeScript entry paths. */
17
+ /**
18
+ * Package imports redirected to absolute TypeScript entry paths. Type
19
+ * checking also applies these aliases to imports within those modules.
20
+ */
18
21
  readonly aliases?: ReadonlyRecord<string, string>;
19
22
  /** Directory used for globbing and package resolution. */
20
23
  readonly cwd?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"TestJSDoc.d.ts","sourceRoot":"","sources":["../../src/TestJSDoc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAiB9D;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,iBAAiB,kDAK3B,wBAAwB,KAAG,OAAO,CAAC,IAAI,CAgKzC,CAAC"}
1
+ {"version":3,"file":"TestJSDoc.d.ts","sourceRoot":"","sources":["../../src/TestJSDoc.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAiB9D;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClD,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;AACH,eAAO,MAAM,iBAAiB,kDAK3B,wBAAwB,KAAG,OAAO,CAAC,IAAI,CAmKzC,CAAC"}
@@ -113,6 +113,7 @@ export const testJSDocExamples = async ({ include, aliases = {}, cwd = process.c
113
113
  noEmit: true,
114
114
  noUnusedLocals: false,
115
115
  noUnusedParameters: false,
116
+ paths: Object.fromEntries(Object.entries(aliases).map(([name, target]) => [name, [target]])),
116
117
  target: "es2022",
117
118
  types: ["node"],
118
119
  },
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export * from "./Cli.ts";
6
6
  export * from "./Crypto.ts";
7
+ export * from "./Fs.ts";
7
8
  export * from "./local-first/Relay.ts";
8
9
  export * from "./Platform.ts";
9
10
  export * from "./Sqlite.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,wBAAwB,CAAC;AACvC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
package/dist/src/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  export * from "./Cli.js";
6
6
  export * from "./Crypto.js";
7
+ export * from "./Fs.js";
7
8
  export * from "./local-first/Relay.js";
8
9
  export * from "./Platform.js";
9
10
  export * from "./Sqlite.js";
@@ -1,8 +1,11 @@
1
- import { type CreateSqliteDriverDep, type RandomDep, type Task, type TimingSafeEqualDep } from "@evolu/common";
1
+ import { type CreateSqliteDriverDep, Port, type RandomDep, type Task, type TimingSafeEqualDep } from "@evolu/common";
2
2
  import { type Relay, type RelayConfig } from "@evolu/common/local-first";
3
3
  export interface NodeJsRelayConfig extends RelayConfig {
4
- /** The port number for the HTTP server. */
5
- readonly port?: number;
4
+ /**
5
+ * The HTTP server's {@link Port}. Zero requests an automatically assigned
6
+ * port.
7
+ */
8
+ readonly port?: Port;
6
9
  }
7
10
  export type RelayDeps = CreateSqliteDriverDep & RandomDep & TimingSafeEqualDep;
8
11
  /** Dependencies for {@link createRelay} using better-sqlite3. */
@@ -16,32 +19,17 @@ export declare const createRelayDeps: () => RelayDeps;
16
19
  * ### Example
17
20
  *
18
21
  * ```ts
19
- * // Ensure the database is created in a predictable location for Docker.
20
- * mkdirSync("data", { recursive: true });
21
- * process.chdir("data");
22
- *
23
- * const console = createConsole({
24
- * // level: "debug",
25
- * formatter: createConsoleFormatter()({
26
- * timestampFormat: "relative",
27
- * }),
22
+ * import { assertType, Port, type Task } from "@evolu/common";
23
+ * import type { Relay } from "@evolu/common/local-first";
24
+ * import { createRelay, type RelayDeps } from "@evolu/nodejs";
25
+ *
26
+ * const main = createRelay({
27
+ * port: Port.orThrow(4000),
28
+ * isOwnerWithinQuota: (_ownerId, requiredBytes) =>
29
+ * requiredBytes <= 1024 * 1024,
28
30
  * });
29
31
  *
30
- * const deps = { ...createRelayDeps(), console };
31
- *
32
- * await runMain(deps)(
33
- * createRelay({
34
- * port: 4000,
35
- *
36
- * // Note: Relay requires URL in format ws://host:port?ownerId=<ownerId>
37
- * // isOwnerAllowed: (_ownerId, { signal: _signal }) => true,
38
- *
39
- * isOwnerWithinQuota: (_ownerId, requiredBytes) => {
40
- * const maxBytes = 1024 * 1024; // 1MB
41
- * return requiredBytes <= maxBytes;
42
- * },
43
- * }),
44
- * );
32
+ * assertType<typeof main, Task<Relay, never, RelayDeps>>();
45
33
  * ```
46
34
  */
47
35
  export declare const createRelay: ({ port, name, isOwnerAllowed, isOwnerWithinQuota, }: NodeJsRelayConfig) => Task<Relay, never, RelayDeps>;
@@ -1 +1 @@
1
- {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/local-first/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,qBAAqB,EAK1B,KAAK,SAAS,EACd,KAAK,IAAI,EACT,KAAK,kBAAkB,EAGxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAQL,KAAK,KAAK,EACV,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAQnC,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,2CAA2C;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,SAAS,GAAG,qBAAqB,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE/E,iEAAiE;AACjE,eAAO,MAAM,eAAe,QAAO,SAIjC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,WAAW,wDAMnB,iBAAiB,KAAG,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAkNlD,CAAC"}
1
+ {"version":3,"file":"Relay.d.ts","sourceRoot":"","sources":["../../../src/local-first/Relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,qBAAqB,EAK1B,IAAI,EACJ,KAAK,SAAS,EACd,KAAK,IAAI,EACT,KAAK,kBAAkB,EAGxB,MAAM,eAAe,CAAC;AACvB,OAAO,EAQL,KAAK,KAAK,EACV,KAAK,WAAW,EACjB,MAAM,2BAA2B,CAAC;AAQnC,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,SAAS,GAAG,qBAAqB,GAAG,SAAS,GAAG,kBAAkB,CAAC;AAE/E,iEAAiE;AACjE,eAAO,MAAM,eAAe,QAAO,SAIjC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,WAAW,wDAMnB,iBAAiB,KAAG,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAkNlD,CAAC"}
@@ -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 { assert, createRandom, createRelation, createSqlite, daemon, Name, ok, OwnerId, tryAsync, Uint8Array, } from "@evolu/common";
53
+ import { assert, createRandom, createRelation, createSqlite, daemon, Name, ok, OwnerId, Port, tryAsync, Uint8Array, } from "@evolu/common";
54
54
  import { applyProtocolMessageAsRelay, createBaseSqliteStorageTables, createRelaySqliteStorage, createRelayStorageTables, defaultProtocolMessageMaxSize, parseOwnerIdFromOwnerWebSocketTransportUrl, } from "@evolu/common/local-first";
55
55
  import { once } from "events";
56
56
  import { existsSync } from "fs";
@@ -73,35 +73,20 @@ export const createRelayDeps = () => ({
73
73
  * ### Example
74
74
  *
75
75
  * ```ts
76
- * // Ensure the database is created in a predictable location for Docker.
77
- * mkdirSync("data", { recursive: true });
78
- * process.chdir("data");
76
+ * import { assertType, Port, type Task } from "@evolu/common";
77
+ * import type { Relay } from "@evolu/common/local-first";
78
+ * import { createRelay, type RelayDeps } from "@evolu/nodejs";
79
79
  *
80
- * const console = createConsole({
81
- * // level: "debug",
82
- * formatter: createConsoleFormatter()({
83
- * timestampFormat: "relative",
84
- * }),
80
+ * const main = createRelay({
81
+ * port: Port.orThrow(4000),
82
+ * isOwnerWithinQuota: (_ownerId, requiredBytes) =>
83
+ * requiredBytes <= 1024 * 1024,
85
84
  * });
86
85
  *
87
- * const deps = { ...createRelayDeps(), console };
88
- *
89
- * await runMain(deps)(
90
- * createRelay({
91
- * port: 4000,
92
- *
93
- * // Note: Relay requires URL in format ws://host:port?ownerId=<ownerId>
94
- * // isOwnerAllowed: (_ownerId, { signal: _signal }) => true,
95
- *
96
- * isOwnerWithinQuota: (_ownerId, requiredBytes) => {
97
- * const maxBytes = 1024 * 1024; // 1MB
98
- * return requiredBytes <= maxBytes;
99
- * },
100
- * }),
101
- * );
86
+ * assertType<typeof main, Task<Relay, never, RelayDeps>>();
102
87
  * ```
103
88
  */
104
- export const createRelay = ({ port = 443, name = Name.orThrow("evolu-relay"), isOwnerAllowed, isOwnerWithinQuota, }) => async (run) => {
89
+ export const createRelay = ({ port = Port.orThrow(443), name = Name.orThrow("evolu-relay"), isOwnerAllowed, isOwnerWithinQuota, }) => async (run) => {
105
90
  const env_1 = { stack: [], error: void 0, hasError: false };
106
91
  try {
107
92
  const disposer = __addDisposableResource(env_1, new AsyncDisposableStack(), true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolu/nodejs",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "Evolu for Node.js",
5
5
  "author": "Daniel Steigerwald <daniel@steigerwald.cz>",
6
6
  "license": "MIT",
@@ -60,7 +60,7 @@
60
60
  "ws": "^8.21.3"
61
61
  },
62
62
  "devDependencies": {
63
- "@evolu/common": "8.8.0",
63
+ "@evolu/common": "8.10.0",
64
64
  "@evolu/oxlint-config": "0.2.0",
65
65
  "@evolu/typescript-config": "0.1.1",
66
66
  "@types/better-sqlite3": "^9.6.0",
@@ -73,9 +73,9 @@
73
73
  "webpack": "^5.109.2"
74
74
  },
75
75
  "peerDependencies": {
76
- "@evolu/common": "^8.0.0",
76
+ "@evolu/common": "^8.10.0",
77
77
  "@evolu/oxlint-config": "^0.2.0",
78
- "@evolu/typescript-config": "^0.1.0",
78
+ "@evolu/typescript-config": "^0.1.1",
79
79
  "oxlint": "^1.79.0",
80
80
  "oxlint-tsgolint": "7.0.2001",
81
81
  "typescript": ">=7.0.0",