-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathwalk-stream.ts
More file actions
116 lines (95 loc) · 3.08 KB
/
Copy pathwalk-stream.ts
File metadata and controls
116 lines (95 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2023-present Eser Ozvataf and other contributors. All rights reserved. Apache-2.0 license.
/**
* Streaming file walk via the Go FFI bridge.
*
* Returns an `AsyncIterable<FileEntry>` backed by `EserAjanCodebaseWalkFilesStream*`.
* Supports `await using` / `Symbol.asyncDispose` for deterministic cleanup.
*
* @module
*/
import { ensureLib, getLib } from "./ffi-client.ts";
import type { FileEntry } from "./file-tools-shared.ts";
// =============================================================================
// Types
// =============================================================================
export type WalkStreamOptions = {
readonly dir?: string;
readonly extensions?: readonly string[];
readonly exclude?: readonly string[];
readonly gitAware?: boolean;
};
// =============================================================================
// WalkStream
// =============================================================================
export class WalkStream implements AsyncIterable<FileEntry>, AsyncDisposable {
readonly #handle: string;
constructor(handle: string) {
this.#handle = handle;
}
async *[Symbol.asyncIterator](): AsyncGenerator<FileEntry> {
const lib = getLib();
if (lib === null) {
throw new Error("FFI library unavailable — cannot iterate walk stream");
}
while (true) {
const raw = lib.symbols.EserAjanCodebaseWalkFilesStreamRead(
this.#handle,
);
if (raw === "null") {
break;
}
const parsed = JSON.parse(raw) as {
path?: string;
name?: string;
size?: number;
isSymlink?: boolean;
error?: string;
};
if (parsed.error !== undefined) {
throw new Error(`walk stream error: ${parsed.error}`);
}
yield {
path: parsed.path ?? "",
name: parsed.name ?? "",
size: parsed.size ?? 0,
isSymlink: parsed.isSymlink ?? false,
};
}
}
[Symbol.asyncDispose](): Promise<void> {
const lib = getLib();
if (lib !== null) {
lib.symbols.EserAjanCodebaseWalkFilesStreamClose(this.#handle);
}
return Promise.resolve();
}
}
// =============================================================================
// Factory
// =============================================================================
export const walkFilesStream = async (
options: WalkStreamOptions = {},
): Promise<WalkStream> => {
await ensureLib();
const lib = getLib();
if (lib === null) {
throw new Error(
"FFI library unavailable — cannot create walk stream",
);
}
const raw = lib.symbols.EserAjanCodebaseWalkFilesStreamCreate(
JSON.stringify({
dir: options.dir ?? ".",
extensions: options.extensions,
exclude: options.exclude,
gitAware: options.gitAware ?? false,
}),
);
const parsed = JSON.parse(raw) as { handle?: string; error?: string };
if (parsed.error !== undefined || parsed.handle === undefined) {
throw new Error(
`walk stream create failed: ${parsed.error ?? "no handle"}`,
);
}
return new WalkStream(parsed.handle);
};