@j0hanz/filesystem-mcp 2.2.0 → 2.4.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/README.md +38 -32
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +21 -40
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -46
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +5 -17
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +32 -40
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/cursor.d.ts +1 -12
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +2 -12
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/errors.d.ts +6 -11
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +62 -117
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +7 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +8 -4
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +0 -11
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +1 -19
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +34 -4
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +151 -12
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -2
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +12 -28
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +19 -29
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +58 -73
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/observability.d.ts +9 -0
- package/dist/core/observability.d.ts.map +1 -1
- package/dist/core/observability.js +13 -2
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts +6 -9
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +28 -55
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +3 -4
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +28 -0
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +57 -2
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +3 -8
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +8 -9
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +3 -22
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +46 -96
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +8 -9
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +11 -14
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +21 -23
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts +4 -2
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +262 -45
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +41 -16
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +147 -82
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +3 -2
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +5 -6
- package/dist/core/util.js.map +1 -1
- package/dist/core/watcher-registry.d.ts +1 -1
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +9 -5
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/index.js +5 -6
- package/dist/index.js.map +1 -1
- package/dist/instructions.js +2 -2
- package/dist/instructions.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +11 -19
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +51 -62
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +4 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +15 -11
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +2 -2
- package/dist/tools/batch.js +3 -3
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +168 -17
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +15 -9
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +48 -94
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +23 -23
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +7 -37
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +101 -92
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +18 -37
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +14 -13
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-roots.d.ts +1 -1
- package/dist/tools/list-roots.js +3 -3
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.d.ts +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +23 -29
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +31 -45
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts +1 -1
- package/dist/tools/patch.js +7 -7
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +17 -28
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +42 -94
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +10 -12
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +32 -49
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +82 -45
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +9 -29
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.js +4 -4
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts +2 -2
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +21 -18
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +2 -2
- package/dist/transport/shared.js +2 -2
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +4 -3
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +7 -6
- package/dist/transport/stdio.js.map +1 -1
- package/dist/transport.d.ts +2 -2
- package/dist/transport.js +2 -2
- package/dist/transport.js.map +1 -1
- package/package.json +6 -13
- package/dist/core/page-store.d.ts +0 -41
- package/dist/core/page-store.d.ts.map +0 -1
- package/dist/core/page-store.js +0 -74
- package/dist/core/page-store.js.map +0 -1
- package/dist/core/primitives.d.ts +0 -34
- package/dist/core/primitives.d.ts.map +0 -1
- package/dist/core/primitives.js +0 -63
- package/dist/core/primitives.js.map +0 -1
package/dist/core/concurrency.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
|
-
import { normalizeUnknownError } from
|
|
3
|
-
import { PARALLEL_CONCURRENCY } from
|
|
2
|
+
import { normalizeUnknownError } from "./errors.js";
|
|
3
|
+
import { PARALLEL_CONCURRENCY } from "./util.js";
|
|
4
4
|
export const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();
|
|
5
5
|
/**
|
|
6
6
|
* The subset `search_text` and `find_files` can actually emit. Their scans call
|
|
@@ -9,30 +9,6 @@ export const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout'])
|
|
|
9
9
|
* both tools' own descriptions.
|
|
10
10
|
*/
|
|
11
11
|
export const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();
|
|
12
|
-
/**
|
|
13
|
-
* Accumulates why an enumeration stopped early. maxResults wins over maxFiles
|
|
14
|
-
* wins over timeout (the most specific cap is the definite cause even if the
|
|
15
|
-
* abort also fired on the same iteration). Call `resolve()` once at the end.
|
|
16
|
-
*/
|
|
17
|
-
export class StopReasonTracker {
|
|
18
|
-
#reason;
|
|
19
|
-
hitMaxResults() {
|
|
20
|
-
this.#reason = 'maxResults';
|
|
21
|
-
}
|
|
22
|
-
hitMaxFiles() {
|
|
23
|
-
if (this.#reason !== 'maxResults')
|
|
24
|
-
this.#reason = 'maxFiles';
|
|
25
|
-
}
|
|
26
|
-
hitAbort() {
|
|
27
|
-
this.#reason ??= 'timeout';
|
|
28
|
-
}
|
|
29
|
-
get truncated() {
|
|
30
|
-
return this.#reason !== undefined;
|
|
31
|
-
}
|
|
32
|
-
resolve() {
|
|
33
|
-
return this.#reason;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
12
|
export async function processInParallel(items, processor, concurrency = PARALLEL_CONCURRENCY, signal) {
|
|
37
13
|
const itemCount = items.length;
|
|
38
14
|
if (itemCount === 0)
|
|
@@ -41,9 +17,17 @@ export async function processInParallel(items, processor, concurrency = PARALLEL
|
|
|
41
17
|
const errors = [];
|
|
42
18
|
signal?.throwIfAborted();
|
|
43
19
|
let nextIndex = 0;
|
|
20
|
+
// Set when a worker saw the signal fire before an item ran — the only
|
|
21
|
+
// shape in which the batch is genuinely incomplete. The annotation
|
|
22
|
+
// matters: without it control-flow analysis keeps the `false` narrowing
|
|
23
|
+
// from the initializer and cannot see the closure assignment.
|
|
24
|
+
let truncated = false;
|
|
44
25
|
const next = async () => {
|
|
45
26
|
while (nextIndex < itemCount) {
|
|
46
|
-
signal?.
|
|
27
|
+
if (signal?.aborted) {
|
|
28
|
+
truncated = true;
|
|
29
|
+
signal.throwIfAborted();
|
|
30
|
+
}
|
|
47
31
|
const index = nextIndex;
|
|
48
32
|
nextIndex += 1;
|
|
49
33
|
const item = items[index];
|
|
@@ -51,8 +35,11 @@ export async function processInParallel(items, processor, concurrency = PARALLEL
|
|
|
51
35
|
continue;
|
|
52
36
|
}
|
|
53
37
|
try {
|
|
38
|
+
// No abort check after the processor: once an item has run, its
|
|
39
|
+
// result is real work that a write caller may have committed to
|
|
40
|
+
// disk. Discarding it here would report a finished append as
|
|
41
|
+
// failed, and a client retry would append twice.
|
|
54
42
|
const value = await processor(item);
|
|
55
|
-
signal?.throwIfAborted();
|
|
56
43
|
results.push({ index, value });
|
|
57
44
|
}
|
|
58
45
|
catch (error) {
|
|
@@ -70,10 +57,14 @@ export async function processInParallel(items, processor, concurrency = PARALLEL
|
|
|
70
57
|
}
|
|
71
58
|
await Promise.allSettled(workers);
|
|
72
59
|
// A deadline (AbortSignal.timeout) hit during the run surfaces here as
|
|
73
|
-
// signal.reason — a TimeoutError — rather than a fresh AbortError, so
|
|
74
|
-
// see TIMEOUT, not CANCELLED.
|
|
75
|
-
//
|
|
76
|
-
|
|
60
|
+
// signal.reason — a TimeoutError — rather than a fresh AbortError, so
|
|
61
|
+
// callers see TIMEOUT, not CANCELLED. Only a run that actually skipped
|
|
62
|
+
// items reports the abort: a deadline firing after the last item
|
|
63
|
+
// finished leaves a complete batch, and returning it beats telling the
|
|
64
|
+
// caller that committed writes failed. The per-item throws above are
|
|
65
|
+
// swallowed by allSettled.
|
|
66
|
+
if (truncated)
|
|
67
|
+
signal?.throwIfAborted();
|
|
77
68
|
results.sort((left, right) => left.index - right.index);
|
|
78
69
|
return { results, errors };
|
|
79
70
|
}
|
|
@@ -84,13 +75,14 @@ export async function processInParallel(items, processor, concurrency = PARALLEL
|
|
|
84
75
|
*
|
|
85
76
|
* `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`
|
|
86
77
|
* lets the caller stop on its own accumulating result count (maxResults). Both
|
|
87
|
-
* are checked before dispatch, so the returned
|
|
88
|
-
*
|
|
78
|
+
* are checked before dispatch, so the returned reason is exactly one — the
|
|
79
|
+
* loop breaks on the first that fires. `undefined` means every entry was
|
|
80
|
+
* dispatched.
|
|
89
81
|
*/
|
|
90
82
|
export async function processEntriesConcurrently(entries, options) {
|
|
91
83
|
const pending = new Set();
|
|
92
84
|
const { signal, concurrency, maxEntries, shouldStop, onEntry, onError, runEntry } = options;
|
|
93
|
-
|
|
85
|
+
let stoppedReason;
|
|
94
86
|
let dispatched = 0;
|
|
95
87
|
const waitForSlot = async () => {
|
|
96
88
|
if (pending.size < concurrency)
|
|
@@ -101,17 +93,17 @@ export async function processEntriesConcurrently(entries, options) {
|
|
|
101
93
|
// The signal is cancellation OR the caller's timeout: stop dispatching and
|
|
102
94
|
// let the caller report the run as incomplete rather than as a full sweep.
|
|
103
95
|
if (signal?.aborted) {
|
|
104
|
-
|
|
96
|
+
stoppedReason = 'timeout';
|
|
105
97
|
break;
|
|
106
98
|
}
|
|
107
99
|
if (maxEntries !== undefined && dispatched >= maxEntries) {
|
|
108
|
-
|
|
100
|
+
stoppedReason = 'maxFiles';
|
|
109
101
|
break;
|
|
110
102
|
}
|
|
111
103
|
// Check the result cap before waiting for a slot so an in-flight task that
|
|
112
104
|
// already crossed the cap stops dispatch without an extra wait...
|
|
113
105
|
if (shouldStop?.()) {
|
|
114
|
-
|
|
106
|
+
stoppedReason = 'maxResults';
|
|
115
107
|
break;
|
|
116
108
|
}
|
|
117
109
|
await waitForSlot();
|
|
@@ -119,7 +111,7 @@ export async function processEntriesConcurrently(entries, options) {
|
|
|
119
111
|
// cap can still be exceeded by at most `concurrency - 1` already-dispatched
|
|
120
112
|
// tasks that are mid-flight; that overrun is inherent to concurrent dispatch.
|
|
121
113
|
if (shouldStop?.()) {
|
|
122
|
-
|
|
114
|
+
stoppedReason = 'maxResults';
|
|
123
115
|
break;
|
|
124
116
|
}
|
|
125
117
|
onEntry();
|
|
@@ -140,7 +132,7 @@ export async function processEntriesConcurrently(entries, options) {
|
|
|
140
132
|
if (pending.size > 0) {
|
|
141
133
|
await Promise.allSettled([...pending]);
|
|
142
134
|
}
|
|
143
|
-
return
|
|
135
|
+
return stoppedReason;
|
|
144
136
|
}
|
|
145
137
|
export function withAbort(promise, signal) {
|
|
146
138
|
if (!signal) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { normalizeUnknownError } from './errors.js';\nimport { PARALLEL_CONCURRENCY } from './util.js';\n\nexport type StoppedReason = 'maxResults' | 'maxFiles' | 'timeout';\n\nexport const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();\n\n/**\n * The subset `search_text` and `find_files` can actually emit. Their scans call\n * only `hitMaxResults` and `hitAbort` (search.ts), never `hitMaxFiles`, so the\n * three-value enum published a value no response can carry and disagreed with\n * both tools' own descriptions.\n */\nexport const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();\n\n/**\n * Accumulates why an enumeration stopped early. maxResults wins over maxFiles\n * wins over timeout (the most specific cap is the definite cause even if the\n * abort also fired on the same iteration). Call `resolve()` once at the end.\n */\nexport class StopReasonTracker {\n #reason?: StoppedReason;\n\n hitMaxResults(): void {\n this.#reason = 'maxResults';\n }\n\n hitMaxFiles(): void {\n if (this.#reason !== 'maxResults') this.#reason = 'maxFiles';\n }\n\n hitAbort(): void {\n this.#reason ??= 'timeout';\n }\n\n get truncated(): boolean {\n return this.#reason !== undefined;\n }\n\n resolve(): StoppedReason | undefined {\n return this.#reason;\n }\n}\n\ninterface ParallelResult<R> {\n results: { index: number; value: R }[];\n errors: { index: number; error: Error }[];\n}\n\nexport async function processInParallel<T, R>(\n items: readonly T[],\n processor: (item: T) => Promise<R>,\n concurrency: number = PARALLEL_CONCURRENCY,\n signal?: AbortSignal,\n): Promise<ParallelResult<R>> {\n const itemCount = items.length;\n if (itemCount === 0) return { results: [], errors: [] };\n\n const results: { index: number; value: R }[] = [];\n const errors: { index: number; error: Error }[] = [];\n\n signal?.throwIfAborted();\n\n let nextIndex = 0;\n\n const next = async (): Promise<void> => {\n while (nextIndex < itemCount) {\n signal?.throwIfAborted();\n\n const index = nextIndex;\n nextIndex += 1;\n const item = items[index];\n if (item === undefined) {\n continue;\n }\n\n try {\n const value = await processor(item);\n signal?.throwIfAborted();\n results.push({ index, value });\n } catch (error) {\n errors.push({\n index,\n error: normalizeUnknownError(error),\n });\n }\n }\n };\n\n const workerCount = Math.min(itemCount, concurrency);\n const workers: Promise<void>[] = new Array<Promise<void>>(workerCount);\n for (let index = 0; index < workerCount; index += 1) {\n workers[index] = next();\n }\n\n await Promise.allSettled(workers);\n // A deadline (AbortSignal.timeout) hit during the run surfaces here as\n // signal.reason — a TimeoutError — rather than a fresh AbortError, so callers\n // see TIMEOUT, not CANCELLED. The per-item throws above are swallowed by\n // allSettled.\n signal?.throwIfAborted();\n\n results.sort((left, right) => left.index - right.index);\n return { results, errors };\n}\n\n/**\n * The streaming sibling of {@link processInParallel}: bounded-concurrency\n * dispatch over an AsyncIterable that may be far larger than the caller wants\n * to walk, so it stops early and reports why.\n *\n * `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`\n * lets the caller stop on its own accumulating result count (maxResults). Both\n * are checked before dispatch, so the returned tracker names exactly one\n * reason — the loop breaks on the first that fires.\n */\nexport async function processEntriesConcurrently(\n entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }>,\n options: {\n signal: AbortSignal | undefined;\n concurrency: number;\n maxEntries?: number;\n shouldStop?: () => boolean;\n onEntry: () => void;\n onError?: (entryPath: string, err: unknown) => void;\n runEntry: (entryPath: string) => Promise<void>;\n },\n): Promise<StopReasonTracker> {\n const pending = new Set<Promise<void>>();\n const { signal, concurrency, maxEntries, shouldStop, onEntry, onError, runEntry } = options;\n const tracker = new StopReasonTracker();\n let dispatched = 0;\n\n const waitForSlot = async (): Promise<void> => {\n if (pending.size < concurrency) return;\n await Promise.race(pending);\n };\n\n for await (const entry of entries) {\n // The signal is cancellation OR the caller's timeout: stop dispatching and\n // let the caller report the run as incomplete rather than as a full sweep.\n if (signal?.aborted) {\n tracker.hitAbort();\n break;\n }\n if (maxEntries !== undefined && dispatched >= maxEntries) {\n tracker.hitMaxFiles();\n break;\n }\n // Check the result cap before waiting for a slot so an in-flight task that\n // already crossed the cap stops dispatch without an extra wait...\n if (shouldStop?.()) {\n tracker.hitMaxResults();\n break;\n }\n await waitForSlot();\n // ...and again after the slot frees, since tasks settle concurrently. The\n // cap can still be exceeded by at most `concurrency - 1` already-dispatched\n // tasks that are mid-flight; that overrun is inherent to concurrent dispatch.\n if (shouldStop?.()) {\n tracker.hitMaxResults();\n break;\n }\n onEntry();\n dispatched++;\n\n // Track a non-rejecting wrapper so a rejected task can never propagate out of\n // Promise.race(pending) in waitForSlot() and abort the loop before the final\n // drain below (which would silently abandon other in-flight tasks).\n // runEntry is expected to catch its own errors; if it unexpectedly throws,\n // record it as a failure rather than silently dropping it.\n const tracked = runEntry(entry.path).catch((err: unknown) => {\n onError?.(entry.path, err);\n });\n pending.add(tracked);\n void tracked.finally(() => {\n pending.delete(tracked);\n });\n }\n\n if (pending.size > 0) {\n await Promise.allSettled([...pending]);\n }\n\n return tracker;\n}\n\nexport function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) {\n return promise;\n }\n signal.throwIfAborted();\n let onAbort: (() => void) | undefined;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n onAbort = () => {\n reject(signal.reason instanceof Error ? signal.reason : new Error('Operation aborted'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]).finally(() => {\n if (onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../src/core/concurrency.ts"],"names":[],"mappings":"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","sourcesContent":["import * as z from 'zod/v4';\n\nimport { normalizeUnknownError } from './errors.ts';\nimport { PARALLEL_CONCURRENCY } from './util.ts';\n\nexport type StoppedReason = 'maxResults' | 'maxFiles' | 'timeout';\n\nexport const StoppedReasonSchema = z.enum(['maxResults', 'maxFiles', 'timeout']).optional();\n\n/**\n * The subset `search_text` and `find_files` can actually emit. Their scans call\n * only `hitMaxResults` and `hitAbort` (search.ts), never `hitMaxFiles`, so the\n * three-value enum published a value no response can carry and disagreed with\n * both tools' own descriptions.\n */\nexport const SearchStoppedReasonSchema = z.enum(['maxResults', 'timeout']).optional();\n\ninterface ParallelResult<R> {\n results: { index: number; value: R }[];\n errors: { index: number; error: Error }[];\n}\n\nexport async function processInParallel<T, R>(\n items: readonly T[],\n processor: (item: T) => Promise<R>,\n concurrency: number = PARALLEL_CONCURRENCY,\n signal?: AbortSignal,\n): Promise<ParallelResult<R>> {\n const itemCount = items.length;\n if (itemCount === 0) return { results: [], errors: [] };\n\n const results: { index: number; value: R }[] = [];\n const errors: { index: number; error: Error }[] = [];\n\n signal?.throwIfAborted();\n\n let nextIndex = 0;\n // Set when a worker saw the signal fire before an item ran — the only\n // shape in which the batch is genuinely incomplete. The annotation\n // matters: without it control-flow analysis keeps the `false` narrowing\n // from the initializer and cannot see the closure assignment.\n let truncated = false as boolean;\n\n const next = async (): Promise<void> => {\n while (nextIndex < itemCount) {\n if (signal?.aborted) {\n truncated = true;\n signal.throwIfAborted();\n }\n\n const index = nextIndex;\n nextIndex += 1;\n const item = items[index];\n if (item === undefined) {\n continue;\n }\n\n try {\n // No abort check after the processor: once an item has run, its\n // result is real work that a write caller may have committed to\n // disk. Discarding it here would report a finished append as\n // failed, and a client retry would append twice.\n const value = await processor(item);\n results.push({ index, value });\n } catch (error) {\n errors.push({\n index,\n error: normalizeUnknownError(error),\n });\n }\n }\n };\n\n const workerCount = Math.min(itemCount, concurrency);\n const workers: Promise<void>[] = new Array<Promise<void>>(workerCount);\n for (let index = 0; index < workerCount; index += 1) {\n workers[index] = next();\n }\n\n await Promise.allSettled(workers);\n // A deadline (AbortSignal.timeout) hit during the run surfaces here as\n // signal.reason — a TimeoutError — rather than a fresh AbortError, so\n // callers see TIMEOUT, not CANCELLED. Only a run that actually skipped\n // items reports the abort: a deadline firing after the last item\n // finished leaves a complete batch, and returning it beats telling the\n // caller that committed writes failed. The per-item throws above are\n // swallowed by allSettled.\n if (truncated) signal?.throwIfAborted();\n\n results.sort((left, right) => left.index - right.index);\n return { results, errors };\n}\n\n/**\n * The streaming sibling of {@link processInParallel}: bounded-concurrency\n * dispatch over an AsyncIterable that may be far larger than the caller wants\n * to walk, so it stops early and reports why.\n *\n * `maxEntries` caps how many entries are dispatched (maxFiles); `shouldStop`\n * lets the caller stop on its own accumulating result count (maxResults). Both\n * are checked before dispatch, so the returned reason is exactly one — the\n * loop breaks on the first that fires. `undefined` means every entry was\n * dispatched.\n */\nexport async function processEntriesConcurrently(\n entries: AsyncIterable<{ path: string }> | Iterable<{ path: string }>,\n options: {\n signal: AbortSignal | undefined;\n concurrency: number;\n maxEntries?: number;\n shouldStop?: () => boolean;\n onEntry: () => void;\n onError?: (entryPath: string, err: unknown) => void;\n runEntry: (entryPath: string) => Promise<void>;\n },\n): Promise<StoppedReason | undefined> {\n const pending = new Set<Promise<void>>();\n const { signal, concurrency, maxEntries, shouldStop, onEntry, onError, runEntry } = options;\n let stoppedReason: StoppedReason | undefined;\n let dispatched = 0;\n\n const waitForSlot = async (): Promise<void> => {\n if (pending.size < concurrency) return;\n await Promise.race(pending);\n };\n\n for await (const entry of entries) {\n // The signal is cancellation OR the caller's timeout: stop dispatching and\n // let the caller report the run as incomplete rather than as a full sweep.\n if (signal?.aborted) {\n stoppedReason = 'timeout';\n break;\n }\n if (maxEntries !== undefined && dispatched >= maxEntries) {\n stoppedReason = 'maxFiles';\n break;\n }\n // Check the result cap before waiting for a slot so an in-flight task that\n // already crossed the cap stops dispatch without an extra wait...\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n await waitForSlot();\n // ...and again after the slot frees, since tasks settle concurrently. The\n // cap can still be exceeded by at most `concurrency - 1` already-dispatched\n // tasks that are mid-flight; that overrun is inherent to concurrent dispatch.\n if (shouldStop?.()) {\n stoppedReason = 'maxResults';\n break;\n }\n onEntry();\n dispatched++;\n\n // Track a non-rejecting wrapper so a rejected task can never propagate out of\n // Promise.race(pending) in waitForSlot() and abort the loop before the final\n // drain below (which would silently abandon other in-flight tasks).\n // runEntry is expected to catch its own errors; if it unexpectedly throws,\n // record it as a failure rather than silently dropping it.\n const tracked = runEntry(entry.path).catch((err: unknown) => {\n onError?.(entry.path, err);\n });\n pending.add(tracked);\n void tracked.finally(() => {\n pending.delete(tracked);\n });\n }\n\n if (pending.size > 0) {\n await Promise.allSettled([...pending]);\n }\n\n return stoppedReason;\n}\n\nexport function withAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {\n if (!signal) {\n return promise;\n }\n signal.throwIfAborted();\n let onAbort: (() => void) | undefined;\n return Promise.race([\n promise,\n new Promise<never>((_, reject) => {\n onAbort = () => {\n reject(signal.reason instanceof Error ? signal.reason : new Error('Operation aborted'));\n };\n signal.addEventListener('abort', onAbort, { once: true });\n }),\n ]).finally(() => {\n if (onAbort) {\n signal.removeEventListener('abort', onAbort);\n }\n });\n}\n"]}
|
package/dist/core/config.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface CliOverrides {
|
|
|
20
20
|
allowMissingRoots?: boolean;
|
|
21
21
|
/** `--deny` entries (merged with DENYLIST by the reader) */
|
|
22
22
|
denyPatterns?: readonly string[];
|
|
23
|
+
/** `--allow` entries (merged with FS_ALLOWLIST by the reader; relieve built-ins only) */
|
|
24
|
+
allowPatterns?: readonly string[];
|
|
23
25
|
}
|
|
24
26
|
/** Written once by cli.ts; an absent key means "not set on the command line". */
|
|
25
27
|
export declare const cli: CliOverrides;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,+NACqL,CAAC;AAE/N,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AACH,eAAO,MAAM,4BAA4B,+NACqL,CAAC;AAE/N,MAAM,WAAW,YAAY;IAC3B,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4DAA4D;IAC5D,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,yFAAyF;IACzF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,iFAAiF;AACjF,eAAO,MAAM,GAAG,EAAE,YAAiB,CAAC"}
|
package/dist/core/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,kDAAkD;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GACvC,4NAA4N,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,gFAAgF;AAChF,kDAAkD;AAElD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,4BAA4B,GACvC,4NAA4N,CAAC;AAqB/N,iFAAiF;AACjF,MAAM,CAAC,MAAM,GAAG,GAAiB,EAAE,CAAC","sourcesContent":["// Resolved CLI-flag overrides — the one owner of the flag-beats-env rule.\n//\n// `parseArgs` (cli.ts) used to write flag values back into `process.env` so\n// deep core readers (path, sensitive, observability, util) would pick them up.\n// That made the environment a hidden mutable config bus. Instead, cli.ts\n// assigns onto `cli` once at startup and each reader consults it first, falling\n// back to the operator's environment. This module deliberately has zero imports\n// so any core module can read it without a cycle.\n\n/**\n * How an operator gives this server a root. Lives here rather than beside its\n * readers because both ends of the repo need it — `index.ts` prints it at\n * startup, `list_roots` returns it when it has nothing to list — and this\n * module is the one place either can import without a cycle.\n */\nexport const NO_POSITIONAL_ROOTS_GUIDANCE =\n 'No positional directories specified. Configure roots with directory arguments, FS_ALLOWED_DIRS, or --allow-cwd. Modern clients with elicitation can also call a tool with a concrete path and approve the requested grant.';\n\nexport interface CliOverrides {\n /** `--log-level` */\n logLevel?: string;\n /** `--max-file-size` (raw string; validated by the reader like the env var) */\n maxFileSize?: string;\n /** `--root-boundary` */\n rootBoundary?: string;\n /** `--allow-sensitive` */\n allowSensitive?: boolean;\n /** `--walk-cwd` */\n allowCwdWalk?: boolean;\n /** `--allow-missing-roots` */\n allowMissingRoots?: boolean;\n /** `--deny` entries (merged with DENYLIST by the reader) */\n denyPatterns?: readonly string[];\n /** `--allow` entries (merged with FS_ALLOWLIST by the reader; relieve built-ins only) */\n allowPatterns?: readonly string[];\n}\n\n/** Written once by cli.ts; an absent key means \"not set on the command line\". */\nexport const cli: CliOverrides = {};\n"]}
|
package/dist/core/cursor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageSnapshotStore } from './
|
|
1
|
+
import type { PageSnapshotStore } from './store.ts';
|
|
2
2
|
/**
|
|
3
3
|
* The full result set a paged tool produced, before slicing. `truncated` is the
|
|
4
4
|
* engine's own stop state — the hard result cap or a timeout cut the set — and
|
|
@@ -24,17 +24,6 @@ export interface PaginatedPage<T, M, R> extends Page<T, M> {
|
|
|
24
24
|
/** The externalized full set. First page only, and only when incomplete. */
|
|
25
25
|
readonly resource?: R;
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
* The single home of the pagination cache-key rule: `JSON.stringify` of a
|
|
29
|
-
* flat object whose `method` names the tool and whose remaining fields
|
|
30
|
-
* identify the query. The literal's field order fixes key stability;
|
|
31
|
-
* `method` keeps tool key spaces disjoint. Byte-identical to the three
|
|
32
|
-
* hand-written tool builders it replaces.
|
|
33
|
-
*/
|
|
34
|
-
export declare function pageQueryKey(query: {
|
|
35
|
-
readonly method: string;
|
|
36
|
-
readonly path: string;
|
|
37
|
-
} & Record<string, unknown>): string;
|
|
38
27
|
/**
|
|
39
28
|
* The one replay-or-produce branch every paged tool runs. A cursor replays a
|
|
40
29
|
* stored snapshot; without one, `produce` runs the query and its result is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAgClE;;;;GAIG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,UAAU,IAAI,CAAC,CAAC,EAAE,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IACxD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE;IAC9C,KAAK,EAAE,iBAAiB,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;CACrE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAwBlC"}
|
package/dist/core/cursor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ErrorCode, FsError } from
|
|
2
|
-
import { invalidCursor } from
|
|
1
|
+
import { ErrorCode, FsError } from "./errors.js";
|
|
2
|
+
import { invalidCursor } from "./store.js";
|
|
3
3
|
function encodePageCursor(cursor) {
|
|
4
4
|
return Buffer.from(JSON.stringify(cursor)).toString('base64url');
|
|
5
5
|
}
|
|
@@ -17,16 +17,6 @@ function pageResult(snapshotId, offset, pageSize, snapshot) {
|
|
|
17
17
|
: undefined,
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
-
/**
|
|
21
|
-
* The single home of the pagination cache-key rule: `JSON.stringify` of a
|
|
22
|
-
* flat object whose `method` names the tool and whose remaining fields
|
|
23
|
-
* identify the query. The literal's field order fixes key stability;
|
|
24
|
-
* `method` keeps tool key spaces disjoint. Byte-identical to the three
|
|
25
|
-
* hand-written tool builders it replaces.
|
|
26
|
-
*/
|
|
27
|
-
export function pageQueryKey(query) {
|
|
28
|
-
return JSON.stringify(query);
|
|
29
|
-
}
|
|
30
20
|
/**
|
|
31
21
|
* The one replay-or-produce branch every paged tool runs. A cursor replays a
|
|
32
22
|
* stored snapshot; without one, `produce` runs the query and its result is
|
package/dist/core/cursor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/core/cursor.ts"],"names":[],"mappings":"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","sourcesContent":["import { ErrorCode, FsError } from './errors.ts';\nimport type { PageSnapshot, PageSnapshotStore } from './store.ts';\nimport { invalidCursor } from './store.ts';\n\ninterface PageCursor {\n readonly snapshotId: string;\n readonly offset: number;\n}\n\nfunction encodePageCursor(cursor: PageCursor): string {\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction pageResult<T, M>(\n snapshotId: string,\n offset: number,\n pageSize: number,\n snapshot: PageSnapshot<T, M>,\n): Page<T, M> {\n if (offset >= snapshot.items.length) throw invalidCursor();\n const page = snapshot.items.slice(offset, offset + pageSize);\n const nextOffset = offset + page.length;\n return {\n page,\n metadata: snapshot.metadata,\n offset,\n nextCursor:\n nextOffset < snapshot.items.length\n ? encodePageCursor({ snapshotId, offset: nextOffset })\n : undefined,\n };\n}\n\n/**\n * The full result set a paged tool produced, before slicing. `truncated` is the\n * engine's own stop state — the hard result cap or a timeout cut the set — and\n * says nothing about page size.\n */\nexport interface ProducedPage<T, M> {\n readonly items: readonly T[];\n readonly metadata: M;\n readonly truncated: boolean;\n}\n\ninterface Page<T, M> {\n readonly page: readonly T[];\n readonly metadata: M;\n readonly nextCursor: string | undefined;\n /**\n * Index of this page's first item in the full set. A tool that reports\n * progress in its text needs it: without it every page can only say how many\n * rows it holds, which is the same number on page 1 and page 9.\n */\n readonly offset: number;\n}\n\nexport interface PaginatedPage<T, M, R> extends Page<T, M> {\n /** The externalized full set. First page only, and only when incomplete. */\n readonly resource?: R;\n}\n\n/**\n * The one replay-or-produce branch every paged tool runs. A cursor replays a\n * stored snapshot; without one, `produce` runs the query and its result is\n * snapshotted for later pages.\n *\n * The response is incomplete when the set spans more than one page or the\n * engine truncated it. Then — and only on the first page — `externalize`\n * stores the full set and its handle rides back as `resource`. Replayed pages\n * never carry it: the stored entry expires on the resource store's own TTL and\n * LRU, not this snapshot's, so replaying its URI would hand back a dead\n * pointer. Nor do they mint a new one; the set was already stored whole.\n */\nexport async function paginate<T, M, R>(params: {\n store: PageSnapshotStore;\n queryKey: string;\n cursor: string | undefined;\n pageSize: number;\n produce: () => Promise<ProducedPage<T, M>>;\n externalize?: ((items: readonly T[], metadata: M) => R) | undefined;\n}): Promise<PaginatedPage<T, M, R>> {\n if (params.cursor !== undefined) {\n const decoded = decodePageCursor(params.cursor);\n const snapshot = params.store.read<T, M>(decoded.snapshotId, params.queryKey);\n return pageResult(decoded.snapshotId, decoded.offset, params.pageSize, snapshot);\n }\n const produced = await params.produce();\n const { items, metadata } = produced;\n const incomplete = items.length > params.pageSize || produced.truncated;\n const first: Page<T, M> =\n items.length <= params.pageSize\n ? { page: items, metadata, nextCursor: undefined, offset: 0 }\n : pageResult(\n params.store.create({\n queryKey: params.queryKey,\n items,\n metadata,\n }),\n 0,\n params.pageSize,\n { items, metadata },\n );\n if (!incomplete || params.externalize === undefined) return first;\n return { ...first, resource: params.externalize(produced.items, produced.metadata) };\n}\n\nfunction decodePageCursor(cursor: string): PageCursor {\n try {\n const text = Buffer.from(cursor, 'base64url').toString('utf-8');\n const parsed = JSON.parse(text) as { snapshotId?: unknown; offset?: unknown };\n if (\n typeof parsed.snapshotId === 'string' &&\n parsed.snapshotId.length > 0 &&\n typeof parsed.offset === 'number' &&\n Number.isInteger(parsed.offset) &&\n parsed.offset >= 0\n ) {\n return { snapshotId: parsed.snapshotId, offset: parsed.offset };\n }\n throw new Error('Invalid page cursor');\n } catch (error) {\n if (error instanceof FsError) throw error;\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n invalidCursor().message,\n undefined,\n error instanceof Error ? error : undefined,\n );\n }\n}\n"]}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -28,9 +28,7 @@ interface ProblemFactoryOptions {
|
|
|
28
28
|
export declare const Problem: {
|
|
29
29
|
readonly invalidInput: (msg: string, o?: ProblemFactoryOptions) => Problem;
|
|
30
30
|
readonly accessDenied: (msg: string, o?: ProblemFactoryOptions) => Problem;
|
|
31
|
-
readonly timeout: (msg: string, o?: ProblemFactoryOptions) => Problem;
|
|
32
31
|
readonly cancelled: (msg: string, o?: ProblemFactoryOptions) => Problem;
|
|
33
|
-
readonly unknown: (msg: string, o?: ProblemFactoryOptions) => Problem;
|
|
34
32
|
readonly fromUnknown: (error: unknown, defaultCode: ErrorCode, path?: string) => Problem;
|
|
35
33
|
readonly toText: (error: unknown, defaultCode: ErrorCode) => {
|
|
36
34
|
code: ErrorCode;
|
|
@@ -43,18 +41,15 @@ export declare const SKIPPABLE_ERRNOS: ReadonlySet<string>;
|
|
|
43
41
|
export declare function isFsError(error: unknown): error is FsError;
|
|
44
42
|
/** FsError traces to a caller-supplied argument; anything else is server-side. */
|
|
45
43
|
export declare function fsErrorCode(error: unknown): ProtocolErrorCode;
|
|
46
|
-
/**
|
|
47
|
-
* Structural discriminator for SDK error classes (`ProtocolError`, `SdkError`).
|
|
48
|
-
* Checks `name` + `code` properties rather than `instanceof` so discrimination
|
|
49
|
-
* survives cross-realm / multi-SDK-copy conditions where the prototype chain
|
|
50
|
-
* breaks. Mirrors the isFsError pattern.
|
|
51
|
-
*/
|
|
52
|
-
export declare function hasErrorShape(error: unknown, name: string): error is Error & {
|
|
53
|
-
code: string | number;
|
|
54
|
-
};
|
|
55
44
|
export declare function isNodeError(error: unknown): error is NodeJS.ErrnoException;
|
|
56
45
|
/** True when `error` is a Node errno error with code `ENOENT` (path not found). */
|
|
57
46
|
export declare function isNotFoundErrno(error: unknown): error is NodeJS.ErrnoException;
|
|
47
|
+
/**
|
|
48
|
+
* Rethrow when `error` reads as cancelled. Answers the question directly rather
|
|
49
|
+
* than through `classify`, which allocated a Problem — and ran
|
|
50
|
+
* `formatUnknownErrorMessage`, i.e. a `JSON.stringify` on a non-Error — for a
|
|
51
|
+
* boolean. This sits in eight catch blocks, several inside per-entry scan loops.
|
|
52
|
+
*/
|
|
58
53
|
export declare function rethrowIfAborted(error: unknown): void;
|
|
59
54
|
export declare function formatUnknownErrorMessage(error: unknown): string;
|
|
60
55
|
export declare function normalizeUnknownError(error: unknown): Error;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;CAcZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,qBAAqB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,eAAO,MAAM,OAAO;iCACE,MAAM,MAAM,qBAAqB,KAAG,OAAO;iCAE3C,MAAM,MAAM,qBAAqB,KAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;CAcZ,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAEnE,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,qBAAqB;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAWD,eAAO,MAAM,OAAO;iCACE,MAAM,MAAM,qBAAqB,KAAG,OAAO;iCAE3C,MAAM,MAAM,qBAAqB,KAAG,OAAO;8BAE9C,MAAM,MAAM,qBAAqB,KAAG,OAAO;kCAEzC,OAAO,eAAe,SAAS,SAAS,MAAM,KAAG,OAAO;6BAe7D,OAAO,eAAe,SAAS,KAAG;QAAE,IAAI,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;CAIzE,CAAC;AAcX,eAAO,MAAM,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAezD,CAAC;AAOF,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,SAAS,CAOpD,CAAC;AAEH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA0C,CAAC;AA+C5F,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAE7D;AAmBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAK1E;AAED,mFAAmF;AACnF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAE9E;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAiBrD;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQhE;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE3D;AAeD,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IAY5E,IAAI,IAAI,IAAI,SAAS,CAEpB;CACF"}
|
package/dist/core/errors.js
CHANGED
|
@@ -25,9 +25,7 @@ function build(code, message, opts = {}) {
|
|
|
25
25
|
export const Problem = {
|
|
26
26
|
invalidInput: (msg, o) => build(ErrorCode.INVALID_INPUT, msg, o),
|
|
27
27
|
accessDenied: (msg, o) => build(ErrorCode.ACCESS_DENIED, msg, o),
|
|
28
|
-
timeout: (msg, o) => build(ErrorCode.TIMEOUT, msg, o),
|
|
29
28
|
cancelled: (msg, o) => build(ErrorCode.CANCELLED, msg, o),
|
|
30
|
-
unknown: (msg, o) => build(ErrorCode.UNKNOWN, msg, o),
|
|
31
29
|
fromUnknown(error, defaultCode, path) {
|
|
32
30
|
const problem = classify(error);
|
|
33
31
|
const shouldOverride = problem.code === ErrorCode.UNKNOWN || problem.code === ErrorCode.IO_ERROR;
|
|
@@ -86,125 +84,59 @@ export const SKIPPABLE_FS_CODES = new Set([
|
|
|
86
84
|
ErrorCode.SYMLINK_NOT_ALLOWED,
|
|
87
85
|
]);
|
|
88
86
|
export const SKIPPABLE_ERRNOS = new Set(['ENOENT', 'EACCES', 'ELOOP']);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!(value instanceof Error))
|
|
99
|
-
return false;
|
|
100
|
-
if (value.name === 'AbortError')
|
|
101
|
-
return true;
|
|
102
|
-
const code = readErrnoCode(value);
|
|
103
|
-
return code === 'ABORT_ERR';
|
|
104
|
-
}
|
|
105
|
-
function isTimeoutSingle(value) {
|
|
106
|
-
if (!(value instanceof Error))
|
|
107
|
-
return false;
|
|
108
|
-
if (value.name === 'TimeoutError')
|
|
109
|
-
return true;
|
|
110
|
-
return readErrnoCode(value) === 'ETIMEDOUT';
|
|
111
|
-
}
|
|
112
|
-
function readSignalSingle(value) {
|
|
113
|
-
if (isAbortSingle(value))
|
|
114
|
-
return { kind: 'abort' };
|
|
115
|
-
if (isTimeoutSingle(value))
|
|
116
|
-
return { kind: 'timeout' };
|
|
117
|
-
const code = readErrnoCode(value);
|
|
118
|
-
if (code !== undefined && ERRNO_RE.test(code)) {
|
|
119
|
-
const v = value;
|
|
120
|
-
return {
|
|
121
|
-
kind: 'errno',
|
|
122
|
-
errno: code,
|
|
123
|
-
...(typeof v.syscall === 'string' ? { syscall: v.syscall } : {}),
|
|
124
|
-
...(typeof v.path === 'string' ? { path: v.path } : {}),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
function walkCauseChain(error) {
|
|
130
|
-
let current = error;
|
|
87
|
+
/**
|
|
88
|
+
* Walk `error` and its `cause` chain for the one fact that decides the code: an
|
|
89
|
+
* abort anywhere wins, then a timeout anywhere, then the FIRST errno seen.
|
|
90
|
+
* `visited` guards a cyclic chain. Returns the Problem directly — the
|
|
91
|
+
* intermediate signal union this replaced existed only to be destructured back
|
|
92
|
+
* into a Problem two functions later, and carried a `syscall` nobody read.
|
|
93
|
+
*/
|
|
94
|
+
function classifyCauseChain(error) {
|
|
95
|
+
const message = formatUnknownErrorMessage(error);
|
|
131
96
|
const visited = new Set();
|
|
132
|
-
let
|
|
133
|
-
let
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
const signal = readSignalSingle(current);
|
|
137
|
-
if (signal !== undefined) {
|
|
138
|
-
if (signal.kind === 'abort')
|
|
139
|
-
abortSeen = true;
|
|
140
|
-
else if (signal.kind === 'timeout')
|
|
141
|
-
timeoutSeen = true;
|
|
142
|
-
else if (signal.kind === 'errno' && errnoSignal === undefined) {
|
|
143
|
-
errnoSignal = signal;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
97
|
+
let aborted = false;
|
|
98
|
+
let timedOut = false;
|
|
99
|
+
let errno;
|
|
100
|
+
for (let current = error; current !== undefined && current !== null && !visited.has(current); current = current.cause) {
|
|
146
101
|
visited.add(current);
|
|
147
|
-
current
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return { kind: 'unknown' };
|
|
156
|
-
}
|
|
157
|
-
function buildProblemFromSignal(signal, error) {
|
|
158
|
-
const message = formatUnknownErrorMessage(error);
|
|
159
|
-
switch (signal.kind) {
|
|
160
|
-
case 'abort':
|
|
161
|
-
return Problem.cancelled(message);
|
|
162
|
-
case 'timeout':
|
|
163
|
-
return Problem.timeout(message);
|
|
164
|
-
case 'errno': {
|
|
165
|
-
const code = ERRNO_MAP[signal.errno] ?? ErrorCode.IO_ERROR;
|
|
166
|
-
return build(code, message, {
|
|
167
|
-
...(signal.path !== undefined ? { path: signal.path } : {}),
|
|
168
|
-
});
|
|
102
|
+
if (!(current instanceof Error))
|
|
103
|
+
continue;
|
|
104
|
+
const raw = current.code;
|
|
105
|
+
const code = typeof raw === 'string' ? raw : undefined;
|
|
106
|
+
// Ordered like the per-error read it replaces: a node that reads as an
|
|
107
|
+
// abort or a timeout never also contributes its errno.
|
|
108
|
+
if (current.name === 'AbortError' || code === 'ABORT_ERR') {
|
|
109
|
+
aborted = true;
|
|
169
110
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
111
|
+
else if (current.name === 'TimeoutError' || code === 'ETIMEDOUT') {
|
|
112
|
+
timedOut = true;
|
|
113
|
+
}
|
|
114
|
+
else if (errno === undefined && code !== undefined && ERRNO_RE.test(code)) {
|
|
115
|
+
const { path } = current;
|
|
116
|
+
errno = { code, ...(typeof path === 'string' ? { path } : {}) };
|
|
175
117
|
}
|
|
176
118
|
}
|
|
119
|
+
if (aborted)
|
|
120
|
+
return Problem.cancelled(message);
|
|
121
|
+
if (timedOut)
|
|
122
|
+
return build(ErrorCode.TIMEOUT, message);
|
|
123
|
+
if (errno !== undefined) {
|
|
124
|
+
return build(ERRNO_MAP[errno.code] ?? ErrorCode.IO_ERROR, message, {
|
|
125
|
+
...(errno.path !== undefined ? { path: errno.path } : {}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return build(ErrorCode.UNKNOWN, message);
|
|
177
129
|
}
|
|
178
130
|
export function isFsError(error) {
|
|
179
|
-
|
|
180
|
-
return false;
|
|
181
|
-
if (!('problem' in error))
|
|
182
|
-
return false;
|
|
183
|
-
const p = error.problem;
|
|
184
|
-
if (p === null || typeof p !== 'object')
|
|
185
|
-
return false;
|
|
186
|
-
const c = p;
|
|
187
|
-
return typeof c['code'] === 'string' && typeof c['message'] === 'string';
|
|
131
|
+
return error instanceof FsError;
|
|
188
132
|
}
|
|
189
133
|
/** FsError traces to a caller-supplied argument; anything else is server-side. */
|
|
190
134
|
export function fsErrorCode(error) {
|
|
191
135
|
return isFsError(error) ? ProtocolErrorCode.InvalidParams : ProtocolErrorCode.InternalError;
|
|
192
136
|
}
|
|
193
|
-
/**
|
|
194
|
-
* Structural discriminator for SDK error classes (`ProtocolError`, `SdkError`).
|
|
195
|
-
* Checks `name` + `code` properties rather than `instanceof` so discrimination
|
|
196
|
-
* survives cross-realm / multi-SDK-copy conditions where the prototype chain
|
|
197
|
-
* breaks. Mirrors the isFsError pattern.
|
|
198
|
-
*/
|
|
199
|
-
export function hasErrorShape(error, name) {
|
|
200
|
-
if (!(error instanceof Error) || error.name !== name)
|
|
201
|
-
return false;
|
|
202
|
-
const c = error.code;
|
|
203
|
-
return typeof c === 'string' || typeof c === 'number';
|
|
204
|
-
}
|
|
205
137
|
function classify(error) {
|
|
206
138
|
if (error === null || error === undefined) {
|
|
207
|
-
return
|
|
139
|
+
return build(ErrorCode.UNKNOWN, 'Unknown error');
|
|
208
140
|
}
|
|
209
141
|
if (isFsError(error))
|
|
210
142
|
return error.problem;
|
|
@@ -212,13 +144,12 @@ function classify(error) {
|
|
|
212
144
|
// `safeParse` inside the SDK's validator seam (tools/define.ts), which never
|
|
213
145
|
// throws — it hands back `z.prettifyError`'s string — and nothing in `src/`
|
|
214
146
|
// calls `.parse()`. A ZodError reaching here would fall through to
|
|
215
|
-
//
|
|
147
|
+
// UNKNOWN with Zod's raw JSON-dump `.message`, so if a `.parse()`
|
|
216
148
|
// is ever added, add the branch back with it rather than discovering this.
|
|
217
149
|
if (!(error instanceof Error)) {
|
|
218
|
-
return
|
|
150
|
+
return build(ErrorCode.UNKNOWN, typeof error === 'string' ? error : '[non-Error thrown]');
|
|
219
151
|
}
|
|
220
|
-
|
|
221
|
-
return buildProblemFromSignal(signal, error);
|
|
152
|
+
return classifyCauseChain(error);
|
|
222
153
|
}
|
|
223
154
|
export function isNodeError(error) {
|
|
224
155
|
if (!(error instanceof Error))
|
|
@@ -232,12 +163,27 @@ export function isNodeError(error) {
|
|
|
232
163
|
export function isNotFoundErrno(error) {
|
|
233
164
|
return isNodeError(error) && error.code === 'ENOENT';
|
|
234
165
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Rethrow when `error` reads as cancelled. Answers the question directly rather
|
|
168
|
+
* than through `classify`, which allocated a Problem — and ran
|
|
169
|
+
* `formatUnknownErrorMessage`, i.e. a `JSON.stringify` on a non-Error — for a
|
|
170
|
+
* boolean. This sits in eight catch blocks, several inside per-entry scan loops.
|
|
171
|
+
*/
|
|
238
172
|
export function rethrowIfAborted(error) {
|
|
239
|
-
if (
|
|
240
|
-
|
|
173
|
+
if (isFsError(error)) {
|
|
174
|
+
if (error.code === ErrorCode.CANCELLED)
|
|
175
|
+
throw error;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const visited = new Set();
|
|
179
|
+
for (let current = error; current !== undefined && current !== null && !visited.has(current); current = current.cause) {
|
|
180
|
+
visited.add(current);
|
|
181
|
+
if (!(current instanceof Error))
|
|
182
|
+
continue;
|
|
183
|
+
if (current.name === 'AbortError' || current.code === 'ABORT_ERR') {
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
241
187
|
}
|
|
242
188
|
export function formatUnknownErrorMessage(error) {
|
|
243
189
|
if (typeof error === 'string')
|
|
@@ -276,7 +222,6 @@ export class FsError extends Error {
|
|
|
276
222
|
...(suggestion !== undefined ? { suggestion } : {}),
|
|
277
223
|
};
|
|
278
224
|
this.name = 'FsError';
|
|
279
|
-
Object.setPrototypeOf(this, FsError.prototype);
|
|
280
225
|
}
|
|
281
226
|
get code() {
|
|
282
227
|
return this.problem.code;
|