@dmop/puru 0.1.10 → 0.1.12
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/AGENTS.md +137 -2
- package/README.md +124 -164
- package/dist/index.cjs +810 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +341 -8
- package/dist/index.d.ts +341 -8
- package/dist/index.js +798 -48
- package/dist/index.js.map +1 -1
- package/llms-full.txt +175 -8
- package/llms.txt +10 -6
- package/package.json +4 -1
package/llms-full.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A thread pool with Go-style concurrency primitives for JavaScript
|
|
4
4
|
|
|
5
|
-
puru manages a pool of worker threads and provides a simple API to run functions off the main thread. No separate worker files, no manual message passing.
|
|
5
|
+
puru manages a pool of worker threads and provides a simple API to run functions off the main thread. Zero runtime dependencies. No separate worker files, no manual message passing.
|
|
6
6
|
|
|
7
7
|
Works on Node.js and Bun. Deno support coming soon.
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ bun add @dmop/puru
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
|
-
import { spawn, chan, WaitGroup, select, after } from '@dmop/puru'
|
|
19
|
+
import { spawn, chan, WaitGroup, ErrGroup, select, after, Timer, Mutex, RWMutex, Cond, background, withTimeout } from '@dmop/puru'
|
|
20
20
|
|
|
21
21
|
// CPU work — runs in a dedicated worker thread
|
|
22
22
|
const { result } = spawn(() => fibonacci(40))
|
|
@@ -66,10 +66,19 @@ Options:
|
|
|
66
66
|
- priority: 'low' | 'normal' | 'high' (default: 'normal')
|
|
67
67
|
- concurrent: boolean (default: false)
|
|
68
68
|
- channels: Record<string, Channel<unknown>> — pass channels to the worker
|
|
69
|
+
- ctx: Context — auto-cancel the task when the context is cancelled
|
|
69
70
|
|
|
70
71
|
Exclusive mode (default): the function gets a dedicated thread. Use for CPU-heavy work.
|
|
71
72
|
Concurrent mode ({ concurrent: true }): multiple tasks share a thread's event loop. Use for async/I/O work.
|
|
72
73
|
|
|
74
|
+
Context integration — tasks auto-cancel when the context expires:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const [ctx, cancel] = withTimeout(background(), 5000)
|
|
78
|
+
const { result } = spawn(() => heavyWork(), { ctx })
|
|
79
|
+
// task auto-cancels after 5s
|
|
80
|
+
```
|
|
81
|
+
|
|
73
82
|
Functions must be self-contained — they cannot capture variables from the enclosing scope:
|
|
74
83
|
|
|
75
84
|
```typescript
|
|
@@ -89,6 +98,10 @@ const ch = chan<string>() // unbuffered, capacity 0
|
|
|
89
98
|
await ch.send(42)
|
|
90
99
|
const value = await ch.recv() // 42
|
|
91
100
|
|
|
101
|
+
// Inspect buffer state (like Go's len(ch) and cap(ch))
|
|
102
|
+
ch.len // number of buffered values
|
|
103
|
+
ch.cap // buffer capacity
|
|
104
|
+
|
|
92
105
|
ch.close()
|
|
93
106
|
await ch.recv() // null (closed)
|
|
94
107
|
|
|
@@ -96,6 +109,10 @@ await ch.recv() // null (closed)
|
|
|
96
109
|
for await (const value of ch) {
|
|
97
110
|
process(value)
|
|
98
111
|
}
|
|
112
|
+
|
|
113
|
+
// Directional views (like Go's chan<- T and <-chan T)
|
|
114
|
+
const sender = ch.sendOnly() // only send() and close()
|
|
115
|
+
const receiver = ch.recvOnly() // only recv() and async iteration
|
|
99
116
|
```
|
|
100
117
|
|
|
101
118
|
Channels in workers — pass channels to spawn() and use them across worker threads:
|
|
@@ -129,9 +146,10 @@ for (let i = 0; i < 4; i++) {
|
|
|
129
146
|
|
|
130
147
|
### WaitGroup
|
|
131
148
|
|
|
132
|
-
Structured concurrency. Spawn multiple tasks, wait for all.
|
|
149
|
+
Structured concurrency. Spawn multiple tasks, wait for all. Accepts optional Context.
|
|
133
150
|
|
|
134
151
|
```typescript
|
|
152
|
+
// Without context
|
|
135
153
|
const wg = new WaitGroup()
|
|
136
154
|
wg.spawn(() => cpuWork()) // exclusive
|
|
137
155
|
wg.spawn(() => fetchData(), { concurrent: true }) // concurrent
|
|
@@ -140,11 +158,18 @@ const results = await wg.wait() // like Promise.all
|
|
|
140
158
|
const settled = await wg.waitSettled() // like Promise.allSettled
|
|
141
159
|
|
|
142
160
|
wg.cancel() // cancel all tasks
|
|
161
|
+
|
|
162
|
+
// With context — all tasks auto-cancel when context expires
|
|
163
|
+
const [ctx, cancel] = withTimeout(background(), 5000)
|
|
164
|
+
const wg2 = new WaitGroup(ctx)
|
|
165
|
+
wg2.spawn(() => cpuWork())
|
|
166
|
+
const results2 = await wg2.wait()
|
|
167
|
+
cancel()
|
|
143
168
|
```
|
|
144
169
|
|
|
145
170
|
### ErrGroup
|
|
146
171
|
|
|
147
|
-
Like WaitGroup, but cancels all remaining tasks on first error (modeled after Go's golang.org/x/sync/errgroup).
|
|
172
|
+
Like WaitGroup, but cancels all remaining tasks on first error — in-flight workers are terminated and queued tasks are discarded (modeled after Go's golang.org/x/sync/errgroup). Accepts optional Context. Supports setLimit() for concurrency throttling.
|
|
148
173
|
|
|
149
174
|
```typescript
|
|
150
175
|
const eg = new ErrGroup()
|
|
@@ -160,6 +185,28 @@ try {
|
|
|
160
185
|
}
|
|
161
186
|
```
|
|
162
187
|
|
|
188
|
+
setLimit(n) — limit max concurrent tasks (like Go's errgroup.SetLimit). Must be called before any spawn().
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
const eg = new ErrGroup()
|
|
192
|
+
eg.setLimit(4) // max 4 tasks in flight
|
|
193
|
+
|
|
194
|
+
for (const url of urls) {
|
|
195
|
+
eg.spawn(() => fetch(url).then(r => r.json()), { concurrent: true })
|
|
196
|
+
}
|
|
197
|
+
const results = await eg.wait() // runs 4 at a time
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
With context:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const [ctx, cancel] = withTimeout(background(), 5000)
|
|
204
|
+
const eg = new ErrGroup(ctx) // auto-cancel all tasks on timeout
|
|
205
|
+
eg.setLimit(2)
|
|
206
|
+
// ...
|
|
207
|
+
cancel()
|
|
208
|
+
```
|
|
209
|
+
|
|
163
210
|
### Mutex
|
|
164
211
|
|
|
165
212
|
Async mutual exclusion. Serialize access to shared resources under concurrency.
|
|
@@ -178,6 +225,31 @@ try { /* critical section */ }
|
|
|
178
225
|
finally { mu.unlock() }
|
|
179
226
|
```
|
|
180
227
|
|
|
228
|
+
### RWMutex
|
|
229
|
+
|
|
230
|
+
Read-write mutex. Multiple concurrent readers, exclusive writers. Like Go's sync.RWMutex.
|
|
231
|
+
|
|
232
|
+
Use instead of Mutex when reads vastly outnumber writes.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const rw = new RWMutex()
|
|
236
|
+
|
|
237
|
+
// Many readers can run concurrently
|
|
238
|
+
const data = await rw.withRLock(() => cache.get('config'))
|
|
239
|
+
|
|
240
|
+
// Writers get exclusive access
|
|
241
|
+
await rw.withLock(() => cache.set('config', newValue))
|
|
242
|
+
|
|
243
|
+
// Manual API
|
|
244
|
+
await rw.rLock()
|
|
245
|
+
try { /* read */ }
|
|
246
|
+
finally { rw.rUnlock() }
|
|
247
|
+
|
|
248
|
+
await rw.lock()
|
|
249
|
+
try { /* write */ }
|
|
250
|
+
finally { rw.unlock() }
|
|
251
|
+
```
|
|
252
|
+
|
|
181
253
|
### Once<T>
|
|
182
254
|
|
|
183
255
|
Run a function exactly once, even if called concurrently. All callers get the same result.
|
|
@@ -188,17 +260,45 @@ const conn = await once.do(() => createExpensiveConnection())
|
|
|
188
260
|
// Subsequent calls return the cached result
|
|
189
261
|
```
|
|
190
262
|
|
|
263
|
+
### Cond
|
|
264
|
+
|
|
265
|
+
Condition variable with Wait/Signal/Broadcast. Like Go's sync.Cond.
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const mu = new Mutex()
|
|
269
|
+
const cond = new Cond(mu)
|
|
270
|
+
let ready = false
|
|
271
|
+
|
|
272
|
+
// Waiter
|
|
273
|
+
await mu.lock()
|
|
274
|
+
while (!ready) await cond.wait() // releases lock, waits, re-acquires
|
|
275
|
+
mu.unlock()
|
|
276
|
+
|
|
277
|
+
// Signaler
|
|
278
|
+
await mu.lock()
|
|
279
|
+
ready = true
|
|
280
|
+
cond.signal() // wake one waiter
|
|
281
|
+
// cond.broadcast() // wake all waiters
|
|
282
|
+
mu.unlock()
|
|
283
|
+
```
|
|
284
|
+
|
|
191
285
|
### select(cases, opts?)
|
|
192
286
|
|
|
193
|
-
Wait for the first of multiple promises to resolve, like Go's select.
|
|
287
|
+
Wait for the first of multiple promises to resolve, like Go's select. Supports both recv and send cases.
|
|
194
288
|
|
|
195
289
|
```typescript
|
|
196
|
-
//
|
|
290
|
+
// Recv case — handler receives the value
|
|
197
291
|
await select([
|
|
198
292
|
[ch.recv(), (value) => console.log('received', value)],
|
|
199
293
|
[after(5000), () => console.log('timeout')],
|
|
200
294
|
])
|
|
201
295
|
|
|
296
|
+
// Send case — handler runs when send completes
|
|
297
|
+
await select([
|
|
298
|
+
[ch.send(42), () => console.log('sent!')],
|
|
299
|
+
[after(1000), () => console.log('send timed out')],
|
|
300
|
+
])
|
|
301
|
+
|
|
202
302
|
// Non-blocking — returns immediately if nothing is ready (Go's select with default)
|
|
203
303
|
await select(
|
|
204
304
|
[[ch.recv(), (value) => process(value)]],
|
|
@@ -206,12 +306,12 @@ await select(
|
|
|
206
306
|
)
|
|
207
307
|
```
|
|
208
308
|
|
|
209
|
-
### after(ms) / ticker(ms)
|
|
309
|
+
### after(ms) / ticker(ms) / Timer(ms)
|
|
210
310
|
|
|
211
311
|
Timers for use with select and async iteration.
|
|
212
312
|
|
|
213
313
|
```typescript
|
|
214
|
-
await after(1000) // one-shot: resolves after 1 second
|
|
314
|
+
await after(1000) // one-shot: resolves after 1 second (fire-and-forget)
|
|
215
315
|
|
|
216
316
|
// Repeating: tick every 500ms
|
|
217
317
|
const t = ticker(500)
|
|
@@ -221,6 +321,73 @@ for await (const _ of t) {
|
|
|
221
321
|
}
|
|
222
322
|
```
|
|
223
323
|
|
|
324
|
+
Timer — resettable one-shot timer. Unlike after(), can be stopped and reset.
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
const t = new Timer(5000)
|
|
328
|
+
|
|
329
|
+
// Use with select for cancellable timeouts
|
|
330
|
+
await select([
|
|
331
|
+
[ch.recv(), (v) => { t.stop(); handle(v) }],
|
|
332
|
+
[t.channel, () => console.log('timed out')],
|
|
333
|
+
])
|
|
334
|
+
|
|
335
|
+
// Reset for debounce
|
|
336
|
+
t.reset(300) // reschedule, creates new channel promise
|
|
337
|
+
await t.channel
|
|
338
|
+
|
|
339
|
+
// Properties
|
|
340
|
+
t.stopped // true after firing or stop()
|
|
341
|
+
t.stop() // returns true if was pending
|
|
342
|
+
t.channel // promise that resolves when timer fires
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Context
|
|
346
|
+
|
|
347
|
+
Hierarchical cancellation, deadlines, and request-scoped values — modeled after Go's context package. Integrates with spawn(), WaitGroup, and ErrGroup.
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { background, withCancel, withTimeout, withDeadline, withValue } from '@dmop/puru'
|
|
351
|
+
|
|
352
|
+
// Root context — never cancelled
|
|
353
|
+
const root = background()
|
|
354
|
+
|
|
355
|
+
// Manual cancellation
|
|
356
|
+
const [ctx, cancel] = withCancel(root)
|
|
357
|
+
cancel() // or cancel('reason')
|
|
358
|
+
ctx.err // CancelledError
|
|
359
|
+
|
|
360
|
+
// Timeout
|
|
361
|
+
const [ctx2, cancel2] = withTimeout(root, 5000)
|
|
362
|
+
ctx2.deadline // Date ~5s from now
|
|
363
|
+
ctx2.done() // promise that resolves when cancelled/expired
|
|
364
|
+
cancel2() // cancel early, clears timer
|
|
365
|
+
|
|
366
|
+
// Absolute deadline
|
|
367
|
+
const [ctx3, cancel3] = withDeadline(root, new Date('2025-12-31'))
|
|
368
|
+
|
|
369
|
+
// Request-scoped values
|
|
370
|
+
const ctx4 = withValue(root, 'requestId', 'abc-123')
|
|
371
|
+
ctx4.value('requestId') // 'abc-123'
|
|
372
|
+
|
|
373
|
+
// Composition — child inherits parent's deadline and cancellation
|
|
374
|
+
const [parent] = withTimeout(root, 10000)
|
|
375
|
+
const child = withValue(parent, 'userId', '42')
|
|
376
|
+
// child cancels when parent does (10s timeout)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Context interface:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
interface Context {
|
|
383
|
+
readonly signal: AbortSignal
|
|
384
|
+
readonly deadline: Date | null
|
|
385
|
+
readonly err: CancelledError | DeadlineExceededError | null
|
|
386
|
+
value<T>(key: symbol | string): T | undefined
|
|
387
|
+
done(): Promise<void>
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
224
391
|
### register(name, fn) / run(name, ...args)
|
|
225
392
|
|
|
226
393
|
Named task registry. Register functions by name, call them by name.
|
package/llms.txt
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A thread pool with Go-style concurrency primitives for JavaScript
|
|
4
4
|
|
|
5
|
-
puru manages a pool of worker threads and provides a simple API to run functions off the main thread. No separate worker files, no manual message passing.
|
|
5
|
+
puru manages a pool of worker threads and provides a simple API to run functions off the main thread. Zero runtime dependencies. No separate worker files, no manual message passing.
|
|
6
6
|
|
|
7
7
|
Works on Node.js and Bun. Deno support coming soon.
|
|
8
8
|
|
|
@@ -13,14 +13,18 @@ bun add @dmop/puru
|
|
|
13
13
|
|
|
14
14
|
## Core API
|
|
15
15
|
|
|
16
|
-
- spawn(fn, opts?) — run a function in a worker thread, returns { result: Promise<T>, cancel() }
|
|
17
|
-
- chan(capacity?) — Go-style channels
|
|
18
|
-
- WaitGroup — spawn multiple tasks, wait for all
|
|
19
|
-
- ErrGroup — like WaitGroup but cancels all on first error
|
|
20
|
-
- select(cases, opts?) — wait for first of multiple promises (like Go's select)
|
|
16
|
+
- spawn(fn, opts?) — run a function in a worker thread, returns { result: Promise<T>, cancel() }. Accepts { ctx } for auto-cancellation via context.
|
|
17
|
+
- chan(capacity?) — Go-style channels with len/cap inspection and directional views (sendOnly/recvOnly)
|
|
18
|
+
- WaitGroup(ctx?) — spawn multiple tasks, wait for all. Optional context for auto-cancellation.
|
|
19
|
+
- ErrGroup(ctx?) — like WaitGroup but cancels all on first error. Supports setLimit(n) for concurrency throttling.
|
|
20
|
+
- select(cases, opts?) — wait for first of multiple promises (like Go's select). Supports send and recv cases.
|
|
21
|
+
- context — background(), withCancel(), withTimeout(), withDeadline(), withValue(). Integrates with spawn/WaitGroup/ErrGroup.
|
|
21
22
|
- Mutex — async mutual exclusion
|
|
23
|
+
- RWMutex — read-write mutex (concurrent readers, exclusive writers)
|
|
22
24
|
- Once<T> — run a function exactly once, all callers get same result
|
|
25
|
+
- Cond — condition variable with wait/signal/broadcast
|
|
23
26
|
- after(ms) / ticker(ms) — timers for use with select and async iteration
|
|
27
|
+
- Timer(ms) — resettable one-shot timer with stop/reset (unlike fire-and-forget after())
|
|
24
28
|
- register(name, fn) / run(name, ...args) — named task registry
|
|
25
29
|
- configure(opts?) — global config (maxThreads, concurrency, idleTimeout, adapter)
|
|
26
30
|
- stats() / resize(n) — pool introspection and runtime scaling
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmop/puru",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "puru (プール) — A thread pool with Go-style concurrency primitives for JavaScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsup",
|
|
32
|
+
"format": "oxfmt src/",
|
|
33
|
+
"format:check": "oxfmt --check src/",
|
|
32
34
|
"lint": "oxlint . --vitest-plugin --node-plugin --deny-warnings -D no-explicit-any",
|
|
33
35
|
"test": "vitest run",
|
|
34
36
|
"test:watch": "vitest",
|
|
@@ -74,6 +76,7 @@
|
|
|
74
76
|
"@vitest/coverage-v8": "^3.2.4",
|
|
75
77
|
"bun-types": "^1.3.11",
|
|
76
78
|
"husky": "^9.1.7",
|
|
79
|
+
"oxfmt": "^0.43.0",
|
|
77
80
|
"oxlint": "^1.58.0",
|
|
78
81
|
"size-limit": "^12.0.1",
|
|
79
82
|
"tsup": "^8.5.0",
|