@dmop/puru 0.1.4 → 0.1.10

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 ADDED
@@ -0,0 +1,184 @@
1
+ # puru — Guide for AI Assistants
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.
4
+
5
+ Full API reference: https://raw.githubusercontent.com/dmop/puru/main/llms-full.txt
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @dmop/puru
11
+ # or
12
+ bun add @dmop/puru
13
+ ```
14
+
15
+ ## The Most Important Rule
16
+
17
+ Functions passed to `spawn()` are serialized via `.toString()` and sent to a worker thread. **They cannot access variables from the outer scope.**
18
+
19
+ ```typescript
20
+ // WRONG — closes over `data`, will throw ReferenceError at runtime
21
+ const data = { id: 1 }
22
+ spawn(() => processData(data))
23
+
24
+ // WRONG — closes over `processData` imported in the outer file
25
+ import { processData } from './utils'
26
+ spawn(() => processData({ id: 1 }))
27
+
28
+ // RIGHT — inline everything the function needs
29
+ spawn(() => {
30
+ function processData(d: { id: number }) {
31
+ return d.id * 2
32
+ }
33
+ return processData({ id: 1 })
34
+ })
35
+
36
+ // RIGHT — inline the data as a literal
37
+ spawn(() => {
38
+ const data = { id: 1 }
39
+ return data.id * 2
40
+ })
41
+ ```
42
+
43
+ Helper functions used inside `spawn()` must also be defined inside the function body:
44
+
45
+ ```typescript
46
+ // WRONG
47
+ function fibonacci(n: number): number {
48
+ if (n <= 1) return n
49
+ return fibonacci(n - 1) + fibonacci(n - 2)
50
+ }
51
+ spawn(() => fibonacci(40)) // ReferenceError: fibonacci is not defined
52
+
53
+ // RIGHT
54
+ spawn(() => {
55
+ function fibonacci(n: number): number {
56
+ if (n <= 1) return n
57
+ return fibonacci(n - 1) + fibonacci(n - 2)
58
+ }
59
+ return fibonacci(40)
60
+ })
61
+ ```
62
+
63
+ ## Common Patterns
64
+
65
+ ### CPU-bound work (exclusive mode)
66
+
67
+ ```typescript
68
+ import { spawn } from '@dmop/puru'
69
+
70
+ const { result } = spawn(() => {
71
+ // define everything you need inside
72
+ function crunch(n: number) {
73
+ let sum = 0
74
+ for (let i = 0; i < n; i++) sum += i
75
+ return sum
76
+ }
77
+ return crunch(1_000_000)
78
+ })
79
+
80
+ console.log(await result)
81
+ ```
82
+
83
+ ### Multiple tasks in parallel (`task()`)
84
+
85
+ ```typescript
86
+ import { task } from '@dmop/puru'
87
+
88
+ const items = [1, 2, 3, 4]
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
+ ### Cross-thread channels (fan-out)
124
+
125
+ ```typescript
126
+ import { chan, spawn } from '@dmop/puru'
127
+
128
+ const input = chan<number>(50)
129
+ const output = chan<number>(50)
130
+
131
+ // 4 worker threads pulling from the same channel
132
+ for (let i = 0; i < 4; i++) {
133
+ spawn(async ({ input, output }) => {
134
+ for await (const n of input) {
135
+ await output.send(n * 2)
136
+ }
137
+ }, { channels: { input, output } })
138
+ }
139
+
140
+ // Producer
141
+ for (let i = 0; i < 100; i++) await input.send(i)
142
+ input.close()
143
+
144
+ // Consume results
145
+ for await (const result of output) {
146
+ console.log(result)
147
+ }
148
+ ```
149
+
150
+ ## What Can Be Sent Through Channels
151
+
152
+ Channel values must be **structured-cloneable**:
153
+
154
+ ```typescript
155
+ // OK
156
+ ch.send(42)
157
+ ch.send('hello')
158
+ ch.send({ id: 1, name: 'foo' })
159
+ ch.send([1, 2, 3])
160
+
161
+ // NOT OK — will throw
162
+ ch.send(() => {}) // functions
163
+ ch.send(Symbol('x')) // symbols
164
+ ch.send(new WeakRef({})) // WeakRefs
165
+ ch.send(null) // null is the "closed" sentinel — use undefined instead
166
+ ```
167
+
168
+ ## Testing
169
+
170
+ Use the inline adapter so tests run on the main thread without real workers:
171
+
172
+ ```typescript
173
+ import { configure } from '@dmop/puru'
174
+
175
+ // In your test setup file
176
+ configure({ adapter: 'inline' })
177
+ ```
178
+
179
+ ## Runtimes
180
+
181
+ - Node.js >= 20: full support
182
+ - Bun: full support
183
+ - Deno: planned
184
+ - Cloudflare Workers / Vercel Edge: not supported (no thread API)