@oh-my-pi/pi-natives 9.4.0 → 9.6.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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # @oh-my-pi/pi-natives
2
2
 
3
- Native Rust functionality compiled to WebAssembly via wasm-bindgen.
3
+ Native Rust functionality via N-API.
4
4
 
5
5
  ## What's Inside
6
6
 
7
- - **Grep**: Regex-based search powered by ripgrep's engine (WASM handles matching, JS handles I/O + gitignore-aware file walking)
7
+ - **Grep**: Regex-based search powered by ripgrep's engine with native file walking and matching
8
8
  - **Find**: Glob-based file/directory discovery with gitignore support (pure TypeScript via `globPaths`)
9
- - **Image**: Image processing via photon-rs (resize, format conversion)
9
+ - **Image**: Image processing via photon-rs (resize, format conversion) exposed through N-API
10
10
 
11
11
  ## Usage
12
12
 
@@ -37,8 +37,8 @@ const pngBytes = await resized.get_bytes();
37
37
  ## Building
38
38
 
39
39
  ```bash
40
- # Build WASM from workspace root (requires Rust + wasm-pack)
41
- bun run build:wasm
40
+ # Build native addon from workspace root (requires Rust)
41
+ bun run build:native
42
42
 
43
43
  # Type check
44
44
  bun run check
@@ -48,16 +48,13 @@ bun run check
48
48
 
49
49
  ```
50
50
  crates/pi-natives/ # Rust source (workspace member)
51
- src/lib.rs # Grep/search + wasm-bindgen bindings
51
+ src/lib.rs # N-API exports
52
52
  src/image.rs # Image processing (photon-rs)
53
53
  Cargo.toml # Rust dependencies
54
- wasm/ # Generated WASM output
55
- pi_natives.wasm # Compiled WASM module
56
- pi_natives.js # wasm-bindgen generated JS glue
57
- pi_natives.d.ts # TypeScript definitions
54
+ native/ # Native addon binaries
55
+ pi_natives.<platform>-<arch>.node
56
+ pi_natives.node
58
57
  src/ # TypeScript wrappers
58
+ native.ts # Native addon loader
59
59
  index.ts # Public API
