@oh-my-pi/pi-natives 9.3.1 → 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 +10 -13
- package/native/pi_natives.darwin-arm64.node +0 -0
- package/native/pi_natives.darwin-x64.node +0 -0
- package/native/pi_natives.linux-arm64.node +0 -0
- package/native/pi_natives.linux-x64.node +0 -0
- package/native/pi_natives.win32-x64.node +0 -0
- package/package.json +6 -6
- package/src/find/types.ts +31 -0
- package/src/grep/index.ts +37 -295
- package/src/grep/types.ts +59 -19
- package/src/highlight/index.ts +5 -13
- package/src/html/index.ts +5 -35
- package/src/html/types.ts +1 -16
- package/src/image/index.ts +52 -73
- package/src/index.ts +20 -93
- package/src/native.ts +159 -0
- package/src/request-options.ts +94 -0
- package/src/text/index.ts +16 -24
- package/src/grep/file-reader.ts +0 -51
- package/src/grep/filters.ts +0 -77
- package/src/grep/worker.ts +0 -212
- package/src/html/worker.ts +0 -40
- package/src/image/types.ts +0 -52
- package/src/image/worker.ts +0 -152
- package/src/pool.ts +0 -362
- package/src/wasix.ts +0 -1745
- package/src/worker-resolver.ts +0 -9
- package/wasm/pi_natives.d.ts +0 -148
- package/wasm/pi_natives.js +0 -891
- package/wasm/pi_natives_bg.wasm +0 -0
- package/wasm/pi_natives_bg.wasm.d.ts +0 -32
package/src/pool.ts
DELETED
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generic worker pool for WASM-based operations.
|
|
3
|
-
*
|
|
4
|
-
* Supports both single-worker (maxWorkers: 1) and multi-worker scenarios.
|
|
5
|
-
* Workers are lazily created and auto-terminated after idle timeout.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/** Base request type - workers must accept messages with this shape. */
|
|
9
|
-
export interface BaseRequest {
|
|
10
|
-
type: string;
|
|
11
|
-
id?: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Base response type - workers must respond with this shape. */
|
|
15
|
-
export interface BaseResponse {
|
|
16
|
-
type: string;
|
|
17
|
-
id: number;
|
|
18
|
-
error?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface WorkerPoolOptions {
|
|
22
|
-
/** URL to the worker script (deprecated: use createWorker for compiled binaries). */
|
|
23
|
-
workerUrl?: string | URL;
|
|
24
|
-
/** Factory function to create workers. Required for compiled binaries where Bun needs static analysis. */
|
|
25
|
-
createWorker?: () => Worker;
|
|
26
|
-
/** Maximum number of workers (default: 4). */
|
|
27
|
-
maxWorkers?: number;
|
|
28
|
-
/** Idle timeout in ms before terminating unused workers (0 = never, default: 30000). */
|
|
29
|
-
idleTimeoutMs?: number;
|
|
30
|
-
/** Timeout for worker initialization in ms (default: 10000). */
|
|
31
|
-
initTimeoutMs?: number;
|
|
32
|
-
/** Grace period after request timeout before force-terminating stuck workers (default: 5000). */
|
|
33
|
-
stuckGracePeriodMs?: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface RequestOptions {
|
|
37
|
-
/** Timeout for this request in ms. After this, the promise rejects but worker gets a grace period. */
|
|
38
|
-
timeoutMs?: number;
|
|
39
|
-
/** Abort signal for this request. */
|
|
40
|
-
signal?: AbortSignal;
|
|
41
|
-
/** Transfer list for postMessage. */
|
|
42
|
-
transfer?: ArrayBufferLike[];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface PooledWorker {
|
|
46
|
-
worker: Worker;
|
|
47
|
-
busy: boolean;
|
|
48
|
-
lastUsed: number;
|
|
49
|
-
currentRequestId: number | null;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
interface PendingRequest<T> {
|
|
53
|
-
resolve: (result: T) => void;
|
|
54
|
-
reject: (error: Error) => void;
|
|
55
|
-
worker?: PooledWorker;
|
|
56
|
-
dispose?: () => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* A pool of workers that process requests in parallel.
|
|
61
|
-
*
|
|
62
|
-
* @typeParam TReq - Request message type (must extend BaseRequest)
|
|
63
|
-
* @typeParam TRes - Response message type (must extend BaseResponse)
|
|
64
|
-
*/
|
|
65
|
-
export class WorkerPool<TReq extends BaseRequest, TRes extends BaseResponse> {
|
|
66
|
-
readonly #options: {
|
|
67
|
-
workerUrl?: string | URL;
|
|
68
|
-
createWorker?: () => Worker;
|
|
69
|
-
maxWorkers: number;
|
|
70
|
-
idleTimeoutMs: number;
|
|
71
|
-
initTimeoutMs: number;
|
|
72
|
-
stuckGracePeriodMs: number;
|
|
73
|
-
};
|
|
74
|
-
readonly #pool: PooledWorker[] = [];
|
|
75
|
-
readonly #waiters: Array<(worker: PooledWorker) => void> = [];
|
|
76
|
-
readonly #pending = new Map<number, PendingRequest<TRes>>();
|
|
77
|
-
#nextRequestId = 1;
|
|
78
|
-
#idleCheckInterval: ReturnType<typeof setInterval> | null = null;
|
|
79
|
-
|
|
80
|
-
constructor(options: WorkerPoolOptions) {
|
|
81
|
-
if (!options.workerUrl && !options.createWorker) {
|
|
82
|
-
throw new Error("WorkerPool requires either workerUrl or createWorker");
|
|
83
|
-
}
|
|
84
|
-
this.#options = {
|
|
85
|
-
workerUrl: options.workerUrl,
|
|
86
|
-
createWorker: options.createWorker,
|
|
87
|
-
maxWorkers: options.maxWorkers ?? 4,
|
|
88
|
-
idleTimeoutMs: options.idleTimeoutMs ?? 30_000,
|
|
89
|
-
initTimeoutMs: options.initTimeoutMs ?? 10_000,
|
|
90
|
-
stuckGracePeriodMs: options.stuckGracePeriodMs ?? 5_000,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Send a request to a worker and wait for the response.
|
|
96
|
-
* Workers are acquired from the pool (or created if under limit).
|
|
97
|
-
*
|
|
98
|
-
* @param msg - Request message
|
|
99
|
-
* @param options - Request options (timeout, transfer)
|
|
100
|
-
*/
|
|
101
|
-
async request<T extends TRes = TRes>(
|
|
102
|
-
msg: TReq | (Omit<TReq, "id"> & { id?: number }),
|
|
103
|
-
options?: RequestOptions,
|
|
104
|
-
): Promise<T> {
|
|
105
|
-
const { timeoutMs, signal, transfer } = options ?? {};
|
|
106
|
-
signal?.throwIfAborted();
|
|
107
|
-
|
|
108
|
-
const worker = await this.#acquireWorker();
|
|
109
|
-
const id = msg.id ?? this.#nextRequestId++;
|
|
110
|
-
const fullMsg = { ...msg, id } as TReq;
|
|
111
|
-
|
|
112
|
-
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
113
|
-
const pending: PendingRequest<T> = {
|
|
114
|
-
resolve: resolve as (result: TRes) => void,
|
|
115
|
-
reject,
|
|
116
|
-
worker,
|
|
117
|
-
};
|
|
118
|
-
this.#pending.set(id, pending as PendingRequest<TRes>);
|
|
119
|
-
|
|
120
|
-
const onAbort = () => {
|
|
121
|
-
this.#handleRequestAbort(id, worker);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
if (timeoutMs && timeoutMs > 0 && signal) {
|
|
125
|
-
const combined = AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]);
|
|
126
|
-
combined.addEventListener("abort", onAbort, { once: true });
|
|
127
|
-
pending.dispose = () => combined.removeEventListener("abort", onAbort);
|
|
128
|
-
} else if (timeoutMs && timeoutMs > 0) {
|
|
129
|
-
const timer = setTimeout(onAbort, timeoutMs);
|
|
130
|
-
pending.dispose = () => clearTimeout(timer);
|
|
131
|
-
} else if (signal) {
|
|
132
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
133
|
-
pending.dispose = () => signal.removeEventListener("abort", onAbort);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
worker.currentRequestId = id;
|
|
137
|
-
if (transfer) {
|
|
138
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
139
|
-
worker.worker.postMessage(fullMsg, transfer as any);
|
|
140
|
-
} else {
|
|
141
|
-
worker.worker.postMessage(fullMsg);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
return promise;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** Terminate all workers and clear pending requests. */
|
|
148
|
-
terminate(): void {
|
|
149
|
-
if (this.#idleCheckInterval) {
|
|
150
|
-
clearInterval(this.#idleCheckInterval);
|
|
151
|
-
this.#idleCheckInterval = null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
for (const w of [...this.#pool]) {
|
|
155
|
-
this.#removeWorker(w);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
this.#waiters.length = 0;
|
|
159
|
-
|
|
160
|
-
for (const pending of this.#pending.values()) {
|
|
161
|
-
pending.reject(new Error("Worker pool terminated"));
|
|
162
|
-
void pending.dispose?.();
|
|
163
|
-
}
|
|
164
|
-
this.#pending.clear();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
#createWorker(): PooledWorker {
|
|
168
|
-
const worker = this.#options.createWorker ? this.#options.createWorker() : new Worker(this.#options.workerUrl!);
|
|
169
|
-
|
|
170
|
-
const pooledWorker: PooledWorker = {
|
|
171
|
-
worker,
|
|
172
|
-
busy: false,
|
|
173
|
-
lastUsed: Date.now(),
|
|
174
|
-
currentRequestId: null,
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
worker.onmessage = (e: MessageEvent<TRes>) => {
|
|
178
|
-
this.#handleMessage(pooledWorker, e.data);
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
worker.onerror = (e: ErrorEvent) => {
|
|
182
|
-
const requestId = pooledWorker.currentRequestId;
|
|
183
|
-
if (requestId !== null) {
|
|
184
|
-
this.#rejectRequest(requestId, new Error(`Worker error: ${e.message}`));
|
|
185
|
-
}
|
|
186
|
-
this.#removeWorker(pooledWorker);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
return pooledWorker;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
#handleMessage(pooledWorker: PooledWorker, msg: TRes): void {
|
|
193
|
-
const pending = this.#pending.get(msg.id);
|
|
194
|
-
if (!pending) return;
|
|
195
|
-
|
|
196
|
-
this.#pending.delete(msg.id);
|
|
197
|
-
void pending.dispose?.();
|
|
198
|
-
|
|
199
|
-
if (msg.type === "error" && "error" in msg) {
|
|
200
|
-
pending.reject(new Error(msg.error ?? "Unknown error"));
|
|
201
|
-
} else {
|
|
202
|
-
pending.resolve(msg);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Release worker back to pool (unless it was the init request)
|
|
206
|
-
if (msg.type !== "ready") {
|
|
207
|
-
pooledWorker.currentRequestId = null;
|
|
208
|
-
this.#releaseWorker(pooledWorker);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
#rejectRequest(id: number, error: Error): void {
|
|
213
|
-
const pending = this.#pending.get(id);
|
|
214
|
-
if (pending) {
|
|
215
|
-
this.#pending.delete(id);
|
|
216
|
-
void pending.dispose?.();
|
|
217
|
-
pending.reject(error);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
#handleRequestAbort(id: number, worker: PooledWorker): void {
|
|
222
|
-
const pending = this.#pending.get(id);
|
|
223
|
-
if (!pending) return;
|
|
224
|
-
|
|
225
|
-
pending.dispose = undefined;
|
|
226
|
-
pending.reject(new Error("Request timeout"));
|
|
227
|
-
|
|
228
|
-
if (this.#options.stuckGracePeriodMs > 0) {
|
|
229
|
-
const timer = setTimeout(() => {
|
|
230
|
-
this.#terminateStuckWorker(id, worker);
|
|
231
|
-
}, this.#options.stuckGracePeriodMs);
|
|
232
|
-
|
|
233
|
-
pending.dispose = () => {
|
|
234
|
-
clearTimeout(timer);
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
#terminateStuckWorker(id: number, worker: PooledWorker): void {
|
|
240
|
-
const pending = this.#pending.get(id);
|
|
241
|
-
if (pending) {
|
|
242
|
-
this.#pending.delete(id);
|
|
243
|
-
void pending.dispose?.();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (worker.currentRequestId !== id) return;
|
|
247
|
-
if (!this.#pool.includes(worker)) return;
|
|
248
|
-
|
|
249
|
-
this.#removeWorker(worker);
|
|
250
|
-
|
|
251
|
-
if (this.#pool.length === 0 && this.#waiters.length > 0) {
|
|
252
|
-
this.#replenishPool();
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async #replenishPool(): Promise<void> {
|
|
257
|
-
const worker = this.#createWorker();
|
|
258
|
-
worker.busy = true;
|
|
259
|
-
this.#pool.push(worker);
|
|
260
|
-
try {
|
|
261
|
-
await this.#initializeWorker(worker);
|
|
262
|
-
this.#releaseWorker(worker);
|
|
263
|
-
} catch {
|
|
264
|
-
this.#removeWorker(worker);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
#removeWorker(pooledWorker: PooledWorker): void {
|
|
269
|
-
const idx = this.#pool.indexOf(pooledWorker);
|
|
270
|
-
if (idx !== -1) {
|
|
271
|
-
this.#pool.splice(idx, 1);
|
|
272
|
-
}
|
|
273
|
-
pooledWorker.worker.postMessage({ type: "destroy" } satisfies BaseRequest);
|
|
274
|
-
pooledWorker.worker.terminate();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#releaseWorker(pooledWorker: PooledWorker): void {
|
|
278
|
-
pooledWorker.busy = false;
|
|
279
|
-
pooledWorker.lastUsed = Date.now();
|
|
280
|
-
|
|
281
|
-
if (this.#waiters.length) {
|
|
282
|
-
const waiter = this.#waiters.shift()!;
|
|
283
|
-
pooledWorker.busy = true;
|
|
284
|
-
waiter(pooledWorker);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
#checkIdleWorkers(): void {
|
|
289
|
-
if (this.#options.idleTimeoutMs === 0) return;
|
|
290
|
-
|
|
291
|
-
const now = Date.now();
|
|
292
|
-
const toRemove: PooledWorker[] = [];
|
|
293
|
-
|
|
294
|
-
for (const w of this.#pool) {
|
|
295
|
-
if (!w.busy && now - w.lastUsed > this.#options.idleTimeoutMs && !this.#waiters.length) {
|
|
296
|
-
toRemove.push(w);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
for (const w of toRemove) {
|
|
301
|
-
this.#removeWorker(w);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (this.#pool.length === 0 && this.#idleCheckInterval) {
|
|
305
|
-
clearInterval(this.#idleCheckInterval);
|
|
306
|
-
this.#idleCheckInterval = null;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
#ensureIdleCheck(): void {
|
|
311
|
-
if (this.#options.idleTimeoutMs > 0 && !this.#idleCheckInterval) {
|
|
312
|
-
this.#idleCheckInterval = setInterval(() => this.#checkIdleWorkers(), 10_000);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async #initializeWorker(pooledWorker: PooledWorker): Promise<void> {
|
|
317
|
-
const id = this.#nextRequestId++;
|
|
318
|
-
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
319
|
-
|
|
320
|
-
const timeout = setTimeout(() => {
|
|
321
|
-
this.#rejectRequest(id, new Error("Worker initialization timeout"));
|
|
322
|
-
}, this.#options.initTimeoutMs);
|
|
323
|
-
|
|
324
|
-
this.#pending.set(id, {
|
|
325
|
-
resolve: () => resolve(),
|
|
326
|
-
reject,
|
|
327
|
-
dispose: () => clearTimeout(timeout),
|
|
328
|
-
} as PendingRequest<TRes>);
|
|
329
|
-
|
|
330
|
-
pooledWorker.currentRequestId = id;
|
|
331
|
-
pooledWorker.worker.postMessage({ type: "init", id } satisfies BaseRequest);
|
|
332
|
-
return promise;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async #acquireWorker(): Promise<PooledWorker> {
|
|
336
|
-
// Try to find an idle worker
|
|
337
|
-
for (const w of this.#pool) {
|
|
338
|
-
if (!w.busy) {
|
|
339
|
-
w.busy = true;
|
|
340
|
-
return w;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Create new worker if under limit
|
|
345
|
-
if (this.#pool.length < this.#options.maxWorkers) {
|
|
346
|
-
const worker = this.#createWorker();
|
|
347
|
-
worker.busy = true;
|
|
348
|
-
this.#pool.push(worker);
|
|
349
|
-
this.#ensureIdleCheck();
|
|
350
|
-
await this.#initializeWorker(worker);
|
|
351
|
-
return worker;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Wait for a worker to become available
|
|
355
|
-
const { promise, resolve } = Promise.withResolvers<PooledWorker>();
|
|
356
|
-
this.#waiters.push(w => {
|
|
357
|
-
w.busy = true;
|
|
358
|
-
resolve(w);
|
|
359
|
-
});
|
|
360
|
-
return promise;
|
|
361
|
-
}
|
|
362
|
-
}
|