@ny4n/durable 0.0.0-stage → 1.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/LICENSE +21 -0
- package/README.md +711 -2
- package/dist/documents.d.ts +66 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +98 -0
- package/dist/documents.js.map +1 -0
- package/dist/entries.d.ts +31 -0
- package/dist/entries.d.ts.map +1 -0
- package/dist/entries.js +31 -0
- package/dist/entries.js.map +1 -0
- package/dist/env/decode.d.ts +16 -0
- package/dist/env/decode.d.ts.map +1 -0
- package/dist/env/decode.js +28 -0
- package/dist/env/decode.js.map +1 -0
- package/dist/env/index.d.ts +263 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +49 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/line-scan.d.ts +13 -0
- package/dist/env/line-scan.d.ts.map +1 -0
- package/dist/env/line-scan.js +147 -0
- package/dist/env/line-scan.js.map +1 -0
- package/dist/env/node-watch.d.ts +29 -0
- package/dist/env/node-watch.d.ts.map +1 -0
- package/dist/env/node-watch.js +485 -0
- package/dist/env/node-watch.js.map +1 -0
- package/dist/env/node.d.ts +56 -0
- package/dist/env/node.d.ts.map +1 -0
- package/dist/env/node.js +1166 -0
- package/dist/env/node.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +53 -0
- package/dist/errors.js.map +1 -0
- package/dist/harness/agent.d.ts +33 -0
- package/dist/harness/agent.d.ts.map +1 -0
- package/dist/harness/agent.js +235 -0
- package/dist/harness/agent.js.map +1 -0
- package/dist/harness/assistant.d.ts +15 -0
- package/dist/harness/assistant.d.ts.map +1 -0
- package/dist/harness/assistant.js +23 -0
- package/dist/harness/assistant.js.map +1 -0
- package/dist/harness/compaction.d.ts +56 -0
- package/dist/harness/compaction.d.ts.map +1 -0
- package/dist/harness/compaction.js +384 -0
- package/dist/harness/compaction.js.map +1 -0
- package/dist/harness/context.d.ts +63 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +221 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/define.d.ts +18 -0
- package/dist/harness/define.d.ts.map +1 -0
- package/dist/harness/define.js +25 -0
- package/dist/harness/define.js.map +1 -0
- package/dist/harness/events.d.ts +180 -0
- package/dist/harness/events.d.ts.map +1 -0
- package/dist/harness/events.js +370 -0
- package/dist/harness/events.js.map +1 -0
- package/dist/harness/generation.d.ts +54 -0
- package/dist/harness/generation.d.ts.map +1 -0
- package/dist/harness/generation.js +514 -0
- package/dist/harness/generation.js.map +1 -0
- package/dist/harness/harness.d.ts +17 -0
- package/dist/harness/harness.d.ts.map +1 -0
- package/dist/harness/harness.js +332 -0
- package/dist/harness/harness.js.map +1 -0
- package/dist/harness/inbox.d.ts +59 -0
- package/dist/harness/inbox.d.ts.map +1 -0
- package/dist/harness/inbox.js +97 -0
- package/dist/harness/inbox.js.map +1 -0
- package/dist/harness/json.d.ts +10 -0
- package/dist/harness/json.d.ts.map +1 -0
- package/dist/harness/json.js +33 -0
- package/dist/harness/json.js.map +1 -0
- package/dist/harness/live.d.ts +142 -0
- package/dist/harness/live.d.ts.map +1 -0
- package/dist/harness/live.js +149 -0
- package/dist/harness/live.js.map +1 -0
- package/dist/harness/output.d.ts +69 -0
- package/dist/harness/output.d.ts.map +1 -0
- package/dist/harness/output.js +318 -0
- package/dist/harness/output.js.map +1 -0
- package/dist/harness/prompt.d.ts +27 -0
- package/dist/harness/prompt.d.ts.map +1 -0
- package/dist/harness/prompt.js +131 -0
- package/dist/harness/prompt.js.map +1 -0
- package/dist/harness/provider.d.ts +14 -0
- package/dist/harness/provider.d.ts.map +1 -0
- package/dist/harness/provider.js +30 -0
- package/dist/harness/provider.js.map +1 -0
- package/dist/harness/registry.d.ts +6 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +101 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/run.d.ts +17 -0
- package/dist/harness/run.d.ts.map +1 -0
- package/dist/harness/run.js +26 -0
- package/dist/harness/run.js.map +1 -0
- package/dist/harness/scheduler.d.ts +106 -0
- package/dist/harness/scheduler.d.ts.map +1 -0
- package/dist/harness/scheduler.js +1743 -0
- package/dist/harness/scheduler.js.map +1 -0
- package/dist/harness/submissions.d.ts +29 -0
- package/dist/harness/submissions.d.ts.map +1 -0
- package/dist/harness/submissions.js +175 -0
- package/dist/harness/submissions.js.map +1 -0
- package/dist/harness/task-graph.d.ts +49 -0
- package/dist/harness/task-graph.d.ts.map +1 -0
- package/dist/harness/task-graph.js +168 -0
- package/dist/harness/task-graph.js.map +1 -0
- package/dist/harness/tool.d.ts +76 -0
- package/dist/harness/tool.d.ts.map +1 -0
- package/dist/harness/tool.js +736 -0
- package/dist/harness/tool.js.map +1 -0
- package/dist/harness/types.d.ts +713 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/usage.d.ts +18 -0
- package/dist/harness/usage.d.ts.map +1 -0
- package/dist/harness/usage.js +57 -0
- package/dist/harness/usage.js.map +1 -0
- package/dist/harness/util.d.ts +19 -0
- package/dist/harness/util.d.ts.map +1 -0
- package/dist/harness/util.js +58 -0
- package/dist/harness/util.js.map +1 -0
- package/dist/harness/view.d.ts +47 -0
- package/dist/harness/view.d.ts.map +1 -0
- package/dist/harness/view.js +206 -0
- package/dist/harness/view.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +9 -0
- package/dist/ids.js.map +1 -0
- package/dist/images/cloudflare.d.ts +4 -0
- package/dist/images/cloudflare.d.ts.map +1 -0
- package/dist/images/cloudflare.js +9 -0
- package/dist/images/cloudflare.js.map +1 -0
- package/dist/images/exif.d.ts +3 -0
- package/dist/images/exif.d.ts.map +1 -0
- package/dist/images/exif.js +71 -0
- package/dist/images/exif.js.map +1 -0
- package/dist/images/index.d.ts +18 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +125 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/node-worker.d.ts +17 -0
- package/dist/images/node-worker.d.ts.map +1 -0
- package/dist/images/node-worker.js +16 -0
- package/dist/images/node-worker.js.map +1 -0
- package/dist/images/node.d.ts +21 -0
- package/dist/images/node.d.ts.map +1 -0
- package/dist/images/node.js +121 -0
- package/dist/images/node.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/session/forks.d.ts +10 -0
- package/dist/session/forks.d.ts.map +1 -0
- package/dist/session/forks.js +42 -0
- package/dist/session/forks.js.map +1 -0
- package/dist/session/observation.d.ts +42 -0
- package/dist/session/observation.d.ts.map +1 -0
- package/dist/session/observation.js +281 -0
- package/dist/session/observation.js.map +1 -0
- package/dist/session/session.d.ts +133 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +811 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/transaction.d.ts +119 -0
- package/dist/session/transaction.d.ts.map +1 -0
- package/dist/session/transaction.js +795 -0
- package/dist/session/transaction.js.map +1 -0
- package/dist/storage/jsonl/index.d.ts +2 -0
- package/dist/storage/jsonl/index.d.ts.map +1 -0
- package/dist/storage/jsonl/index.js +2 -0
- package/dist/storage/jsonl/index.js.map +1 -0
- package/dist/storage/jsonl/node.d.ts +6 -0
- package/dist/storage/jsonl/node.d.ts.map +1 -0
- package/dist/storage/jsonl/node.js +8 -0
- package/dist/storage/jsonl/node.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +143 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -0
- package/dist/storage/jsonl/storage.js +682 -0
- package/dist/storage/jsonl/storage.js.map +1 -0
- package/dist/storage/memory.d.ts +62 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +733 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/scan.d.ts +18 -0
- package/dist/storage/scan.d.ts.map +1 -0
- package/dist/storage/scan.js +29 -0
- package/dist/storage/scan.js.map +1 -0
- package/dist/storage/sqlite/cloudflare.d.ts +46 -0
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -0
- package/dist/storage/sqlite/cloudflare.js +106 -0
- package/dist/storage/sqlite/cloudflare.js.map +1 -0
- package/dist/storage/sqlite/database.d.ts +37 -0
- package/dist/storage/sqlite/database.d.ts.map +1 -0
- package/dist/storage/sqlite/database.js +2 -0
- package/dist/storage/sqlite/database.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +4 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +3 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -0
- package/dist/storage/sqlite/migrations.js +111 -0
- package/dist/storage/sqlite/migrations.js.map +1 -0
- package/dist/storage/sqlite/node.d.ts +41 -0
- package/dist/storage/sqlite/node.d.ts.map +1 -0
- package/dist/storage/sqlite/node.js +179 -0
- package/dist/storage/sqlite/node.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +65 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/storage/sqlite/storage.js +670 -0
- package/dist/storage/sqlite/storage.js.map +1 -0
- package/dist/tasks.d.ts +6 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +5 -0
- package/dist/tasks.js.map +1 -0
- package/dist/testing/assertions.d.ts +5 -0
- package/dist/testing/assertions.d.ts.map +1 -0
- package/dist/testing/assertions.js +25 -0
- package/dist/testing/assertions.js.map +1 -0
- package/dist/testing/env-conformance.d.ts +7 -0
- package/dist/testing/env-conformance.d.ts.map +1 -0
- package/dist/testing/env-conformance.js +431 -0
- package/dist/testing/env-conformance.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +6 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/runner.d.ts +12 -0
- package/dist/testing/runner.d.ts.map +1 -0
- package/dist/testing/runner.js +23 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/storage-benchmark.d.ts +41 -0
- package/dist/testing/storage-benchmark.d.ts.map +1 -0
- package/dist/testing/storage-benchmark.js +350 -0
- package/dist/testing/storage-benchmark.js.map +1 -0
- package/dist/testing/storage-conformance.d.ts +4 -0
- package/dist/testing/storage-conformance.d.ts.map +1 -0
- package/dist/testing/storage-conformance.js +1213 -0
- package/dist/testing/storage-conformance.js.map +1 -0
- package/dist/testing/types.d.ts +37 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +2 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/tools/bash.d.ts +55 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +138 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit-diff.d.ts +89 -0
- package/dist/tools/edit-diff.d.ts.map +1 -0
- package/dist/tools/edit-diff.js +386 -0
- package/dist/tools/edit-diff.js.map +1 -0
- package/dist/tools/edit.d.ts +18 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +117 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/env.d.ts +5 -0
- package/dist/tools/env.d.ts.map +1 -0
- package/dist/tools/env.js +7 -0
- package/dist/tools/env.js.map +1 -0
- package/dist/tools/file-mutation-queue.d.ts +9 -0
- package/dist/tools/file-mutation-queue.d.ts.map +1 -0
- package/dist/tools/file-mutation-queue.js +52 -0
- package/dist/tools/file-mutation-queue.js.map +1 -0
- package/dist/tools/image-processor.d.ts +47 -0
- package/dist/tools/image-processor.d.ts.map +1 -0
- package/dist/tools/image-processor.js +30 -0
- package/dist/tools/image-processor.js.map +1 -0
- package/dist/tools/image.d.ts +20 -0
- package/dist/tools/image.d.ts.map +1 -0
- package/dist/tools/image.js +178 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/index.d.ts +46 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/path-utils.d.ts +5 -0
- package/dist/tools/path-utils.d.ts.map +1 -0
- package/dist/tools/path-utils.js +26 -0
- package/dist/tools/path-utils.js.map +1 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +235 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +31 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/truncate.d.ts +64 -0
- package/dist/truncate.d.ts.map +1 -0
- package/dist/truncate.js +154 -0
- package/dist/truncate.js.map +1 -0
- package/dist/types.d.ts +932 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +140 -5
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { watch as fsWatch } from "node:fs";
|
|
3
|
+
import { lstat, readdir, readFile, stat, statfs } from "node:fs/promises";
|
|
4
|
+
import { dirname, relative, sep } from "node:path";
|
|
5
|
+
import { FileError } from "./index.js";
|
|
6
|
+
const DEBOUNCE_MS = 50;
|
|
7
|
+
/**
|
|
8
|
+
* On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported
|
|
9
|
+
* (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers
|
|
10
|
+
* catches them.
|
|
11
|
+
*/
|
|
12
|
+
const FSEVENTS_SETTLE_MS = 500;
|
|
13
|
+
const DEFAULT_POLL_MS = 2000;
|
|
14
|
+
const DEFAULT_MAX_DIRECTORIES = 10_000;
|
|
15
|
+
/** In polling mode, recently modified small files are also compared by content: a second write within the file system's
|
|
16
|
+
* timestamp granularity can keep size and modification time. */
|
|
17
|
+
const HASH_MAX_BYTES = 256 * 1024;
|
|
18
|
+
const HASH_RECENT_MS = 5000;
|
|
19
|
+
/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */
|
|
20
|
+
const UNRELIABLE_FILE_SYSTEMS = new Set([
|
|
21
|
+
0x6969, // NFS
|
|
22
|
+
0x517b, // SMB
|
|
23
|
+
0xff534d42, // CIFS
|
|
24
|
+
0xfe534d42, // SMB2
|
|
25
|
+
0x65735546, // FUSE (sshfs, Android shared storage)
|
|
26
|
+
0x01021997, // 9P (WSL2 Windows drives)
|
|
27
|
+
0x0bd00bd0, // Lustre
|
|
28
|
+
0x47504653, // GPFS
|
|
29
|
+
0x00c36400, // Ceph
|
|
30
|
+
0x5346414f, // OpenAFS
|
|
31
|
+
0x6b414653, // kAFS
|
|
32
|
+
0x5dca2df5, // sdcardfs
|
|
33
|
+
]);
|
|
34
|
+
class BudgetExceeded extends Error {
|
|
35
|
+
}
|
|
36
|
+
function isNodeError(error) {
|
|
37
|
+
return error instanceof Error && "code" in error;
|
|
38
|
+
}
|
|
39
|
+
function isDenied(error) {
|
|
40
|
+
return isNodeError(error) && (error.code === "EACCES" || error.code === "EPERM");
|
|
41
|
+
}
|
|
42
|
+
function entryOf(stats, hash) {
|
|
43
|
+
const kind = stats.isFile()
|
|
44
|
+
? "file"
|
|
45
|
+
: stats.isDirectory()
|
|
46
|
+
? "directory"
|
|
47
|
+
: stats.isSymbolicLink()
|
|
48
|
+
? "symlink"
|
|
49
|
+
: "other";
|
|
50
|
+
return {
|
|
51
|
+
kind,
|
|
52
|
+
dev: stats.dev,
|
|
53
|
+
ino: stats.ino,
|
|
54
|
+
size: kind === "directory" ? 0 : stats.size,
|
|
55
|
+
mtimeMs: kind === "directory" ? 0 : stats.mtimeMs,
|
|
56
|
+
...(hash === undefined ? {} : { hash }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function sameEntry(a, b) {
|
|
60
|
+
return (a.kind === b.kind &&
|
|
61
|
+
a.dev === b.dev &&
|
|
62
|
+
a.ino === b.ino &&
|
|
63
|
+
a.size === b.size &&
|
|
64
|
+
a.mtimeMs === b.mtimeMs &&
|
|
65
|
+
a.hash === b.hash);
|
|
66
|
+
}
|
|
67
|
+
/** Ancestors of `path`, nearest first, up to the root. */
|
|
68
|
+
function ancestorsOf(path) {
|
|
69
|
+
const result = [];
|
|
70
|
+
for (let current = dirname(path);; current = dirname(current)) {
|
|
71
|
+
result.push(current);
|
|
72
|
+
if (dirname(current) === current)
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Whether `path` is `ancestor` or below it. */
|
|
77
|
+
function isWithin(path, ancestor) {
|
|
78
|
+
return path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);
|
|
79
|
+
}
|
|
80
|
+
function excluded(target, name) {
|
|
81
|
+
return (target.hidden && name.startsWith(".")) || target.names.has(name);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between
|
|
85
|
+
* snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its
|
|
86
|
+
* contents therefore never depends on which events an operating system sends. New directories get their watchers
|
|
87
|
+
* before they are scanned again, so nothing written into them before the watcher existed is missed.
|
|
88
|
+
*/
|
|
89
|
+
export class NodeFileWatcher {
|
|
90
|
+
#targets;
|
|
91
|
+
#onChange;
|
|
92
|
+
#pollIntervalMs;
|
|
93
|
+
#maxDirectories;
|
|
94
|
+
#watchers = new Map();
|
|
95
|
+
#events = new Set();
|
|
96
|
+
#mode;
|
|
97
|
+
#snapshot = new Map();
|
|
98
|
+
#timer;
|
|
99
|
+
#settleTimer;
|
|
100
|
+
#running;
|
|
101
|
+
#dirty = false;
|
|
102
|
+
#closed = false;
|
|
103
|
+
constructor(targets, onChange, mode, options) {
|
|
104
|
+
this.#targets = targets;
|
|
105
|
+
this.#onChange = onChange;
|
|
106
|
+
this.#mode = mode;
|
|
107
|
+
this.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;
|
|
108
|
+
this.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;
|
|
109
|
+
}
|
|
110
|
+
/** Establish coverage: watchers first, then the snapshot later changes are compared with. */
|
|
111
|
+
static async open(targets, resolvePath, onChange, options) {
|
|
112
|
+
const resolved = targets.map((target) => ({
|
|
113
|
+
path: resolvePath(target.path),
|
|
114
|
+
recursive: target.recursive === true,
|
|
115
|
+
hidden: target.exclude?.hidden === true,
|
|
116
|
+
names: new Set(target.exclude?.names ?? []),
|
|
117
|
+
}));
|
|
118
|
+
// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps
|
|
119
|
+
// every watched directory open: watching would break renames of their parents, so Windows polls.
|
|
120
|
+
const mode = options.mode ??
|
|
121
|
+
(process.platform === "win32" || (await anyUnreliable(resolved.map((target) => target.path)))
|
|
122
|
+
? "polling"
|
|
123
|
+
: "native");
|
|
124
|
+
const watcher = new NodeFileWatcher(resolved, onChange, mode, options);
|
|
125
|
+
try {
|
|
126
|
+
await watcher.#sync(false);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
watcher.#stop();
|
|
130
|
+
if (error instanceof BudgetExceeded)
|
|
131
|
+
throw new FileError("invalid", error.message);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
watcher.#schedulePoll();
|
|
135
|
+
return watcher;
|
|
136
|
+
}
|
|
137
|
+
get mode() {
|
|
138
|
+
return this.#mode;
|
|
139
|
+
}
|
|
140
|
+
async close(_context) {
|
|
141
|
+
if (this.#closed)
|
|
142
|
+
return;
|
|
143
|
+
this.#stop();
|
|
144
|
+
await this.#running;
|
|
145
|
+
}
|
|
146
|
+
#stop() {
|
|
147
|
+
this.#closed = true;
|
|
148
|
+
clearTimeout(this.#timer);
|
|
149
|
+
this.#timer = undefined;
|
|
150
|
+
clearTimeout(this.#settleTimer);
|
|
151
|
+
this.#settleTimer = undefined;
|
|
152
|
+
for (const { watcher } of this.#watchers.values())
|
|
153
|
+
watcher.close();
|
|
154
|
+
this.#watchers.clear();
|
|
155
|
+
}
|
|
156
|
+
#deliver(change) {
|
|
157
|
+
if (this.#closed)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
this.#onChange(change);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// A throwing callback must not stop watching.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
#schedulePoll() {
|
|
167
|
+
if (this.#closed || this.#mode !== "polling")
|
|
168
|
+
return;
|
|
169
|
+
this.#timer = setTimeout(() => {
|
|
170
|
+
this.#timer = undefined;
|
|
171
|
+
void this.#flush().finally(() => this.#schedulePoll());
|
|
172
|
+
}, this.#pollIntervalMs);
|
|
173
|
+
}
|
|
174
|
+
#scheduleFlush() {
|
|
175
|
+
if (this.#closed || this.#timer !== undefined)
|
|
176
|
+
return;
|
|
177
|
+
this.#timer = setTimeout(() => {
|
|
178
|
+
this.#timer = undefined;
|
|
179
|
+
void this.#flush();
|
|
180
|
+
}, DEBOUNCE_MS);
|
|
181
|
+
}
|
|
182
|
+
#scheduleSettle() {
|
|
183
|
+
if (this.#closed)
|
|
184
|
+
return;
|
|
185
|
+
clearTimeout(this.#settleTimer);
|
|
186
|
+
this.#settleTimer = setTimeout(() => {
|
|
187
|
+
this.#settleTimer = undefined;
|
|
188
|
+
void this.#flush();
|
|
189
|
+
}, FSEVENTS_SETTLE_MS);
|
|
190
|
+
}
|
|
191
|
+
#flush() {
|
|
192
|
+
if (this.#running !== undefined) {
|
|
193
|
+
this.#dirty = true;
|
|
194
|
+
return this.#running;
|
|
195
|
+
}
|
|
196
|
+
this.#running = (async () => {
|
|
197
|
+
try {
|
|
198
|
+
do {
|
|
199
|
+
this.#dirty = false;
|
|
200
|
+
const events = [...this.#events];
|
|
201
|
+
this.#events.clear();
|
|
202
|
+
const changed = await this.#sync(true);
|
|
203
|
+
for (const path of events)
|
|
204
|
+
changed.add(path);
|
|
205
|
+
if (changed.size > 0)
|
|
206
|
+
this.#deliver({ paths: [...changed].sort() });
|
|
207
|
+
} while (this.#dirty && !this.#closed);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
211
|
+
const fileError = error instanceof FileError
|
|
212
|
+
? error
|
|
213
|
+
: new FileError(isDenied(error) ? "permission_denied" : "invalid", message);
|
|
214
|
+
this.#deliver({ error: fileError });
|
|
215
|
+
this.#stop();
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
this.#running = undefined;
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
return this.#running;
|
|
222
|
+
}
|
|
223
|
+
/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */
|
|
224
|
+
async #sync(report) {
|
|
225
|
+
const changed = new Set();
|
|
226
|
+
for (let round = 0; round < 10 && !this.#closed; round++) {
|
|
227
|
+
const scan = await this.#scan();
|
|
228
|
+
// Closed during the scan: installing watchers now would leak them.
|
|
229
|
+
if (this.#closed)
|
|
230
|
+
break;
|
|
231
|
+
const next = scan.snapshot;
|
|
232
|
+
if (report)
|
|
233
|
+
for (const path of this.#diff(this.#snapshot, next))
|
|
234
|
+
changed.add(path);
|
|
235
|
+
this.#snapshot = next;
|
|
236
|
+
if (this.#mode === "polling" || !this.#reconcileWatchers(scan))
|
|
237
|
+
break;
|
|
238
|
+
if (process.platform === "darwin")
|
|
239
|
+
this.#scheduleSettle();
|
|
240
|
+
// Something written into a new directory before its watcher existed shows up in the next round.
|
|
241
|
+
report = true;
|
|
242
|
+
}
|
|
243
|
+
return changed;
|
|
244
|
+
}
|
|
245
|
+
#diff(previous, next) {
|
|
246
|
+
const changed = [];
|
|
247
|
+
for (const [path, entry] of next) {
|
|
248
|
+
const before = previous.get(path);
|
|
249
|
+
if (before === undefined || !sameEntry(before, entry))
|
|
250
|
+
changed.push(this.#reported(path));
|
|
251
|
+
}
|
|
252
|
+
for (const path of previous.keys())
|
|
253
|
+
if (!next.has(path))
|
|
254
|
+
changed.push(this.#reported(path));
|
|
255
|
+
return changed;
|
|
256
|
+
}
|
|
257
|
+
/** An ancestor that changed identity moved every target below it; report those targets. */
|
|
258
|
+
#reported(path) {
|
|
259
|
+
if (this.#targets.some((target) => isWithin(path, target.path)))
|
|
260
|
+
return path;
|
|
261
|
+
return this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;
|
|
262
|
+
}
|
|
263
|
+
async #scan() {
|
|
264
|
+
const snapshot = new Map();
|
|
265
|
+
const linkedFiles = new Set();
|
|
266
|
+
// Directories are counted once, but traversed once per target: overlapping targets differ in recursion and
|
|
267
|
+
// exclusions, and an entry recorded for one target must still be descended into for another.
|
|
268
|
+
const counted = new Set();
|
|
269
|
+
const traversed = new Set();
|
|
270
|
+
/** Kinds of listed entries, not following links: a target that links to a directory is recorded by `stat`, but
|
|
271
|
+
* symbolic links below a recursive target are not followed. */
|
|
272
|
+
const listed = new Map();
|
|
273
|
+
const countDirectory = (path) => {
|
|
274
|
+
counted.add(path);
|
|
275
|
+
if (counted.size > this.#maxDirectories) {
|
|
276
|
+
throw new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const record = async (path, stats) => {
|
|
280
|
+
let hash;
|
|
281
|
+
if (this.#mode === "polling" &&
|
|
282
|
+
stats.isFile() &&
|
|
283
|
+
stats.size <= HASH_MAX_BYTES &&
|
|
284
|
+
Date.now() - stats.mtimeMs < HASH_RECENT_MS) {
|
|
285
|
+
hash = await readFile(path).then((content) => createHash("sha256").update(content).digest("hex"), () => undefined);
|
|
286
|
+
}
|
|
287
|
+
snapshot.set(path, entryOf(stats, hash));
|
|
288
|
+
};
|
|
289
|
+
const scanDirectory = async (index, target, directory) => {
|
|
290
|
+
const key = `${index}\0${directory}`;
|
|
291
|
+
if (traversed.has(key))
|
|
292
|
+
return;
|
|
293
|
+
traversed.add(key);
|
|
294
|
+
let names;
|
|
295
|
+
try {
|
|
296
|
+
names = await readdir(directory);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
// The watched directory itself must be readable; below it, unreadable directories are skipped.
|
|
300
|
+
if (directory === target.path && isDenied(error))
|
|
301
|
+
throw error;
|
|
302
|
+
if (isNodeError(error) && ["ENOENT", "EACCES", "EPERM", "ENOTDIR"].includes(error.code ?? ""))
|
|
303
|
+
return;
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
for (const name of names) {
|
|
307
|
+
if (excluded(target, name))
|
|
308
|
+
continue;
|
|
309
|
+
const path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;
|
|
310
|
+
let kind = listed.get(path);
|
|
311
|
+
if (kind === undefined) {
|
|
312
|
+
const stats = await lstat(path).catch(() => undefined);
|
|
313
|
+
if (stats === undefined)
|
|
314
|
+
continue;
|
|
315
|
+
kind = entryOf(stats).kind;
|
|
316
|
+
listed.set(path, kind);
|
|
317
|
+
// A target's own entry (following links) wins over its listing by another target.
|
|
318
|
+
if (!snapshot.has(path))
|
|
319
|
+
await record(path, stats);
|
|
320
|
+
}
|
|
321
|
+
if (target.recursive && kind === "directory") {
|
|
322
|
+
countDirectory(path);
|
|
323
|
+
await scanDirectory(index, target, path);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
for (const [index, target] of this.#targets.entries()) {
|
|
328
|
+
for (const ancestor of ancestorsOf(target.path)) {
|
|
329
|
+
if (snapshot.has(ancestor))
|
|
330
|
+
continue;
|
|
331
|
+
const stats = await lstat(ancestor).catch(() => undefined);
|
|
332
|
+
// Identity only: an ancestor's own timestamps change with every unrelated sibling.
|
|
333
|
+
if (stats !== undefined)
|
|
334
|
+
snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });
|
|
335
|
+
}
|
|
336
|
+
// The target itself may be a symbolic link to what is watched; follow it. A missing target is watched for its
|
|
337
|
+
// creation; one that cannot be reached for lack of permission fails.
|
|
338
|
+
let stats;
|
|
339
|
+
try {
|
|
340
|
+
stats = await stat(target.path);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
if (isDenied(error))
|
|
344
|
+
throw error;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
await record(target.path, stats);
|
|
348
|
+
if (stats.isFile() && (await lstat(target.path).catch(() => undefined))?.isSymbolicLink()) {
|
|
349
|
+
linkedFiles.add(target.path);
|
|
350
|
+
}
|
|
351
|
+
if (stats.isDirectory()) {
|
|
352
|
+
countDirectory(target.path);
|
|
353
|
+
await scanDirectory(index, target, target.path);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return { snapshot, linkedFiles };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Watch every existing ancestor of each target, each target directory, each target that is a symbolic link to a file
|
|
360
|
+
* (changes to that file are not events of the link's directory), and on Linux each directory below a recursive target
|
|
361
|
+
* (elsewhere one recursive watcher per target). Returns whether a watcher was added.
|
|
362
|
+
*/
|
|
363
|
+
#reconcileWatchers({ snapshot, linkedFiles }) {
|
|
364
|
+
const wanted = new Map();
|
|
365
|
+
for (const path of linkedFiles)
|
|
366
|
+
wanted.set(path, false);
|
|
367
|
+
const perDirectory = process.platform === "linux" || process.platform === "android";
|
|
368
|
+
for (const target of this.#targets) {
|
|
369
|
+
for (const ancestor of ancestorsOf(target.path)) {
|
|
370
|
+
if (snapshot.get(ancestor)?.kind === "directory" && !wanted.has(ancestor))
|
|
371
|
+
wanted.set(ancestor, false);
|
|
372
|
+
}
|
|
373
|
+
if (snapshot.get(target.path)?.kind !== "directory")
|
|
374
|
+
continue;
|
|
375
|
+
const recursiveRoot = target.recursive && !perDirectory;
|
|
376
|
+
wanted.set(target.path, recursiveRoot);
|
|
377
|
+
if (target.recursive && perDirectory) {
|
|
378
|
+
for (const [path, entry] of snapshot) {
|
|
379
|
+
if (entry.kind === "directory" && path !== target.path && isWithin(path, target.path)) {
|
|
380
|
+
wanted.set(path, false);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const [path, installed] of this.#watchers) {
|
|
386
|
+
const entry = snapshot.get(path);
|
|
387
|
+
// Gone, or replaced: a watcher follows the directory it was installed on, not the path.
|
|
388
|
+
if (!wanted.has(path) || entry === undefined || entry.dev !== installed.dev || entry.ino !== installed.ino) {
|
|
389
|
+
installed.watcher.close();
|
|
390
|
+
this.#watchers.delete(path);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
let added = false;
|
|
394
|
+
for (const [path, recursive] of wanted) {
|
|
395
|
+
const entry = snapshot.get(path);
|
|
396
|
+
if (this.#watchers.has(path) || entry === undefined)
|
|
397
|
+
continue;
|
|
398
|
+
const linked = linkedFiles.has(path);
|
|
399
|
+
try {
|
|
400
|
+
const watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) => linked
|
|
401
|
+
? this.#onLinkedFileEvent(path)
|
|
402
|
+
: this.#onEvent(path, filename === null ? undefined : String(filename)));
|
|
403
|
+
// A watched directory that disappears or fails: rescan, which also replaces the watcher.
|
|
404
|
+
watcher.on("error", () => {
|
|
405
|
+
watcher.close();
|
|
406
|
+
if (this.#watchers.get(path)?.watcher === watcher)
|
|
407
|
+
this.#watchers.delete(path);
|
|
408
|
+
this.#scheduleFlush();
|
|
409
|
+
});
|
|
410
|
+
this.#watchers.set(path, { watcher, dev: entry.dev, ino: entry.ino });
|
|
411
|
+
added = true;
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.
|
|
415
|
+
if (isNodeError(error) && error.code !== "ENOENT" && error.code !== "EACCES" && error.code !== "EPERM") {
|
|
416
|
+
this.#switchToPolling();
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return added;
|
|
422
|
+
}
|
|
423
|
+
#switchToPolling() {
|
|
424
|
+
if (this.#mode === "polling")
|
|
425
|
+
return;
|
|
426
|
+
this.#mode = "polling";
|
|
427
|
+
for (const { watcher } of this.#watchers.values())
|
|
428
|
+
watcher.close();
|
|
429
|
+
this.#watchers.clear();
|
|
430
|
+
this.#deliver({ overflow: true });
|
|
431
|
+
clearTimeout(this.#timer);
|
|
432
|
+
this.#timer = undefined;
|
|
433
|
+
this.#schedulePoll();
|
|
434
|
+
}
|
|
435
|
+
#onEvent(directory, filename) {
|
|
436
|
+
if (this.#closed)
|
|
437
|
+
return;
|
|
438
|
+
const path = filename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;
|
|
439
|
+
// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.
|
|
440
|
+
const relevant = this.#inScope(path);
|
|
441
|
+
if (relevant)
|
|
442
|
+
this.#events.add(this.#reported(path));
|
|
443
|
+
if (relevant || filename === undefined)
|
|
444
|
+
this.#scheduleFlush();
|
|
445
|
+
}
|
|
446
|
+
/** The file a target links to changed: report the target. */
|
|
447
|
+
#onLinkedFileEvent(target) {
|
|
448
|
+
if (this.#closed)
|
|
449
|
+
return;
|
|
450
|
+
this.#events.add(target);
|
|
451
|
+
this.#scheduleFlush();
|
|
452
|
+
}
|
|
453
|
+
#inScope(path) {
|
|
454
|
+
for (const target of this.#targets) {
|
|
455
|
+
if (isWithin(target.path, path))
|
|
456
|
+
return true;
|
|
457
|
+
if (!isWithin(path, target.path) || path === target.path)
|
|
458
|
+
continue;
|
|
459
|
+
const components = relative(target.path, path).split(sep);
|
|
460
|
+
if (!target.recursive && components.length > 1)
|
|
461
|
+
continue;
|
|
462
|
+
if (components.some((name) => excluded(target, name)))
|
|
463
|
+
continue;
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */
|
|
470
|
+
async function anyUnreliable(paths) {
|
|
471
|
+
if (process.platform !== "linux" && process.platform !== "android")
|
|
472
|
+
return false;
|
|
473
|
+
for (const path of paths) {
|
|
474
|
+
for (const candidate of [path, ...ancestorsOf(path)]) {
|
|
475
|
+
const info = await statfs(candidate).catch(() => undefined);
|
|
476
|
+
if (info === undefined)
|
|
477
|
+
continue;
|
|
478
|
+
if (UNRELIABLE_FILE_SYSTEMS.has(info.type))
|
|
479
|
+
return true;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
//# sourceMappingURL=node-watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-watch.js","sourceRoot":"","sources":["../../src/env/node-watch.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { type FSWatcher, watch as fsWatch, type Stats } from \"node:fs\";\nimport { lstat, readdir, readFile, stat, statfs } from \"node:fs/promises\";\nimport { dirname, relative, sep } from \"node:path\";\nimport type { Context } from \"@ny4n/chord\";\nimport { FileError, type FileWatcher, type WatchChange, type WatchTarget } from \"./index.ts\";\n\n/** How `NodeExecutionEnv` watches. */\nexport interface NodeWatchOptions {\n\t/**\n\t * Force a mode. By default `polling` is chosen on Windows, where native watchers keep directories open and so block\n\t * renaming their parents, and for file systems that do not report remote changes.\n\t */\n\tmode?: \"native\" | \"polling\";\n\t/** Interval between snapshots in `polling` mode; default 2000 ms. */\n\tpollIntervalMs?: number;\n\t/** Most directories one watcher covers; default 10,000. */\n\tmaxDirectories?: number;\n}\n\ntype ResolvedTarget = {\n\treadonly path: string;\n\treadonly recursive: boolean;\n\treadonly hidden: boolean;\n\treadonly names: ReadonlySet<string>;\n};\n\n/** What a snapshot remembers of one path. Directories and ancestors are compared by identity only. */\ntype Entry = {\n\treadonly kind: \"file\" | \"directory\" | \"symlink\" | \"other\";\n\treadonly dev: number;\n\treadonly ino: number;\n\treadonly size: number;\n\treadonly mtimeMs: number;\n\treadonly hash?: string;\n};\n\ntype Snapshot = Map<string, Entry>;\n\n/** A native watcher and the identity of what it was installed on: a path replaced by another file needs a new one. */\ntype Installed = { readonly watcher: FSWatcher; readonly dev: number; readonly ino: number };\n\n/** A scan: the snapshot, and targets that are symbolic links to files, whose files need their own watchers. */\ntype Scan = { readonly snapshot: Snapshot; readonly linkedFiles: ReadonlySet<string> };\n\nconst DEBOUNCE_MS = 50;\n/**\n * On macOS, `fs.watch` returns before libuv's FSEvents stream is live, and changes in between are never reported\n * (about 2% of changes made right after the call, none after 100 ms). A rescan this long after installing watchers\n * catches them.\n */\nconst FSEVENTS_SETTLE_MS = 500;\nconst DEFAULT_POLL_MS = 2000;\nconst DEFAULT_MAX_DIRECTORIES = 10_000;\n/** In polling mode, recently modified small files are also compared by content: a second write within the file system's\n * timestamp granularity can keep size and modification time. */\nconst HASH_MAX_BYTES = 256 * 1024;\nconst HASH_RECENT_MS = 5000;\n\n/** Linux `statfs` magic numbers of file systems that accept watches but do not report changes made elsewhere. */\nconst UNRELIABLE_FILE_SYSTEMS = new Set([\n\t0x6969, // NFS\n\t0x517b, // SMB\n\t0xff534d42, // CIFS\n\t0xfe534d42, // SMB2\n\t0x65735546, // FUSE (sshfs, Android shared storage)\n\t0x01021997, // 9P (WSL2 Windows drives)\n\t0x0bd00bd0, // Lustre\n\t0x47504653, // GPFS\n\t0x00c36400, // Ceph\n\t0x5346414f, // OpenAFS\n\t0x6b414653, // kAFS\n\t0x5dca2df5, // sdcardfs\n]);\n\nclass BudgetExceeded extends Error {}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n\treturn error instanceof Error && \"code\" in error;\n}\n\nfunction isDenied(error: unknown): boolean {\n\treturn isNodeError(error) && (error.code === \"EACCES\" || error.code === \"EPERM\");\n}\n\nfunction entryOf(stats: Stats, hash?: string): Entry {\n\tconst kind = stats.isFile()\n\t\t? \"file\"\n\t\t: stats.isDirectory()\n\t\t\t? \"directory\"\n\t\t\t: stats.isSymbolicLink()\n\t\t\t\t? \"symlink\"\n\t\t\t\t: \"other\";\n\treturn {\n\t\tkind,\n\t\tdev: stats.dev,\n\t\tino: stats.ino,\n\t\tsize: kind === \"directory\" ? 0 : stats.size,\n\t\tmtimeMs: kind === \"directory\" ? 0 : stats.mtimeMs,\n\t\t...(hash === undefined ? {} : { hash }),\n\t};\n}\n\nfunction sameEntry(a: Entry, b: Entry): boolean {\n\treturn (\n\t\ta.kind === b.kind &&\n\t\ta.dev === b.dev &&\n\t\ta.ino === b.ino &&\n\t\ta.size === b.size &&\n\t\ta.mtimeMs === b.mtimeMs &&\n\t\ta.hash === b.hash\n\t);\n}\n\n/** Ancestors of `path`, nearest first, up to the root. */\nfunction ancestorsOf(path: string): string[] {\n\tconst result: string[] = [];\n\tfor (let current = dirname(path); ; current = dirname(current)) {\n\t\tresult.push(current);\n\t\tif (dirname(current) === current) return result;\n\t}\n}\n\n/** Whether `path` is `ancestor` or below it. */\nfunction isWithin(path: string, ancestor: string): boolean {\n\treturn path === ancestor || path.startsWith(ancestor.endsWith(sep) ? ancestor : ancestor + sep);\n}\n\nfunction excluded(target: ResolvedTarget, name: string): boolean {\n\treturn (target.hidden && name.startsWith(\".\")) || target.names.has(name);\n}\n\n/**\n * Watches by snapshots: native events only trigger a debounced rescan, and changes are the difference between\n * snapshots plus the event paths. A replaced file, a renamed or recreated ancestor, or a directory created with its\n * contents therefore never depends on which events an operating system sends. New directories get their watchers\n * before they are scanned again, so nothing written into them before the watcher existed is missed.\n */\nexport class NodeFileWatcher implements FileWatcher {\n\treadonly #targets: readonly ResolvedTarget[];\n\treadonly #onChange: (change: WatchChange) => void;\n\treadonly #pollIntervalMs: number;\n\treadonly #maxDirectories: number;\n\treadonly #watchers = new Map<string, Installed>();\n\treadonly #events = new Set<string>();\n\t#mode: \"native\" | \"polling\";\n\t#snapshot: Snapshot = new Map();\n\t#timer: ReturnType<typeof setTimeout> | undefined;\n\t#settleTimer: ReturnType<typeof setTimeout> | undefined;\n\t#running: Promise<void> | undefined;\n\t#dirty = false;\n\t#closed = false;\n\n\tprivate constructor(\n\t\ttargets: readonly ResolvedTarget[],\n\t\tonChange: (change: WatchChange) => void,\n\t\tmode: \"native\" | \"polling\",\n\t\toptions: NodeWatchOptions,\n\t) {\n\t\tthis.#targets = targets;\n\t\tthis.#onChange = onChange;\n\t\tthis.#mode = mode;\n\t\tthis.#pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_MS;\n\t\tthis.#maxDirectories = options.maxDirectories ?? DEFAULT_MAX_DIRECTORIES;\n\t}\n\n\t/** Establish coverage: watchers first, then the snapshot later changes are compared with. */\n\tstatic async open(\n\t\ttargets: readonly WatchTarget[],\n\t\tresolvePath: (path: string) => string,\n\t\tonChange: (change: WatchChange) => void,\n\t\toptions: NodeWatchOptions,\n\t): Promise<NodeFileWatcher> {\n\t\tconst resolved = targets.map((target) => ({\n\t\t\tpath: resolvePath(target.path),\n\t\t\trecursive: target.recursive === true,\n\t\t\thidden: target.exclude?.hidden === true,\n\t\t\tnames: new Set(target.exclude?.names ?? []),\n\t\t}));\n\t\t// Windows refuses to rename a directory while another directory below it is open, and a native watcher keeps\n\t\t// every watched directory open: watching would break renames of their parents, so Windows polls.\n\t\tconst mode =\n\t\t\toptions.mode ??\n\t\t\t(process.platform === \"win32\" || (await anyUnreliable(resolved.map((target) => target.path)))\n\t\t\t\t? \"polling\"\n\t\t\t\t: \"native\");\n\t\tconst watcher = new NodeFileWatcher(resolved, onChange, mode, options);\n\t\ttry {\n\t\t\tawait watcher.#sync(false);\n\t\t} catch (error) {\n\t\t\twatcher.#stop();\n\t\t\tif (error instanceof BudgetExceeded) throw new FileError(\"invalid\", error.message);\n\t\t\tthrow error;\n\t\t}\n\t\twatcher.#schedulePoll();\n\t\treturn watcher;\n\t}\n\n\tget mode(): \"native\" | \"polling\" {\n\t\treturn this.#mode;\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.#closed) return;\n\t\tthis.#stop();\n\t\tawait this.#running;\n\t}\n\n\t#stop(): void {\n\t\tthis.#closed = true;\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = undefined;\n\t\tfor (const { watcher } of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t}\n\n\t#deliver(change: WatchChange): void {\n\t\tif (this.#closed) return;\n\t\ttry {\n\t\t\tthis.#onChange(change);\n\t\t} catch {\n\t\t\t// A throwing callback must not stop watching.\n\t\t}\n\t}\n\n\t#schedulePoll(): void {\n\t\tif (this.#closed || this.#mode !== \"polling\") return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush().finally(() => this.#schedulePoll());\n\t\t}, this.#pollIntervalMs);\n\t}\n\n\t#scheduleFlush(): void {\n\t\tif (this.#closed || this.#timer !== undefined) return;\n\t\tthis.#timer = setTimeout(() => {\n\t\t\tthis.#timer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, DEBOUNCE_MS);\n\t}\n\n\t#scheduleSettle(): void {\n\t\tif (this.#closed) return;\n\t\tclearTimeout(this.#settleTimer);\n\t\tthis.#settleTimer = setTimeout(() => {\n\t\t\tthis.#settleTimer = undefined;\n\t\t\tvoid this.#flush();\n\t\t}, FSEVENTS_SETTLE_MS);\n\t}\n\n\t#flush(): Promise<void> {\n\t\tif (this.#running !== undefined) {\n\t\t\tthis.#dirty = true;\n\t\t\treturn this.#running;\n\t\t}\n\t\tthis.#running = (async () => {\n\t\t\ttry {\n\t\t\t\tdo {\n\t\t\t\t\tthis.#dirty = false;\n\t\t\t\t\tconst events = [...this.#events];\n\t\t\t\t\tthis.#events.clear();\n\t\t\t\t\tconst changed = await this.#sync(true);\n\t\t\t\t\tfor (const path of events) changed.add(path);\n\t\t\t\t\tif (changed.size > 0) this.#deliver({ paths: [...changed].sort() });\n\t\t\t\t} while (this.#dirty && !this.#closed);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tconst fileError =\n\t\t\t\t\terror instanceof FileError\n\t\t\t\t\t\t? error\n\t\t\t\t\t\t: new FileError(isDenied(error) ? \"permission_denied\" : \"invalid\", message);\n\t\t\t\tthis.#deliver({ error: fileError });\n\t\t\t\tthis.#stop();\n\t\t\t} finally {\n\t\t\t\tthis.#running = undefined;\n\t\t\t}\n\t\t})();\n\t\treturn this.#running;\n\t}\n\n\t/** Rescan, report differences, and install watchers for new directories, rescanning until none are new. */\n\tasync #sync(report: boolean): Promise<Set<string>> {\n\t\tconst changed = new Set<string>();\n\t\tfor (let round = 0; round < 10 && !this.#closed; round++) {\n\t\t\tconst scan = await this.#scan();\n\t\t\t// Closed during the scan: installing watchers now would leak them.\n\t\t\tif (this.#closed) break;\n\t\t\tconst next = scan.snapshot;\n\t\t\tif (report) for (const path of this.#diff(this.#snapshot, next)) changed.add(path);\n\t\t\tthis.#snapshot = next;\n\t\t\tif (this.#mode === \"polling\" || !this.#reconcileWatchers(scan)) break;\n\t\t\tif (process.platform === \"darwin\") this.#scheduleSettle();\n\t\t\t// Something written into a new directory before its watcher existed shows up in the next round.\n\t\t\treport = true;\n\t\t}\n\t\treturn changed;\n\t}\n\n\t#diff(previous: Snapshot, next: Snapshot): string[] {\n\t\tconst changed: string[] = [];\n\t\tfor (const [path, entry] of next) {\n\t\t\tconst before = previous.get(path);\n\t\t\tif (before === undefined || !sameEntry(before, entry)) changed.push(this.#reported(path));\n\t\t}\n\t\tfor (const path of previous.keys()) if (!next.has(path)) changed.push(this.#reported(path));\n\t\treturn changed;\n\t}\n\n\t/** An ancestor that changed identity moved every target below it; report those targets. */\n\t#reported(path: string): string {\n\t\tif (this.#targets.some((target) => isWithin(path, target.path))) return path;\n\t\treturn this.#targets.find((target) => isWithin(target.path, path))?.path ?? path;\n\t}\n\n\tasync #scan(): Promise<Scan> {\n\t\tconst snapshot: Snapshot = new Map();\n\t\tconst linkedFiles = new Set<string>();\n\t\t// Directories are counted once, but traversed once per target: overlapping targets differ in recursion and\n\t\t// exclusions, and an entry recorded for one target must still be descended into for another.\n\t\tconst counted = new Set<string>();\n\t\tconst traversed = new Set<string>();\n\t\t/** Kinds of listed entries, not following links: a target that links to a directory is recorded by `stat`, but\n\t\t * symbolic links below a recursive target are not followed. */\n\t\tconst listed = new Map<string, Entry[\"kind\"]>();\n\t\tconst countDirectory = (path: string): void => {\n\t\t\tcounted.add(path);\n\t\t\tif (counted.size > this.#maxDirectories) {\n\t\t\t\tthrow new BudgetExceeded(`Watched paths exceed ${this.#maxDirectories} directories`);\n\t\t\t}\n\t\t};\n\t\tconst record = async (path: string, stats: Stats): Promise<void> => {\n\t\t\tlet hash: string | undefined;\n\t\t\tif (\n\t\t\t\tthis.#mode === \"polling\" &&\n\t\t\t\tstats.isFile() &&\n\t\t\t\tstats.size <= HASH_MAX_BYTES &&\n\t\t\t\tDate.now() - stats.mtimeMs < HASH_RECENT_MS\n\t\t\t) {\n\t\t\t\thash = await readFile(path).then(\n\t\t\t\t\t(content) => createHash(\"sha256\").update(content).digest(\"hex\"),\n\t\t\t\t\t() => undefined,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsnapshot.set(path, entryOf(stats, hash));\n\t\t};\n\t\tconst scanDirectory = async (index: number, target: ResolvedTarget, directory: string): Promise<void> => {\n\t\t\tconst key = `${index}\\0${directory}`;\n\t\t\tif (traversed.has(key)) return;\n\t\t\ttraversed.add(key);\n\t\t\tlet names: string[];\n\t\t\ttry {\n\t\t\t\tnames = await readdir(directory);\n\t\t\t} catch (error) {\n\t\t\t\t// The watched directory itself must be readable; below it, unreadable directories are skipped.\n\t\t\t\tif (directory === target.path && isDenied(error)) throw error;\n\t\t\t\tif (isNodeError(error) && [\"ENOENT\", \"EACCES\", \"EPERM\", \"ENOTDIR\"].includes(error.code ?? \"\")) return;\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tfor (const name of names) {\n\t\t\t\tif (excluded(target, name)) continue;\n\t\t\t\tconst path = `${directory.endsWith(sep) ? directory : directory + sep}${name}`;\n\t\t\t\tlet kind = listed.get(path);\n\t\t\t\tif (kind === undefined) {\n\t\t\t\t\tconst stats = await lstat(path).catch(() => undefined);\n\t\t\t\t\tif (stats === undefined) continue;\n\t\t\t\t\tkind = entryOf(stats).kind;\n\t\t\t\t\tlisted.set(path, kind);\n\t\t\t\t\t// A target's own entry (following links) wins over its listing by another target.\n\t\t\t\t\tif (!snapshot.has(path)) await record(path, stats);\n\t\t\t\t}\n\t\t\t\tif (target.recursive && kind === \"directory\") {\n\t\t\t\t\tcountDirectory(path);\n\t\t\t\t\tawait scanDirectory(index, target, path);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t\tfor (const [index, target] of this.#targets.entries()) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.has(ancestor)) continue;\n\t\t\t\tconst stats = await lstat(ancestor).catch(() => undefined);\n\t\t\t\t// Identity only: an ancestor's own timestamps change with every unrelated sibling.\n\t\t\t\tif (stats !== undefined) snapshot.set(ancestor, { ...entryOf(stats), size: 0, mtimeMs: 0 });\n\t\t\t}\n\t\t\t// The target itself may be a symbolic link to what is watched; follow it. A missing target is watched for its\n\t\t\t// creation; one that cannot be reached for lack of permission fails.\n\t\t\tlet stats: Stats;\n\t\t\ttry {\n\t\t\t\tstats = await stat(target.path);\n\t\t\t} catch (error) {\n\t\t\t\tif (isDenied(error)) throw error;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tawait record(target.path, stats);\n\t\t\tif (stats.isFile() && (await lstat(target.path).catch(() => undefined))?.isSymbolicLink()) {\n\t\t\t\tlinkedFiles.add(target.path);\n\t\t\t}\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\tcountDirectory(target.path);\n\t\t\t\tawait scanDirectory(index, target, target.path);\n\t\t\t}\n\t\t}\n\t\treturn { snapshot, linkedFiles };\n\t}\n\n\t/**\n\t * Watch every existing ancestor of each target, each target directory, each target that is a symbolic link to a file\n\t * (changes to that file are not events of the link's directory), and on Linux each directory below a recursive target\n\t * (elsewhere one recursive watcher per target). Returns whether a watcher was added.\n\t */\n\t#reconcileWatchers({ snapshot, linkedFiles }: Scan): boolean {\n\t\tconst wanted = new Map<string, boolean>();\n\t\tfor (const path of linkedFiles) wanted.set(path, false);\n\t\tconst perDirectory = process.platform === \"linux\" || process.platform === \"android\";\n\t\tfor (const target of this.#targets) {\n\t\t\tfor (const ancestor of ancestorsOf(target.path)) {\n\t\t\t\tif (snapshot.get(ancestor)?.kind === \"directory\" && !wanted.has(ancestor)) wanted.set(ancestor, false);\n\t\t\t}\n\t\t\tif (snapshot.get(target.path)?.kind !== \"directory\") continue;\n\t\t\tconst recursiveRoot = target.recursive && !perDirectory;\n\t\t\twanted.set(target.path, recursiveRoot);\n\t\t\tif (target.recursive && perDirectory) {\n\t\t\t\tfor (const [path, entry] of snapshot) {\n\t\t\t\t\tif (entry.kind === \"directory\" && path !== target.path && isWithin(path, target.path)) {\n\t\t\t\t\t\twanted.set(path, false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const [path, installed] of this.#watchers) {\n\t\t\tconst entry = snapshot.get(path);\n\t\t\t// Gone, or replaced: a watcher follows the directory it was installed on, not the path.\n\t\t\tif (!wanted.has(path) || entry === undefined || entry.dev !== installed.dev || entry.ino !== installed.ino) {\n\t\t\t\tinstalled.watcher.close();\n\t\t\t\tthis.#watchers.delete(path);\n\t\t\t}\n\t\t}\n\t\tlet added = false;\n\t\tfor (const [path, recursive] of wanted) {\n\t\t\tconst entry = snapshot.get(path);\n\t\t\tif (this.#watchers.has(path) || entry === undefined) continue;\n\t\t\tconst linked = linkedFiles.has(path);\n\t\t\ttry {\n\t\t\t\tconst watcher = fsWatch(path, { recursive, persistent: false }, (_event, filename) =>\n\t\t\t\t\tlinked\n\t\t\t\t\t\t? this.#onLinkedFileEvent(path)\n\t\t\t\t\t\t: this.#onEvent(path, filename === null ? undefined : String(filename)),\n\t\t\t\t);\n\t\t\t\t// A watched directory that disappears or fails: rescan, which also replaces the watcher.\n\t\t\t\twatcher.on(\"error\", () => {\n\t\t\t\t\twatcher.close();\n\t\t\t\t\tif (this.#watchers.get(path)?.watcher === watcher) this.#watchers.delete(path);\n\t\t\t\t\tthis.#scheduleFlush();\n\t\t\t\t});\n\t\t\t\tthis.#watchers.set(path, { watcher, dev: entry.dev, ino: entry.ino });\n\t\t\t\tadded = true;\n\t\t\t} catch (error) {\n\t\t\t\t// Out of watches or unsupported: compare snapshots from now on, and say coverage was uncertain.\n\t\t\t\tif (isNodeError(error) && error.code !== \"ENOENT\" && error.code !== \"EACCES\" && error.code !== \"EPERM\") {\n\t\t\t\t\tthis.#switchToPolling();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn added;\n\t}\n\n\t#switchToPolling(): void {\n\t\tif (this.#mode === \"polling\") return;\n\t\tthis.#mode = \"polling\";\n\t\tfor (const { watcher } of this.#watchers.values()) watcher.close();\n\t\tthis.#watchers.clear();\n\t\tthis.#deliver({ overflow: true });\n\t\tclearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#schedulePoll();\n\t}\n\n\t#onEvent(directory: string, filename: string | undefined): void {\n\t\tif (this.#closed) return;\n\t\tconst path =\n\t\t\tfilename === undefined ? directory : `${directory.endsWith(sep) ? directory : directory + sep}${filename}`;\n\t\t// Events about unrelated siblings of an ancestor, or about excluded entries, are ignored.\n\t\tconst relevant = this.#inScope(path);\n\t\tif (relevant) this.#events.add(this.#reported(path));\n\t\tif (relevant || filename === undefined) this.#scheduleFlush();\n\t}\n\n\t/** The file a target links to changed: report the target. */\n\t#onLinkedFileEvent(target: string): void {\n\t\tif (this.#closed) return;\n\t\tthis.#events.add(target);\n\t\tthis.#scheduleFlush();\n\t}\n\n\t#inScope(path: string): boolean {\n\t\tfor (const target of this.#targets) {\n\t\t\tif (isWithin(target.path, path)) return true;\n\t\t\tif (!isWithin(path, target.path) || path === target.path) continue;\n\t\t\tconst components = relative(target.path, path).split(sep);\n\t\t\tif (!target.recursive && components.length > 1) continue;\n\t\t\tif (components.some((name) => excluded(target, name))) continue;\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n}\n\n/** Whether any path, or its nearest existing ancestor, is on a file system that does not report remote changes. */\nasync function anyUnreliable(paths: readonly string[]): Promise<boolean> {\n\tif (process.platform !== \"linux\" && process.platform !== \"android\") return false;\n\tfor (const path of paths) {\n\t\tfor (const candidate of [path, ...ancestorsOf(path)]) {\n\t\t\tconst info = await statfs(candidate).catch(() => undefined);\n\t\t\tif (info === undefined) continue;\n\t\t\tif (UNRELIABLE_FILE_SYSTEMS.has(info.type)) return true;\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn false;\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Context } from "@ny4n/chord";
|
|
2
|
+
import { type BinaryReader, type DirReader, type ExecutionEnv, ExecutionError, FileError, type FileInfo, type FileWatcher, type Result, type ShellExecOptions, type ShellExecResult, type TextLineReader, type WatchChange, type WatchTarget } from "./index.ts";
|
|
3
|
+
import { type NodeWatchOptions } from "./node-watch.ts";
|
|
4
|
+
export declare class NodeExecutionEnv implements ExecutionEnv {
|
|
5
|
+
/** Every local environment sees the same files. */
|
|
6
|
+
readonly id: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
private shellPath?;
|
|
9
|
+
private shellEnv?;
|
|
10
|
+
private watchOptions;
|
|
11
|
+
private activeChildPids;
|
|
12
|
+
constructor(options: {
|
|
13
|
+
cwd: string;
|
|
14
|
+
shellPath?: string;
|
|
15
|
+
shellEnv?: NodeJS.ProcessEnv;
|
|
16
|
+
watch?: NodeWatchOptions;
|
|
17
|
+
});
|
|
18
|
+
watch(targets: readonly WatchTarget[], onChange: (change: WatchChange) => void, context: Context): Promise<Result<FileWatcher, FileError>>;
|
|
19
|
+
absolutePath(path: string, _context: Context): Promise<Result<string, FileError>>;
|
|
20
|
+
joinPath(parts: string[], _context: Context): Promise<Result<string, FileError>>;
|
|
21
|
+
exec(command: string | readonly string[], options: ShellExecOptions | undefined, context: Context): Promise<Result<ShellExecResult, ExecutionError>>;
|
|
22
|
+
openTextLineReader(path: string, context: Context): Promise<Result<TextLineReader, FileError>>;
|
|
23
|
+
readTextFile(path: string, context: Context): Promise<Result<string, FileError>>;
|
|
24
|
+
readTextLines(path: string, options: {
|
|
25
|
+
maxLines?: number;
|
|
26
|
+
} | undefined, context: Context): Promise<Result<string[], FileError>>;
|
|
27
|
+
readBinaryFile(path: string, context: Context): Promise<Result<Uint8Array, FileError>>;
|
|
28
|
+
openBinaryReader(path: string, options: {
|
|
29
|
+
noFollow?: boolean;
|
|
30
|
+
} | undefined, context: Context): Promise<Result<BinaryReader, FileError>>;
|
|
31
|
+
writeFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;
|
|
32
|
+
appendFile(path: string, content: string | Uint8Array, context: Context): Promise<Result<void, FileError>>;
|
|
33
|
+
truncateFile(path: string, size: number, context: Context): Promise<Result<void, FileError>>;
|
|
34
|
+
flushFile(path: string, context: Context): Promise<Result<void, FileError>>;
|
|
35
|
+
renameFile(sourcePath: string, destinationPath: string, context: Context): Promise<Result<void, FileError>>;
|
|
36
|
+
fileInfo(path: string, context: Context): Promise<Result<FileInfo, FileError>>;
|
|
37
|
+
listDir(path: string, context: Context): Promise<Result<FileInfo[], FileError>>;
|
|
38
|
+
openDirReader(path: string, context: Context): Promise<Result<DirReader, FileError>>;
|
|
39
|
+
canonicalPath(path: string, context: Context): Promise<Result<string, FileError>>;
|
|
40
|
+
exists(path: string, context: Context): Promise<Result<boolean, FileError>>;
|
|
41
|
+
createDir(path: string, options: {
|
|
42
|
+
recursive?: boolean;
|
|
43
|
+
} | undefined, context: Context): Promise<Result<void, FileError>>;
|
|
44
|
+
remove(path: string, options: {
|
|
45
|
+
recursive?: boolean;
|
|
46
|
+
force?: boolean;
|
|
47
|
+
} | undefined, context: Context): Promise<Result<void, FileError>>;
|
|
48
|
+
createTempDir(prefix: string | undefined, context: Context): Promise<Result<string, FileError>>;
|
|
49
|
+
createTempFile(options: {
|
|
50
|
+
prefix?: string;
|
|
51
|
+
suffix?: string;
|
|
52
|
+
} | undefined, context: Context): Promise<Result<string, FileError>>;
|
|
53
|
+
cleanup(_context: Context): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export type { NodeWatchOptions } from "./node-watch.ts";
|
|
56
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/env/node.ts"],"names":[],"mappings":"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"}
|