@dmop/puru 0.1.5 → 0.1.11
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 +148 -37
- package/README.md +123 -443
- package/dist/index.cjs +510 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +290 -17
- package/dist/index.d.ts +290 -17
- package/dist/index.js +501 -49
- package/dist/index.js.map +1 -1
- package/llms-full.txt +175 -8
- package/llms.txt +9 -5
- package/package.json +34 -4
package/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# puru — Guide for AI Assistants
|
|
2
2
|
|
|
3
|
-
puru is a thread pool library for JavaScript with Go-style concurrency primitives (channels, WaitGroup, select). It runs functions off the main thread with no worker files and no boilerplate.
|
|
3
|
+
puru is a thread pool library for JavaScript with Go-style concurrency primitives (channels, WaitGroup, ErrGroup, select, context, Mutex, RWMutex, Cond, Timer). It runs functions off the main thread with no worker files and no boilerplate.
|
|
4
4
|
|
|
5
5
|
Full API reference: https://raw.githubusercontent.com/dmop/puru/main/llms-full.txt
|
|
6
6
|
|
|
@@ -80,68 +80,179 @@ const { result } = spawn(() => {
|
|
|
80
80
|
console.log(await result)
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
### Multiple tasks in parallel (
|
|
83
|
+
### Multiple tasks in parallel (`task()`)
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
|
-
import {
|
|
86
|
+
import { task } from '@dmop/puru'
|
|
87
87
|
|
|
88
88
|
const items = [1, 2, 3, 4]
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
const processItem = task((item: number) => item * 2)
|
|
90
|
+
const results = await Promise.all(items.map((item) => processItem(item)))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Concurrent I/O (concurrent mode)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { spawn } from '@dmop/puru'
|
|
97
|
+
|
|
98
|
+
const user = spawn(
|
|
99
|
+
() => fetch('https://api.example.com/users/1').then((r) => r.json()),
|
|
100
|
+
{ concurrent: true },
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const orders = spawn(
|
|
104
|
+
() => fetch('https://api.example.com/users/1/orders').then((r) => r.json()),
|
|
105
|
+
{ concurrent: true },
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const results = await Promise.all([user.result, orders.result])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Cancel on first error (ErrGroup)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { ErrGroup } from '@dmop/puru'
|
|
115
|
+
|
|
116
|
+
const eg = new ErrGroup()
|
|
117
|
+
eg.spawn(() => fetch('https://api.example.com/users/1').then((r) => r.json()), { concurrent: true })
|
|
118
|
+
eg.spawn(() => fetch('https://api.example.com/users/1/orders').then((r) => r.json()), { concurrent: true })
|
|
119
|
+
|
|
120
|
+
const [user, orders] = await eg.wait() // throws on first error, cancels the rest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### ErrGroup with concurrency limit
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { ErrGroup } from '@dmop/puru'
|
|
127
|
+
|
|
128
|
+
const eg = new ErrGroup()
|
|
129
|
+
eg.setLimit(4) // max 4 tasks in flight at once
|
|
130
|
+
|
|
131
|
+
for (const url of urls) {
|
|
132
|
+
eg.spawn(() => fetch(url).then(r => r.json()), { concurrent: true })
|
|
97
133
|
}
|
|
134
|
+
|
|
135
|
+
const results = await eg.wait()
|
|
98
136
|
```
|
|
99
137
|
|
|
100
|
-
|
|
138
|
+
### Context-integrated spawn (auto-cancel)
|
|
101
139
|
|
|
102
140
|
```typescript
|
|
103
|
-
import {
|
|
141
|
+
import { spawn, background, withTimeout } from '@dmop/puru'
|
|
104
142
|
|
|
105
|
-
//
|
|
106
|
-
|
|
143
|
+
// Task auto-cancels when context expires — no manual wiring needed
|
|
144
|
+
const [ctx, cancel] = withTimeout(background(), 5000)
|
|
145
|
+
const { result } = spawn(() => heavyWork(), { ctx })
|
|
107
146
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
147
|
+
try {
|
|
148
|
+
console.log(await result)
|
|
149
|
+
} finally {
|
|
150
|
+
cancel()
|
|
112
151
|
}
|
|
113
|
-
const results = await wg.wait()
|
|
114
152
|
```
|
|
115
153
|
|
|
116
|
-
###
|
|
154
|
+
### Context with WaitGroup / ErrGroup
|
|
117
155
|
|
|
118
156
|
```typescript
|
|
119
|
-
import { WaitGroup } from '@dmop/puru'
|
|
157
|
+
import { background, withTimeout, WaitGroup } from '@dmop/puru'
|
|
120
158
|
|
|
121
|
-
const
|
|
122
|
-
const wg = new WaitGroup()
|
|
159
|
+
const [ctx, cancel] = withTimeout(background(), 5000)
|
|
123
160
|
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
// Pass context to WaitGroup — all tasks auto-cancel when ctx expires
|
|
162
|
+
const wg = new WaitGroup(ctx)
|
|
163
|
+
wg.spawn(() => { /* CPU work */ return 42 })
|
|
164
|
+
wg.spawn(() => fetch('https://api.example.com/data').then(r => r.json()), { concurrent: true })
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const results = await wg.wait()
|
|
168
|
+
} catch {
|
|
169
|
+
console.log('timed out or cancelled')
|
|
170
|
+
} finally {
|
|
171
|
+
cancel()
|
|
126
172
|
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### RWMutex (read-write lock)
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { RWMutex } from '@dmop/puru'
|
|
179
|
+
|
|
180
|
+
const rw = new RWMutex()
|
|
181
|
+
|
|
182
|
+
// Many readers can run concurrently
|
|
183
|
+
const data = await rw.withRLock(() => cache.get('config'))
|
|
127
184
|
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
// Writers get exclusive access
|
|
186
|
+
await rw.withLock(() => cache.set('config', newValue))
|
|
130
187
|
```
|
|
131
188
|
|
|
132
|
-
###
|
|
189
|
+
### Timer (resettable one-shot)
|
|
133
190
|
|
|
134
191
|
```typescript
|
|
135
|
-
import {
|
|
192
|
+
import { Timer, select } from '@dmop/puru'
|
|
136
193
|
|
|
137
|
-
|
|
138
|
-
register('fetchOrders', (id: number) => fetch(`/api/orders/${id}`).then(r => r.json()))
|
|
194
|
+
const t = new Timer(5000)
|
|
139
195
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
196
|
+
// Use with select for cancellable timeouts
|
|
197
|
+
await select([
|
|
198
|
+
[ch.recv(), (v) => { t.stop(); handle(v) }],
|
|
199
|
+
[t.channel, () => console.log('timed out')],
|
|
200
|
+
])
|
|
143
201
|
|
|
144
|
-
|
|
202
|
+
// Reset for debounce patterns
|
|
203
|
+
t.reset(300)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Cond (condition variable)
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { Mutex, Cond } from '@dmop/puru'
|
|
210
|
+
|
|
211
|
+
const mu = new Mutex()
|
|
212
|
+
const cond = new Cond(mu)
|
|
213
|
+
let ready = false
|
|
214
|
+
|
|
215
|
+
// Waiter
|
|
216
|
+
await mu.lock()
|
|
217
|
+
while (!ready) await cond.wait()
|
|
218
|
+
mu.unlock()
|
|
219
|
+
|
|
220
|
+
// Signaler
|
|
221
|
+
await mu.lock()
|
|
222
|
+
ready = true
|
|
223
|
+
cond.broadcast() // wake all waiters
|
|
224
|
+
mu.unlock()
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Directional channels
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { chan } from '@dmop/puru'
|
|
231
|
+
import type { SendOnly, RecvOnly } from '@dmop/puru'
|
|
232
|
+
|
|
233
|
+
const ch = chan<number>(10)
|
|
234
|
+
|
|
235
|
+
function producer(out: SendOnly<number>) {
|
|
236
|
+
await out.send(42)
|
|
237
|
+
out.close()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function consumer(inp: RecvOnly<number>) {
|
|
241
|
+
for await (const v of inp) console.log(v)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
producer(ch.sendOnly())
|
|
245
|
+
consumer(ch.recvOnly())
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Channel inspection
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const ch = chan<number>(100)
|
|
252
|
+
await ch.send(1)
|
|
253
|
+
await ch.send(2)
|
|
254
|
+
console.log(ch.len) // 2 (buffered values)
|
|
255
|
+
console.log(ch.cap) // 100 (buffer capacity)
|
|
145
256
|
```
|
|
146
257
|
|
|
147
258
|
### Cross-thread channels (fan-out)
|
|
@@ -202,7 +313,7 @@ configure({ adapter: 'inline' })
|
|
|
202
313
|
|
|
203
314
|
## Runtimes
|
|
204
315
|
|
|
205
|
-
- Node.js >=
|
|
316
|
+
- Node.js >= 20: full support
|
|
206
317
|
- Bun: full support
|
|
207
318
|
- Deno: planned
|
|
208
319
|
- Cloudflare Workers / Vercel Edge: not supported (no thread API)
|