60
- grep/ # Grep with worker pool
61
- image/ # Async image processing via worker
62
- pool.ts # Generic worker pool infrastructure
63
60
  ```
Binary file
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-natives",
3
- "version": "9.4.0",
4
- "description": "Native Rust functionality compiled to WebAssembly via wasm-bindgen",
3
+ "version": "9.6.0",
4
+ "description": "Native Rust functionality via N-API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "types": "./src/index.ts",
@@ -13,13 +13,13 @@
13
13
  },
14
14
  "files": [
15
15
  "src",
16
- "wasm"
16
+ "native"
17
17
  ],
18
18
  "scripts": {
19
- "build:wasm": "bun scripts/build-wasm.ts",
19
+ "build:native": "bun scripts/build-native.ts",
20
20
  "check": "biome check . && tsgo -p tsconfig.json",
21
21
  "fix": "biome check --write --unsafe .",
22
- "test": "bun test",
22
+ "test": "bun run build:native && bun test",
23
23
  "bench": "bun bench/grep.ts"
24
24
  },
25
25
  "author": "Can Bölük",
@@ -30,7 +30,7 @@
30
30
  "directory": "packages/natives"
31
31
  },
32
32
  "dependencies": {
33
- "@oh-my-pi/pi-utils": "9.4.0"
33
+ "@oh-my-pi/pi-utils": "9.6.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^25.0.10"
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Types for native find API.
3
+ */
4
+
5
+ export interface FindOptions {
6
+ /** Glob pattern to match (e.g., `*.ts`) */
7
+ pattern: string;
8
+ /** Directory to search */
9
+ path: string;
10
+ /** Filter by file type: "file", "dir", or "symlink" */
11
+ fileType?: "file" | "dir" | "symlink";
12
+ /** Include hidden files (default: false) */
13
+ hidden?: boolean;
14
+ /** Maximum number of results */
15
+ maxResults?: number;
16
+ /** Respect .gitignore files (default: true) */
17
+ gitignore?: boolean;
18
+ /** Sort results by mtime (most recent first) before applying limit */
19
+ sortByMtime?: boolean;
20
+ }
21
+
22
+ export interface FindMatch {
23
+ path: string;
24
+ fileType: "file" | "dir" | "symlink";
25
+ mtime?: number;
26
+ }
27
+
28
+ export interface FindResult {
29
+ matches: FindMatch[];
30
+ totalMatches: number;
31
+ }
package/src/grep/index.ts CHANGED
@@ -1,329 +1,71 @@
1
1
  /**
2
- * Native ripgrep wrapper using wasm-bindgen.
3
- *
4
- * JS handles filesystem operations (directory walking, file reading).
5
- * WASM handles pure regex matching using ripgrep's engine.
2
+ * Native ripgrep wrapper using N-API.
6
3
  */
7
4
 
8
- import * as fs from "node:fs/promises";
9
- import * as path from "node:path";
10
- import { globPaths } from "@oh-my-pi/pi-utils";
11
- import {
12
- CompiledPattern as WasmCompiledPattern,
13
- has_match as wasmHasMatch,
14
- search as wasmSearch,
15
- } from "../../wasm/pi_natives";
16
- import { WorkerPool } from "../pool";
17
- import { resolveWorkerSpecifier } from "../worker-resolver";
18
- import { FileReader } from "./file-reader";
19
- import { buildGlobPattern, matchesTypeFilter, resolveTypeFilter } from "./filters";
5
+ import { native } from "../native";
6
+ import { wrapRequestOptions } from "../request-options";
20
7
  import type {
21
8
  ContextLine,
9
+ FuzzyFindMatch,
10
+ FuzzyFindOptions,
11
+ FuzzyFindResult,
22
12
  GrepMatch,
23
13
  GrepOptions,
24
14
  GrepResult,
25
15
  GrepSummary,
26
- WasmSearchResult,
27
- WorkerRequest,
28
- WorkerResponse,
16
+ SearchOptions,
17
+ SearchResult,
29
18
  } from "./types";
30
19
 
31
- export type { ContextLine, GrepMatch, GrepOptions, GrepResult, GrepSummary };
32
-
33
- // =============================================================================
34
- // File Walking
35
- // =============================================================================
36
-
37
- function filterUndefined<T extends Record<string, unknown>>(obj: T): T {
38
- const result = {} as T;
39
- for (const [key, value] of Object.entries(obj)) {
40
- if (value !== undefined) {
41
- (result as Record<string, unknown>)[key] = value;
42
- }
43
- }
44
- return result;
45
- }
46
-
47
- // =============================================================================
48
- // Grep Implementation
49
- // =============================================================================
50
-
51
- const GREP_WORKERS = (() => {
52
- const val = process.env.OMP_GREP_WORKERS;
53
- if (val === undefined) return true;
54
- const n = Number.parseInt(val, 10);
55
- return Number.isNaN(n) || n > 0;
56
- })();
20
+ export type {
21
+ ContextLine,
22
+ FuzzyFindMatch,
23
+ FuzzyFindOptions,
24
+ FuzzyFindResult,
25
+ GrepMatch,
26
+ GrepOptions,
27
+ GrepResult,
28
+ GrepSummary,
29
+ SearchOptions,
30
+ SearchResult,
31
+ };
57
32
 
58
33
  /**
59
- * Search files for a regex pattern (direct, single-threaded).
34
+ * Search files for a regex pattern with optional streaming callback.
60
35
  */
61
- async function grepDirect(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise<GrepResult> {
62
- const searchPath = path.resolve(options.path);
63
- const outputMode = options.mode ?? "content";
64
- const wasmMode = outputMode === "content" ? "content" : "count";
65
-
66
- const stat = await fs.stat(searchPath);
67
- const isFile = stat.isFile();
68
-
69
- using compiledPattern = new WasmCompiledPattern(
70
- filterUndefined({
71
- pattern: options.pattern,
72
- ignoreCase: options.ignoreCase,
73
- multiline: options.multiline,
74
- context: options.context,
75
- maxColumns: options.maxColumns,
76
- mode: wasmMode,
77
- }),
78
- );
79
-
80
- const typeFilter = resolveTypeFilter(options.type);
81
- const globPattern = buildGlobPattern(options.glob);
82
-
83
- const matches: GrepMatch[] = [];
84
- let totalMatches = 0;
85
- let filesWithMatches = 0;
86
- let filesSearched = 0;
87
- let limitReached = false;
88
- const maxCount = options.maxCount;
89
- const globalOffset = options.offset ?? 0;
90
-
91
- if (isFile) {
92
- if (typeFilter && !matchesTypeFilter(searchPath, typeFilter)) {
93
- return {
94
- matches,
95
- totalMatches,
96
- filesWithMatches,
97
- filesSearched,
98
- limitReached: limitReached || undefined,
99
- };
100
- }
101
-
102
- const fileReader = new FileReader();
103
- const content = await fileReader.read(searchPath);
104
- if (!content) {
105
- return {
106
- matches,
107
- totalMatches,
108
- filesWithMatches,
109
- filesSearched,
110
- limitReached: limitReached || undefined,
111
- };
112
- }
113
- filesSearched = 1;
114
-
115
- const result = compiledPattern.search_bytes(
116
- content,
117
- maxCount,
118
- globalOffset > 0 ? globalOffset : undefined,
119
- ) as WasmSearchResult;
120
-
121
- if (result.error) {
122
- throw new Error(result.error);
123
- }
124
-
125
- if (result.matchCount > 0) {
126
- filesWithMatches = 1;
127
- totalMatches = result.matchCount;
128
-
129
- if (outputMode === "content") {
130
- for (const m of result.matches) {
131
- const match: GrepMatch = {
132
- path: searchPath,
133
- lineNumber: m.lineNumber,
134
- line: m.line,
135
- contextBefore: m.contextBefore?.length ? m.contextBefore : undefined,
136
- contextAfter: m.contextAfter?.length ? m.contextAfter : undefined,
137
- truncated: m.truncated || undefined,
138
- };
139
- matches.push(match);
140
- onMatch?.(match);
141
- }
142
- } else {
143
- const match: GrepMatch = {
144
- path: searchPath,
145
- lineNumber: 0,
146
- line: "",
147
- matchCount: result.matchCount,
148
- };
149
- matches.push(match);
150
- onMatch?.(match);
151
- }
152
-
153
- limitReached = result.limitReached || (maxCount !== undefined && totalMatches >= maxCount);
154
- }
155
- } else {
156
- const paths = await globPaths(globPattern, {
157
- cwd: searchPath,
158
- dot: options.hidden ?? true,
159
- onlyFiles: true,
160
- gitignore: true,
161
- });
162
-
163
- const fileReader = new FileReader();
164
- for (const relativePath of paths) {
165
- if (limitReached) break;
166
- if (typeFilter && !matchesTypeFilter(relativePath, typeFilter)) {
167
- continue;
168
- }
169
-
170
- const normalizedPath = relativePath.replace(/\\/g, "/");
171
- const fullPath = path.join(searchPath, normalizedPath);
172
-
173
- const content = await fileReader.read(fullPath);
174
- if (!content) continue;
175
-
176
- filesSearched++;
177
-
178
- if (!compiledPattern.has_match_bytes(content)) {
179
- continue;
180
- }
181
-
182
- const fileOffset = globalOffset > 0 ? Math.max(globalOffset - totalMatches, 0) : 0;
183
- const remaining = maxCount !== undefined ? Math.max(maxCount - totalMatches, 0) : undefined;
184
- if (remaining === 0) {
185
- limitReached = true;
186
- break;
187
- }
188
- const result = compiledPattern.search_bytes(
189
- content,
190
- remaining,
191
- fileOffset > 0 ? fileOffset : undefined,
192
- ) as WasmSearchResult;
193
-
194
- if (result.error) {
195
- continue;
196
- }
197
-
198
- if (result.matchCount > 0) {
199
- filesWithMatches++;
200
- totalMatches += result.matchCount;
201
-
202
- if (outputMode === "content") {
203
- for (const m of result.matches) {
204
- const match: GrepMatch = {
205
- path: normalizedPath,
206
- lineNumber: m.lineNumber,
207
- line: m.line,
208
- contextBefore: m.contextBefore?.length ? m.contextBefore : undefined,
209
- contextAfter: m.contextAfter?.length ? m.contextAfter : undefined,
210
- truncated: m.truncated || undefined,
211
- };
212
- matches.push(match);
213
- onMatch?.(match);
214
- }
215
- } else {
216
- const match: GrepMatch = {
217
- path: normalizedPath,
218
- lineNumber: 0,
219
- line: "",
220
- matchCount: result.matchCount,
221
- };
222
- matches.push(match);
223
- onMatch?.(match);
224
- }
225
-
226
- if (result.limitReached || (maxCount !== undefined && totalMatches >= maxCount)) {
227
- limitReached = true;
228
- }
229
- }
230
- }
231
- }
232
-
233
- return {
234
- matches,
235
- totalMatches,
236
- filesWithMatches,
237
- filesSearched,
238
- limitReached: limitReached || undefined,
239
- };
36
+ export async function grep(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise<GrepResult> {
37
+ return wrapRequestOptions(() => native.grep(options, onMatch), options);
240
38
  }
241
39
 
242
- // =============================================================================
243
- // Content Search (lower-level API)
244
- // =============================================================================
245
-
246
40
  /**
247
41
  * Search a single file's content for a pattern.
248
42
  * Lower-level API for when you already have file content.
43
+ *
44
+ * Accepts `Uint8Array`/`Buffer` for zero-copy when content is already UTF-8 encoded.
249
45
  */
250
- export function searchContent(
251
- content: string,
252
- options: {
253
- pattern: string;
254
- ignoreCase?: boolean;
255
- multiline?: boolean;
256
- maxCount?: number;
257
- offset?: number;
258
- context?: number;
259
- maxColumns?: number;
260
- mode?: "content" | "count";
261
- },
262
- ): WasmSearchResult {
263
- return wasmSearch(content, filterUndefined(options)) as WasmSearchResult;
46
+ export function searchContent(content: string | Uint8Array, options: SearchOptions): SearchResult {
47
+ return native.search(content, options);
264
48
  }
265
49
 
266
50
  /**
267
51
  * Quick check if content contains a pattern match.
52
+ *
53
+ * Accepts `Uint8Array`/`Buffer` for zero-copy when content/pattern are already UTF-8 encoded.
268
54
  */
269
55
  export function hasMatch(
270
- content: string,
271
- pattern: string,
56
+ content: string | Uint8Array,
57
+ pattern: string | Uint8Array,
272
58
  options?: { ignoreCase?: boolean; multiline?: boolean },
273
59
  ): boolean {
274
- return wasmHasMatch(content, pattern, options?.ignoreCase ?? false, options?.multiline ?? false);
60
+ return native.hasMatch(content, pattern, options?.ignoreCase ?? false, options?.multiline ?? false);
275
61
  }
276
62
 
277
- // =============================================================================
278
- // Public API
279
- // =============================================================================
280
-
281
63
  /**
282
- * Search files for a regex pattern.
64
+ * Fuzzy file path search for autocomplete.
283
65
  *
284
- * Uses worker pool by default. Set `OMP_GREP_WORKERS=0` to disable.
66
+ * Searches for files and directories whose paths contain the query substring
67
+ * (case-insensitive). Respects .gitignore by default.
285
68
  */
286
- export async function grep(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise<GrepResult> {
287
- if (GREP_WORKERS) {
288
- return await grepPoolInternal(options);
289
- }
290
- return await grepDirect(options, onMatch);
291
- }
292
-
293
- /**
294
- * Search files using worker pool (always, ignores OMP_GREP_WORKERS).
295
- */
296
- export async function grepPool(options: GrepOptions): Promise<GrepResult> {
297
- return await grepPoolInternal(options);
298
- }
299
-
300
- // =============================================================================
301
- // Worker Pool
302
- // =============================================================================
303
-
304
- const pool = new WorkerPool<WorkerRequest, WorkerResponse>({
305
- createWorker: () =>
306
- new Worker(
307
- resolveWorkerSpecifier({
308
- compiled: "./packages/natives/src/grep/worker.ts",
309
- dev: new URL("./worker.ts", import.meta.url),
310
- }),
311
- ),
312
- maxWorkers: 4,
313
- idleTimeoutMs: 30_000,
314
- });
315
-
316
- async function grepPoolInternal(request: GrepOptions): Promise<GrepResult> {
317
- const response = await pool.request<Extract<WorkerResponse, { type: "result" }>>({
318
- type: "grep",
319
- request,
320
- });
321
- return response.result;
69
+ export async function fuzzyFind(options: FuzzyFindOptions): Promise<FuzzyFindResult> {
70
+ return wrapRequestOptions(() => native.fuzzyFind(options), options);
322
71
  }
323
-
324
- /** Terminate all grep workers. */
325
- export function terminate(): void {
326
- pool.terminate();
327
- }
328
-
329
- export { grepDirect };
package/src/grep/types.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import type { RequestOptions } from "../request-options";
2
+
1
3
  /** Options for searching files. */
2
- export interface GrepOptions {
4
+ export interface GrepOptions extends RequestOptions {
3
5
  /** Regex pattern to search for */
4
6
  pattern: string;
5
7
  /** Directory or file to search */
@@ -52,31 +54,69 @@ export interface GrepResult extends GrepSummary {
52
54
  matches: GrepMatch[];
53
55
  }
54
56
 
55
- /** WASM match result from the compiled pattern. */
56
- export interface WasmMatch {
57
+ export interface SearchOptions {
58
+ /** Regex pattern to search for */
59
+ pattern: string;
60
+ /** Case-insensitive search */
61
+ ignoreCase?: boolean;
62
+ /** Enable multiline matching */
63
+ multiline?: boolean;
64
+ /** Maximum number of matches to return */
65
+ maxCount?: number;
66
+ /** Skip first N matches */
67
+ offset?: number;
68
+ /** Lines of context before/after matches */
69
+ context?: number;
70
+ /** Truncate lines longer than this (characters) */
71
+ maxColumns?: number;
72
+ /** Output mode */
73
+ mode?: "content" | "count";
74
+ }
75
+
76
+ export interface SearchMatch {
57
77
  lineNumber: number;
58
78
  line: string;
59
- contextBefore: ContextLine[];
60
- contextAfter: ContextLine[];
61
- truncated: boolean;
79
+ contextBefore?: ContextLine[];
80
+ contextAfter?: ContextLine[];
81
+ truncated?: boolean;
62
82
  }
63
83
 
64
- /** WASM search result. */
65
- export interface WasmSearchResult {
66
- matches: WasmMatch[];
84
+ export interface SearchResult {
85
+ matches: SearchMatch[];
67
86
  matchCount: number;
68
87
  limitReached: boolean;
69
88
  error?: string;
70
89
  }
71
90
 
72
- /** Message types from main thread to worker. */
73
- export type WorkerRequest =
74
- | { type: "init"; id: number }
75
- | { type: "grep"; id: number; request: GrepOptions }
76
- | { type: "destroy" };
91
+ export type WasmMatch = SearchMatch;
92
+ export type WasmSearchResult = SearchResult;
77
93
 
78
- /** Message types from worker to main thread. */
79
- export type WorkerResponse =
80
- | { type: "ready"; id: number }
81
- | { type: "result"; id: number; result: GrepResult }
82
- | { type: "error"; id: number; error: string };
94
+ /** Options for fuzzy file path search. */
95
+ export interface FuzzyFindOptions extends RequestOptions {
96
+ /** Substring query to match against file paths (case-insensitive). */
97
+ query: string;
98
+ /** Directory to search. */
99
+ path: string;
100
+ /** Include hidden files (default: false). */
101
+ hidden?: boolean;
102
+ /** Respect .gitignore (default: true). */
103
+ gitignore?: boolean;
104
+ /** Maximum number of matches to return (default: 100). */
105
+ maxResults?: number;
106
+ }
107
+
108
+ /** A single match in fuzzy find results. */
109
+ export interface FuzzyFindMatch {
110
+ /** Relative path from the search root (uses `/` separators). */
111
+ path: string;
112
+ /** Whether this entry is a directory. */
113
+ isDirectory: boolean;
114
+ }
115
+
116
+ /** Result of fuzzy file path search. */
117
+ export interface FuzzyFindResult {
118
+ /** Matched entries (up to `maxResults`). */
119
+ matches: FuzzyFindMatch[];
120
+ /** Total number of matches found (may exceed `matches.length`). */
121
+ totalMatches: number;
122
+ }
@@ -1,16 +1,8 @@
1
1
  /**
2
- * Syntax highlighting powered by WASM (syntect).
2
+ * Syntax highlighting powered by native syntect bindings.
3
3
  */
4
4
 
5
- import * as wasm from "../../wasm/pi_natives";
6
-
7
- type WasmHighlightExports = typeof wasm & {
8
- highlight_code: (code: string, lang: string | null | undefined, colors: HighlightColors) => string;
9
- supports_language: (lang: string) => boolean;
10
- get_supported_languages: () => string[];
11
- };
12
-
13
- const wasmHighlight = wasm as WasmHighlightExports;
5
+ import { native } from "../native";
14
6
 
15
7
  /**
16
8
  * Theme colors for syntax highlighting.
@@ -41,19 +33,19 @@ export interface HighlightColors {
41
33
  * @returns Highlighted code as a single string with ANSI color codes
42
34
  */
43
35
  export function highlightCode(code: string, lang: string | undefined, colors: HighlightColors): string {
44
- return wasmHighlight.highlight_code(code, lang, colors);
36
+ return native.highlightCode(code, lang, colors);
45
37
  }
46
38
 
47
39
  /**
48
40
  * Check if a language is supported for highlighting.
49
41
  */
50
42
  export function supportsLanguage(lang: string): boolean {
51
- return wasmHighlight.supports_language(lang);
43
+ return native.supportsLanguage(lang);
52
44
  }
53
45
 
54
46
  /**
55
47
  * Get list of all supported languages.
56
48
  */
57
49
  export function getSupportedLanguages(): string[] {
58
- return wasmHighlight.get_supported_languages();
50
+ return native.getSupportedLanguages();
59
51
  }
package/src/html/index.ts CHANGED
@@ -1,27 +1,13 @@
1
1
  /**
2
- * HTML to Markdown conversion powered by WASM.
3
- *
4
- * Conversion happens in a worker thread to avoid blocking the main thread.
2
+ * HTML to Markdown conversion powered by native bindings.
5
3
  */
6
4
 
7
- import { type RequestOptions, WorkerPool } from "../pool";
8
- import { resolveWorkerSpecifier } from "../worker-resolver";
9
- import type { HtmlRequest, HtmlResponse, HtmlToMarkdownOptions } from "./types";
5
+ import { native } from "../native";
6
+ import { type RequestOptions, wrapRequestOptions } from "../request-options";
7
+ import type { HtmlToMarkdownOptions } from "./types";
10
8
 
11
9
  export type { HtmlToMarkdownOptions } from "./types";
12
10
 
13
- const pool = new WorkerPool<HtmlRequest, HtmlResponse>({
14
- createWorker: () =>
15
- new Worker(
16
- resolveWorkerSpecifier({
17
- compiled: "./packages/natives/src/html/worker.ts",
18
- dev: new URL("./worker.ts", import.meta.url),
19
- }),
20
- ),
21
- maxWorkers: 2,
22
- idleTimeoutMs: 30_000,
23
- });
24
-
25
11
  /**
26
12
  * Convert HTML to Markdown.
27
13
  *
@@ -34,21 +20,5 @@ export async function htmlToMarkdown(
34
20
  options?: HtmlToMarkdownOptions,
35
21
  req?: RequestOptions,
36
22
  ): Promise<string> {
37
- const response = await pool.request<Extract<HtmlResponse, { type: "converted" }>>(
38
- {
39
- type: "convert",
40
- html,
41
- options,
42
- },
43
- req,
44
- );
45
- return response.markdown;
46
- }
47
-
48
- /**
49
- * Terminate the HTML worker pool.
50
- * Call this when shutting down to clean up resources.
51
- */
52
- export function terminate(): void {
53
- pool.terminate();
23
+ return wrapRequestOptions(() => native.htmlToMarkdown(html, options), req);
54
24
  }