git.iko.soy/rowtype-yoga/purescript-yoga-om-workerbees
on main help sign in
Initial commit: Extract Om WorkerBees to standalone repository

- Core WorkerBees library with Om integration - Shared memory primitives (SharedInt, SharedState, SharedMap) - Atomic operations and spinlock synchronization - Console-based examples (Fibonacci, SharedCounter, SharedState, SharedMap) - Worker implementations for multi-threaded computation - Full documentation (README, examples/README, QUICKSTART)

commit4562f5a1ffce97cee8b899dc1dd61bcb058bf93a authorMark <mark.eibes@gmail.com> dateSat 7 Feb 2026, 09:17 (8 months ago)
26 files changed+3946
.gitignore+6 README.md+203 bun.lock+598 examples/QUICKSTART.md+285 examples/README.md+163 examples/build-workers.sh+38 package.json+28 spago.lock+1535 spago.yaml+49 src/Examples/FibonacciDemo.purs+58 src/Examples/SharedCounterDemo.purs+71 src/Examples/SharedMapDemo.purs+90 src/Examples/SharedStateDemo.purs+84 src/Examples/Workers/CounterWorker.purs+28 src/Examples/Workers/FibonacciWorker.purs+31 src/Examples/Workers/HashWorker.purs+42 src/Yoga/Om/WorkerBees.purs+86 src/Yoga/Om/WorkerBees/Atomics.js+14 src/Yoga/Om/WorkerBees/Atomics.purs+48 src/Yoga/Om/WorkerBees/SharedArrayBuffer.js+13 src/Yoga/Om/WorkerBees/SharedArrayBuffer.purs+33 src/Yoga/Om/WorkerBees/SharedInt.purs+75 src/Yoga/Om/WorkerBees/SharedMap.js+37 src/Yoga/Om/WorkerBees/SharedMap.purs+191 src/Yoga/Om/WorkerBees/SharedState.js+16 src/Yoga/Om/WorkerBees/SharedState.purs+124
.gitignorenew file+6
@@ -0,0 +1,6 @@
1+node_modules/
2+.spago/
3+output*/
4+dist/
5+.purs-repl
6+.psc-ide-port
README.mdnew file+203
@@ -0,0 +1,203 @@
1+# purescript-yoga-om-workerbees
2+
3+Multi-threaded computation for the Om effect monad using Node.js worker threads.
4+
5+This library provides a clean, type-safe interface for running CPU-bound computations across multiple worker threads, with support for shared memory primitives and atomic operations.
6+
7+## Features
8+
9+- **Worker Pool Management**: Create and manage pools of worker threads
10+- **Shared Memory Primitives**:
11+ - `SharedInt`: Atomic Int32 reference with compare-and-swap
12+ - `SharedState`: JSON-serialized state with spinlock synchronization
13+ - `SharedMap`: Concurrent hash map with striped locks
14+- **Type-Safe**: Full PureScript type safety for worker communication
15+- **Om Integration**: Seamless integration with the Om effect monad
16+- **Automatic Work Distribution**: Distribute tasks across available workers
17+
18+## Installation
19+
20+```bash
21+spago install yoga-om-workerbees
22+```
23+
24+**Dependencies:**
25+- `purescript-yoga-om` - The Om effect monad
26+- `purescript-node-workerbees` - Low-level worker thread bindings
27+- `purescript-yoga-json` - JSON serialization
28+
29+## Quick Start
30+
31+### 1. Create a Worker Module
32+
33+```purescript
34+module MyWorker where
35+
36+import Prelude
37+import Node.WorkerBees as WB
38+
39+-- Worker function: Int -> Int
40+fibWorker :: Int -> Int
41+fibWorker n
42+ | n <= 1 = n
43+ | otherwise = fibWorker (n - 1) + fibWorker (n - 2)
44+
45+-- Export as worker main
46+main :: Effect Unit
47+main = WB.makeAsMain fibWorker
48+```
49+
50+### 2. Bundle the Worker
51+
52+```bash
53+spago bundle \
54+ --module MyWorker \
55+ --outfile dist/workers/MyWorker.js \
56+ --platform node
57+```
58+
59+### 3. Use the Worker Pool
60+
61+```purescript
62+module Main where
63+
64+import Prelude
65+import Effect.Aff (launchAff_)
66+import Yoga.Om.WorkerBees (makePool, distributeWork, terminatePool)
67+
68+main :: Effect Unit
69+main = launchAff_ do
70+ -- Create worker pool
71+ pool <- makePool
72+ { workerPath: "./dist/workers/MyWorker.js"
73+ , poolSize: 4
74+ }
75+
76+ -- Distribute work
77+ let inputs = [35, 36, 37, 38]
78+ results <- distributeWork pool inputs
79+
80+ -- Clean up
81+ terminatePool pool
82+```
83+
84+## Shared Memory
85+
86+### SharedInt (Atomic Counter)
87+
88+```purescript
89+import Yoga.Om.WorkerBees.SharedInt as SharedInt
90+
91+-- Create atomic counter
92+counter <- SharedInt.new 0 # liftEffect
93+
94+-- Atomic operations
95+SharedInt.add counter 1 # liftEffect
96+SharedInt.sub counter 1 # liftEffect
97+value <- SharedInt.read counter # liftEffect
98+
99+-- Compare-and-swap
100+success <- SharedInt.compareAndSwap counter 0 42 # liftEffect
101+
102+-- Atomic modify
103+SharedInt.modify counter (_ + 1) # liftEffect
104+```
105+
106+### SharedState (JSON State)
107+
108+```purescript
109+import Yoga.Om.WorkerBees.SharedState as SharedState
110+
111+type AppState = { counter :: Int, message :: String }
112+
113+-- Create shared state (1KB buffer)
114+state <- SharedState.new 1024 { counter: 0, message: "Hello" } # liftEffect
115+
116+-- Read/write
117+current <- SharedState.read state # liftEffect
118+SharedState.write state { counter: 1, message: "Updated" } # liftEffect
119+
120+-- Atomic modify
121+SharedState.modify state (\s -> s { counter = s.counter + 1 }) # liftEffect
122+```
123+
124+### SharedMap (Concurrent Hash Map)
125+
126+```purescript
127+import Yoga.Om.WorkerBees.SharedMap as SharedMap
128+
129+-- Create map (16 stripes, 4KB per entry)
130+map <- SharedMap.new 16 4096 # liftEffect
131+
132+-- Insert/lookup/delete
133+SharedMap.insert map "key" { value: 42 } # liftEffect
134+maybeValue <- SharedMap.lookup map "key" # liftEffect
135+SharedMap.delete map "key" # liftEffect
136+
137+-- Atomic modify
138+SharedMap.modify map "key" (\v -> v { value = v.value + 1 }) # liftEffect
139+```
140+
141+## Examples
142+
143+Run the console examples:
144+
145+```bash
146+# Install dependencies
147+bun install
148+
149+# Fibonacci calculation with worker pool
150+bun run example:fibonacci
151+
152+# Shared atomic counter
153+bun run example:counter
154+
155+# Shared JSON state
156+bun run example:state
157+
158+# Concurrent hash map
159+bun run example:map
160+```
161+
162+See the `examples/` directory for full source code.
163+
164+## When to Use WorkerBees
165+
166+**Use WorkerBees for:**
167+- CPU-bound computations (cryptography, image processing, parsing)
168+- Parallel processing of large datasets
169+- Tasks that would block the event loop
170+
171+**Don't use WorkerBees for:**
172+- I/O operations (use `Aff` instead - Node.js already handles I/O concurrency)
173+- Simple computations (overhead of worker communication isn't worth it)
174+- Shared state when Aff + `Ref` would suffice
175+
176+## Architecture
177+
178+- **WorkerPool**: Manages a pool of worker threads
179+- **Sendable**: Type-safe serialization for worker communication
180+- **SharedArrayBuffer**: Native shared memory between threads
181+- **Atomics**: Low-level atomic operations (load, store, compareExchange)
182+- **SharedInt/SharedState/SharedMap**: High-level shared memory abstractions
183+
184+## Performance Tips
185+
186+1. **Pool Size**: Default is 4 workers. Adjust based on CPU cores and workload:
187+ ```purescript
188+ makePool { workerPath, poolSize: 8 }
189+ ```
190+
191+2. **Batch Work**: Distribute many small tasks rather than few large ones for better load balancing
192+
193+3. **Shared Memory**: Use `makePoolWithData` to share memory instead of copying data:
194+ ```purescript
195+ counter <- SharedInt.new 0 # liftEffect
196+ pool <- makePoolWithData (SharedInt.toSendable counter) config
197+ ```
198+
199+4. **Worker Bundling**: Always bundle workers with `--platform node` for optimal performance
200+
201+## License
202+
203+MIT
bun.locknew file+598
@@ -0,0 +1,598 @@
1+{
2+ "lockfileVersion": 1,
3+ "workspaces": {
4+ "": {
5+ "name": "purescript-yoga-om-workerbees",
6+ "devDependencies": {
7+ "purescript": "^0.15.15",
8+ "purs-tidy": "^0.10.0",
9+ "spago": "next",
10+ },
11+ },
12+ },
13+ "packages": {
14+ "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
15+
16+ "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
17+
18+ "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.1", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ=="],
19+
20+ "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
21+
22+ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
23+
24+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@4.0.1", "", { "dependencies": { "@nodelib/fs.stat": "4.0.0", "run-parallel": "^1.2.0" } }, "sha512-vAkI715yhnmiPupY+dq+xenu5Tdf2TBQ66jLvBIcCddtz+5Q8LbMKaf9CIJJreez8fQ8fgaY+RaywQx8RJIWpw=="],
25+
26+ "@nodelib/fs.stat": ["@nodelib/fs.stat@4.0.0", "", {}, "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg=="],
27+
28+ "@nodelib/fs.walk": ["@nodelib/fs.walk@3.0.1", "", { "dependencies": { "@nodelib/fs.scandir": "4.0.1", "fastq": "^1.15.0" } }, "sha512-nIh/M6Kh3ZtOmlY00DaUYB4xeeV6F3/ts1l29iwl3/cfyY/OuCfUx+v08zgx8TKPTifXRcjjqVQ4KB2zOYSbyw=="],
29+
30+ "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="],
31+
32+ "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
33+
34+ "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="],
35+
36+ "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
37+
38+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
39+
40+ "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
41+
42+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
43+
44+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
45+
46+ "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
47+
48+ "aproba": ["aproba@1.2.0", "", {}, "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="],
49+
50+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
51+
52+ "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
53+
54+ "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
55+
56+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
57+
58+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
59+
60+ "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
61+
62+ "better-sqlite3": ["better-sqlite3@12.6.2", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA=="],
63+
64+ "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
65+
66+ "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
67+
68+ "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
69+
70+ "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
71+
72+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
73+
74+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
75+
76+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
77+
78+ "buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
79+
80+ "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
81+
82+ "byline": ["byline@5.0.0", "", {}, "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q=="],
83+
84+ "cacache": ["cacache@11.3.3", "", { "dependencies": { "bluebird": "^3.5.5", "chownr": "^1.1.1", "figgy-pudding": "^3.5.1", "glob": "^7.1.4", "graceful-fs": "^4.1.15", "lru-cache": "^5.1.1", "mississippi": "^3.0.0", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", "promise-inflight": "^1.0.1", "rimraf": "^2.6.3", "ssri": "^6.0.1", "unique-filename": "^1.1.1", "y18n": "^4.0.0" } }, "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA=="],
85+
86+ "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
87+
88+ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
89+
90+ "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
91+
92+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
93+
94+ "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
95+
96+ "color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
97+
98+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
99+
100+ "concat-stream": ["concat-stream@1.6.2", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" } }, "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw=="],
101+
102+ "copy-concurrently": ["copy-concurrently@1.0.5", "", { "dependencies": { "aproba": "^1.1.1", "fs-write-stream-atomic": "^1.0.8", "iferr": "^0.1.5", "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.0" } }, "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A=="],
103+
104+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
105+
106+ "cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
107+
108+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
109+
110+ "cyclist": ["cyclist@1.0.2", "", {}, "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA=="],
111+
112+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
113+
114+ "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
115+
116+ "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
117+
118+ "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
119+
120+ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
121+
122+ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
123+
124+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
125+
126+ "duplexify": ["duplexify@3.7.1", "", { "dependencies": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" } }, "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g=="],
127+
128+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
129+
130+ "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
131+
132+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
133+
134+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
135+
136+ "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
137+
138+ "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
139+
140+ "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
141+
142+ "execa": ["execa@2.1.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^3.0.0", "onetime": "^5.1.0", "p-finally": "^2.0.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw=="],
143+
144+ "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
145+
146+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
147+
148+ "figgy-pudding": ["figgy-pudding@3.5.2", "", {}, "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="],
149+
150+ "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
151+
152+ "filesize": ["filesize@4.2.1", "", {}, "sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA=="],
153+
154+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
155+
156+ "flush-write-stream": ["flush-write-stream@1.1.1", "", { "dependencies": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" } }, "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w=="],
157+
158+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
159+
160+ "from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="],
161+
162+ "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
163+
164+ "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="],
165+
166+ "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
167+
168+ "fs-write-stream-atomic": ["fs-write-stream-atomic@1.0.10", "", { "dependencies": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" } }, "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA=="],
169+
170+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
171+
172+ "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
173+
174+ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
175+
176+ "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
177+
178+ "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
179+
180+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
181+
182+ "has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
183+
184+ "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
185+
186+ "http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
187+
188+ "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
189+
190+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
191+
192+ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
193+
194+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
195+
196+ "iferr": ["iferr@0.1.5", "", {}, "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA=="],
197+
198+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
199+
200+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
201+
202+ "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="],
203+
204+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
205+
206+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
207+
208+ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
209+
210+ "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
211+
212+ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
213+
214+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
215+
216+ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
217+
218+ "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
219+
220+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
221+
222+ "is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
223+
224+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
225+
226+ "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
227+
228+ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
229+
230+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
231+
232+ "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
233+
234+ "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
235+
236+ "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
237+
238+ "log-symbols": ["log-symbols@3.0.0", "", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="],
239+
240+ "log-update": ["log-update@4.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", "slice-ansi": "^4.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg=="],
241+
242+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
243+
244+ "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="],
245+
246+ "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
247+
248+ "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
249+
250+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
251+
252+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
253+
254+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
255+
256+ "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
257+
258+ "minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="],
259+
260+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
261+
262+ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
263+
264+ "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="],
265+
266+ "minipass-fetch": ["minipass-fetch@2.1.2", "", { "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA=="],
267+
268+ "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="],
269+
270+ "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="],
271+
272+ "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
273+
274+ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
275+
276+ "mississippi": ["mississippi@3.0.0", "", { "dependencies": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", "end-of-stream": "^1.1.0", "flush-write-stream": "^1.0.0", "from2": "^2.1.0", "parallel-transform": "^1.1.0", "pump": "^3.0.0", "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" } }, "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA=="],
277+
278+ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
279+
280+ "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
281+
282+ "move-concurrently": ["move-concurrently@1.0.1", "", { "dependencies": { "aproba": "^1.1.1", "copy-concurrently": "^1.0.0", "fs-write-stream-atomic": "^1.0.8", "mkdirp": "^0.5.1", "rimraf": "^2.5.4", "run-queue": "^1.0.3" } }, "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ=="],
283+
284+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
285+
286+ "nan": ["nan@2.25.0", "", {}, "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g=="],
287+
288+ "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
289+
290+ "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
291+
292+ "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
293+
294+ "npm-run-path": ["npm-run-path@3.1.0", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg=="],
295+
296+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
297+
298+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
299+
300+ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
301+
302+ "p-finally": ["p-finally@2.0.1", "", {}, "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="],
303+
304+ "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
305+
306+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
307+
308+ "parallel-transform": ["parallel-transform@1.2.0", "", { "dependencies": { "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" } }, "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg=="],
309+
310+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
311+
312+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
313+
314+ "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
315+
316+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
317+
318+ "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
319+
320+ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
321+
322+ "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
323+
324+ "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
325+
326+ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
327+
328+ "pumpify": ["pumpify@1.5.1", "", { "dependencies": { "duplexify": "^3.6.0", "inherits": "^2.0.3", "pump": "^2.0.0" } }, "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ=="],
329+
330+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
331+
332+ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
333+
334+ "purescript": ["purescript@0.15.15", "", { "dependencies": { "purescript-installer": "^0.3.5" }, "bin": { "purs": "purs.bin" } }, "sha512-4gwLNJa4KRiV+Wl+lyMY4MUFM6cSnIqvz7MvSTdLdvXJN2P9CrABnNbSspaokkNRFd9soWHBFDlYsAtRchqHtg=="],
335+
336+ "purescript-installer": ["purescript-installer@0.3.5", "", { "dependencies": { "byline": "^5.0.0", "cacache": "^11.3.2", "chalk": "^2.4.2", "env-paths": "^2.2.0", "execa": "^2.0.3", "filesize": "^4.1.2", "is-plain-obj": "^2.0.0", "log-symbols": "^3.0.0", "log-update": "^4.0.0", "make-fetch-happen": "^10.0.0", "minimist": "^1.2.0", "ms": "^2.1.2", "once": "^1.4.0", "rimraf": "^2.6.3", "semver": "^7.3.7", "tar": "^6.1.11", "which": "^1.3.1", "zen-observable": "^0.8.14" }, "bin": { "install-purescript": "index.js" } }, "sha512-w04BBvW4BSQlspLsZ9Bs5vtUBZqvC4bC+FizC5GUp2/rpmYvXJ0CTf79Q3MKo2A8p1ZjqbhCI+SqdTQ30UZUIQ=="],
337+
338+ "purs-tidy": ["purs-tidy@0.10.1", "", { "bin": { "purs-tidy": "bin/index.js" } }, "sha512-i1QvMaDEaZXv/GWZNFWs5CISiBOkwPhG4D1S4Rw6zUCGaE+NQNWTjvwY21rifynGa2N2TiBJRC61LkORbmGxrA=="],
339+
340+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
341+
342+ "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
343+
344+ "readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
345+
346+ "readline-sync": ["readline-sync@1.4.10", "", {}, "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw=="],
347+
348+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
349+
350+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
351+
352+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
353+
354+ "rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
355+
356+ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
357+
358+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
359+
360+ "run-queue": ["run-queue@1.0.3", "", { "dependencies": { "aproba": "^1.1.1" } }, "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg=="],
361+
362+ "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
363+
364+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
365+
366+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
367+
368+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
369+
370+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
371+
372+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
373+
374+ "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
375+
376+ "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
377+
378+ "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
379+
380+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
381+
382+ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
383+
384+ "socks-proxy-agent": ["socks-proxy-agent@7.0.0", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww=="],
385+
386+ "spago": ["spago@0.93.45", "", { "dependencies": { "@nodelib/fs.walk": "^3.0.1", "better-sqlite3": "^12.5.0", "env-paths": "^3.0.0", "fs-extra": "^11.3.0", "fuse.js": "^7.1.0", "glob": "^11.0.1", "markdown-it": "^14.1.0", "micromatch": "^4.0.8", "open": "^10.1.0", "picomatch": "^4.0.2", "punycode": "^2.3.1", "readline-sync": "^1.4.10", "semver": "^7.7.1", "spdx-expression-parse": "^4.0.0", "ssh2": "^1.16.0", "supports-color": "^10.0.0", "tar": "^7.4.3", "tmp": "^0.2.3", "xhr2": "^0.2.1", "yaml": "^2.7.0" }, "bin": { "spago": "bin/bundle.js" } }, "sha512-t9h4etkmwZVlZ0eQeLQbNbcJlNSjZC9+xTmjcUykQ6+Hq5g4tMH5GKEoObtDiU+me+tujFzQA/W+DyCa2L98wQ=="],
387+
388+ "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
389+
390+ "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="],
391+
392+ "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="],
393+
394+ "ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="],
395+
396+ "ssri": ["ssri@6.0.2", "", { "dependencies": { "figgy-pudding": "^3.5.1" } }, "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q=="],
397+
398+ "stream-each": ["stream-each@1.2.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" } }, "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw=="],
399+
400+ "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
401+
402+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
403+
404+ "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
405+
406+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
407+
408+ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
409+
410+ "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
411+
412+ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
413+
414+ "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="],
415+
416+ "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
417+
418+ "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
419+
420+ "through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="],
421+
422+ "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
423+
424+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
425+
426+ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
427+
428+ "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
429+
430+ "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
431+
432+ "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
433+
434+ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
435+
436+ "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="],
437+
438+ "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="],
439+
440+ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
441+
442+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
443+
444+ "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
445+
446+ "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
447+
448+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
449+
450+ "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
451+
452+ "xhr2": ["xhr2@0.2.1", "", {}, "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw=="],
453+
454+ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
455+
456+ "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
457+
458+ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
459+
460+ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
461+
462+ "zen-observable": ["zen-observable@0.8.15", "", {}, "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="],
463+
464+ "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
465+
466+ "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
467+
468+ "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
469+
470+ "cacache/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
471+
472+ "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
473+
474+ "chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
475+
476+ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
477+
478+ "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
479+
480+ "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
481+
482+ "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
483+
484+ "make-fetch-happen/cacache": ["cacache@16.1.3", "", { "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "glob": "^8.0.1", "infer-owner": "^1.0.4", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", "unique-filename": "^2.0.0" } }, "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ=="],
485+
486+ "make-fetch-happen/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
487+
488+ "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
489+
490+ "make-fetch-happen/ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="],
491+
492+ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
493+
494+ "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
495+
496+ "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
497+
498+ "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
499+
500+ "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
501+
502+ "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
503+
504+ "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
505+
506+ "path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
507+
508+ "pumpify/pump": ["pump@2.0.1", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA=="],
509+
510+ "purescript-installer/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
511+
512+ "purescript-installer/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
513+
514+ "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
515+
516+ "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
517+
518+ "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
519+
520+ "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
521+
522+ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
523+
524+ "@npmcli/move-file/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
525+
526+ "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
527+
528+ "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
529+
530+ "make-fetch-happen/cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
531+
532+ "make-fetch-happen/cacache/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="],
533+
534+ "make-fetch-happen/cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
535+
536+ "make-fetch-happen/cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
537+
538+ "make-fetch-happen/cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
539+
540+ "make-fetch-happen/cacache/unique-filename": ["unique-filename@2.0.1", "", { "dependencies": { "unique-slug": "^3.0.0" } }, "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A=="],
541+
542+ "make-fetch-happen/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
543+
544+ "minipass-collect/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
545+
546+ "minipass-fetch/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
547+
548+ "minipass-fetch/minizlib/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
549+
550+ "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
551+
552+ "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
553+
554+ "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
555+
556+ "purescript-installer/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
557+
558+ "purescript-installer/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
559+
560+ "purescript-installer/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
561+
562+ "purescript-installer/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
563+
564+ "purescript-installer/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
565+
566+ "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
567+
568+ "slice-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
569+
570+ "wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
571+
572+ "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
573+
574+ "make-fetch-happen/cacache/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
575+
576+ "make-fetch-happen/cacache/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
577+
578+ "make-fetch-happen/cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
579+
580+ "make-fetch-happen/cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
581+
582+ "make-fetch-happen/cacache/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
583+
584+ "make-fetch-happen/cacache/unique-filename/unique-slug": ["unique-slug@3.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w=="],
585+
586+ "purescript-installer/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
587+
588+ "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
589+
590+ "wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
591+
592+ "make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
593+
594+ "make-fetch-happen/cacache/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
595+
596+ "make-fetch-happen/cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
597+ }
598+}
examples/QUICKSTART.mdnew file+285
@@ -0,0 +1,285 @@
1+# WorkerBees Quick Start Guide
2+
3+This guide will walk you through creating your first multi-threaded PureScript application using WorkerBees.
4+
5+## Step 1: Install Dependencies
6+
7+```bash
8+spago install yoga-om-workerbees
9+```
10+
11+## Step 2: Create a Worker Module
12+
13+Create `src/Workers/MyWorker.purs`:
14+
15+```purescript
16+module Workers.MyWorker where
17+
18+import Prelude
19+import Effect (Effect)
20+import Node.WorkerBees as WB
21+
22+-- This is the function that will run in worker threads
23+-- For this example, we'll compute the square of a number
24+squareWorker :: Int -> Int
25+squareWorker n = n * n
26+
27+-- Export as worker main - this is required!
28+main :: Effect Unit
29+main = WB.makeAsMain squareWorker
30+```
31+
32+**Important:** The worker module must export a `main` function that calls `WB.makeAsMain`.
33+
34+## Step 3: Create the Main Application
35+
36+Create `src/Main.purs`:
37+
38+```purescript
39+module Main where
40+
41+import Prelude
42+
43+import Data.Array as Array
44+import Data.Traversable (for_)
45+import Effect (Effect)
46+import Effect.Aff (launchAff_)
47+import Effect.Class.Console (log)
48+import Yoga.Om.WorkerBees (makePool, distributeWork, terminatePool)
49+
50+main :: Effect Unit
51+main = launchAff_ do
52+ log "Starting worker pool..."
53+
54+ -- Step 1: Create worker pool
55+ pool <- makePool
56+ { workerPath: "./dist/workers/MyWorker.js"
57+ , poolSize: 4 -- Use 4 worker threads
58+ }
59+
60+ -- Step 2: Prepare input data
61+ let inputs = Array.range 1 10 -- [1, 2, 3, ..., 10]
62+
63+ log $ "Computing squares of: " <> show inputs
64+
65+ -- Step 3: Distribute work across workers
66+ results <- distributeWork pool inputs
67+
68+ -- Step 4: Print results
69+ log "Results:"
70+ for_ (Array.zip inputs results) \(input, result) -> do
71+ log $ " " <> show input <> "² = " <> show result
72+
73+ -- Step 5: Clean up
74+ log "Terminating pool..."
75+ terminatePool pool
76+
77+ log "Done!"
78+```
79+
80+## Step 4: Bundle the Worker
81+
82+Create a build script `scripts/build-workers.sh`:
83+
84+```bash
85+#!/usr/bin/env bash
86+set -e
87+
88+echo "Building PureScript..."
89+spago build
90+
91+echo "Bundling worker..."
92+spago bundle \
93+ --module Workers.MyWorker \
94+ --outfile dist/workers/MyWorker.js \
95+ --platform node
96+
97+echo "Worker bundled successfully!"
98+```
99+
100+Make it executable:
101+
102+```bash
103+chmod +x scripts/build-workers.sh
104+```
105+
106+## Step 5: Update package.json
107+
108+Add scripts to your `package.json`:
109+
110+```json
111+{
112+ "scripts": {
113+ "build": "spago build",
114+ "build:workers": "bash scripts/build-workers.sh",
115+ "start": "bun run build:workers && spago run"
116+ }
117+}
118+```
119+
120+## Step 6: Run Your Application
121+
122+```bash
123+bun run start
124+```
125+
126+You should see output like:
127+
128+```
129+Starting worker pool...
130+Computing squares of: [1,2,3,4,5,6,7,8,9,10]
131+Results:
132+ 1² = 1
133+ 2² = 4
134+ 3² = 9
135+ 4² = 16
136+ 5² = 25
137+ 6² = 36
138+ 7² = 49
139+ 8² = 64
140+ 9² = 81
141+ 10² = 100
142+Terminating pool...
143+Done!
144+```
145+
146+## Understanding What Happened
147+
148+1. **Worker Creation**: `makePool` created 4 worker threads, each running `dist/workers/MyWorker.js`
149+
150+2. **Work Distribution**: `distributeWork` automatically:
151+ - Split the 10 inputs across 4 workers
152+ - Sent tasks to available workers
153+ - Collected results in order
154+
155+3. **Parallel Execution**: Workers computed squares in parallel (faster than sequential for real CPU-bound work)
156+
157+4. **Cleanup**: `terminatePool` terminated all workers and freed resources
158+
159+## Next Steps
160+
161+### Add Shared Memory
162+
163+Create a shared counter that workers can increment:
164+
165+```purescript
166+import Yoga.Om.WorkerBees (makePoolWithData)
167+import Yoga.Om.WorkerBees.SharedInt as SharedInt
168+
169+main :: Effect Unit
170+main = launchAff_ do
171+ -- Create shared counter
172+ counter <- SharedInt.new 0 # liftEffect
173+
174+ -- Create pool with shared data
175+ pool <- makePoolWithData (SharedInt.toSendable counter)
176+ { workerPath: "./dist/workers/CounterWorker.js"
177+ , poolSize: 4
178+ }
179+
180+ -- Workers can now access the counter via context.workerData
181+ -- ...
182+
183+ finalValue <- SharedInt.read counter # liftEffect
184+ log $ "Final counter value: " <> show finalValue
185+```
186+
187+Worker side:
188+
189+```purescript
190+module Workers.CounterWorker where
191+
192+import Prelude
193+import Effect (Effect)
194+import Node.WorkerBees as WB
195+import Yoga.Om.WorkerBees.SharedInt as SharedInt
196+
197+counterWorker :: WB.SendWrapper SharedInt.SharedInt -> Int -> Effect Int
198+counterWorker sendable _ = do
199+ counter <- SharedInt.fromSendable sendable
200+ SharedInt.add counter 1
201+ SharedInt.read counter
202+
203+main :: Effect Unit
204+main = WB.makeAsMain counterWorker
205+```
206+
207+### Use More Complex Data Types
208+
209+Workers can handle any `Sendable` type (JSON-serializable):
210+
211+```purescript
212+type Task =
213+ { id :: Int
214+ , operation :: String
215+ , value :: Number
216+ }
217+
218+type Result =
219+ { taskId :: Int
220+ , result :: Number
221+ , processingTime :: Number
222+ }
223+
224+-- Worker function
225+processTask :: Task -> Effect Result
226+processTask task = do
227+ -- ... process task ...
228+ pure { taskId: task.id, result: 42.0, processingTime: 10.0 }
229+```
230+
231+### Error Handling
232+
233+Add error handling to workers:
234+
235+```purescript
236+import Data.Either (Either(..))
237+import Control.Monad.Error.Class (try)
238+
239+safeWorker :: Int -> Effect (Either String Int)
240+safeWorker n = try do
241+ when (n < 0) $ throw "Negative numbers not allowed"
242+ pure (n * n)
243+```
244+
245+## Common Pitfalls
246+
247+1. **Forgetting to Bundle**: Workers must be bundled with `spago bundle --platform node`
248+
249+2. **Wrong Worker Path**: Path must be relative to where you run the app (usually `./dist/workers/...`)
250+
251+3. **Not Calling `makeAsMain`**: Worker `main` function must call `WB.makeAsMain yourFunction`
252+
253+4. **Using for I/O**: WorkerBees is for CPU-bound tasks. Use `Aff` for I/O operations.
254+
255+5. **Forgetting `terminatePool`**: Always clean up workers to free resources
256+
257+## Tips for Production
258+
259+1. **Adjust Pool Size**: Match CPU cores for CPU-bound work:
260+ ```purescript
261+ poolSize: 8 -- For 8-core machine
262+ ```
263+
264+2. **Batch Tasks**: Send many small tasks instead of few large ones for better load balancing
265+
266+3. **Monitor Performance**: Add timing to see if workers actually help:
267+ ```purescript
268+ start <- nowDateTime # liftEffect
269+ results <- distributeWork pool inputs
270+ end <- nowDateTime # liftEffect
271+ let duration = diff end start
272+ log $ "Completed in: " <> show duration
273+ ```
274+
275+4. **Graceful Shutdown**: Always terminate pools on app shutdown to avoid zombie processes
276+
277+## More Examples
278+
279+Check the `examples/` directory for more advanced usage:
280+- `FibonacciDemo.purs` - CPU-bound computation
281+- `SharedCounterDemo.purs` - Atomic operations
282+- `SharedStateDemo.purs` - JSON state management
283+- `SharedMapDemo.purs` - Concurrent hash map
284+
285+Happy parallel computing! 🚀
examples/README.mdnew file+163
@@ -0,0 +1,163 @@
1+# WorkerBees Examples
2+
3+This directory contains console-based examples demonstrating the WorkerBees library functionality.
4+
5+## Running Examples
6+
7+All examples require building the workers first:
8+
9+```bash
10+bun run build:workers
11+```
12+
13+Then run any example:
14+
15+```bash
16+bun run example:fibonacci # Fibonacci calculation
17+bun run example:counter # Shared atomic counter
18+bun run example:state # Shared JSON state
19+bun run example:map # Concurrent hash map
20+```
21+
22+## Example Descriptions
23+
24+### FibonacciDemo
25+
26+**File:** `FibonacciDemo.purs`
27+**Worker:** `Workers/FibonacciWorker.purs`
28+
29+Demonstrates basic worker pool usage with CPU-bound computation:
30+- Creates a pool of 4 workers
31+- Distributes Fibonacci calculations across workers
32+- Collects and displays results
33+- Shows proper cleanup
34+
35+**Key concepts:**
36+- Worker pool creation
37+- Work distribution
38+- Result collection
39+
40+### SharedCounterDemo
41+
42+**File:** `SharedCounterDemo.purs`
43+**Worker:** `Workers/CounterWorker.purs`
44+
45+Demonstrates `SharedInt` (atomic Int32 reference):
46+- Creates a shared atomic counter
47+- Passes counter to workers via `workerData`
48+- 100 concurrent increment operations
49+- Shows final counter value
50+
51+**Key concepts:**
52+- Shared memory creation
53+- Atomic operations
54+- Thread-safe increments
55+- `makePoolWithData` for sharing state
56+
57+### SharedStateDemo
58+
59+**File:** `SharedStateDemo.purs`
60+
61+Demonstrates `SharedState` (JSON-serialized state with spinlock):
62+- Creates shared state with record type
63+- Read and write operations
64+- Atomic modify operations
65+- Spinlock synchronization
66+
67+**Key concepts:**
68+- JSON serialization
69+- Atomic modify with CAS loop
70+- Spinlock-based synchronization
71+- Buffer size management
72+
73+### SharedMapDemo
74+
75+**File:** `SharedMapDemo.purs`
76+
77+Demonstrates `SharedMap` (concurrent hash map):
78+- Creates striped concurrent map
79+- Insert, lookup, modify, delete operations
80+- Shows concurrent access patterns
81+- Demonstrates striped lock benefits
82+
83+**Key concepts:**
84+- Concurrent hash map
85+- Striped locking
86+- Key-value operations
87+- Lock-free reads
88+
89+## Worker Implementations
90+
91+### FibonacciWorker
92+
93+Classic recursive Fibonacci - CPU-bound computation perfect for demonstrating worker benefits.
94+
95+```purescript
96+fibWorker :: Int -> Int
97+fibWorker n
98+ | n <= 1 = n
99+ | otherwise = fibWorker (n - 1) + fibWorker (n - 2)
100+```
101+
102+### CounterWorker
103+
104+Increments a shared counter and returns the new value:
105+
106+```purescript
107+counterWorker :: SendWrapper SharedInt -> Int -> Effect Int
108+counterWorker sendable _ = do
109+ counter <- SharedInt.fromSendable sendable
110+ SharedInt.add counter 1
111+ SharedInt.read counter
112+```
113+
114+### HashWorker
115+
116+Processes text (calculates hash codes) - demonstrates string processing workload:
117+
118+```purescript
119+hashWorker :: String -> Effect Int
120+hashWorker text = pure $ hashString text
121+```
122+
123+## Building Workers Manually
124+
125+The `build-workers.sh` script bundles all workers, but you can also build individually:
126+
127+```bash
128+spago bundle \
129+ --module Examples.Workers.FibonacciWorker \
130+ --outfile dist/workers/FibonacciWorker.js \
131+ --platform node
132+```
133+
134+**Important:** Always use `--platform node` for worker bundles!
135+
136+## Tips for Writing Your Own Examples
137+
138+1. **Worker Entry Point**: Always use `WB.makeAsMain yourFunction` as the worker's `main`
139+
140+2. **Bundle Before Running**: Workers must be bundled to `dist/workers/*.js` before use
141+
142+3. **Shared Memory**: Use `SendWrapper` types for passing shared memory to workers:
143+ ```purescript
144+ counter <- SharedInt.new 0 # liftEffect
145+ pool <- makePoolWithData (SharedInt.toSendable counter) config
146+ ```
147+
148+4. **Error Handling**: Workers should handle errors gracefully - unhandled errors will crash the worker
149+
150+5. **Cleanup**: Always call `terminatePool` when done to free resources
151+
152+## Performance Notes
153+
154+- **Pool Size**: Examples use 4 workers by default. Adjust based on your CPU cores.
155+- **Batch Size**: `distributeWork` automatically batches tasks across available workers.
156+- **Shared Memory**: Faster than message passing when state is frequently accessed.
157+- **Overhead**: Worker communication has overhead - only use for CPU-bound tasks that take >10ms.
158+
159+## Further Reading
160+
161+- See the main `README.md` for API documentation
162+- See `QUICKSTART.md` for a step-by-step tutorial
163+- Check worker source code in `Workers/` for implementation details
examples/build-workers.shnew file+38
@@ -0,0 +1,38 @@
1+#!/usr/bin/env bash
2+
3+# Build script for worker modules
4+# This bundles the workers into standalone JavaScript files
5+
6+set -e
7+
8+# Ensure purs is in PATH
9+export PATH="./node_modules/.bin:$PATH"
10+
11+echo "Building workers..."
12+
13+# Build all PureScript modules first
14+echo " Compiling PureScript..."
15+spago build
16+
17+# Bundle Fibonacci worker
18+echo " Bundling FibonacciWorker..."
19+spago bundle \
20+ --module Examples.Workers.FibonacciWorker \
21+ --outfile dist/workers/FibonacciWorker.js \
22+ --platform node
23+
24+# Bundle Hash worker
25+echo " Bundling HashWorker..."
26+spago bundle \
27+ --module Examples.Workers.HashWorker \
28+ --outfile dist/workers/HashWorker.js \
29+ --platform node
30+
31+# Bundle Counter worker (shared memory example)
32+echo " Bundling CounterWorker..."
33+spago bundle \
34+ --module Examples.Workers.CounterWorker \
35+ --outfile dist/workers/CounterWorker.js \
36+ --platform node
37+
38+echo "Workers built successfully!"
package.jsonnew file+28
@@ -0,0 +1,28 @@
1+{
2+ "name": "purescript-yoga-om-workerbees",
3+ "version": "0.1.0",
4+ "description": "Multi-threaded computation for the Om effect monad using Node.js worker threads",
5+ "type": "module",
6+ "scripts": {
7+ "build": "spago build",
8+ "build:workers": "bash examples/build-workers.sh",
9+ "example:fibonacci": "bun run build:workers && spago run --main Examples.FibonacciDemo",
10+ "example:counter": "bun run build:workers && spago run --main Examples.SharedCounterDemo",
11+ "example:state": "bun run build:workers && spago run --main Examples.SharedStateDemo",
12+ "example:map": "bun run build:workers && spago run --main Examples.SharedMapDemo"
13+ },
14+ "keywords": [
15+ "purescript",
16+ "om",
17+ "worker-threads",
18+ "multithreading",
19+ "shared-memory",
20+ "atomics"
21+ ],
22+ "license": "MIT",
23+ "devDependencies": {
24+ "purescript": "^0.15.15",
25+ "purs-tidy": "^0.10.0",
26+ "spago": "next"
27+ }
28+}
spago.locknew file+1535
@@ -0,0 +1,1535 @@
1+{
2+ "workspace": {
3+ "packages": {
4+ "yoga-om-workerbees": {
5+ "path": "./",
6+ "core": {
7+ "dependencies": [
8+ {
9+ "aff": ">=7.0.0 <8.0.0"
10+ },
11+ {
12+ "arraybuffer-types": ">=3.0.0 <4.0.0"
13+ },
14+ {
15+ "console": ">=6.0.0 <7.0.0"
16+ },
17+ {
18+ "effect": ">=4.0.0 <5.0.0"
19+ },
20+ {
21+ "either": ">=6.0.0 <7.0.0"
22+ },
23+ {
24+ "foldable-traversable": ">=6.0.0 <7.0.0"
25+ },
26+ {
27+ "maybe": ">=6.0.0 <7.0.0"
28+ },
29+ {
30+ "newtype": ">=5.0.0 <6.0.0"
31+ },
32+ {
33+ "node-buffer": ">=9.0.0 <10.0.0"
34+ },
35+ "node-workerbees",
36+ {
37+ "parallel": ">=7.0.0 <8.0.0"
38+ },
39+ {
40+ "prelude": ">=6.0.0 <7.0.0"
41+ },
42+ {
43+ "transformers": ">=6.0.0 <7.0.0"
44+ },
45+ {
46+ "unsafe-coerce": ">=6.0.0 <7.0.0"
47+ },
48+ {
49+ "yoga-json": ">=5.0.0 <6.0.0"
50+ },
51+ "yoga-om"
52+ ],
53+ "build_plan": [
54+ "aff",
55+ "argonaut-core",
56+ "arraybuffer-types",
57+ "arrays",
58+ "avar",
59+ "bifunctors",
60+ "catenable-lists",
61+ "console",
62+ "const",
63+ "contravariant",
64+ "control",
65+ "datetime",
66+ "distributive",
67+ "effect",
68+ "either",
69+ "enums",
70+ "exceptions",
71+ "exists",
72+ "foldable-traversable",
73+ "foreign",
74+ "foreign-object",
75+ "free",
76+ "functions",
77+ "functors",
78+ "gen",
79+ "heterogeneous",
80+ "identity",
81+ "integers",
82+ "invariant",
83+ "js-bigints",
84+ "js-date",
85+ "lazy",
86+ "lists",
87+ "maybe",
88+ "newtype",
89+ "node-buffer",
90+ "node-workerbees",
91+ "nonempty",
92+ "now",
93+ "nullable",
94+ "numbers",
95+ "ordered-collections",
96+ "orders",
97+ "parallel",
98+ "partial",
99+ "prelude",
100+ "profunctor",
101+ "record",
102+ "record-studio",
103+ "refs",
104+ "safe-coerce",
105+ "st",
106+ "strings",
107+ "tailrec",
108+ "transformers",
109+ "tuples",
110+ "type-equality",
111+ "typelevel-prelude",
112+ "uncurried-transformers",
113+ "unfoldable",
114+ "unsafe-coerce",
115+ "variant",
116+ "yoga-json",
117+ "yoga-om",
118+ "yoga-tree"
119+ ]
120+ },
121+ "test": {
122+ "dependencies": [],
123+ "build_plan": []
124+ }
125+ }
126+ },
127+ "package_set": {
128+ "address": {
129+ "registry": "51.1.0"
130+ },
131+ "compiler": ">=0.15.15 <0.16.0",
132+ "content": {
133+ "abc-parser": "2.0.1",
134+ "ace": "9.1.0",
135+ "address-rfc2821": "0.1.1",
136+ "aff": "7.1.0",
137+ "aff-bus": "6.0.0",
138+ "aff-coroutines": "9.0.0",
139+ "aff-promise": "4.0.0",
140+ "aff-retry": "2.0.0",
141+ "affjax": "13.0.0",
142+ "affjax-node": "1.0.0",
143+ "affjax-web": "1.0.0",
144+ "ansi": "7.0.0",
145+ "apexcharts": "0.5.0",
146+ "applicative-phases": "1.0.0",
147+ "argonaut": "9.0.0",
148+ "argonaut-aeson-generic": "0.4.1",
149+ "argonaut-codecs": "9.1.0",
150+ "argonaut-core": "7.0.0",
151+ "argonaut-generic": "8.0.0",
152+ "argonaut-traversals": "10.0.0",
153+ "argparse-basic": "2.0.0",
154+ "array-builder": "0.1.2",
155+ "array-search": "0.5.6",
156+ "arraybuffer": "13.2.0",
157+ "arraybuffer-builder": "3.1.0",
158+ "arraybuffer-types": "3.0.2",
159+ "arrays": "7.3.0",
160+ "arrays-extra": "0.6.1",
161+ "arrays-zipper": "2.0.1",
162+ "ask": "1.0.0",
163+ "assert": "6.0.0",
164+ "assert-multiple": "0.3.4",
165+ "avar": "5.0.0",
166+ "b64": "0.0.8",
167+ "barbies": "1.0.1",
168+ "barlow-lens": "0.9.0",
169+ "bifunctors": "6.0.0",
170+ "bigints": "7.0.1",
171+ "bolson": "0.3.9",
172+ "bookhound": "0.1.7",
173+ "bower-json": "3.0.0",
174+ "call-by-name": "4.0.1",
175+ "canvas": "6.0.0",
176+ "canvas-action": "9.0.0",
177+ "cartesian": "1.0.6",
178+ "catenable-lists": "7.0.0",
179+ "cbor-stream": "1.0.4",
180+ "chameleon": "1.0.0",
181+ "chameleon-halogen": "1.0.3",
182+ "chameleon-react-basic": "1.1.0",
183+ "chameleon-styled": "2.5.0",
184+ "chameleon-transformers": "1.0.0",
185+ "channel": "1.0.0",
186+ "checked-exceptions": "3.1.1",
187+ "choku": "1.0.1",
188+ "classless": "0.1.1",
189+ "classless-arbitrary": "0.1.1",
190+ "classless-decode-json": "0.1.1",
191+ "classless-encode-json": "0.1.3",
192+ "classnames": "2.0.0",
193+ "codec": "6.1.0",
194+ "codec-argonaut": "10.0.0",
195+ "codec-json": "1.2.0",
196+ "colors": "7.0.1",
197+ "concur-core": "0.5.0",
198+ "concur-react": "0.5.0",
199+ "concurrent-queues": "3.0.0",
200+ "console": "6.1.0",
201+ "const": "6.0.0",
202+ "contravariant": "6.0.0",
203+ "control": "6.0.0",
204+ "convertable-options": "1.0.0",
205+ "coroutines": "7.0.0",
206+ "css": "6.0.0",
207+ "css-frameworks": "1.0.1",
208+ "csv-stream": "2.0.2",
209+ "data-mvc": "0.0.2",
210+ "datetime": "6.1.0",
211+ "datetime-parsing": "0.2.0",
212+ "debounce": "0.1.0",
213+ "debug": "6.0.2",
214+ "decimals": "7.1.0",
215+ "default-values": "1.0.1",
216+ "deku": "0.9.23",
217+ "deno": "0.0.5",
218+ "dissect": "1.0.0",
219+ "distributive": "6.0.0",
220+ "dom-filereader": "7.0.0",
221+ "dom-indexed": "12.0.0",
222+ "dom-simple": "0.4.0",
223+ "dotenv": "4.0.3",
224+ "droplet": "0.6.0",
225+ "dts": "1.0.0",
226+ "dual-numbers": "1.0.2",
227+ "dynamic-buffer": "3.0.1",
228+ "echarts-simple": "0.0.1",
229+ "effect": "4.0.0",
230+ "either": "6.1.0",
231+ "elmish": "0.11.3",
232+ "elmish-enzyme": "0.1.1",
233+ "elmish-hooks": "0.10.0",
234+ "elmish-html": "0.8.2",
235+ "elmish-testing-library": "0.3.2",
236+ "email-validate": "7.0.0",
237+ "encoding": "0.0.9",
238+ "enums": "6.0.1",
239+ "env-names": "0.3.4",
240+ "error": "2.0.0",
241+ "eta-conversion": "0.3.2",
242+ "exceptions": "6.0.0",
243+ "exists": "6.0.0",
244+ "exitcodes": "4.0.0",
245+ "expect-inferred": "3.0.0",
246+ "fahrtwind": "2.0.0",
247+ "fallback": "0.1.0",
248+ "fast-vect": "1.2.0",
249+ "fetch": "4.1.0",
250+ "fetch-argonaut": "1.0.1",
251+ "fetch-core": "5.1.0",
252+ "fetch-yoga-json": "1.1.0",
253+ "ffi-simple": "0.5.1",
254+ "fft-js": "0.1.0",
255+ "filterable": "5.0.0",
256+ "fix-functor": "0.1.0",
257+ "fixed-points": "7.0.0",
258+ "fixed-precision": "5.0.0",
259+ "flame": "1.3.0",
260+ "float32": "2.0.0",
261+ "fmt": "0.2.1",
262+ "foldable-traversable": "6.0.0",
263+ "foldable-traversable-extra": "0.0.6",
264+ "foreign": "7.0.0",
265+ "foreign-object": "4.1.0",
266+ "foreign-readwrite": "3.4.0",
267+ "forgetmenot": "0.1.0",
268+ "fork": "6.0.0",
269+ "form-urlencoded": "7.0.0",
270+ "formatters": "7.0.0",
271+ "framer-motion": "1.0.1",
272+ "free": "7.1.0",
273+ "freeap": "7.0.0",
274+ "freer-free": "0.0.1",
275+ "freet": "7.0.0",
276+ "functions": "6.0.0",
277+ "functor1": "3.0.0",
278+ "functors": "5.0.0",
279+ "fuzzy": "0.4.0",
280+ "gen": "4.0.0",
281+ "generate-values": "1.0.1",
282+ "generic-router": "0.0.1",
283+ "geojson": "0.0.5",
284+ "geometry-plane": "1.0.3",
285+ "gojs": "0.1.1",
286+ "grain": "3.0.0",
287+ "grain-router": "3.0.0",
288+ "grain-virtualized": "3.0.0",
289+ "graphs": "8.1.0",
290+ "group": "4.1.1",
291+ "halogen": "7.0.0",
292+ "halogen-bootstrap5": "5.3.2",
293+ "halogen-canvas": "1.0.0",
294+ "halogen-css": "10.0.0",
295+ "halogen-echarts-simple": "0.0.4",
296+ "halogen-formless": "4.0.3",
297+ "halogen-helix": "1.0.0",
298+ "halogen-hooks": "0.6.3",
299+ "halogen-hooks-extra": "0.9.0",
300+ "halogen-infinite-scroll": "1.1.0",
301+ "halogen-store": "0.5.4",
302+ "halogen-storybook": "2.0.0",
303+ "halogen-subscriptions": "2.0.0",
304+ "halogen-svg-elems": "8.0.0",
305+ "halogen-typewriter": "1.0.4",
306+ "halogen-vdom": "8.0.0",
307+ "halogen-vdom-string-renderer": "0.5.0",
308+ "halogen-xterm": "2.0.0",
309+ "heckin": "2.0.1",
310+ "heterogeneous": "0.6.0",
311+ "homogeneous": "0.4.0",
312+ "http-methods": "6.0.0",
313+ "httpurple": "4.0.0",
314+ "humdrum": "0.0.1",
315+ "hyrule": "2.3.8",
316+ "identity": "6.0.0",
317+ "identy": "4.0.1",
318+ "indexed-db": "1.0.0",
319+ "indexed-monad": "3.0.0",
320+ "int64": "3.0.0",
321+ "integers": "6.0.0",
322+ "interpolate": "5.0.2",
323+ "intersection-observer": "1.0.1",
324+ "invariant": "6.0.0",
325+ "jarilo": "1.0.1",
326+ "jelly": "0.10.0",
327+ "jelly-router": "0.3.0",
328+ "jelly-signal": "0.4.0",
329+ "jest": "1.0.0",
330+ "js-abort-controller": "1.0.0",
331+ "js-bigints": "2.2.1",
332+ "js-date": "8.0.0",
333+ "js-fetch": "0.2.1",
334+ "js-fileio": "3.0.0",
335+ "js-intl": "1.0.4",
336+ "js-iterators": "0.1.1",
337+ "js-maps": "0.1.2",
338+ "js-promise": "1.0.0",
339+ "js-promise-aff": "1.0.0",
340+ "js-timers": "6.1.0",
341+ "js-uri": "3.1.0",
342+ "json": "1.1.0",
343+ "json-codecs": "5.0.0",
344+ "justifill": "0.5.0",
345+ "jwt": "0.0.9",
346+ "labeled-data": "0.2.0",
347+ "language-cst-parser": "0.14.0",
348+ "lazy": "6.0.0",
349+ "lazy-joe": "1.0.0",
350+ "lcg": "4.0.0",
351+ "leibniz": "5.0.0",
352+ "leveldb": "1.0.1",
353+ "liminal": "1.0.1",
354+ "linalg": "6.0.0",
355+ "lists": "7.0.0",
356+ "literals": "1.0.2",
357+ "logging": "3.0.0",
358+ "logging-journald": "0.4.0",
359+ "lumi-components": "18.0.0",
360+ "machines": "7.0.0",
361+ "maps-eager": "0.4.1",
362+ "marionette": "1.0.0",
363+ "marionette-react-basic-hooks": "0.1.1",
364+ "marked": "0.1.0",
365+ "matrices": "5.0.1",
366+ "matryoshka": "1.0.0",
367+ "maybe": "6.0.0",
368+ "media-types": "6.0.0",
369+ "meowclient": "1.0.0",
370+ "midi": "4.0.0",
371+ "milkis": "9.0.0",
372+ "minibench": "4.0.1",
373+ "mmorph": "7.0.0",
374+ "monad-control": "5.0.0",
375+ "monad-logger": "1.3.1",
376+ "monad-loops": "0.5.0",
377+ "monad-unlift": "1.0.1",
378+ "monoid-extras": "0.0.1",
379+ "monoidal": "0.16.0",
380+ "morello": "0.4.0",
381+ "mote": "3.0.0",
382+ "motsunabe": "2.0.0",
383+ "mvc": "0.0.1",
384+ "mysql": "6.0.1",
385+ "n3": "0.1.0",
386+ "nano-id": "1.1.0",
387+ "nanoid": "0.1.0",
388+ "naturals": "3.0.0",
389+ "nested-functor": "0.2.1",
390+ "newtype": "5.0.0",
391+ "nextjs": "0.1.1",
392+ "nextui": "0.2.0",
393+ "node-buffer": "9.0.0",
394+ "node-child-process": "11.1.0",
395+ "node-event-emitter": "3.0.0",
396+ "node-execa": "5.0.0",
397+ "node-fs": "9.1.0",
398+ "node-glob-basic": "1.3.0",
399+ "node-http": "9.1.0",
400+ "node-http2": "1.1.1",
401+ "node-human-signals": "1.0.0",
402+ "node-net": "5.1.0",
403+ "node-os": "5.1.0",
404+ "node-path": "5.0.0",
405+ "node-process": "11.2.0",
406+ "node-readline": "8.1.0",
407+ "node-sqlite3": "8.0.0",
408+ "node-stream-pipes": "1.4.1",
409+ "node-streams": "9.0.0",
410+ "node-tls": "0.3.1",
411+ "node-url": "7.0.1",
412+ "node-zlib": "0.4.0",
413+ "nonempty": "7.0.0",
414+ "now": "6.0.0",
415+ "npm-package-json": "2.0.0",
416+ "nullable": "6.0.0",
417+ "numberfield": "0.1.0",
418+ "numbers": "9.0.1",
419+ "oak": "3.1.1",
420+ "oak-debug": "1.2.2",
421+ "object-maps": "0.3.0",
422+ "ocarina": "1.5.4",
423+ "open-folds": "6.3.0",
424+ "open-memoize": "6.1.0",
425+ "open-pairing": "6.1.0",
426+ "options": "7.0.0",
427+ "optparse": "5.0.1",
428+ "ordered-collections": "3.2.0",
429+ "ordered-set": "0.4.0",
430+ "orders": "6.0.0",
431+ "owoify": "1.2.0",
432+ "pairs": "9.0.1",
433+ "parallel": "7.0.0",
434+ "parsing": "10.2.0",
435+ "parsing-dataview": "3.2.4",
436+ "partial": "4.0.0",
437+ "pathy": "9.0.0",
438+ "pha": "0.13.0",
439+ "phaser": "0.7.0",
440+ "phylio": "1.1.2",
441+ "pipes": "8.0.0",
442+ "pirates-charm": "0.0.1",
443+ "pmock": "0.9.0",
444+ "point-free": "1.0.0",
445+ "pointed-list": "0.5.1",
446+ "polymorphic-vectors": "4.0.0",
447+ "posix-types": "6.0.0",
448+ "postgresql": "1.6.3",
449+ "precise": "6.0.0",
450+ "precise-datetime": "7.0.0",
451+ "prelude": "6.0.1",
452+ "prettier-printer": "3.0.0",
453+ "profunctor": "6.0.1",
454+ "profunctor-lenses": "8.0.0",
455+ "protobuf": "4.3.0",
456+ "psa-utils": "8.0.0",
457+ "psci-support": "6.0.0",
458+ "punycode": "1.0.0",
459+ "qualified-do": "2.2.0",
460+ "quantities": "12.2.0",
461+ "quickcheck": "8.0.1",
462+ "quickcheck-combinators": "0.1.3",
463+ "quickcheck-laws": "7.0.0",
464+ "quickcheck-utf8": "0.0.0",
465+ "random": "6.0.0",
466+ "rationals": "6.0.0",
467+ "rdf": "0.1.0",
468+ "react": "11.0.0",
469+ "react-aria": "0.2.0",
470+ "react-basic": "17.0.0",
471+ "react-basic-classic": "3.0.0",
472+ "react-basic-dnd": "10.1.0",
473+ "react-basic-dom": "6.1.0",
474+ "react-basic-emotion": "7.1.0",
475+ "react-basic-hooks": "8.2.0",
476+ "react-basic-storybook": "2.0.0",
477+ "react-dom": "8.0.0",
478+ "react-halo": "3.0.0",
479+ "react-icons": "1.1.5",
480+ "react-markdown": "0.1.0",
481+ "react-testing-library": "4.0.1",
482+ "react-virtuoso": "1.0.0",
483+ "reactix": "0.6.1",
484+ "read": "1.0.1",
485+ "recharts": "1.1.0",
486+ "record": "4.0.0",
487+ "record-extra": "5.0.1",
488+ "record-ptional-fields": "0.1.2",
489+ "record-studio": "1.0.4",
490+ "refs": "6.0.0",
491+ "remotedata": "5.0.1",
492+ "resize-observer": "1.0.0",
493+ "resource": "2.0.1",
494+ "resourcet": "1.0.0",
495+ "result": "1.0.3",
496+ "return": "0.2.0",
497+ "ring-modules": "5.0.1",
498+ "rito": "0.3.4",
499+ "rough-notation": "1.0.2",
500+ "routing": "11.0.0",
501+ "routing-duplex": "0.7.0",
502+ "run": "5.0.0",
503+ "safe-coerce": "2.0.0",
504+ "safely": "4.0.1",
505+ "school-of-music": "1.3.0",
506+ "selection-foldable": "0.2.0",
507+ "selective-functors": "1.0.1",
508+ "semirings": "7.0.0",
509+ "signal": "13.0.0",
510+ "simple-emitter": "3.0.1",
511+ "simple-i18n": "2.0.1",
512+ "simple-json": "9.0.0",
513+ "simple-json-generics": "0.2.1",
514+ "simple-ulid": "3.0.0",
515+ "sized-matrices": "1.0.0",
516+ "sized-vectors": "5.0.2",
517+ "slug": "3.0.8",
518+ "small-ffi": "4.0.1",
519+ "soundfonts": "4.1.0",
520+ "sparse-matrices": "1.3.0",
521+ "sparse-polynomials": "2.0.5",
522+ "spec": "7.6.0",
523+ "spec-mocha": "5.1.0",
524+ "spec-quickcheck": "5.0.0",
525+ "splitmix": "2.1.0",
526+ "ssrs": "1.0.0",
527+ "st": "6.2.0",
528+ "statistics": "0.3.2",
529+ "strictlypositiveint": "1.0.1",
530+ "string-parsers": "8.0.0",
531+ "strings": "6.0.1",
532+ "strings-extra": "4.0.0",
533+ "stringutils": "0.0.12",
534+ "substitute": "0.2.3",
535+ "supply": "0.2.0",
536+ "svg-parser": "3.0.0",
537+ "systemd-journald": "0.3.0",
538+ "tagged": "4.0.2",
539+ "tailrec": "6.1.0",
540+ "tecton": "0.2.1",
541+ "tecton-halogen": "0.2.0",
542+ "test-unit": "17.0.0",
543+ "thermite": "6.3.1",
544+ "thermite-dom": "0.3.1",
545+ "these": "6.0.0",
546+ "toestand": "0.9.0",
547+ "transformation-matrix": "1.0.1",
548+ "transformers": "6.0.0",
549+ "tree-rose": "4.0.2",
550+ "ts-bridge": "4.0.0",
551+ "tuples": "7.0.0",
552+ "two-or-more": "1.0.0",
553+ "type-equality": "4.0.1",
554+ "typedenv": "2.0.1",
555+ "typelevel": "6.0.0",
556+ "typelevel-lists": "2.1.0",
557+ "typelevel-peano": "1.0.1",
558+ "typelevel-prelude": "7.0.0",
559+ "typelevel-regex": "0.0.3",
560+ "typelevel-rows": "0.1.0",
561+ "typisch": "0.4.0",
562+ "uint": "7.0.0",
563+ "ulid": "3.0.1",
564+ "uncurried-transformers": "1.1.0",
565+ "undefined": "2.0.0",
566+ "undefined-is-not-a-problem": "1.1.0",
567+ "unfoldable": "6.0.0",
568+ "unicode": "6.0.0",
569+ "unique": "0.6.1",
570+ "unlift": "1.0.1",
571+ "unordered-collections": "3.1.0",
572+ "unsafe-coerce": "6.0.0",
573+ "unsafe-reference": "5.0.0",
574+ "untagged-to-tagged": "0.1.4",
575+ "untagged-union": "1.0.0",
576+ "uri": "9.0.0",
577+ "uuid": "9.0.0",
578+ "uuidv4": "1.0.0",
579+ "validation": "6.0.0",
580+ "variant": "8.0.0",
581+ "variant-encodings": "2.0.0",
582+ "vectorfield": "1.0.1",
583+ "vectors": "2.1.0",
584+ "versions": "7.0.0",
585+ "visx": "0.0.2",
586+ "web-clipboard": "5.0.0",
587+ "web-cssom": "2.0.0",
588+ "web-cssom-view": "0.1.0",
589+ "web-dom": "6.0.0",
590+ "web-dom-parser": "8.0.0",
591+ "web-dom-xpath": "3.0.0",
592+ "web-encoding": "3.0.0",
593+ "web-events": "4.0.0",
594+ "web-fetch": "4.0.1",
595+ "web-file": "4.0.0",
596+ "web-geometry": "0.1.0",
597+ "web-html": "4.1.0",
598+ "web-pointerevents": "2.0.0",
599+ "web-proletarian": "1.0.0",
600+ "web-promise": "3.2.0",
601+ "web-resize-observer": "2.1.0",
602+ "web-router": "1.0.0",
603+ "web-socket": "4.0.0",
604+ "web-storage": "5.0.0",
605+ "web-streams": "4.0.0",
606+ "web-touchevents": "4.0.0",
607+ "web-uievents": "5.0.0",
608+ "web-url": "2.0.0",
609+ "web-workers": "1.1.0",
610+ "web-xhr": "5.0.1",
611+ "webextension-polyfill": "0.1.0",
612+ "webgpu": "0.0.1",
613+ "which": "2.0.0",
614+ "xterm": "1.0.0",
615+ "yoga-fetch": "1.0.1",
616+ "yoga-json": "5.1.0",
617+ "yoga-om": "0.1.0",
618+ "yoga-postgres": "6.0.0",
619+ "yoga-tree": "1.0.0",
620+ "z3": "0.0.2",
621+ "zipperarray": "2.0.0"
622+ }
623+ },
624+ "extra_packages": {
625+ "node-workerbees": {
626+ "git": "https://github.com/natefaubion/purescript-node-workerbees.git",
627+ "ref": "v0.3.1",
628+ "dependencies": [
629+ "aff",
630+ "argonaut-core",
631+ "effect",
632+ "either",
633+ "exceptions",
634+ "maybe",
635+ "newtype",
636+ "parallel",
637+ "prelude",
638+ "transformers",
639+ "unsafe-coerce"
640+ ]
641+ },
642+ "yoga-json": {
643+ "path": "../purescript-yoga-json"
644+ },
645+ "yoga-om": {
646+ "path": "../purescript-yoga-om"
647+ }
648+ }
649+ },
650+ "packages": {
651+ "aff": {
652+ "type": "registry",
653+ "version": "7.1.0",
654+ "integrity": "sha256-/nOpDQVnKIoP1pQ1ZZBYpadK0TuMlG4WwufGzJpsfD0=",
655+ "dependencies": [
656+ "arrays",
657+ "bifunctors",
658+ "control",
659+ "datetime",
660+ "effect",
661+ "either",
662+ "exceptions",
663+ "foldable-traversable",
664+ "functions",
665+ "maybe",
666+ "newtype",
667+ "parallel",
668+ "prelude",
669+ "refs",
670+ "tailrec",
671+ "transformers",
672+ "unsafe-coerce"
673+ ]
674+ },
675+ "argonaut-core": {
676+ "type": "registry",
677+ "version": "7.0.0",
678+ "integrity": "sha256-gYZihtBIKthVuXXoiPphMookSf8M5aHyFYkcIWqlxTM=",
679+ "dependencies": [
680+ "arrays",
681+ "control",
682+ "either",
683+ "foreign-object",
684+ "functions",
685+ "gen",
686+ "maybe",
687+ "nonempty",
688+ "prelude",
689+ "strings",
690+ "tailrec"
691+ ]
692+ },
693+ "arraybuffer-types": {
694+ "type": "registry",
695+ "version": "3.0.2",
696+ "integrity": "sha256-p05cJnSkyeoB7VHzMyc2Eb1RwUaB7Nl0sQSqEGNEUD8=",
697+ "dependencies": []
698+ },
699+ "arrays": {
700+ "type": "registry",
701+ "version": "7.3.0",
702+ "integrity": "sha256-GD4z7LCi9wzi7FCQqbWhvs8paoUK9NO+HwgXZ+KP8Rk=",
703+ "dependencies": [
704+ "bifunctors",
705+ "control",
706+ "foldable-traversable",
707+ "functions",
708+ "maybe",
709+ "nonempty",
710+ "partial",
711+ "prelude",
712+ "safe-coerce",
713+ "st",
714+ "tailrec",
715+ "tuples",
716+ "unfoldable",
717+ "unsafe-coerce"
718+ ]
719+ },
720+ "avar": {
721+ "type": "registry",
722+ "version": "5.0.0",
723+ "integrity": "sha256-9tAeYrHAFSPkbJ7UCtpyLq7QawSrwf/nwLT9BcmfUx8=",
724+ "dependencies": [
725+ "aff",
726+ "effect",
727+ "either",
728+ "exceptions",
729+ "functions",
730+ "maybe"
731+ ]
732+ },
733+ "bifunctors": {
734+ "type": "registry",
735+ "version": "6.0.0",
736+ "integrity": "sha256-tXhmlMiyVy6mbUreVZQt/2EGEsPr0T1PjJi+YCG5J7E=",
737+ "dependencies": [
738+ "const",
739+ "either",
740+ "newtype",
741+ "prelude",
742+ "tuples"
743+ ]
744+ },
745+ "catenable-lists": {
746+ "type": "registry",
747+ "version": "7.0.0",
748+ "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=",
749+ "dependencies": [
750+ "control",
751+ "foldable-traversable",
752+ "lists",
753+ "maybe",
754+ "prelude",
755+ "tuples",
756+ "unfoldable"
757+ ]
758+ },
759+ "console": {
760+ "type": "registry",
761+ "version": "6.1.0",
762+ "integrity": "sha256-3T5PPz1mATZmV0C4GzkCkpLlCa5xG7u8ZNgnLrrrI1Q=",
763+ "dependencies": [
764+ "effect",
765+ "prelude"
766+ ]
767+ },
768+ "const": {
769+ "type": "registry",
770+ "version": "6.0.0",
771+ "integrity": "sha256-S+m0lQsxnji0z1XRUmkn+3RFuWJ5CXWuIr1u3i8Bfoc=",
772+ "dependencies": [
773+ "invariant",
774+ "newtype",
775+ "prelude"
776+ ]
777+ },
778+ "contravariant": {
779+ "type": "registry",
780+ "version": "6.0.0",
781+ "integrity": "sha256-nuckEWqL80NaPmR/l9AUyrfycNh18XcvlZV9PAamwqc=",
782+ "dependencies": [
783+ "const",
784+ "either",
785+ "newtype",
786+ "prelude",
787+ "tuples"
788+ ]
789+ },
790+ "control": {
791+ "type": "registry",
792+ "version": "6.0.0",
793+ "integrity": "sha256-0vxyYh5FL+ilxgLo954w1LqCUC0RSIivNNlb9CaNUQg=",
794+ "dependencies": [
795+ "newtype",
796+ "prelude"
797+ ]
798+ },
799+ "datetime": {
800+ "type": "registry",
801+ "version": "6.1.0",
802+ "integrity": "sha256-tbv6eDXy6QOD7YSknxYSbsDF32OC2JrNbSBpzMs7Doc=",
803+ "dependencies": [
804+ "bifunctors",
805+ "control",
806+ "either",
807+ "enums",
808+ "foldable-traversable",
809+ "functions",
810+ "gen",
811+ "integers",
812+ "lists",
813+ "maybe",
814+ "newtype",
815+ "numbers",
816+ "ordered-collections",
817+ "partial",
818+ "prelude",
819+ "tuples"
820+ ]
821+ },
822+ "distributive": {
823+ "type": "registry",
824+ "version": "6.0.0",
825+ "integrity": "sha256-netyJK4B32PDYHD0tF2AenKgo3urzbI770ycM0pArAo=",
826+ "dependencies": [
827+ "identity",
828+ "newtype",
829+ "prelude",
830+ "tuples",
831+ "type-equality"
832+ ]
833+ },
834+ "effect": {
835+ "type": "registry",
836+ "version": "4.0.0",
837+ "integrity": "sha256-DdqU3bncRTjgMVPPRlAuGHVeajSPEcRaRhNuio7if6s=",
838+ "dependencies": [
839+ "prelude"
840+ ]
841+ },
842+ "either": {
843+ "type": "registry",
844+ "version": "6.1.0",
845+ "integrity": "sha256-tBHx3PgtH4GZOApZCDkcM7szRTYJinrUrVGWazQCUUg=",
846+ "dependencies": [
847+ "control",
848+ "invariant",
849+ "maybe",
850+ "prelude"
851+ ]
852+ },
853+ "enums": {
854+ "type": "registry",
855+ "version": "6.0.1",
856+ "integrity": "sha256-sdZOmLX5+5pASGpvVyT0vSD5BBYQjZiayjV5XiPutso=",
857+ "dependencies": [
858+ "control",
859+ "either",
860+ "gen",
861+ "maybe",
862+ "newtype",
863+ "nonempty",
864+ "partial",
865+ "prelude",
866+ "tuples",
867+ "unfoldable"
868+ ]
869+ },
870+ "exceptions": {
871+ "type": "registry",
872+ "version": "6.0.0",
873+ "integrity": "sha256-z65UIKhOyVXzV8QUxtky4OiYVESNPegtuBFUdAddbTM=",
874+ "dependencies": [
875+ "effect",
876+ "either",
877+ "maybe",
878+ "prelude"
879+ ]
880+ },
881+ "exists": {
882+ "type": "registry",
883+ "version": "6.0.0",
884+ "integrity": "sha256-vtLbrWNaI+pzx/2fw2AQnCovoLtcosClIhjO26QKkh8=",
885+ "dependencies": [
886+ "unsafe-coerce"
887+ ]
888+ },
889+ "foldable-traversable": {
890+ "type": "registry",
891+ "version": "6.0.0",
892+ "integrity": "sha256-NGGWyCio/xjce6fPP7y3wwg7CheqllID6aJudDxbWhA=",
893+ "dependencies": [
894+ "bifunctors",
895+ "const",
896+ "control",
897+ "either",
898+ "functors",
899+ "identity",
900+ "maybe",
901+ "newtype",
902+ "orders",
903+ "prelude",
904+ "tuples"
905+ ]
906+ },
907+ "foreign": {
908+ "type": "registry",
909+ "version": "7.0.0",
910+ "integrity": "sha256-jiDRMVQfovGsbbA/FzbzYf6iWk9i2b5gNrIjz+gFfsI=",
911+ "dependencies": [
912+ "either",
913+ "functions",
914+ "identity",
915+ "integers",
916+ "lists",
917+ "maybe",
918+ "prelude",
919+ "strings",
920+ "transformers"
921+ ]
922+ },
923+ "foreign-object": {
924+ "type": "registry",
925+ "version": "4.1.0",
926+ "integrity": "sha256-x/Q7r80z/vmHRKvPwhYmHP/DnJciPtsyGfRTMzayKIU=",
927+ "dependencies": [
928+ "arrays",
929+ "foldable-traversable",
930+ "functions",
931+ "gen",
932+ "lists",
933+ "maybe",
934+ "prelude",
935+ "st",
936+ "tailrec",
937+ "tuples",
938+ "typelevel-prelude",
939+ "unfoldable"
940+ ]
941+ },
942+ "free": {
943+ "type": "registry",
944+ "version": "7.1.0",
945+ "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=",
946+ "dependencies": [
947+ "catenable-lists",
948+ "control",
949+ "distributive",
950+ "either",
951+ "exists",
952+ "foldable-traversable",
953+ "invariant",
954+ "lazy",
955+ "maybe",
956+ "prelude",
957+ "tailrec",
958+ "transformers",
959+ "tuples",
960+ "unsafe-coerce"
961+ ]
962+ },
963+ "functions": {
964+ "type": "registry",
965+ "version": "6.0.0",
966+ "integrity": "sha256-0jgtOr+SFO4UScKc40oA/9Vdcf2rLn3h3vZlY0KfqVo=",
967+ "dependencies": [
968+ "prelude"
969+ ]
970+ },
971+ "functors": {
972+ "type": "registry",
973+ "version": "5.0.0",
974+ "integrity": "sha256-W1o/4wcp6S3chodYL8uixlXK2mfr1L+SzM1R+whqUco=",
975+ "dependencies": [
976+ "bifunctors",
977+ "const",
978+ "contravariant",
979+ "control",
980+ "distributive",
981+ "either",
982+ "invariant",
983+ "maybe",
984+ "newtype",
985+ "prelude",
986+ "profunctor",
987+ "tuples",
988+ "unsafe-coerce"
989+ ]
990+ },
991+ "gen": {
992+ "type": "registry",
993+ "version": "4.0.0",
994+ "integrity": "sha256-kyOateeOtQa07vfTMYeSuCKdelgKE1R65fwKkZ10wsY=",
995+ "dependencies": [
996+ "either",
997+ "foldable-traversable",
998+ "identity",
999+ "maybe",
1000+ "newtype",
1001+ "nonempty",
1002+ "prelude",
1003+ "tailrec",
1004+ "tuples",
1005+ "unfoldable"
1006+ ]
1007+ },
1008+ "heterogeneous": {
1009+ "type": "registry",
1010+ "version": "0.6.0",
1011+ "integrity": "sha256-nQgk2iHYe5LIi6ZzREgWaCU3y8oCwdhDSukT6xVMWdk=",
1012+ "dependencies": [
1013+ "either",
1014+ "foldable-traversable",
1015+ "functors",
1016+ "prelude",
1017+ "record",
1018+ "tuples",
1019+ "variant"
1020+ ]
1021+ },
1022+ "identity": {
1023+ "type": "registry",
1024+ "version": "6.0.0",
1025+ "integrity": "sha256-RY/iXPpxpqvKHYfJeFVNI1J06kohB9rbtWYuha8t0LE=",
1026+ "dependencies": [
1027+ "control",
1028+ "invariant",
1029+ "newtype",
1030+ "prelude"
1031+ ]
1032+ },
1033+ "integers": {
1034+ "type": "registry",
1035+ "version": "6.0.0",
1036+ "integrity": "sha256-Y8yozC1uHRLcruobEyv5/bMKzE1BoKJ/olHv6o+bgm8=",
1037+ "dependencies": [
1038+ "maybe",
1039+ "numbers",
1040+ "prelude"
1041+ ]
1042+ },
1043+ "invariant": {
1044+ "type": "registry",
1045+ "version": "6.0.0",
1046+ "integrity": "sha256-HyoT1I5qIHRq8RSZji6zZDvqnpVjLw748utrKwZtqsw=",
1047+ "dependencies": [
1048+ "control",
1049+ "prelude"
1050+ ]
1051+ },
1052+ "js-bigints": {
1053+ "type": "registry",
1054+ "version": "2.2.1",
1055+ "integrity": "sha256-r1rE6Olv5b3JrP32lY2uKJ+eBRTsrVnC983K8VWwqe4=",
1056+ "dependencies": [
1057+ "integers",
1058+ "maybe",
1059+ "prelude"
1060+ ]
1061+ },
1062+ "js-date": {
1063+ "type": "registry",
1064+ "version": "8.0.0",
1065+ "integrity": "sha256-DNQrp4xYc8o80jtxo0aOkUeXGi6h0Xidkk2ZQ8Y4l5Y=",
1066+ "dependencies": [
1067+ "datetime",
1068+ "effect",
1069+ "exceptions",
1070+ "foreign",
1071+ "integers",
1072+ "now"
1073+ ]
1074+ },
1075+ "lazy": {
1076+ "type": "registry",
1077+ "version": "6.0.0",
1078+ "integrity": "sha256-MHRC+1Pc+AjortBp4b/e1e6KpEpsDwaJBNBB7TOL+1I=",
1079+ "dependencies": [
1080+ "control",
1081+ "foldable-traversable",
1082+ "invariant",
1083+ "prelude"
1084+ ]
1085+ },
1086+ "lists": {
1087+ "type": "registry",
1088+ "version": "7.0.0",
1089+ "integrity": "sha256-/mGd7wLoU+wsTcqii7SSUByxsvwvjnu2PjwAD47yYb4=",
1090+ "dependencies": [
1091+ "bifunctors",
1092+ "control",
1093+ "foldable-traversable",
1094+ "lazy",
1095+ "maybe",
1096+ "newtype",
1097+ "nonempty",
1098+ "partial",
1099+ "prelude",
1100+ "tailrec",
1101+ "tuples",
1102+ "unfoldable"
1103+ ]
1104+ },
1105+ "maybe": {
1106+ "type": "registry",
1107+ "version": "6.0.0",
1108+ "integrity": "sha256-8X6XEgtZ5lqfkEuc1hWOofnL6mwhk8gSJTxwvbR/SbY=",
1109+ "dependencies": [
1110+ "control",
1111+ "invariant",
1112+ "newtype",
1113+ "prelude"
1114+ ]
1115+ },
1116+ "newtype": {
1117+ "type": "registry",
1118+ "version": "5.0.0",
1119+ "integrity": "sha256-dt6w0cty4OS+Pjt9fsh3iU6ottqSU83mdAZkaEEvo6c=",
1120+ "dependencies": [
1121+ "prelude",
1122+ "safe-coerce"
1123+ ]
1124+ },
1125+ "node-buffer": {
1126+ "type": "registry",
1127+ "version": "9.0.0",
1128+ "integrity": "sha256-RdQjilmEHqsT4fwpCr8Mavw8KTafYrS3ZM6zeAAVAl8=",
1129+ "dependencies": [
1130+ "arraybuffer-types",
1131+ "effect",
1132+ "maybe",
1133+ "nullable",
1134+ "st",
1135+ "unsafe-coerce"
1136+ ]
1137+ },
1138+ "node-workerbees": {
1139+ "type": "git",
1140+ "url": "https://github.com/natefaubion/purescript-node-workerbees.git",
1141+ "rev": "5f429932c6fbb0d0adb595d9970f249523a20041",
1142+ "dependencies": [
1143+ "aff",
1144+ "argonaut-core",
1145+ "effect",
1146+ "either",
1147+ "exceptions",
1148+ "maybe",
1149+ "newtype",
1150+ "parallel",
1151+ "prelude",
1152+ "transformers",
1153+ "unsafe-coerce"
1154+ ]
1155+ },
1156+ "nonempty": {
1157+ "type": "registry",
1158+ "version": "7.0.0",
1159+ "integrity": "sha256-Ctkq8/+KiGqCE53Y/LvibA/coy7ev9HtRZU+GYS7yfQ=",
1160+ "dependencies": [
1161+ "control",
1162+ "foldable-traversable",
1163+ "maybe",
1164+ "prelude",
1165+ "tuples",
1166+ "unfoldable"
1167+ ]
1168+ },
1169+ "now": {
1170+ "type": "registry",
1171+ "version": "6.0.0",
1172+ "integrity": "sha256-mTyG4s8tps3XIf0jjV/lME2/QlwMzrQly2wqfSSUM3o=",
1173+ "dependencies": [
1174+ "datetime",
1175+ "effect"
1176+ ]
1177+ },
1178+ "nullable": {
1179+ "type": "registry",
1180+ "version": "6.0.0",
1181+ "integrity": "sha256-CDAZk+L9Sc0GCFTkE8n+qdp4Ys8avvnkU+m2hVALt7M=",
1182+ "dependencies": [
1183+ "effect",
1184+ "functions",
1185+ "maybe"
1186+ ]
1187+ },
1188+ "numbers": {
1189+ "type": "registry",
1190+ "version": "9.0.1",
1191+ "integrity": "sha256-ZydYckgwRAnRaFC7pu6fADhzdwX+UqHZrfJyet64zls=",
1192+ "dependencies": [
1193+ "functions",
1194+ "maybe"
1195+ ]
1196+ },
1197+ "ordered-collections": {
1198+ "type": "registry",
1199+ "version": "3.2.0",
1200+ "integrity": "sha256-NaEXc2cz/7lJfxPWEja4XlZbGxuRqJwFKQJjsJf51kQ=",
1201+ "dependencies": [
1202+ "arrays",
1203+ "foldable-traversable",
1204+ "gen",
1205+ "lists",
1206+ "maybe",
1207+ "partial",
1208+ "prelude",
1209+ "st",
1210+ "tailrec",
1211+ "tuples",
1212+ "unfoldable"
1213+ ]
1214+ },
1215+ "orders": {
1216+ "type": "registry",
1217+ "version": "6.0.0",
1218+ "integrity": "sha256-nSMxZK7wxyDULkjZqvMyB71mVo6b0AyLLHosDVbv1XM=",
1219+ "dependencies": [
1220+ "newtype",
1221+ "prelude"
1222+ ]
1223+ },
1224+ "parallel": {
1225+ "type": "registry",
1226+ "version": "7.0.0",
1227+ "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=",
1228+ "dependencies": [
1229+ "control",
1230+ "effect",
1231+ "either",
1232+ "foldable-traversable",
1233+ "functors",
1234+ "maybe",
1235+ "newtype",
1236+ "prelude",
1237+ "profunctor",
1238+ "refs",
1239+ "transformers"
1240+ ]
1241+ },
1242+ "partial": {
1243+ "type": "registry",
1244+ "version": "4.0.0",
1245+ "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1246+ "dependencies": []
1247+ },
1248+ "prelude": {
1249+ "type": "registry",
1250+ "version": "6.0.1",
1251+ "integrity": "sha256-qicw3g9AkjIxyVp73o1dF9BiTCMWouPk9rYe6AASBCU=",
1252+ "dependencies": []
1253+ },
1254+ "profunctor": {
1255+ "type": "registry",
1256+ "version": "6.0.1",
1257+ "integrity": "sha256-Ow0mJC+PIIRq/a3mzCWY5vxuTgR/e+Ee68+mWeSRnWY=",
1258+ "dependencies": [
1259+ "control",
1260+ "distributive",
1261+ "either",
1262+ "exists",
1263+ "invariant",
1264+ "newtype",
1265+ "prelude",
1266+ "tuples"
1267+ ]
1268+ },
1269+ "record": {
1270+ "type": "registry",
1271+ "version": "4.0.0",
1272+ "integrity": "sha256-WQ8fqp4X1wTA70J4qUcPW4Z7DCwx6e5KJnT/EuTdWSc=",
1273+ "dependencies": [
1274+ "functions",
1275+ "prelude",
1276+ "unsafe-coerce"
1277+ ]
1278+ },
1279+ "record-studio": {
1280+ "type": "registry",
1281+ "version": "1.0.4",
1282+ "integrity": "sha256-CRnFvSXpDDzio+buibUxFU9zPZDd7/lzrYC1Ya3FuIw=",
1283+ "dependencies": [
1284+ "heterogeneous",
1285+ "lists",
1286+ "prelude",
1287+ "record",
1288+ "typelevel-prelude",
1289+ "unsafe-coerce"
1290+ ]
1291+ },
1292+ "refs": {
1293+ "type": "registry",
1294+ "version": "6.0.0",
1295+ "integrity": "sha256-KKD5G9dR2SauuDGULUJsxjqDOEi1Jqm+2Sq5U2mY8YU=",
1296+ "dependencies": [
1297+ "effect",
1298+ "prelude"
1299+ ]
1300+ },
1301+ "safe-coerce": {
1302+ "type": "registry",
1303+ "version": "2.0.0",
1304+ "integrity": "sha256-EJrxtKt5xC7TYjBZrSBO/J7Aw3DmtilYjt4olpAXWVc=",
1305+ "dependencies": [
1306+ "unsafe-coerce"
1307+ ]
1308+ },
1309+ "st": {
1310+ "type": "registry",
1311+ "version": "6.2.0",
1312+ "integrity": "sha256-WI4MEkkwUd4pnwZQ3G8VKWgX5t1RSNJludm5e9boaRo=",
1313+ "dependencies": [
1314+ "partial",
1315+ "prelude",
1316+ "tailrec",
1317+ "unsafe-coerce"
1318+ ]
1319+ },
1320+ "strings": {
1321+ "type": "registry",
1322+ "version": "6.0.1",
1323+ "integrity": "sha256-FNto2hoNW5Da6W6crIk77YXMagBvOGHZE1kO7eZ1vLM=",
1324+ "dependencies": [
1325+ "arrays",
1326+ "control",
1327+ "either",
1328+ "enums",
1329+ "foldable-traversable",
1330+ "gen",
1331+ "integers",
1332+ "maybe",
1333+ "newtype",
1334+ "nonempty",
1335+ "partial",
1336+ "prelude",
1337+ "tailrec",
1338+ "tuples",
1339+ "unfoldable",
1340+ "unsafe-coerce"
1341+ ]
1342+ },
1343+ "tailrec": {
1344+ "type": "registry",
1345+ "version": "6.1.0",
1346+ "integrity": "sha256-xrorCfP0xV1wubs27idQCHQqdvSUuqmmL/am7Ji1wVU=",
1347+ "dependencies": [
1348+ "bifunctors",
1349+ "effect",
1350+ "either",
1351+ "identity",
1352+ "maybe",
1353+ "partial",
1354+ "prelude",
1355+ "refs"
1356+ ]
1357+ },
1358+ "transformers": {
1359+ "type": "registry",
1360+ "version": "6.0.0",
1361+ "integrity": "sha256-Nl3yXAIeeTzP6MyrkazkK7pbgeVqUm+ynLv7U5cpJYQ=",
1362+ "dependencies": [
1363+ "control",
1364+ "distributive",
1365+ "effect",
1366+ "either",
1367+ "exceptions",
1368+ "foldable-traversable",
1369+ "identity",
1370+ "lazy",
1371+ "maybe",
1372+ "newtype",
1373+ "prelude",
1374+ "tailrec",
1375+ "tuples",
1376+ "unfoldable"
1377+ ]
1378+ },
1379+ "tuples": {
1380+ "type": "registry",
1381+ "version": "7.0.0",
1382+ "integrity": "sha256-BKy7EfK8S1KeTCwyWqyW1wwcpSAQuh3IsczSVgS6fzY=",
1383+ "dependencies": [
1384+ "control",
1385+ "invariant",
1386+ "prelude"
1387+ ]
1388+ },
1389+ "type-equality": {
1390+ "type": "registry",
1391+ "version": "4.0.1",
1392+ "integrity": "sha256-BBqYOSnAKmayRvR5mtZoCf4SlYs2ipatD/+5nQx73aw=",
1393+ "dependencies": []
1394+ },
1395+ "typelevel-prelude": {
1396+ "type": "registry",
1397+ "version": "7.0.0",
1398+ "integrity": "sha256-+pHi14/40mTj/+lmCWqL0T7iXIXD1+NSg/KItyyoB0A=",
1399+ "dependencies": [
1400+ "prelude",
1401+ "type-equality"
1402+ ]
1403+ },
1404+ "uncurried-transformers": {
1405+ "type": "registry",
1406+ "version": "1.1.0",
1407+ "integrity": "sha256-AqqBMzVLzozVoDorE3QcVVRDkLd7O3624mRHCDnPhaI=",
1408+ "dependencies": [
1409+ "control",
1410+ "effect",
1411+ "either",
1412+ "functions",
1413+ "identity",
1414+ "prelude",
1415+ "safe-coerce",
1416+ "tailrec",
1417+ "transformers",
1418+ "tuples"
1419+ ]
1420+ },
1421+ "unfoldable": {
1422+ "type": "registry",
1423+ "version": "6.0.0",
1424+ "integrity": "sha256-BK/6By1sKp/ztk+9CF3q43SV0QDTLVdsGqFerKHSmbg=",
1425+ "dependencies": [
1426+ "foldable-traversable",
1427+ "maybe",
1428+ "partial",
1429+ "prelude",
1430+ "tuples"
1431+ ]
1432+ },
1433+ "unsafe-coerce": {
1434+ "type": "registry",
1435+ "version": "6.0.0",
1436+ "integrity": "sha256-0L1QsaY20OILjfU5TV72d3U/tSjhmL9hJ32WMp857Lk=",
1437+ "dependencies": []
1438+ },
1439+ "variant": {
1440+ "type": "registry",
1441+ "version": "8.0.0",
1442+ "integrity": "sha256-Ydrgl8liWEwYQOWW76PdZtg3vxKpoKgsnmFyTLgZkoU=",
1443+ "dependencies": [
1444+ "enums",
1445+ "lists",
1446+ "maybe",
1447+ "partial",
1448+ "prelude",
1449+ "record",
1450+ "tuples",
1451+ "unsafe-coerce"
1452+ ]
1453+ },
1454+ "yoga-json": {
1455+ "type": "local",
1456+ "path": "../purescript-yoga-json",
1457+ "dependencies": [
1458+ "arrays",
1459+ "bifunctors",
1460+ "control",
1461+ "datetime",
1462+ "effect",
1463+ "either",
1464+ "exceptions",
1465+ "foldable-traversable",
1466+ "foreign",
1467+ "foreign-object",
1468+ "free",
1469+ "identity",
1470+ "integers",
1471+ "js-bigints",
1472+ "js-date",
1473+ "lists",
1474+ "maybe",
1475+ "newtype",
1476+ "nullable",
1477+ "numbers",
1478+ "ordered-collections",
1479+ "partial",
1480+ "prelude",
1481+ "record",
1482+ "safe-coerce",
1483+ "strings",
1484+ "transformers",
1485+ "tuples",
1486+ "typelevel-prelude",
1487+ "unsafe-coerce",
1488+ "variant",
1489+ "yoga-tree"
1490+ ]
1491+ },
1492+ "yoga-om": {
1493+ "type": "local",
1494+ "path": "../purescript-yoga-om",
1495+ "dependencies": [
1496+ "aff",
1497+ "avar",
1498+ "console",
1499+ "control",
1500+ "datetime",
1501+ "effect",
1502+ "either",
1503+ "exceptions",
1504+ "functions",
1505+ "maybe",
1506+ "newtype",
1507+ "parallel",
1508+ "prelude",
1509+ "record",
1510+ "record-studio",
1511+ "tailrec",
1512+ "transformers",
1513+ "tuples",
1514+ "typelevel-prelude",
1515+ "uncurried-transformers",
1516+ "unsafe-coerce",
1517+ "variant"
1518+ ]
1519+ },
1520+ "yoga-tree": {
1521+ "type": "registry",
1522+ "version": "1.0.0",
1523+ "integrity": "sha256-s8rIVEQkRi1VrMc92T9ceXcY0X6r5A4QqT6f7GgmDQI=",
1524+ "dependencies": [
1525+ "arrays",
1526+ "control",
1527+ "foldable-traversable",
1528+ "free",
1529+ "maybe",
1530+ "prelude",
1531+ "tailrec"
1532+ ]
1533+ }
1534+ }
1535+}
spago.yamlnew file+49
@@ -0,0 +1,49 @@
1+workspace:
2+ packageSet:
3+ registry: 51.1.0
4+ extraPackages:
5+ node-workerbees:
6+ git: https://github.com/natefaubion/purescript-node-workerbees.git
7+ ref: v0.3.1
8+ dependencies:
9+ - aff
10+ - argonaut-core
11+ - effect
12+ - either
13+ - exceptions
14+ - maybe
15+ - newtype
16+ - parallel
17+ - prelude
18+ - transformers
19+ - unsafe-coerce
20+ yoga-om:
21+ path: ../purescript-yoga-om
22+ yoga-json:
23+ path: ../purescript-yoga-json
24+
25+package:
26+ name: yoga-om-workerbees
27+ publish:
28+ version: 0.1.0
29+ license: MIT
30+ dependencies:
31+ - aff: ">=7.0.0 <8.0.0"
32+ - arraybuffer-types: ">=3.0.0 <4.0.0"
33+ - console: ">=6.0.0 <7.0.0"
34+ - effect: ">=4.0.0 <5.0.0"
35+ - either: ">=6.0.0 <7.0.0"
36+ - foldable-traversable: ">=6.0.0 <7.0.0"
37+ - maybe: ">=6.0.0 <7.0.0"
38+ - newtype: ">=5.0.0 <6.0.0"
39+ - node-buffer: ">=9.0.0 <10.0.0"
40+ - node-workerbees
41+ - parallel: ">=7.0.0 <8.0.0"
42+ - prelude: ">=6.0.0 <7.0.0"
43+ - transformers: ">=6.0.0 <7.0.0"
44+ - unsafe-coerce: ">=6.0.0 <7.0.0"
45+ - yoga-json: ">=5.0.0 <6.0.0"
46+ - yoga-om
47+ test:
48+ main: Test.Main
49+ dependencies: []
src/Examples/FibonacciDemo.pursnew file+58
@@ -0,0 +1,58 @@
1+module Examples.FibonacciDemo where
2+
3+import Prelude
4+
5+import Prelude
6+
7+import Control.Parallel (parTraverse)
8+import Data.Array as Array
9+import Data.Traversable (for_)
10+import Data.Tuple (Tuple(..))
11+import Effect (Effect)
12+import Effect.Aff (Aff, launchAff_)
13+import Effect.Class.Console (log)
14+import Node.WorkerBees.Aff.Pool as Pool
15+import Node.WorkerBees as WB
16+
17+-- | Simple console demo of Fibonacci calculation using worker pool
18+-- |
19+-- | This demonstrates:
20+-- | - Creating a worker pool
21+-- | - Distributing work across multiple threads
22+-- | - Collecting results
23+-- | - Proper cleanup
24+type FibInput = { n :: Int }
25+type FibOutput = { result :: Int, thread :: Int }
26+
27+fibDemo :: Aff Unit
28+fibDemo = do
29+ log "=== Fibonacci Worker Demo ==="
30+ log ""
31+
32+ -- Create worker pool with 4 workers
33+ log "Creating worker pool..."
34+ let worker = (WB.unsafeWorkerFromPath "./dist/workers/FibonacciWorker.js" :: WB.Worker Unit FibInput FibOutput)
35+ pool <- Pool.make worker unit 4
36+
37+ -- Test inputs: Calculate Fibonacci for these numbers
38+ let inputs = [35, 36, 37, 38, 39, 40]
39+ let fibInputs = map (\n -> { n }) inputs
40+
41+ log $ "Calculating Fibonacci for: " <> show inputs
42+ log ""
43+
44+ -- Distribute work and collect results
45+ results <- parTraverse (Pool.invoke pool) fibInputs
46+
47+ -- Print results
48+ for_ (Array.zip inputs results) \(Tuple input result) -> do
49+ log $ "fib(" <> show input <> ") = " <> show result.result
50+
51+ log ""
52+ log "Cleaning up..."
53+ Pool.terminate pool
54+
55+ log "Done!"
56+
57+main :: Effect Unit
58+main = launchAff_ fibDemo
src/Examples/SharedCounterDemo.pursnew file+71
@@ -0,0 +1,71 @@
1+module Examples.SharedCounterDemo where
2+
3+import Prelude
4+
5+import Prelude
6+
7+import Control.Parallel (parTraverse)
8+import Data.Array as Array
9+import Data.Traversable (for_)
10+import Effect (Effect)
11+import Effect.Aff (Aff, launchAff_)
12+import Effect.Class (liftEffect)
13+import Effect.Class.Console (log)
14+import Node.WorkerBees.Aff.Pool as Pool
15+import Node.WorkerBees as WB
16+import Yoga.Om.WorkerBees.SharedInt as SharedInt
17+
18+-- | Console demo of SharedInt (atomic counter) across worker threads
19+-- |
20+-- | This demonstrates:
21+-- | - Creating a shared atomic counter
22+-- | - Passing shared memory to workers via workerData
23+-- | - Concurrent increments from multiple workers
24+-- | - Reading the final value
25+type CounterInput = { n :: Int }
26+type CounterOutput = { result :: Int, count :: Int }
27+
28+counterDemo :: Aff Unit
29+counterDemo = do
30+ log "=== Shared Counter Demo ==="
31+ log ""
32+
33+ -- Create shared counter starting at 0
34+ log "Creating shared counter..."
35+ counter <- liftEffect $ SharedInt.new 0
36+
37+ -- Create worker pool with shared counter as workerData
38+ log "Creating worker pool with shared counter..."
39+ let counterSendable = SharedInt.toSendable counter
40+ let worker = (WB.unsafeWorkerFromPath "./dist/workers/CounterWorker.js" :: WB.Worker (WB.SendWrapper SharedInt.SharedInt) CounterInput CounterOutput)
41+ pool <- Pool.make worker counterSendable 4
42+
43+ -- Send 100 increment tasks (each worker will increment the shared counter)
44+ let tasks = Array.range 1 100
45+ let counterInputs = map (\n -> { n }) tasks
46+
47+ log "Distributing 100 increment tasks across workers..."
48+ log ""
49+
50+ results <- parTraverse (Pool.invoke pool) counterInputs
51+
52+ -- Print some sample results (showing concurrent increments)
53+ log "Sample results from workers:"
54+ for_ (Array.take 10 results) \result -> do
55+ log $ " Result: " <> show result.result <> ", Counter: " <> show result.count
56+
57+ log "..."
58+ log ""
59+
60+ -- Read final counter value
61+ finalValue <- liftEffect $ SharedInt.read counter
62+ log $ "Final counter value: " <> show finalValue
63+
64+ log ""
65+ log "Cleaning up..."
66+ Pool.terminate pool
67+
68+ log "Done!"
69+
70+main :: Effect Unit
71+main = launchAff_ counterDemo
src/Examples/SharedMapDemo.pursnew file+90
@@ -0,0 +1,90 @@
1+module Examples.SharedMapDemo where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Maybe (Maybe(..))
7+import Data.Traversable (for_)
8+import Effect (Effect)
9+import Effect.Aff (Aff, launchAff_)
10+import Effect.Class (liftEffect)
11+import Effect.Class.Console (log)
12+import Yoga.JSON as JSON
13+import Yoga.Om.WorkerBees.SharedMap as SharedMap
14+
15+type UserRecord =
16+ { id :: Int
17+ , name :: String
18+ , active :: Boolean
19+ }
20+
21+-- | Console demo of SharedMap (concurrent hash map with striped locks)
22+-- |
23+-- | This demonstrates:
24+-- | - Creating a shared concurrent hash map
25+-- | - Insert, lookup, and modify operations
26+-- | - Thread-safe concurrent access
27+-- | - Striped lock-based synchronization
28+mapDemo :: Aff Unit
29+mapDemo = do
30+ log "=== Shared Map Demo ==="
31+ log ""
32+
33+ -- Create shared map with 16 stripes and 4KB per entry
34+ log "Creating shared map (16 stripes, 4KB per entry)..."
35+ userMap <- liftEffect $ SharedMap.new 16 4096
36+
37+ -- Insert some users
38+ log ""
39+ log "Inserting users..."
40+
41+ let users =
42+ [ { id: 1, name: "Alice", active: true }
43+ , { id: 2, name: "Bob", active: false }
44+ , { id: 3, name: "Charlie", active: true }
45+ ]
46+
47+ for_ users \user -> do
48+ let key = "user:" <> show user.id
49+ liftEffect $ SharedMap.insert key user userMap
50+ log $ " Inserted: " <> key <> " -> " <> JSON.writeJSON user
51+
52+ -- Lookup users
53+ log ""
54+ log "Looking up users..."
55+
56+ for_ (Array.range 1 3) \userId -> do
57+ let key = "user:" <> show userId
58+ maybeUser <- liftEffect $ SharedMap.lookup key userMap
59+ case maybeUser of
60+ Just user -> log $ " Found: " <> key <> " -> " <> JSON.writeJSON user
61+ Nothing -> log $ " Not found: " <> key
62+
63+ -- Modify a user
64+ log ""
65+ log "Modifying user:2 (set active = true)..."
66+
67+ _ <- liftEffect $ SharedMap.modify "user:2" (\user -> user { active = true }) userMap
68+
69+ -- Lookup the modified user
70+ maybeUser2 <- liftEffect $ SharedMap.lookup "user:2" userMap
71+ case maybeUser2 of
72+ Just user -> log $ " Updated: " <> JSON.writeJSON user
73+ Nothing -> log " User not found"
74+
75+ -- Delete a user
76+ log ""
77+ log "Deleting user:1..."
78+ liftEffect $ SharedMap.delete "user:1" userMap
79+
80+ -- Try to lookup deleted user
81+ maybeUser1 <- liftEffect $ SharedMap.lookup "user:1" userMap
82+ case maybeUser1 of
83+ Just _ -> log " ERROR: User still exists!"
84+ Nothing -> log " User successfully deleted"
85+
86+ log ""
87+ log "Done!"
88+
89+main :: Effect Unit
90+main = launchAff_ mapDemo
src/Examples/SharedStateDemo.pursnew file+84
@@ -0,0 +1,84 @@
1+module Examples.SharedStateDemo where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Effect (Effect)
7+import Effect.Aff (Aff, Milliseconds(..), delay, launchAff_)
8+import Effect.Class (liftEffect)
9+import Effect.Class.Console (log)
10+import Yoga.JSON as JSON
11+import Yoga.Om.WorkerBees.SharedState as SharedState
12+
13+type AppState =
14+ { counter :: Int
15+ , message :: String
16+ , isActive :: Boolean
17+ }
18+
19+-- | Console demo of SharedState (JSON-serialized shared state with spinlock)
20+-- |
21+-- | This demonstrates:
22+-- | - Creating shared state with JSON serialization
23+-- | - Reading and writing shared state
24+-- | - Atomic modify operations
25+-- | - Spinlock-based synchronization
26+stateDemo :: Aff Unit
27+stateDemo = do
28+ log "=== Shared State Demo ==="
29+ log ""
30+
31+ -- Create initial state
32+ let initialState :: AppState
33+ initialState =
34+ { counter: 0
35+ , message: "Hello"
36+ , isActive: true
37+ }
38+
39+ log "Creating shared state..."
40+ log $ "Initial state: " <> JSON.writeJSON initialState
41+
42+ -- Create shared state with 1KB buffer
43+ state <- liftEffect $ SharedState.new 1024 initialState
44+
45+ -- Read the state
46+ log ""
47+ log "Reading state..."
48+ currentState <- liftEffect $ SharedState.read state
49+ log $ "Current state: " <> JSON.writeJSON currentState
50+
51+ -- Modify the state atomically
52+ log ""
53+ log "Modifying state (increment counter, update message)..."
54+ _ <- liftEffect $ SharedState.modify state
55+ (\s -> s { counter = s.counter + 1, message = "Updated!" })
56+
57+ -- Read again
58+ newState <- liftEffect $ SharedState.read state
59+ log $ "New state: " <> JSON.writeJSON newState
60+
61+ -- Simulate concurrent modifications
62+ log ""
63+ log "Simulating concurrent modifications..."
64+
65+ let modifyLoop :: Int -> Effect Unit
66+ modifyLoop n = do
67+ _ <- SharedState.modify state \s ->
68+ s { counter = s.counter + 1 }
69+ when (n > 0) $ modifyLoop (n - 1)
70+
71+ -- Run 100 modifications
72+ liftEffect $ modifyLoop 100
73+
74+ -- Small delay to ensure all modifications complete
75+ delay (Milliseconds 10.0)
76+
77+ finalState <- liftEffect $ SharedState.read state
78+ log $ "After 100 modifications: " <> JSON.writeJSON finalState
79+
80+ log ""
81+ log "Done!"
82+
83+main :: Effect Unit
84+main = launchAff_ stateDemo
src/Examples/Workers/CounterWorker.pursnew file+28
@@ -0,0 +1,28 @@
1+module Examples.Workers.CounterWorker where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Node.WorkerBees (SendWrapper)
7+import Node.WorkerBees as WB
8+import Yoga.Om.WorkerBees.SharedInt as SharedInt
9+
10+type CounterInput = { n :: Int }
11+type CounterOutput = { result :: Int, count :: Int }
12+
13+fibonacci :: Int -> Int
14+fibonacci n
15+ | n <= 1 = n
16+ | otherwise = fibonacci (n - 1) + fibonacci (n - 2)
17+
18+worker :: WB.WorkerContext (SendWrapper SharedInt.SharedInt) CounterInput CounterOutput -> Effect Unit
19+worker ctx = do
20+ let counter = SharedInt.fromSendable ctx.workerData
21+ ctx.receive \{ n } -> do
22+ let result = fibonacci n
23+ _ <- SharedInt.add counter 1
24+ count <- SharedInt.read counter
25+ ctx.reply { result, count }
26+
27+main :: Effect Unit
28+main = WB.makeAsMain worker
src/Examples/Workers/FibonacciWorker.pursnew file+31
@@ -0,0 +1,31 @@
1+module Examples.Workers.FibonacciWorker where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Node.WorkerBees as WB
7+
8+-- | Input type for the worker
9+type FibInput = { n :: Int }
10+
11+-- | Output type for the worker
12+type FibOutput = { result :: Int, thread :: Int }
13+
14+-- | CPU-intensive Fibonacci calculation (intentionally inefficient for demo)
15+fibonacci :: Int -> Int
16+fibonacci n
17+ | n <= 1 = n
18+ | otherwise = fibonacci (n - 1) + fibonacci (n - 2)
19+
20+-- | Worker main function
21+-- | This receives inputs and sends back outputs
22+worker :: WB.WorkerContext Unit FibInput FibOutput -> Effect Unit
23+worker ctx = ctx.receive \{ n } -> do
24+ let result = fibonacci n
25+ -- Could get thread ID from ctx.threadId if needed
26+ ctx.reply { result, thread: 0 }
27+
28+-- | Entry point for the worker
29+-- | This must be the main function when bundled
30+main :: Effect Unit
31+main = WB.makeAsMain worker
src/Examples/Workers/HashWorker.pursnew file+42
@@ -0,0 +1,42 @@
1+module Examples.Workers.HashWorker where
2+
3+import Prelude
4+
5+import Data.Foldable (foldl)
6+import Data.String as String
7+import Data.String.CodeUnits as SCU
8+import Effect (Effect)
9+import Node.WorkerBees as WB
10+
11+-- | Input type for the worker
12+type HashInput = { text :: String, iterations :: Int }
13+
14+-- | Output type for the worker
15+type HashOutput = { hash :: String, length :: Int }
16+
17+-- | Simple hash function (for demonstration - not cryptographically secure!)
18+simpleHash :: String -> Int
19+simpleHash str = do
20+ let chars = SCU.toCharArray str
21+ foldl (\acc _ -> (acc * 31 + String.length str) `mod` 1000000007) 0 chars
22+
23+-- | CPU-intensive hash calculation with multiple iterations
24+computeHash :: String -> Int -> String
25+computeHash text iterations = do
26+ let
27+ go 0 current = current
28+ go n current = do
29+ let hashed = show (simpleHash current) <> current
30+ go (n - 1) hashed
31+ go iterations text
32+
33+-- | Worker main function
34+-- | This receives inputs and sends back outputs
35+worker :: WB.WorkerContext Unit HashInput HashOutput -> Effect Unit
36+worker ctx = ctx.receive \{ text, iterations } -> do
37+ let hash = computeHash text iterations
38+ ctx.reply { hash, length: String.length hash }
39+
40+-- | Entry point for the worker
41+main :: Effect Unit
42+main = WB.makeAsMain worker
src/Yoga/Om/WorkerBees.pursnew file+86
@@ -0,0 +1,86 @@
1+module Yoga.Om.WorkerBees
2+ ( WorkerPool
3+ , PoolConfig
4+ , defaultPoolConfig
5+ , makePool
6+ , makePoolWithData
7+ , terminatePool
8+ , distributeWork
9+ , module Exports
10+ , module SharedInt
11+ , module SharedState
12+ , module SharedMap
13+ ) where
14+
15+import Prelude
16+
17+import Control.Parallel (parTraverse)
18+import Data.Traversable (class Traversable)
19+import Node.WorkerBees (Worker, class Sendable) as Exports
20+import Node.WorkerBees as WB
21+import Node.WorkerBees.Aff.Pool as Pool
22+import Yoga.Om (Om)
23+import Yoga.Om as Om
24+import Yoga.Om.WorkerBees.SharedInt (SharedInt) as SharedInt
25+import Yoga.Om.WorkerBees.SharedState (SharedState) as SharedState
26+import Yoga.Om.WorkerBees.SharedMap (SharedMap) as SharedMap
27+
28+-- | Configuration for a worker pool
29+type PoolConfig =
30+ { numWorkers :: Int
31+ , workerPath :: String
32+ }
33+
34+-- | Default pool configuration (4 workers)
35+defaultPoolConfig :: String -> PoolConfig
36+defaultPoolConfig workerPath =
37+ { numWorkers: 4
38+ , workerPath
39+ }
40+
41+-- | Worker pool handle
42+type WorkerPool input output = Pool.WorkerPool input output
43+
44+-- | Create a worker pool. Must be paired with `terminatePool`.
45+makePool
46+ :: forall ctx errs input output
47+ . WB.Sendable input
48+ => WB.Sendable output
49+ => PoolConfig
50+ -> Om ctx errs (WorkerPool input output)
51+makePool config = Om.fromAff do
52+ let worker = WB.unsafeWorkerFromPath config.workerPath
53+ Pool.make worker unit config.numWorkers
54+
55+-- | Create a worker pool with shared data accessible to all workers via `workerData`.
56+makePoolWithData
57+ :: forall ctx errs input output a
58+ . WB.Sendable input
59+ => WB.Sendable output
60+ => WB.Sendable a
61+ => PoolConfig
62+ -> a
63+ -> Om ctx errs (WorkerPool input output)
64+makePoolWithData config workerData = Om.fromAff do
65+ let worker = WB.unsafeWorkerFromPath config.workerPath
66+ Pool.make worker workerData config.numWorkers
67+
68+-- | Terminate a worker pool and all its threads.
69+terminatePool
70+ :: forall ctx errs input output
71+ . WorkerPool input output
72+ -> Om ctx errs Unit
73+terminatePool pool = Om.fromAff do
74+ Pool.terminate pool
75+
76+-- | Distribute work across the worker pool in parallel.
77+-- | Each input is submitted to the pool and processed by the next available worker.
78+distributeWork
79+ :: forall ctx errs f input output
80+ . Traversable f
81+ => WB.Sendable input
82+ => WorkerPool input output
83+ -> f input
84+ -> Om ctx errs (f output)
85+distributeWork pool inputs = Om.fromAff do
86+ parTraverse (Pool.invoke pool) inputs
src/Yoga/Om/WorkerBees/Atomics.jsnew file+14
@@ -0,0 +1,14 @@
1+export const loadImpl = (arr, idx) => Atomics.load(arr, idx);
2+
3+export const storeImpl = (arr, idx, val) => Atomics.store(arr, idx, val);
4+
5+export const addImpl = (arr, idx, val) => Atomics.add(arr, idx, val);
6+
7+export const subImpl = (arr, idx, val) => Atomics.sub(arr, idx, val);
8+
9+export const exchangeImpl = (arr, idx, val) => Atomics.exchange(arr, idx, val);
10+
11+export const compareExchangeImpl = (arr, idx, expected, replacement) =>
12+ Atomics.compareExchange(arr, idx, expected, replacement);
13+
14+export const notifyImpl = (arr, idx, count) => Atomics.notify(arr, idx, count);
src/Yoga/Om/WorkerBees/Atomics.pursnew file+48
@@ -0,0 +1,48 @@
1+module Yoga.Om.WorkerBees.Atomics
2+ ( load
3+ , store
4+ , add
5+ , sub
6+ , exchange
7+ , compareExchange
8+ , notify
9+ ) where
10+
11+import Data.ArrayBuffer.Types (Int32Array)
12+import Effect (Effect)
13+import Effect.Uncurried (EffectFn2, EffectFn3, EffectFn4, runEffectFn2, runEffectFn3, runEffectFn4)
14+
15+foreign import loadImpl :: EffectFn2 Int32Array Int Int
16+
17+load :: Int32Array -> Int -> Effect Int
18+load = runEffectFn2 loadImpl
19+
20+foreign import storeImpl :: EffectFn3 Int32Array Int Int Int
21+
22+store :: Int32Array -> Int -> Int -> Effect Int
23+store = runEffectFn3 storeImpl
24+
25+foreign import addImpl :: EffectFn3 Int32Array Int Int Int
26+
27+add :: Int32Array -> Int -> Int -> Effect Int
28+add = runEffectFn3 addImpl
29+
30+foreign import subImpl :: EffectFn3 Int32Array Int Int Int
31+
32+sub :: Int32Array -> Int -> Int -> Effect Int
33+sub = runEffectFn3 subImpl
34+
35+foreign import exchangeImpl :: EffectFn3 Int32Array Int Int Int
36+
37+exchange :: Int32Array -> Int -> Int -> Effect Int
38+exchange = runEffectFn3 exchangeImpl
39+
40+foreign import compareExchangeImpl :: EffectFn4 Int32Array Int Int Int Int
41+
42+compareExchange :: Int32Array -> Int -> Int -> Int -> Effect Int
43+compareExchange = runEffectFn4 compareExchangeImpl
44+
45+foreign import notifyImpl :: EffectFn3 Int32Array Int Int Int
46+
47+notify :: Int32Array -> Int -> Int -> Effect Int
48+notify = runEffectFn3 notifyImpl
src/Yoga/Om/WorkerBees/SharedArrayBuffer.jsnew file+13
@@ -0,0 +1,13 @@
1+export const newImpl = (byteLength) => {
2+ return new SharedArrayBuffer(byteLength);
3+};
4+
5+export const byteLengthImpl = (sab) => sab.byteLength;
6+
7+export const toInt32ArrayImpl = (sab) => {
8+ return new Int32Array(sab);
9+};
10+
11+export const toInt32ArraySliceImpl = (sab, byteOffset, length) => {
12+ return new Int32Array(sab, byteOffset, length);
13+};
src/Yoga/Om/WorkerBees/SharedArrayBuffer.pursnew file+33
@@ -0,0 +1,33 @@
1+module Yoga.Om.WorkerBees.SharedArrayBuffer
2+ ( SharedArrayBuffer
3+ , new
4+ , byteLength
5+ , toInt32Array
6+ , toInt32ArraySlice
7+ ) where
8+
9+import Data.ArrayBuffer.Types (Int32Array)
10+import Effect (Effect)
11+import Effect.Uncurried (EffectFn1, EffectFn3, runEffectFn1, runEffectFn3)
12+
13+foreign import data SharedArrayBuffer :: Type
14+
15+foreign import newImpl :: EffectFn1 Int SharedArrayBuffer
16+
17+new :: Int -> Effect SharedArrayBuffer
18+new = runEffectFn1 newImpl
19+
20+foreign import byteLengthImpl :: SharedArrayBuffer -> Int
21+
22+byteLength :: SharedArrayBuffer -> Int
23+byteLength = byteLengthImpl
24+
25+foreign import toInt32ArrayImpl :: EffectFn1 SharedArrayBuffer Int32Array
26+
27+toInt32Array :: SharedArrayBuffer -> Effect Int32Array
28+toInt32Array = runEffectFn1 toInt32ArrayImpl
29+
30+foreign import toInt32ArraySliceImpl :: EffectFn3 SharedArrayBuffer Int Int Int32Array
31+
32+toInt32ArraySlice :: SharedArrayBuffer -> Int -> Int -> Effect Int32Array
33+toInt32ArraySlice = runEffectFn3 toInt32ArraySliceImpl
src/Yoga/Om/WorkerBees/SharedInt.pursnew file+75
@@ -0,0 +1,75 @@
1+module Yoga.Om.WorkerBees.SharedInt
2+ ( SharedInt
3+ , new
4+ , read
5+ , write
6+ , add
7+ , sub
8+ , compareAndSwap
9+ , modify
10+ , toSendable
11+ , fromSendable
12+ ) where
13+
14+import Prelude
15+
16+import Data.ArrayBuffer.Types (Int32Array)
17+import Effect (Effect)
18+import Node.WorkerBees (SendWrapper, unsafeWrap)
19+import Node.WorkerBees as WB
20+import Yoga.Om.WorkerBees.Atomics as Atomics
21+import Yoga.Om.WorkerBees.SharedArrayBuffer as SAB
22+
23+-- | An atomic integer reference backed by SharedArrayBuffer.
24+-- | Can be shared across worker threads via `toSendable`/`fromSendable`.
25+newtype SharedInt = SharedInt Int32Array
26+
27+-- | Create a new SharedInt initialized to the given value.
28+new :: Int -> Effect SharedInt
29+new initial = do
30+ sab <- SAB.new 4
31+ arr <- SAB.toInt32Array sab
32+ _ <- Atomics.store arr 0 initial
33+ pure (SharedInt arr)
34+
35+-- | Atomically read the current value.
36+read :: SharedInt -> Effect Int
37+read (SharedInt arr) = Atomics.load arr 0
38+
39+-- | Atomically write a new value.
40+write :: SharedInt -> Int -> Effect Unit
41+write (SharedInt arr) val = void $ Atomics.store arr 0 val
42+
43+-- | Atomically add to the current value. Returns the OLD value.
44+add :: SharedInt -> Int -> Effect Int
45+add (SharedInt arr) val = Atomics.add arr 0 val
46+
47+-- | Atomically subtract from the current value. Returns the OLD value.
48+sub :: SharedInt -> Int -> Effect Int
49+sub (SharedInt arr) val = Atomics.sub arr 0 val
50+
51+-- | Atomic compare-and-swap. If current value equals `expected`,
52+-- | replace with `replacement`. Returns the old value.
53+compareAndSwap :: SharedInt -> Int -> Int -> Effect Int
54+compareAndSwap (SharedInt arr) expected replacement =
55+ Atomics.compareExchange arr 0 expected replacement
56+
57+-- | Atomically modify the value using a CAS loop.
58+-- | Retries until the CAS succeeds.
59+modify :: SharedInt -> (Int -> Int) -> Effect Int
60+modify ref f = go
61+ where
62+ go = do
63+ current <- read ref
64+ let next = f current
65+ old <- compareAndSwap ref current next
66+ if old == current then pure next
67+ else go
68+
69+-- | Wrap for sending to a worker thread.
70+toSendable :: SharedInt -> SendWrapper SharedInt
71+toSendable = unsafeWrap
72+
73+-- | Unwrap after receiving on a worker thread.
74+fromSendable :: SendWrapper SharedInt -> SharedInt
75+fromSendable = WB.unwrap
src/Yoga/Om/WorkerBees/SharedMap.jsnew file+37
@@ -0,0 +1,37 @@
1+const encoder = new TextEncoder();
2+const decoder = new TextDecoder();
3+
4+// DJB2 hash: string -> segment index
5+export const hashKeyImpl = (key, numSegments) => {
6+ let hash = 5381;
7+ for (let i = 0; i < key.length; i++) {
8+ hash = ((hash << 5) + hash + key.charCodeAt(i)) | 0;
9+ }
10+ return (hash >>> 0) % numSegments;
11+};
12+
13+// Write JSON string at a byte offset in the SharedArrayBuffer
14+// Returns the byte length written
15+export const writeSegmentDataImpl = (sab, byteOffset, json) => {
16+ const bytes = encoder.encode(json);
17+ const target = new Uint8Array(sab, byteOffset, bytes.byteLength);
18+ target.set(bytes);
19+ return bytes.byteLength;
20+};
21+
22+// Read JSON string from a byte offset in the SharedArrayBuffer
23+export const readSegmentDataImpl = (sab, byteOffset, len) => {
24+ const source = new Uint8Array(sab, byteOffset, len);
25+ return decoder.decode(source);
26+};
27+
28+// Read an Int32 at a byte offset (for reading header in fromSendable)
29+export const readInt32Impl = (sab, byteOffset) => {
30+ const view = new Int32Array(sab, byteOffset, 1);
31+ return view[0];
32+};
33+
34+// Get UTF-8 byte length of a string (for overflow check before writing)
35+export const stringByteLengthImpl = (str) => {
36+ return encoder.encode(str).byteLength;
37+};
src/Yoga/Om/WorkerBees/SharedMap.pursnew file+191
@@ -0,0 +1,191 @@
1+module Yoga.Om.WorkerBees.SharedMap
2+ ( SharedMap
3+ , new
4+ , lookup
5+ , insert
6+ , delete
7+ , modify
8+ , toSendable
9+ , fromSendable
10+ ) where
11+
12+import Prelude
13+
14+import Data.Array as Array
15+import Data.ArrayBuffer.Types (Int32Array)
16+import Data.Function.Uncurried (Fn1, Fn2, runFn1, runFn2)
17+import Data.Maybe (Maybe(..))
18+import Data.Traversable (for)
19+import Data.Either (Either(..))
20+import Effect (Effect)
21+import Effect.Exception (throw, throwException, try)
22+import Effect.Uncurried (EffectFn2, EffectFn3, runEffectFn2, runEffectFn3)
23+import Foreign.Object as FObject
24+import Partial.Unsafe (unsafeCrashWith)
25+import Node.WorkerBees (SendWrapper, unsafeWrap)
26+import Node.WorkerBees as WB
27+import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON_, writeJSON)
28+import Yoga.Om.WorkerBees.Atomics as Atomics
29+import Yoga.Om.WorkerBees.SharedArrayBuffer as SAB
30+import Yoga.Om.WorkerBees.SharedArrayBuffer (SharedArrayBuffer)
31+
32+-- | Memory layout:
33+-- | Bytes 0-3: numSegments (Int32)
34+-- | Bytes 4-7: maxBytesPerSegment (Int32)
35+-- | Then N segments, each: [lock: 4B] [len: 4B] [JSON data: maxBytesPerSegment]
36+
37+type Segment =
38+ { lock :: Int32Array
39+ , lenSlot :: Int32Array
40+ , dataOffset :: Int
41+ }
42+
43+-- | A shared concurrent map backed by SharedArrayBuffer with striped locks.
44+-- | Keys are Strings, values must be JSON-serializable.
45+-- | The keyspace is split into N segments for N-way concurrency.
46+newtype SharedMap :: Type -> Type
47+newtype SharedMap a = SharedMap
48+ { sab :: SharedArrayBuffer
49+ , numSegments :: Int
50+ , maxBytesPerSegment :: Int
51+ , segments :: Array Segment
52+ }
53+
54+headerBytes :: Int
55+headerBytes = 8
56+
57+segmentHeaderBytes :: Int
58+segmentHeaderBytes = 8
59+
60+-- | Create a new empty SharedMap.
61+-- | `numSegments` controls concurrency (e.g., 16 or 64).
62+-- | `maxBytesPerSegment` is the max JSON size per segment.
63+new :: forall a. Int -> Int -> Effect (SharedMap a)
64+new numSegments maxBytesPerSegment = do
65+ let segmentSize = segmentHeaderBytes + maxBytesPerSegment
66+ let totalBytes = headerBytes + numSegments * segmentSize
67+ sab <- SAB.new totalBytes
68+ -- Write header (use Int32Array view over first 8 bytes)
69+ headerView <- SAB.toInt32ArraySlice sab 0 2
70+ _ <- Atomics.store headerView 0 numSegments
71+ _ <- Atomics.store headerView 1 maxBytesPerSegment
72+ -- Initialize segments with empty objects
73+ segments <- for (Array.range 0 (numSegments - 1)) \i -> do
74+ let base = headerBytes + i * segmentSize
75+ lock <- SAB.toInt32ArraySlice sab base 1
76+ lenSlot <- SAB.toInt32ArraySlice sab (base + 4) 1
77+ let dataOffset = base + segmentHeaderBytes
78+ let json = "{}"
79+ len <- runEffectFn3 writeSegmentDataImpl sab dataOffset json
80+ _ <- Atomics.store lenSlot 0 len
81+ pure { lock, lenSlot, dataOffset }
82+ pure (SharedMap { sab, numSegments, maxBytesPerSegment, segments })
83+
84+-- | Look up a key. Returns Nothing if not found.
85+lookup :: forall a. ReadForeign a => String -> SharedMap a -> Effect (Maybe a)
86+lookup key m = do
87+ let seg = segmentFor key m
88+ withLock seg do
89+ obj <- readSegment m seg
90+ pure (FObject.lookup key obj)
91+
92+-- | Insert or overwrite a key-value pair.
93+insert :: forall a. ReadForeign a => WriteForeign a => String -> a -> SharedMap a -> Effect Unit
94+insert key val m = do
95+ let seg = segmentFor key m
96+ withLock seg do
97+ obj <- readSegment m seg
98+ writeSegment m seg (FObject.insert key val obj)
99+
100+-- | Delete a key. No-op if key doesn't exist.
101+delete :: forall a. ReadForeign a => WriteForeign a => String -> SharedMap a -> Effect Unit
102+delete key m = do
103+ let seg = segmentFor key m
104+ withLock seg do
105+ obj <- readSegment m seg
106+ writeSegment m seg (FObject.delete key obj)
107+
108+-- | Atomically modify the value at a key. Returns the new value, or Nothing if not found.
109+modify :: forall a. ReadForeign a => WriteForeign a => String -> (a -> a) -> SharedMap a -> Effect (Maybe a)
110+modify key f m = do
111+ let seg = segmentFor key m
112+ withLock seg do
113+ obj <- readSegment m seg
114+ case FObject.lookup key obj of
115+ Nothing -> pure Nothing
116+ Just val -> do
117+ let next = f val
118+ writeSegment m seg (FObject.insert key next obj)
119+ pure (Just next)
120+
121+-- | Wrap for worker transfer.
122+toSendable :: forall a. SharedMap a -> SendWrapper (SharedMap a)
123+toSendable = unsafeWrap
124+
125+-- | Unwrap on the worker side. Reconstructs Int32Array views from the shared buffer.
126+fromSendable :: forall a. SendWrapper (SharedMap a) -> Effect (SharedMap a)
127+fromSendable sw = do
128+ let (SharedMap s) = WB.unwrap sw
129+ ns <- runEffectFn2 readInt32Impl s.sab 0
130+ mbs <- runEffectFn2 readInt32Impl s.sab 4
131+ let segmentSize = segmentHeaderBytes + mbs
132+ segments <- for (Array.range 0 (ns - 1)) \i -> do
133+ let base = headerBytes + i * segmentSize
134+ lock <- SAB.toInt32ArraySlice s.sab base 1
135+ lenSlot <- SAB.toInt32ArraySlice s.sab (base + 4) 1
136+ let dataOffset = base + segmentHeaderBytes
137+ pure { lock, lenSlot, dataOffset }
138+ pure (SharedMap { sab: s.sab, numSegments: ns, maxBytesPerSegment: mbs, segments })
139+
140+-- Internal helpers
141+
142+segmentFor :: forall a. String -> SharedMap a -> Segment
143+segmentFor key (SharedMap { numSegments, segments }) = do
144+ let idx = runFn2 hashKeyImpl key numSegments
145+ case Array.index segments idx of
146+ Just seg -> seg
147+ Nothing -> unsafeCrashWith "SharedMap: hash out of range"
148+
149+withLock :: forall a. Segment -> Effect a -> Effect a
150+withLock seg action = do
151+ acquireLock seg
152+ result <- try action
153+ releaseLock seg
154+ case result of
155+ Right val -> pure val
156+ Left err -> throwException err
157+
158+acquireLock :: Segment -> Effect Unit
159+acquireLock { lock } = go
160+ where
161+ go = do
162+ old <- Atomics.compareExchange lock 0 0 1
163+ when (old /= 0) go
164+
165+releaseLock :: Segment -> Effect Unit
166+releaseLock { lock } = void $ Atomics.store lock 0 0
167+
168+readSegment :: forall a. ReadForeign a => SharedMap a -> Segment -> Effect (FObject.Object a)
169+readSegment (SharedMap { sab }) { lenSlot, dataOffset } = do
170+ len <- Atomics.load lenSlot 0
171+ json <- runEffectFn3 readSegmentDataImpl sab dataOffset len
172+ case readJSON_ json of
173+ Just obj -> pure obj
174+ Nothing -> throw "SharedMap: failed to deserialize segment"
175+
176+writeSegment :: forall a. WriteForeign a => SharedMap a -> Segment -> FObject.Object a -> Effect Unit
177+writeSegment (SharedMap { sab, maxBytesPerSegment }) { lenSlot, dataOffset } obj = do
178+ let json = writeJSON obj
179+ let len = runFn1 stringByteLengthImpl json
180+ when (len > maxBytesPerSegment) do
181+ throw "SharedMap: segment data exceeds maxBytesPerSegment"
182+ _ <- runEffectFn3 writeSegmentDataImpl sab dataOffset json
183+ void $ Atomics.store lenSlot 0 len
184+
185+-- FFI
186+
187+foreign import hashKeyImpl :: Fn2 String Int Int
188+foreign import writeSegmentDataImpl :: EffectFn3 SharedArrayBuffer Int String Int
189+foreign import readSegmentDataImpl :: EffectFn3 SharedArrayBuffer Int Int String
190+foreign import readInt32Impl :: EffectFn2 SharedArrayBuffer Int Int
191+foreign import stringByteLengthImpl :: Fn1 String Int
src/Yoga/Om/WorkerBees/SharedState.jsnew file+16
@@ -0,0 +1,16 @@
1+const encoder = new TextEncoder();
2+const decoder = new TextDecoder();
3+
4+// Write a JSON string into the SharedArrayBuffer starting at byte offset 8
5+export const writeDataImpl = (sab, jsonStr) => {
6+ const bytes = encoder.encode(jsonStr);
7+ const target = new Uint8Array(sab, 8, bytes.byteLength);
8+ target.set(bytes);
9+ return bytes.byteLength;
10+};
11+
12+// Read a JSON string from the SharedArrayBuffer starting at byte offset 8
13+export const readDataImpl = (sab, byteLength) => {
14+ const source = new Uint8Array(sab, 8, byteLength);
15+ return decoder.decode(source);
16+};
src/Yoga/Om/WorkerBees/SharedState.pursnew file+124
@@ -0,0 +1,124 @@
1+module Yoga.Om.WorkerBees.SharedState
2+ ( SharedState
3+ , new
4+ , read
5+ , write
6+ , modify
7+ , toSendable
8+ , fromSendable
9+ ) where
10+
11+import Prelude
12+
13+import Data.ArrayBuffer.Types (Int32Array)
14+import Data.Either (Either(..))
15+import Data.Maybe (Maybe(..))
16+import Effect (Effect)
17+import Effect.Exception (throw, throwException, try)
18+import Effect.Uncurried (EffectFn2, runEffectFn2)
19+import Node.WorkerBees (SendWrapper, unsafeWrap)
20+import Node.WorkerBees as WB
21+import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON_, writeJSON)
22+import Yoga.Om.WorkerBees.Atomics as Atomics
23+import Yoga.Om.WorkerBees.SharedArrayBuffer as SAB
24+import Yoga.Om.WorkerBees.SharedArrayBuffer (SharedArrayBuffer)
25+
26+-- | Buffer layout:
27+-- | - Int32 slot 0 (bytes 0..3): mutex lock (0 = unlocked, 1 = locked)
28+-- | - Int32 slot 1 (bytes 4..7): data length in bytes
29+-- | - Bytes 8..N: UTF-8 encoded JSON string
30+
31+-- | A shared mutable state that can be accessed from multiple worker threads.
32+-- | Values are JSON-serialized into a SharedArrayBuffer with a spinlock mutex.
33+newtype SharedState :: Type -> Type
34+newtype SharedState a = SharedState
35+ { lock :: Int32Array
36+ , lenSlot :: Int32Array
37+ , sab :: SharedArrayBuffer
38+ , maxBytes :: Int
39+ }
40+
41+-- | Create a new SharedState with an initial value.
42+-- | `maxBytes` is the maximum size for the serialized JSON data.
43+new :: forall a. WriteForeign a => Int -> a -> Effect (SharedState a)
44+new maxBytes initial = do
45+ let totalBytes = 8 + maxBytes
46+ sab <- SAB.new totalBytes
47+ lock <- SAB.toInt32ArraySlice sab 0 1
48+ lenSlot <- SAB.toInt32ArraySlice sab 4 1
49+ let json = writeJSON initial
50+ len <- runEffectFn2 writeDataImpl sab json
51+ _ <- Atomics.store lenSlot 0 len
52+ pure (SharedState { lock, lenSlot, sab, maxBytes })
53+
54+-- | Atomically read the current state.
55+read :: forall a. ReadForeign a => SharedState a -> Effect a
56+read state = withLock state do
57+ readData state
58+
59+-- | Atomically write a new state value.
60+write :: forall a. WriteForeign a => SharedState a -> a -> Effect Unit
61+write state val = withLock state do
62+ writeData state val
63+
64+-- | Atomically modify the state. Returns the new value.
65+modify :: forall a. ReadForeign a => WriteForeign a => SharedState a -> (a -> a) -> Effect a
66+modify state f = withLock state do
67+ current <- readData state
68+ let next = f current
69+ writeData state next
70+ pure next
71+
72+-- | Wrap for worker transfer. The underlying SharedArrayBuffer is shared.
73+toSendable :: forall a. SharedState a -> SendWrapper (SharedState a)
74+toSendable = unsafeWrap
75+
76+-- | Unwrap on the worker side. Reconstructs Int32Array views from the shared buffer.
77+fromSendable :: forall a. SendWrapper (SharedState a) -> Effect (SharedState a)
78+fromSendable sw = do
79+ let (SharedState s) = WB.unwrap sw
80+ lock <- SAB.toInt32ArraySlice s.sab 0 1
81+ lenSlot <- SAB.toInt32ArraySlice s.sab 4 1
82+ pure (SharedState { lock, lenSlot, sab: s.sab, maxBytes: s.maxBytes })
83+
84+-- Internal: spinlock
85+
86+withLock :: forall a b. SharedState a -> Effect b -> Effect b
87+withLock state action = do
88+ acquireLock state
89+ result <- try action
90+ releaseLock state
91+ case result of
92+ Right val -> pure val
93+ Left err -> throwException err
94+
95+acquireLock :: forall a. SharedState a -> Effect Unit
96+acquireLock (SharedState { lock }) = go
97+ where
98+ go = do
99+ old <- Atomics.compareExchange lock 0 0 1
100+ when (old /= 0) go
101+
102+releaseLock :: forall a. SharedState a -> Effect Unit
103+releaseLock (SharedState { lock }) = void $ Atomics.store lock 0 0
104+
105+-- Internal: data read/write (must hold lock)
106+
107+writeData :: forall a. WriteForeign a => SharedState a -> a -> Effect Unit
108+writeData (SharedState { sab, lenSlot }) val = do
109+ let json = writeJSON val
110+ len <- runEffectFn2 writeDataImpl sab json
111+ void $ Atomics.store lenSlot 0 len
112+
113+readData :: forall a. ReadForeign a => SharedState a -> Effect a
114+readData (SharedState { sab, lenSlot }) = do
115+ len <- Atomics.load lenSlot 0
116+ json <- runEffectFn2 readDataImpl sab len
117+ case readJSON_ json of
118+ Just val -> pure val
119+ Nothing -> throw "SharedState: failed to deserialize"
120+
121+-- FFI
122+
123+foreign import writeDataImpl :: EffectFn2 SharedArrayBuffer String Int
124+foreign import readDataImpl :: EffectFn2 SharedArrayBuffer Int String