@ff-labs/fff-node 0.1.0-nightly.28ebf7a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Binary resolution utilities for fff-node
3
+ *
4
+ * Resolves the native library from:
5
+ * 1. Platform-specific npm package (e.g. @ff-labs/fff-bin-darwin-arm64)
6
+ * 2. Local dev build (target/release or target/debug)
7
+ */
8
+ /**
9
+ * Check if the binary exists in any known location
10
+ */
11
+ export declare function binaryExists(): boolean;
12
+ /**
13
+ * Find the native library binary.
14
+ *
15
+ * Resolution order:
16
+ * - Dev workspace: local dev build first, then npm package
17
+ * - Production: npm package first, then dev build
18
+ *
19
+ * @returns Absolute path to the library, or null if not found
20
+ */
21
+ export declare function findBinary(): string | null;
22
+ //# sourceMappingURL=binary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary.d.ts","sourceRoot":"","sources":["../../src/binary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8CH;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AA0DD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAuB1C"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Binary resolution utilities for fff-node
3
+ *
4
+ * Resolves the native library from:
5
+ * 1. Platform-specific npm package (e.g. @ff-labs/fff-bin-darwin-arm64)
6
+ * 2. Local dev build (target/release or target/debug)
7
+ */
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { createRequire } from "node:module";
10
+ import { dirname, join } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { getLibFilename, getNpmPackageName } from "./platform.js";
13
+ /**
14
+ * Get the current file's directory
15
+ */
16
+ function getCurrentDir() {
17
+ const url = import.meta.url;
18
+ if (url.startsWith("file://")) {
19
+ return dirname(fileURLToPath(url));
20
+ }
21
+ return dirname(url);
22
+ }
23
+ /**
24
+ * Get the package root directory
25
+ */
26
+ function getPackageDir() {
27
+ const currentDir = getCurrentDir();
28
+ // In dev: src/ -> package root
29
+ // In dist: dist/src/ -> package root
30
+ // We look for package.json to find the actual root
31
+ let dir = currentDir;
32
+ for (let i = 0; i < 5; i++) {
33
+ if (existsSync(join(dir, "package.json"))) {
34
+ try {
35
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
36
+ if (pkg.name === "@ff-labs/fff-node") {
37
+ return dir;
38
+ }
39
+ }
40
+ catch {
41
+ // Not our package.json, keep going up
42
+ }
43
+ }
44
+ dir = dirname(dir);
45
+ }
46
+ // Fallback: assume we're one level deep in src/
47
+ return dirname(currentDir);
48
+ }
49
+ /**
50
+ * Check if the binary exists in any known location
51
+ */
52
+ export function binaryExists() {
53
+ return findBinary() !== null;
54
+ }
55
+ /**
56
+ * Try to resolve the binary from the platform-specific npm package.
57
+ *
58
+ * When users install @ff-labs/fff-node, npm automatically installs the matching
59
+ * optionalDependency (e.g. @ff-labs/fff-bin-darwin-arm64). We resolve the binary
60
+ * path by requiring that package's package.json and looking for the binary
61
+ * in the same directory.
62
+ */
63
+ function resolveFromNpmPackage() {
64
+ const packageName = getNpmPackageName();
65
+ try {
66
+ // Use createRequire to resolve the platform package's location
67
+ const require = createRequire(join(getPackageDir(), "package.json"));
68
+ const packageJsonPath = require.resolve(`${packageName}/package.json`);
69
+ const packageDir = dirname(packageJsonPath);
70
+ const binaryPath = join(packageDir, getLibFilename());
71
+ if (existsSync(binaryPath)) {
72
+ return binaryPath;
73
+ }
74
+ }
75
+ catch {
76
+ // Package not installed - this is expected on unsupported platforms
77
+ // or when installed without optional dependencies
78
+ }
79
+ return null;
80
+ }
81
+ /**
82
+ * Get the development binary path (for local development)
83
+ */
84
+ function getDevBinaryPath() {
85
+ const packageDir = getPackageDir();
86
+ const workspaceRoot = join(packageDir, "..", "..");
87
+ const possiblePaths = [
88
+ join(workspaceRoot, "target", "release", getLibFilename()),
89
+ join(workspaceRoot, "target", "debug", getLibFilename()),
90
+ ];
91
+ for (const path of possiblePaths) {
92
+ if (existsSync(path)) {
93
+ return path;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ function isDevWorkspace() {
99
+ const packageDir = getPackageDir();
100
+ const workspaceRoot = join(packageDir, "..", "..");
101
+ return existsSync(join(workspaceRoot, "Cargo.toml"));
102
+ }
103
+ /**
104
+ * Find the native library binary.
105
+ *
106
+ * Resolution order:
107
+ * - Dev workspace: local dev build first, then npm package
108
+ * - Production: npm package first, then dev build
109
+ *
110
+ * @returns Absolute path to the library, or null if not found
111
+ */
112
+ export function findBinary() {
113
+ if (isDevWorkspace()) {
114
+ // 1. Local bin/ directory (populated by `make prepare-node`)
115
+ const binPath = join(getPackageDir(), "bin", getLibFilename());
116
+ if (existsSync(binPath))
117
+ return binPath;
118
+ // 2. Local dev build (target/release or target/debug)
119
+ const devPath = getDevBinaryPath();
120
+ if (devPath)
121
+ return devPath;
122
+ // 3. Fallback to npm package
123
+ const npmPath = resolveFromNpmPackage();
124
+ if (npmPath)
125
+ return npmPath;
126
+ return null;
127
+ }
128
+ // Production: npm package first
129
+ const npmPath = resolveFromNpmPackage();
130
+ if (npmPath)
131
+ return npmPath;
132
+ // Fallback: local dev build (e.g. user built from source)
133
+ return getDevBinaryPath();
134
+ }
135
+ //# sourceMappingURL=binary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary.js","sourceRoot":"","sources":["../../src/binary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Node.js FFI bindings for the fff-c native library using ffi-rs
3
+ *
4
+ * This module uses ffi-rs to call into the Rust C library.
5
+ * All functions follow the Result pattern for error handling.
6
+ *
7
+ * The API is instance-based: `ffiCreate` returns an opaque handle that must
8
+ * be passed to all subsequent calls and freed with `ffiDestroy`.
9
+ *
10
+ * ## Memory management
11
+ *
12
+ * Every `fff_*` function returning `*mut FffResult` allocates with Rust's Box.
13
+ * We MUST call `fff_free_result` to properly deallocate (not libc::free).
14
+ *
15
+ * ## FffResult struct reading
16
+ *
17
+ * The FffResult struct layout (#[repr(C)]):
18
+ * offset 0: success (bool, 1 byte + 7 padding)
19
+ * offset 8: data pointer (8 bytes) - *mut c_char (JSON string or null)
20
+ * offset 16: error pointer (8 bytes) - *mut c_char (error message or null)
21
+ * offset 24: handle pointer (8 bytes) - *mut c_void (instance handle or null)
22
+ *
23
+ * ## Two-step approach for reading + freeing
24
+ *
25
+ * ffi-rs auto-dereferences struct retType pointers, losing the original pointer.
26
+ * We solve this by:
27
+ * 1. Calling the C function with `retType: DataType.External` to get the raw pointer
28
+ * 2. Using `restorePointer` to read the struct fields from the raw pointer
29
+ * 3. Calling `fff_free_result` with the original raw pointer
30
+ *
31
+ * ## Null pointer detection
32
+ *
33
+ * `isNullPointer` from ffi-rs correctly detects null C pointers wrapped as
34
+ * V8 External objects. We use this instead of truthy checks.
35
+ */
36
+ import { type JsExternal } from "ffi-rs";
37
+ import type { Result } from "./types.js";
38
+ /**
39
+ * Opaque native handle type. Callers must not inspect or modify this value.
40
+ */
41
+ export type NativeHandle = JsExternal;
42
+ /**
43
+ * Create a new file finder instance.
44
+ *
45
+ * Returns the opaque native handle on success. The handle must be passed to
46
+ * all subsequent FFI calls and freed with `ffiDestroy`.
47
+ */
48
+ export declare function ffiCreate(optsJson: string): Result<NativeHandle>;
49
+ /**
50
+ * Destroy and clean up an instance.
51
+ */
52
+ export declare function ffiDestroy(handle: NativeHandle): void;
53
+ /**
54
+ * Perform fuzzy search.
55
+ */
56
+ export declare function ffiSearch(handle: NativeHandle, query: string, optsJson: string): Result<unknown>;
57
+ /**
58
+ * Trigger file scan.
59
+ */
60
+ export declare function ffiScanFiles(handle: NativeHandle): Result<void>;
61
+ /**
62
+ * Check if scanning.
63
+ */
64
+ export declare function ffiIsScanning(handle: NativeHandle): boolean;
65
+ /**
66
+ * Get scan progress.
67
+ */
68
+ export declare function ffiGetScanProgress(handle: NativeHandle): Result<unknown>;
69
+ /**
70
+ * Wait for a tree scan to complete.
71
+ */
72
+ export declare function ffiWaitForScan(handle: NativeHandle, timeoutMs: number): Result<boolean>;
73
+ /**
74
+ * Restart index in new path.
75
+ */
76
+ export declare function ffiRestartIndex(handle: NativeHandle, newPath: string): Result<void>;
77
+ /**
78
+ * Refresh git status.
79
+ */
80
+ export declare function ffiRefreshGitStatus(handle: NativeHandle): Result<number>;
81
+ /**
82
+ * Track query completion.
83
+ */
84
+ export declare function ffiTrackQuery(handle: NativeHandle, query: string, filePath: string): Result<boolean>;
85
+ /**
86
+ * Get historical query.
87
+ */
88
+ export declare function ffiGetHistoricalQuery(handle: NativeHandle, offset: number): Result<string | null>;
89
+ /**
90
+ * Health check.
91
+ *
92
+ * `handle` can be null for a limited check (version + git only).
93
+ * When null, we pass DataType.U64 with value 0 as a null pointer workaround
94
+ * since ffi-rs does not accept `null` for External parameters.
95
+ */
96
+ export declare function ffiHealthCheck(handle: NativeHandle | null, testPath: string): Result<unknown>;
97
+ /**
98
+ * Live grep - search file contents.
99
+ */
100
+ export declare function ffiLiveGrep(handle: NativeHandle, query: string, optsJson: string): Result<unknown>;
101
+ /**
102
+ * Multi-pattern grep - Aho-Corasick multi-needle search.
103
+ */
104
+ export declare function ffiMultiGrep(handle: NativeHandle, optsJson: string): Result<unknown>;
105
+ /**
106
+ * Ensure the library is loaded.
107
+ *
108
+ * Loads the native library from the platform-specific npm package
109
+ * or a local dev build. Throws if the binary is not found.
110
+ */
111
+ export declare function ensureLoaded(): void;
112
+ /**
113
+ * Check if the library is available.
114
+ */
115
+ export declare function isAvailable(): boolean;
116
+ /**
117
+ * Close the library and release ffi-rs resources.
118
+ * Call this when completely done with the library.
119
+ */
120
+ export declare function closeLibrary(): void;
121
+ //# sourceMappingURL=ffi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffi.d.ts","sourceRoot":"","sources":["../../src/ffi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAIL,KAAK,UAAU,EAKhB,MAAM,QAAQ,CAAC;AAEhB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAmLzC;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,CAyBhE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CASrD;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,OAAO,CAAC,CAMjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,CAE/D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAS3D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAExE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAQvF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAMnF;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,CAAC,CAcxE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,OAAO,CAAC,CAQjB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CASvB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,GAAG,IAAI,EAC3B,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,OAAO,CAAC,CA8CjB;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,OAAO,CAAC,CAMjB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAMpF;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAOrC;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAKnC"}
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Node.js FFI bindings for the fff-c native library using ffi-rs
3
+ *
4
+ * This module uses ffi-rs to call into the Rust C library.
5
+ * All functions follow the Result pattern for error handling.
6
+ *
7
+ * The API is instance-based: `ffiCreate` returns an opaque handle that must
8
+ * be passed to all subsequent calls and freed with `ffiDestroy`.
9
+ *
10
+ * ## Memory management
11
+ *
12
+ * Every `fff_*` function returning `*mut FffResult` allocates with Rust's Box.
13
+ * We MUST call `fff_free_result` to properly deallocate (not libc::free).
14
+ *
15
+ * ## FffResult struct reading
16
+ *
17
+ * The FffResult struct layout (#[repr(C)]):
18
+ * offset 0: success (bool, 1 byte + 7 padding)
19
+ * offset 8: data pointer (8 bytes) - *mut c_char (JSON string or null)
20
+ * offset 16: error pointer (8 bytes) - *mut c_char (error message or null)
21
+ * offset 24: handle pointer (8 bytes) - *mut c_void (instance handle or null)
22
+ *
23
+ * ## Two-step approach for reading + freeing
24
+ *
25
+ * ffi-rs auto-dereferences struct retType pointers, losing the original pointer.
26
+ * We solve this by:
27
+ * 1. Calling the C function with `retType: DataType.External` to get the raw pointer
28
+ * 2. Using `restorePointer` to read the struct fields from the raw pointer
29
+ * 3. Calling `fff_free_result` with the original raw pointer
30
+ *
31
+ * ## Null pointer detection
32
+ *
33
+ * `isNullPointer` from ffi-rs correctly detects null C pointers wrapped as
34
+ * V8 External objects. We use this instead of truthy checks.
35
+ */
36
+ import { close, DataType, isNullPointer, load, open, restorePointer, wrapPointer, } from "ffi-rs";
37
+ import { findBinary } from "./binary.js";
38
+ import { err } from "./types.js";
39
+ const LIBRARY_KEY = "fff_c";
40
+ // Track whether the library is loaded
41
+ let isLoaded = false;
42
+ /**
43
+ * Struct type definition for FffResult used with restorePointer.
44
+ *
45
+ * Uses U8 for the bool success field (correct alignment with ffi-rs).
46
+ * Uses External for ALL pointer fields to avoid hangs on null char* pointers
47
+ * (ffi-rs hangs when trying to read DataType.String from null char*).
48
+ */
49
+ const FFF_RESULT_STRUCT = {
50
+ success: DataType.U8,
51
+ data: DataType.External,
52
+ error: DataType.External,
53
+ handle: DataType.External,
54
+ };
55
+ /**
56
+ * Load the native library using ffi-rs
57
+ */
58
+ function loadLibrary() {
59
+ if (isLoaded)
60
+ return;
61
+ const binaryPath = findBinary();
62
+ if (!binaryPath) {
63
+ throw new Error("fff native library not found. Run `npx @ff-labs/fff-node download` or build from source with `cargo build --release -p fff-c`");
64
+ }
65
+ open({ library: LIBRARY_KEY, path: binaryPath });
66
+ isLoaded = true;
67
+ }
68
+ /**
69
+ * Convert snake_case keys to camelCase recursively
70
+ */
71
+ function snakeToCamel(obj) {
72
+ if (obj === null || obj === undefined)
73
+ return obj;
74
+ if (typeof obj !== "object")
75
+ return obj;
76
+ if (Array.isArray(obj))
77
+ return obj.map(snakeToCamel);
78
+ const result = {};
79
+ for (const [key, value] of Object.entries(obj)) {
80
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
81
+ result[camelKey] = snakeToCamel(value);
82
+ }
83
+ return result;
84
+ }
85
+ /**
86
+ * Read a C string (char*) from an ffi-rs External pointer.
87
+ *
88
+ * Uses restorePointer + wrapPointer to dereference the char* and read the
89
+ * null-terminated string. Returns null if the pointer is null.
90
+ */
91
+ function readCString(ptr) {
92
+ if (isNullPointer(ptr))
93
+ return null;
94
+ try {
95
+ const [str] = restorePointer({
96
+ retType: [DataType.String],
97
+ paramsValue: wrapPointer([ptr]),
98
+ });
99
+ return str;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ /**
106
+ * Call a C function that returns `*mut FffResult` and get both the raw pointer
107
+ * (for freeing) and the parsed struct fields.
108
+ *
109
+ * Step 1: Call function with `DataType.External` retType → raw pointer
110
+ * Step 2: Use `restorePointer` to read struct fields from the raw pointer
111
+ */
112
+ function callRaw(funcName, paramsType, paramsValue) {
113
+ const rawPtr = load({
114
+ library: LIBRARY_KEY,
115
+ funcName,
116
+ retType: DataType.External,
117
+ paramsType,
118
+ paramsValue,
119
+ freeResultMemory: false,
120
+ });
121
+ const [structData] = restorePointer({
122
+ retType: [FFF_RESULT_STRUCT],
123
+ paramsValue: wrapPointer([rawPtr]),
124
+ });
125
+ return { rawPtr, struct: structData };
126
+ }
127
+ /**
128
+ * Free a FffResult pointer by calling fff_free_result.
129
+ *
130
+ * This frees the FffResult struct and its data/error strings using Rust's
131
+ * Box::from_raw and CString::from_raw. The handle field is NOT freed.
132
+ */
133
+ function freeResult(resultPtr) {
134
+ try {
135
+ load({
136
+ library: LIBRARY_KEY,
137
+ funcName: "fff_free_result",
138
+ retType: DataType.Void,
139
+ paramsType: [DataType.External],
140
+ paramsValue: [resultPtr],
141
+ });
142
+ }
143
+ catch {
144
+ // Ignore cleanup errors
145
+ }
146
+ }
147
+ /**
148
+ * Call a fff C function that returns *mut FffResult, parse the result,
149
+ * free the native memory, and return a typed Result<T>.
150
+ *
151
+ * Strategy:
152
+ * 1. Call the C function with External retType to get the raw pointer
153
+ * 2. Read struct fields via restorePointer
154
+ * 3. Based on success flag, read the data or error string
155
+ * 4. Call fff_free_result with the original raw pointer to free Rust memory
156
+ * 5. Parse JSON data and convert snake_case to camelCase
157
+ */
158
+ function callFfiResult(funcName, paramsType, paramsValue) {
159
+ loadLibrary();
160
+ const { rawPtr, struct: structData } = callRaw(funcName, paramsType, paramsValue);
161
+ const success = structData.success !== 0;
162
+ try {
163
+ if (success) {
164
+ const dataStr = readCString(structData.data);
165
+ if (dataStr === null || dataStr === "") {
166
+ return { ok: true, value: undefined };
167
+ }
168
+ try {
169
+ const parsed = JSON.parse(dataStr);
170
+ const transformed = snakeToCamel(parsed);
171
+ return { ok: true, value: transformed };
172
+ }
173
+ catch {
174
+ // For simple values like "true" or numbers
175
+ return { ok: true, value: dataStr };
176
+ }
177
+ }
178
+ else {
179
+ const errorStr = readCString(structData.error);
180
+ return err(errorStr || "Unknown error");
181
+ }
182
+ }
183
+ finally {
184
+ freeResult(rawPtr);
185
+ }
186
+ }
187
+ /**
188
+ * Create a new file finder instance.
189
+ *
190
+ * Returns the opaque native handle on success. The handle must be passed to
191
+ * all subsequent FFI calls and freed with `ffiDestroy`.
192
+ */
193
+ export function ffiCreate(optsJson) {
194
+ loadLibrary();
195
+ const { rawPtr, struct: structData } = callRaw("fff_create", [DataType.String], [optsJson]);
196
+ const success = structData.success !== 0;
197
+ try {
198
+ if (success) {
199
+ const handle = structData.handle;
200
+ if (isNullPointer(handle)) {
201
+ return err("fff_create returned null handle");
202
+ }
203
+ return { ok: true, value: handle };
204
+ }
205
+ else {
206
+ const errorStr = readCString(structData.error);
207
+ return err(errorStr || "Unknown error");
208
+ }
209
+ }
210
+ finally {
211
+ freeResult(rawPtr);
212
+ }
213
+ }
214
+ /**
215
+ * Destroy and clean up an instance.
216
+ */
217
+ export function ffiDestroy(handle) {
218
+ loadLibrary();
219
+ load({
220
+ library: LIBRARY_KEY,
221
+ funcName: "fff_destroy",
222
+ retType: DataType.Void,
223
+ paramsType: [DataType.External],
224
+ paramsValue: [handle],
225
+ });
226
+ }
227
+ /**
228
+ * Perform fuzzy search.
229
+ */
230
+ export function ffiSearch(handle, query, optsJson) {
231
+ return callFfiResult("fff_search", [DataType.External, DataType.String, DataType.String], [handle, query, optsJson]);
232
+ }
233
+ /**
234
+ * Trigger file scan.
235
+ */
236
+ export function ffiScanFiles(handle) {
237
+ return callFfiResult("fff_scan_files", [DataType.External], [handle]);
238
+ }
239
+ /**
240
+ * Check if scanning.
241
+ */
242
+ export function ffiIsScanning(handle) {
243
+ loadLibrary();
244
+ return load({
245
+ library: LIBRARY_KEY,
246
+ funcName: "fff_is_scanning",
247
+ retType: DataType.Boolean,
248
+ paramsType: [DataType.External],
249
+ paramsValue: [handle],
250
+ });
251
+ }
252
+ /**
253
+ * Get scan progress.
254
+ */
255
+ export function ffiGetScanProgress(handle) {
256
+ return callFfiResult("fff_get_scan_progress", [DataType.External], [handle]);
257
+ }
258
+ /**
259
+ * Wait for a tree scan to complete.
260
+ */
261
+ export function ffiWaitForScan(handle, timeoutMs) {
262
+ const result = callFfiResult("fff_wait_for_scan", [DataType.External, DataType.U64], [handle, timeoutMs]);
263
+ if (!result.ok)
264
+ return result;
265
+ return { ok: true, value: result.value === true || result.value === "true" };
266
+ }
267
+ /**
268
+ * Restart index in new path.
269
+ */
270
+ export function ffiRestartIndex(handle, newPath) {
271
+ return callFfiResult("fff_restart_index", [DataType.External, DataType.String], [handle, newPath]);
272
+ }
273
+ /**
274
+ * Refresh git status.
275
+ */
276
+ export function ffiRefreshGitStatus(handle) {
277
+ const result = callFfiResult("fff_refresh_git_status", [DataType.External], [handle]);
278
+ if (!result.ok)
279
+ return result;
280
+ return {
281
+ ok: true,
282
+ value: typeof result.value === "number"
283
+ ? result.value
284
+ : parseInt(result.value, 10),
285
+ };
286
+ }
287
+ /**
288
+ * Track query completion.
289
+ */
290
+ export function ffiTrackQuery(handle, query, filePath) {
291
+ const result = callFfiResult("fff_track_query", [DataType.External, DataType.String, DataType.String], [handle, query, filePath]);
292
+ if (!result.ok)
293
+ return result;
294
+ return { ok: true, value: result.value === true || result.value === "true" };
295
+ }
296
+ /**
297
+ * Get historical query.
298
+ */
299
+ export function ffiGetHistoricalQuery(handle, offset) {
300
+ const result = callFfiResult("fff_get_historical_query", [DataType.External, DataType.U64], [handle, offset]);
301
+ if (!result.ok)
302
+ return result;
303
+ if (result.value === null || result.value === "null")
304
+ return { ok: true, value: null };
305
+ return result;
306
+ }
307
+ /**
308
+ * Health check.
309
+ *
310
+ * `handle` can be null for a limited check (version + git only).
311
+ * When null, we pass DataType.U64 with value 0 as a null pointer workaround
312
+ * since ffi-rs does not accept `null` for External parameters.
313
+ */
314
+ export function ffiHealthCheck(handle, testPath) {
315
+ loadLibrary();
316
+ if (handle === null) {
317
+ // Use U64(0) as a null pointer since ffi-rs rejects null for External params
318
+ const rawPtr = load({
319
+ library: LIBRARY_KEY,
320
+ funcName: "fff_health_check",
321
+ retType: DataType.External,
322
+ paramsType: [DataType.U64, DataType.String],
323
+ paramsValue: [0, testPath],
324
+ freeResultMemory: false,
325
+ });
326
+ const [structData] = restorePointer({
327
+ retType: [FFF_RESULT_STRUCT],
328
+ paramsValue: wrapPointer([rawPtr]),
329
+ });
330
+ const success = structData.success !== 0;
331
+ try {
332
+ if (success) {
333
+ const dataStr = readCString(structData.data);
334
+ if (dataStr === null || dataStr === "") {
335
+ return { ok: true, value: undefined };
336
+ }
337
+ try {
338
+ return { ok: true, value: snakeToCamel(JSON.parse(dataStr)) };
339
+ }
340
+ catch {
341
+ return { ok: true, value: dataStr };
342
+ }
343
+ }
344
+ else {
345
+ const errorStr = readCString(structData.error);
346
+ return err(errorStr || "Unknown error");
347
+ }
348
+ }
349
+ finally {
350
+ freeResult(rawPtr);
351
+ }
352
+ }
353
+ return callFfiResult("fff_health_check", [DataType.External, DataType.String], [handle, testPath]);
354
+ }
355
+ /**
356
+ * Live grep - search file contents.
357
+ */
358
+ export function ffiLiveGrep(handle, query, optsJson) {
359
+ return callFfiResult("fff_live_grep", [DataType.External, DataType.String, DataType.String], [handle, query, optsJson]);
360
+ }
361
+ /**
362
+ * Multi-pattern grep - Aho-Corasick multi-needle search.
363
+ */
364
+ export function ffiMultiGrep(handle, optsJson) {
365
+ return callFfiResult("fff_multi_grep", [DataType.External, DataType.String], [handle, optsJson]);
366
+ }
367
+ /**
368
+ * Ensure the library is loaded.
369
+ *
370
+ * Loads the native library from the platform-specific npm package
371
+ * or a local dev build. Throws if the binary is not found.
372
+ */
373
+ export function ensureLoaded() {
374
+ loadLibrary();
375
+ }
376
+ /**
377
+ * Check if the library is available.
378
+ */
379
+ export function isAvailable() {
380
+ try {
381
+ loadLibrary();
382
+ return true;
383
+ }
384
+ catch {
385
+ return false;
386
+ }
387
+ }
388
+ /**
389
+ * Close the library and release ffi-rs resources.
390
+ * Call this when completely done with the library.
391
+ */
392
+ export function closeLibrary() {
393
+ if (isLoaded) {
394
+ close(LIBRARY_KEY);
395
+ isLoaded = false;
396
+ }
397
+ }
398
+ //# sourceMappingURL=ffi.js.map