git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
52 files changed+1828−722
since splitsnapshots
unifiedsplit
.c8rc.json+22 .github/workflows/ci.yml+7−2 .gitignore+2 LICENSE+21 README.md+73−17 bun.lock+220−113 compile-fail-tests/ClientBadBodyType.purs+12 compile-fail-tests/ClientBadCurriedBodyType.purs+12 compile-fail-tests/ClientBadHeaderType.purs+13 compile-fail-tests/ClientBadOptionalQueryType.purs+13 compile-fail-tests/ClientBadPathType.purs+12 compile-fail-tests/ClientExtraBodyField.purs+12 compile-fail-tests/ClientExtraHeaderField.purs+13 compile-fail-tests/ClientExtraOptionalQueryField.purs+13 compile-fail-tests/ClientExtraPathField.purs+12 compile-fail-tests/ClientMissingBodyArgument.purs+12 compile-fail-tests/ClientMissingBodyField.purs+12 compile-fail-tests/ClientMissingHeaderArgument.purs+13 compile-fail-tests/ClientMissingHeaderField.purs+13 compile-fail-tests/ClientMissingPathField.purs+12 compile-fail-tests/ClientMissingRequiredQuery.purs+13 compile-fail-tests/ClientMissingThirdArgument.purs+13 compile-fail-tests/run.js+101 package.json+15−3 spago.lock+112−200 spago.yaml+19−14 src/Yoga/Fetch/Om.js+1 src/Yoga/Fetch/Om.purs+135−20 src/Yoga/Fetch/Om/BuildUrl.js+86−2 src/Yoga/Fetch/Om/BuildUrl.purs+43−13 src/Yoga/Fetch/Om/ClientFunction.purs+24−15 src/Yoga/Fetch/Om/MakeRequest.purs+6−13 src/Yoga/Fetch/Om/ParseResponse.purs+11−30 src/Yoga/Fetch/Om/QueryDefaults.purs−135 src/Yoga/Fetch/Om/ReadOptional.js+4−1 src/Yoga/Fetch/Om/ReadOptional.purs+3−2 src/Yoga/Fetch/Om/Simple.purs+6−15 src/Yoga/Fetch/Om/SplitResponses.purs+12−2 src/Yoga/Fetch/Om/StreamDecode.js+12−1 src/Yoga/Fetch/Om/StreamDecode.purs+43−2 test/BodylessResponse.Example.purs+1−1 test/BodylessResponse.Spec.purs+31−4 test/BuildUrl.Spec.purs+74−6 test/BuildUrlProperty.Spec.purs+256 test/Complete.Example.purs+14−19 test/Example.purs+13−8 test/MakeRequest.Spec.purs+15−5 test/PlainTextResponse.Spec.purs+49−8 test/Polymorphic.Spec.purs+20−17 test/Roundtrip.Spec.purs+144−54 test/StreamDecode.Spec.js+1 test/StreamDecode.Spec.purs+32
.c8rc.jsonnew file+22
@@ -0,0 +1,22 @@
1+{
2+ "all": true,
3+ "include": [
4+ "output/Yoga.Fetch.Om*/index.js",
5+ "output/Yoga.Fetch.Om*/foreign.js"
6+ ],
7+ "exclude": [
8+ "output/Yoga.Fetch.Om.ExtractParams/index.js",
9+ "output/Yoga.Fetch.Om.SplitResponses/index.js"
10+ ],
11+ "reporter": [
12+ "text",
13+ "html",
14+ "lcov"
15+ ],
16+ "reports-dir": "coverage",
17+ "check-coverage": true,
18+ "statements": 95,
19+ "branches": 90,
20+ "functions": 85,
21+ "lines": 95
22+}
.github/workflows/ci.yml+7−2
@@ -12,5 +12,10 @@ jobs:
1212 steps:
1313 - uses: actions/checkout@v4
1414 - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
15− - run: spago build
16− - run: spago test
15+ - run: bun run coverage
16+ - uses: actions/upload-artifact@v4
17+ if: always()
18+ with:
19+ name: coverage
20+ path: coverage/
21+ - run: bun compile-fail-tests/run.js
.gitignore+2
@@ -1,5 +1,7 @@
11output/
22output-compile-fail/
3+coverage/
4+generated-docs/
35node_modules/
46.spago/
57.psc-ide-port
LICENSEnew file+21
@@ -0,0 +1,21 @@
1+The MIT License (MIT)
2+
3+Copyright (c) 2026 Mark Eibes
4+
5+Permission is hereby granted, free of charge, to any person obtaining a copy
6+of this software and associated documentation files (the "Software"), to deal
7+in the Software without restriction, including without limitation the rights
8+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+copies of the Software, and to permit persons to whom the Software is
10+furnished to do so, subject to the following conditions:
11+
12+The above copyright notice and this permission notice shall be included in all
13+copies or substantial portions of the Software.
14+
15+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+SOFTWARE.
README.md+73−17
@@ -8,6 +8,34 @@ Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions
88spago install yoga-fetch-om
99```
1010
11+Optional query examples below use [`justifill`](https://pursuit.purescript.org/packages/purescript-justifill):
12+
13+```bash
14+spago install justifill
15+```
16+
17+## Migrating from 0.7
18+
19+Version 0.8 exposes optional query parameters as explicit `Maybe` fields. Calls that passed plain values no longer compile:
20+
21+```purescript
22+-- 0.7: offset omitted
23+users <- api.listUsers { limit: 10 }
24+
25+-- 0.8: every optional field is explicit
26+users <- api.listUsers { limit: Just 10, offset: Nothing }
27+allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
28+```
29+
30+Import `Maybe(..)` from `Data.Maybe` when constructing these records directly. Alternatively, [`justifill`](https://pursuit.purescript.org/packages/purescript-justifill) wraps supplied values in `Just` and fills omitted fields with `Nothing`:
31+
32+```purescript
33+-- offset remains omitted at the call site
34+users <- api.listUsers (justifill { limit: 10 })
35+```
36+
37+Path parameters and request bodies keep their existing argument shapes.
38+
1139## Quick Start
1240
1341### Example 1: Simple GET and POST Requests
@@ -30,8 +58,13 @@ main = do
3058### Example 2: Type-Safe API Client with Full CRUD
3159
3260```purescript
61+import Justifill (justifill)
62+
3363type UserAPI =
34− { getUser ::
64+ { health ::
65+ Route GET "health" {}
66+ ( ok :: { body :: { status :: String } } )
67+ , getUser ::
3568 Route GET ("users" / "id" : Int) {}
3669 ( ok :: { body :: User }
3770 , notFound :: { body :: ErrorMessage }
@@ -59,7 +92,8 @@ type UserAPI =
5992
6093api = client @UserAPI "https://api.example.com"
6194
62−-- Create a user
95+-- Generated methods use ordinary function application.
96+health <- api.health
6397user <- api.createUser { name: "Alice", email: "alice@example.com" }
6498 # handleErrors
6599 { badRequest: \err -> do
@@ -67,15 +101,21 @@ user <- api.createUser { name: "Alice", email: "alice@example.com" }
67101 throw err
68102 }
69103
70−-- Update with path param + body
71−updated <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
104+-- Path parameters and request bodies remain separate arguments.
105+updated <- api.updateUser
106+ { id: user.id }
107+ { name: "Alice Updated", email: user.email }
72108 # handleErrors
73109 { notFound: \_ -> throw userNotFound
74110 , badRequest: \err -> throw validationError
75111 }
76112
77−-- Query with pagination
78−users <- api.listUsers { limit: 10, offset: 0 }
113+-- Optional query parameters are explicit `Maybe` fields. Justifill can wrap
114+-- supplied values in `Just` and fill omitted fields with `Nothing`.
115+users <- api.listUsers (justifill { limit: 10 })
116+
117+-- The equivalent call without Justifill:
118+allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
79119```
80120
81121## More Examples
@@ -92,10 +132,10 @@ See the test files for complete, runnable examples with all imports:
92132
93133### ✅ Type-Safe Everything
94134
95−- **Path parameters**: `/users/:id` requires `{ id: Int }`
96−- **Query parameters**: Type-safe query strings with `?`
97−- **Request bodies**: Automatic JSON serialization
98−- **Response bodies**: Automatic JSON parsing
135+- **Path parameters**: `/users/:id` requires `{ id :: Int }`; names and values are percent-encoded
136+- **Query parameters**: Type-safe, percent-encoded query strings with explicit `Maybe` fields for optional values
137+- **Request bodies**: `JSON`, `PlainText`, and URL-encoded `FormData`
138+- **Response bodies**: Parsed JSON, `PlainText` as `String`, and `Streaming a` as `Strom {} () a`
99139- **Error handling**: Exhaustive pattern matching on variants
100140
101141### ✅ Single Source of Truth
@@ -115,12 +155,12 @@ Changes to routes automatically update both client and server!
115155### ✅ Automatic Derivation
116156
117157No manual client code:
118−- ✅ URL building with path parameter substitution
119−- ✅ Query string construction
120−- ✅ JSON request serialization
121−- ✅ JSON response parsing
158+- ✅ URL building with segment-safe path substitution
159+- ✅ Query construction that preserves existing queries and fragments
160+- ✅ JSON, plain-text, and URL-encoded request serialization
161+- ✅ JSON and plain-text response decoding
162+- ✅ Incremental UTF-8 and binary response streams
122163- ✅ Status code → variant mapping
123−- ✅ CORS and credentials handling
124164
125165### ✅ Integration with yoga-om
126166
@@ -140,8 +180,9 @@ The library uses PureScript's type system to:
140180
1411811. **Extract parameters** from route definitions at compile time
142182 - Path params: `"users" / "id" : Int` → `{ id :: Int }`
143− - Query params: `:? { limit :: Int }` → `{ limit :: Int }`
144− - Body params: `{ body :: JSON User }` → request body
183+ - Optional query params: `:? { limit :: Int }` → `{ limit :: Maybe Int }`
184+ - Required query params: `:? { limit :: Required Int }` → `{ limit :: Int }`
185+ - Body params: `{ body :: JSON User }` → `User`
145186
1461872. **Build URLs** automatically
147188 - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
@@ -186,6 +227,21 @@ api = client @UserAPI "https://api.example.com"
186227- ✅ Variant response handling
187228- ✅ Om monad integration
188229
230+## Testing
231+
232+```sh
233+bun test
234+bun run test:compile-fail
235+bun run coverage
236+```
237+The suite combines example-based specifications with QuickCheck properties for
238+URL encoding, suffix preservation, repeated parameters, optional fields, and
239+query-separator invariants.
240+
241+The coverage command uses compiler source maps to report PureScript source
242+coverage, includes JavaScript FFI modules, enforces regression thresholds, and
243+writes HTML and LCOV reports to `coverage/`.
244+
189245## License
190246
191247MIT
bun.lock+220−113
@@ -3,11 +3,24 @@
33 "workspaces": {
44 "": {
55 "name": "purescript-yoga-fetch-om",
6+ "dependencies": {
7+ "pino": "^10.3.1",
8+ },
69 "devDependencies": {
10+ "@fastify/cookie": "^11.1.2",
11+ "@fastify/cors": "^11.3.0",
12+ "@fastify/formbody": "^9.0.0",
13+ "@fastify/helmet": "^13.1.0",
14+ "@fastify/multipart": "^10.1.0",
15+ "@fastify/rate-limit": "^11.2.0",
16+ "@fastify/websocket": "^11.3.0",
17+ "argon2": "^0.45.1",
18+ "c8": "^12.0.0",
719 "fastify": "^5.7.4",
20+ "jose": "^6.2.8",
821 "jsdom": "^28.0.0",
922 "purescript": "^0.15.15",
10− "spago": "0.93.42",
23+ "spago": "^1.0.3",
1124 "vitest": "^2.1.8",
1225 },
1326 },
@@ -21,6 +34,8 @@
2134
2235 "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
2336
37+ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
38+
2439 "@csstools/color-helpers": ["@csstools/color-helpers@6.0.1", "", {}, "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ=="],
2540
2641 "@csstools/css-calc": ["@csstools/css-calc@3.0.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-q4d82GTl8BIlh/dTnVsWmxnbWJeb3kiU8eUH71UxlxnS+WIaALmtzTL8gR15PkYOexMQYVk0CO4qIG93C1IvPA=="],
@@ -33,6 +48,8 @@
3348
3449 "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
3550
51+ "@epic-web/invariant": ["@epic-web/invariant@1.0.0", "", {}, "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA=="],
52+
3653 "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
3754
3855 "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
@@ -83,37 +100,63 @@
83100
84101 "@fastify/ajv-compiler": ["@fastify/ajv-compiler@4.0.5", "", { "dependencies": { "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0" } }, "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A=="],
85102
103+ "@fastify/busboy": ["@fastify/busboy@3.2.1", "", {}, "sha512-tgK4O+57iz5ycYNGXE5ZWj1ES03lD2XnnBYWSbU/3wYZRMQzCUq7Ycds/RdyBZQeL5MU4fxBt6lzbIWf/Bickw=="],
104+
105+ "@fastify/cookie": ["@fastify/cookie@11.1.2", "", { "dependencies": { "cookie": "^2.0.0", "fastify-plugin": "^6.0.0" } }, "sha512-Dtrpk/YOGUsbRMvP/8ZqPpwnMRv0qSqodFdoQ2B589Obc7jw4s4Qla+cV72Bsm7WsZJnqlYFX/i7uSBq0xzg6g=="],
106+
107+ "@fastify/cors": ["@fastify/cors@11.3.0", "", { "dependencies": { "fastify-plugin": "^6.0.0", "toad-cache": "^3.7.0" } }, "sha512-ggQGua+xHv1MvePbPr0v//xLYEsCXbWspquXCJS9Ot5YoRXq8J8ZWzHnxDBVnbtXosvistXo6LtNzOJswf64Fw=="],
108+
109+ "@fastify/deepmerge": ["@fastify/deepmerge@3.2.1", "", {}, "sha512-N5Oqvltoa2r9z1tbx4xjky0oRR60v+T47Ic4J1ukoVQcptLOrIdRnCSdTGmOmajZuHVKlTnfcmrjyqsGEW1ztA=="],
110+
86111 "@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
87112
88113 "@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@5.0.3", "", { "dependencies": { "fast-json-stringify": "^6.0.0" } }, "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ=="],
89114
115+ "@fastify/formbody": ["@fastify/formbody@9.0.0", "", { "dependencies": { "fast-querystring": "^1.1.2", "fastify-plugin": "^6.0.0" } }, "sha512-T/af26CSrUARBCvsEmv+DJLPfZlrRKESzqironxP1j7qzuLyKcoZtj6MuTGShuKx1THXugoie2oFbUJxXfGFzA=="],
116+
90117 "@fastify/forwarded": ["@fastify/forwarded@3.0.1", "", {}, "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw=="],
91118
119+ "@fastify/helmet": ["@fastify/helmet@13.1.0", "", { "dependencies": { "fastify-plugin": "^6.0.0", "helmet": "^8.0.0" } }, "sha512-SvVOU0IrzYJW1BvSkfq9G1WUdW3dnaRUvg6m0BtgGMBmML62No0VmSu087jecH58SFbicbREgZTPJ89mAguupA=="],
120+
92121 "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
93122
123+ "@fastify/multipart": ["@fastify/multipart@10.1.0", "", { "dependencies": { "@fastify/busboy": "^3.0.0", "@fastify/deepmerge": "^3.0.0", "@fastify/error": "^4.0.0", "fastify-plugin": "^6.0.0", "secure-json-parse": "^4.0.0" } }, "sha512-b2r6CovmLQvLFJ5HJDtxVigZjcO9TwkRGslhiNQxsGJwilm5B3eqvXPOVLudC44zXMXJITpQ/iEATIE7zoXqcQ=="],
124+
94125 "@fastify/proxy-addr": ["@fastify/proxy-addr@5.1.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw=="],
95126
127+ "@fastify/rate-limit": ["@fastify/rate-limit@11.2.0", "", { "dependencies": { "@lukeed/ms": "^2.0.2", "fastify-plugin": "^6.0.0", "ip-address": "^10.2.0", "toad-cache": "^3.7.0" } }, "sha512-X7osJd4XSvMoejYrnJkSZYYjY1eNYoBqhjlzf1RakC2204qExFqZFTKj5+T7VuzA/iUI9Z3UoSqQRkB2HpG0oQ=="],
128+
129+ "@fastify/websocket": ["@fastify/websocket@11.3.0", "", { "dependencies": { "duplexify": "^4.1.3", "fastify-plugin": "^6.0.0", "ws": "^8.16.0" } }, "sha512-g89ag4BCcD9YP5wBZXixzoLnuf5j89p/sXFcfpCiv2pdEkYYukBEoK3heVzqsp0EAtszVDc2BBZG0KZqeAShIA=="],
130+
96131 "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
97132
98− "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
133+ "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
99134
100135 "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
101136
137+ "@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="],
138+
139+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
140+
102141 "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
103142
104− "@nodelib/fs.scandir": ["@nodelib/fs.scandir@3.0.0", "", { "dependencies": { "@nodelib/fs.stat": "3.0.0", "run-parallel": "^1.2.0" } }, "sha512-ktI9+PxfHYtKjF3cLTUAh2N+b8MijCRPNwKJNqTVdL0gB0QxLU2rIRaZ1t71oEa3YBDE6bukH1sR0+CDnpp/Mg=="],
143+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
144+
145+ "@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
105146
106− "@nodelib/fs.stat": ["@nodelib/fs.stat@3.0.0", "", {}, "sha512-2tQOI38s19P9i7X/Drt0v8iMA+KMsgdhB/dyPER+e+2Y8L1Z7QvnuRdW/uLuf5YRFUYmnj4bMA6qCuZHFI1GDQ=="],
147+ "@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=="],
107148
108− "@nodelib/fs.walk": ["@nodelib/fs.walk@2.0.0", "", { "dependencies": { "@nodelib/fs.scandir": "3.0.0", "fastq": "^1.15.0" } }, "sha512-54voNDBobGdMl3BUXSu7UaDh1P85PGHWlJ5e0XhPugo1JulOyCtp2I+5ri4wplGDJ8QGwPEQW7/x3yTLU7yF1A=="],
149+ "@nodelib/fs.stat": ["@nodelib/fs.stat@4.0.0", "", {}, "sha512-ctr6bByzksKRCV0bavi8WoQevU6plSp2IkllIsEqaiKe2mwNNnaluhnRhcsgGZHrrHk57B3lf95MkLMO3STYcg=="],
150+
151+ "@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=="],
109152
110153 "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="],
111154
112155 "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
113156
114− "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
157+ "@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
115158
116− "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
159+ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
117160
118161 "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
119162
@@ -169,6 +212,8 @@
169212
170213 "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
171214
215+ "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
216+
172217 "@vitest/expect": ["@vitest/expect@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw=="],
173218
174219 "@vitest/mocker": ["@vitest/mocker@2.1.9", "", { "dependencies": { "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.12" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg=="],
@@ -197,12 +242,14 @@
197242
198243 "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
199244
200− "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
245+ "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
201246
202247 "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
203248
204249 "aproba": ["aproba@1.2.0", "", {}, "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="],
205250
251+ "argon2": ["argon2@0.45.1", "", { "dependencies": { "@phc/format": "^1.0.0", "cross-env": "^10.1.0", "node-addon-api": "^8.9.0", "node-gyp-build": "^4.8.4" } }, "sha512-skm+/WCjkGqCQxF7FG1LuZXM5yvbFjgbfiCGsud2oLgaDhh6b6dbH0b1EkghbM+xx4Bj8Ape+KKgixoIlWZicQ=="],
252+
206253 "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
207254
208255 "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
@@ -215,28 +262,18 @@
215262
216263 "avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
217264
218− "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
219−
220− "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
265+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
221266
222267 "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
223268
224− "better-sqlite3": ["better-sqlite3@11.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ=="],
225−
226269 "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
227270
228− "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
229−
230− "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
231−
232271 "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
233272
234− "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
273+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
235274
236275 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
237276
238− "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
239−
240277 "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
241278
242279 "buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
@@ -245,6 +282,8 @@
245282
246283 "byline": ["byline@5.0.0", "", {}, "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q=="],
247284
285+ "c8": ["c8@12.0.0", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", "test-exclude": "^8.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^18.0.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "monocart-coverage-reports": "^2" }, "optionalPeers": ["monocart-coverage-reports"], "bin": { "c8": "bin/c8.js" } }, "sha512-4zpJvrd1nKWutnnKC2pXkFmb6iM1l+ffN//o1CzlTNwW7GSOs9a1xrLqkC48nU8oEkjmPZLPiwMsIaOvoF4Pqg=="],
286+
248287 "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
249288
250289 "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=="],
@@ -261,6 +300,8 @@
261300
262301 "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
263302
303+ "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
304+
264305 "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
265306
266307 "color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
@@ -269,7 +310,9 @@
269310
270311 "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=="],
271312
272− "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
313+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
314+
315+ "cookie": ["cookie@2.0.1", "", {}, "sha512-yuToqVvRrj6pfDXREyQAAv8SkAEk/8GS3jQRTiUMm66TVtBYmqQeoEjL2Lmq8Rpo6271vH76InTChTitEAm65w=="],
273316
274317 "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=="],
275318
@@ -277,6 +320,8 @@
277320
278321 "cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
279322
323+ "cross-env": ["cross-env@10.1.0", "", { "dependencies": { "@epic-web/invariant": "^1.0.0", "cross-spawn": "^7.0.6" }, "bin": { "cross-env": "dist/bin/cross-env.js", "cross-env-shell": "dist/bin/cross-env-shell.js" } }, "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw=="],
324+
280325 "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
281326
282327 "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
@@ -291,12 +336,8 @@
291336
292337 "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
293338
294− "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
295−
296339 "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
297340
298− "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
299−
300341 "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
301342
302343 "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
@@ -305,13 +346,9 @@
305346
306347 "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
307348
308− "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
309−
310− "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=="],
349+ "duplexify": ["duplexify@4.1.3", "", { "dependencies": { "end-of-stream": "^1.4.1", "inherits": "^2.0.3", "readable-stream": "^3.1.1", "stream-shift": "^1.0.2" } }, "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA=="],
311350
312− "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
313−
314− "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
351+ "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
315352
316353 "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
317354
@@ -327,14 +364,14 @@
327364
328365 "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
329366
367+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
368+
330369 "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
331370
332371 "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
333372
334373 "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=="],
335374
336− "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
337−
338375 "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
339376
340377 "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
@@ -349,26 +386,26 @@
349386
350387 "fastify": ["fastify@5.7.4", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^6.0.0", "find-my-way": "^9.0.0", "light-my-request": "^6.0.0", "pino": "^10.1.0", "process-warning": "^5.0.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA=="],
351388
389+ "fastify-plugin": ["fastify-plugin@6.0.0", "", {}, "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg=="],
390+
352391 "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
353392
354393 "figgy-pudding": ["figgy-pudding@3.5.2", "", {}, "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="],
355394
356− "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
357−
358395 "filesize": ["filesize@4.2.1", "", {}, "sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA=="],
359396
360397 "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
361398
362399 "find-my-way": ["find-my-way@9.4.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^5.0.0" } }, "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w=="],
363400
401+ "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
402+
364403 "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=="],
365404
366405 "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
367406
368407 "from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="],
369408
370− "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
371−
372409 "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=="],
373410
374411 "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
@@ -381,18 +418,24 @@
381418
382419 "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
383420
384− "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
421+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
385422
386− "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
423+ "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
424+
425+ "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
387426
388− "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
427+ "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=="],
389428
390429 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
391430
392− "has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
431+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
432+
433+ "helmet": ["helmet@8.3.0", "", {}, "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w=="],
393434
394435 "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
395436
437+ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
438+
396439 "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
397440
398441 "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
@@ -403,8 +446,6 @@
403446
404447 "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
405448
406− "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
407−
408449 "iferr": ["iferr@0.1.5", "", {}, "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA=="],
409450
410451 "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
@@ -417,9 +458,7 @@
417458
418459 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
419460
420− "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
421−
422− "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
461+ "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="],
423462
424463 "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
425464
@@ -445,7 +484,15 @@
445484
446485 "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
447486
448− "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
487+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
488+
489+ "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
490+
491+ "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="],
492+
493+ "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
494+
495+ "jose": ["jose@6.2.8", "", {}, "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ=="],
449496
450497 "jsdom": ["jsdom@28.0.0", "", { "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", "@exodus/bytes": "^1.11.0", "cssstyle": "^5.3.7", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "undici": "^7.20.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA=="],
451498
@@ -459,6 +506,8 @@
459506
460507 "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
461508
509+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
510+
462511 "log-symbols": ["log-symbols@3.0.0", "", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="],
463512
464513 "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=="],
@@ -469,6 +518,8 @@
469518
470519 "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
471520
521+ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
522+
472523 "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=="],
473524
474525 "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=="],
@@ -483,9 +534,7 @@
483534
484535 "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
485536
486− "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
487−
488− "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
537+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
489538
490539 "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
491540
@@ -507,8 +556,6 @@
507556
508557 "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
509558
510− "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
511−
512559 "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=="],
513560
514561 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@@ -517,11 +564,11 @@
517564
518565 "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
519566
520− "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
521−
522567 "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
523568
524− "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="],
569+ "node-addon-api": ["node-addon-api@8.9.2", "", {}, "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg=="],
570+
571+ "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
525572
526573 "npm-run-path": ["npm-run-path@3.1.0", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg=="],
527574
@@ -535,6 +582,10 @@
535582
536583 "p-finally": ["p-finally@2.0.1", "", {}, "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="],
537584
585+ "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
586+
587+ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
588+
538589 "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
539590
540591 "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
@@ -543,11 +594,13 @@
543594
544595 "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
545596
597+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
598+
546599 "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
547600
548601 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
549602
550− "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
603+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
551604
552605 "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
553606
@@ -565,8 +618,6 @@
565618
566619 "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
567620
568− "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=="],
569−
570621 "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
571622
572623 "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
@@ -591,9 +642,7 @@
591642
592643 "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
593644
594− "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=="],
595−
596− "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=="],
645+ "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=="],
597646
598647 "readline-sync": ["readline-sync@1.4.10", "", {}, "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw=="],
599648
@@ -643,11 +692,7 @@
643692
644693 "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
645694
646− "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
647−
648− "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
649−
650− "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=="],
695+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
651696
652697 "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=="],
653698
@@ -661,7 +706,7 @@
661706
662707 "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
663708
664− "spago": ["spago@0.93.42", "", { "dependencies": { "@nodelib/fs.walk": "^2.0.0", "better-sqlite3": "^11.0.0", "env-paths": "^3.0.0", "fs-extra": "^11.2.0", "fuse.js": "^7.0.0", "glob": "^10.4.1", "markdown-it": "^14.1.0", "micromatch": "^4.0.7", "open": "^10.1.0", "picomatch": "^4.0.2", "punycode": "^2.3.1", "readline-sync": "^1.4.10", "semver": "^7.6.2", "spdx-expression-parse": "^4.0.0", "ssh2": "^1.15.0", "supports-color": "^9.4.0", "tar": "^7.2.0", "tmp": "^0.2.3", "xhr2": "^0.2.1", "yaml": "^2.4.5" }, "bin": { "spago": "bin/bundle.js" } }, "sha512-R8/CCfR3jtbN3NFZOso+sBTKsaGDu96j8X55JA8CDx12GyB3f64wpwbIXKJ8Y+E4UMjYWhTB2vkP+kqf4R5Y5w=="],
709+ "spago": ["spago@1.0.4", "", { "dependencies": { "@nodelib/fs.walk": "^3.0.1", "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-iv1HBJppzGJRWWd/DZ32oselcKrhq/PboBdXy5VMX4GiwRqLCUUp3loZ6Yhm8zymNRKHkN/w52cxX6k521nwng=="],
665710
666711 "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
667712
@@ -683,29 +728,21 @@
683728
684729 "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
685730
686− "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=="],
731+ "string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
687732
688− "string-width-cjs": ["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=="],
733+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
689734
690− "string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
691−
692− "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
693−
694− "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
735+ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
695736
696737 "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
697738
698− "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
699−
700− "supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="],
739+ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
701740
702741 "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
703742
704743 "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=="],
705744
706− "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=="],
707−
708− "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=="],
745+ "test-exclude": ["test-exclude@8.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^13.0.6", "minimatch": "^10.2.2" } }, "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ=="],
709746
710747 "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="],
711748
@@ -735,8 +772,6 @@
735772
736773 "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
737774
738− "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
739−
740775 "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
741776
742777 "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
@@ -755,6 +790,8 @@
755790
756791 "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
757792
793+ "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
794+
758795 "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
759796
760797 "vite-node": ["vite-node@2.1.9", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA=="],
@@ -773,12 +810,12 @@
773810
774811 "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
775812
776− "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=="],
777−
778− "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
813+ "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
779814
780815 "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
781816
817+ "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
818+
782819 "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
783820
784821 "xhr2": ["xhr2@0.2.1", "", {}, "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw=="],
@@ -789,42 +826,58 @@
789826
790827 "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
791828
792− "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
829+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
793830
794831 "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
795832
796833 "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
797834
798− "zen-observable": ["zen-observable@0.8.15", "", {}, "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="],
835+ "yargs": ["yargs@18.1.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^8.2.1", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg=="],
799836
800− "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
837+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
801838
802− "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
839+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
803840
804− "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
841+ "zen-observable": ["zen-observable@0.8.15", "", {}, "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="],
805842
806843 "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
807844
808845 "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
809846
810− "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=="],
811−
812847 "cacache/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
813848
814849 "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=="],
815850
816851 "cacache/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
817852
853+ "cacache/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
854+
818855 "chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
819856
857+ "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
858+
859+ "concat-stream/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=="],
860+
820861 "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
821862
822− "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
863+ "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
864+
865+ "flush-write-stream/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=="],
866+
867+ "from2/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=="],
823868
824869 "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
825870
871+ "fs-write-stream-atomic/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=="],
872+
873+ "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
874+
875+ "light-my-request/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
876+
826877 "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
827878
879+ "log-update/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=="],
880+
828881 "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=="],
829882
830883 "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
@@ -853,7 +906,11 @@
853906
854907 "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
855908
856− "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
909+ "mississippi/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=="],
910+
911+ "parallel-transform/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=="],
912+
913+ "pumpify/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=="],
857914
858915 "pumpify/pump": ["pump@2.0.1", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA=="],
859916
@@ -861,40 +918,58 @@
861918
862919 "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=="],
863920
864− "readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
921+ "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
865922
866923 "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=="],
867924
868925 "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
869926
927+ "socks/ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
928+
870929 "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
871930
872− "string_decoder/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
931+ "test-exclude/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
873932
874− "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
933+ "through2/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=="],
875934
876− "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=="],
935+ "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
877936
878− "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
937+ "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
879938
880− "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
939+ "yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
881940
882− "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
941+ "@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=="],
883942
884− "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
943+ "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
885944
886− "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
945+ "cacache/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
887946
888− "@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=="],
947+ "chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
889948
890− "bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
949+ "concat-stream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
891950
892− "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
951+ "concat-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
893952
894− "cacache/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
953+ "flush-write-stream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
954+
955+ "flush-write-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
956+
957+ "from2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
958+
959+ "from2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
895960
896961 "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
897962
963+ "fs-write-stream-atomic/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
964+
965+ "fs-write-stream-atomic/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
966+
967+ "log-update/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
968+
969+ "log-update/wrap-ansi/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=="],
970+
971+ "log-update/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
972+
898973 "make-fetch-happen/cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
899974
900975 "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=="],
@@ -925,6 +1000,14 @@
9251000
9261001 "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
9271002
1003+ "mississippi/duplexify/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=="],
1004+
1005+ "parallel-transform/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
1006+
1007+ "parallel-transform/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
1008+
1009+ "pumpify/duplexify/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=="],
1010+
9281011 "purescript-installer/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
9291012
9301013 "purescript-installer/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
@@ -939,16 +1022,22 @@
9391022
9401023 "slice-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
9411024
942− "tar-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
1025+ "test-exclude/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
9431026
944− "wrap-ansi-cjs/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
1027+ "through2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
9451028
946− "wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
1029+ "through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
9471030
9481031 "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
9491032
9501033 "cacache/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
9511034
1035+ "log-update/wrap-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
1036+
1037+ "log-update/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
1038+
1039+ "log-update/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
1040+
9521041 "make-fetch-happen/cacache/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
9531042
9541043 "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=="],
@@ -961,22 +1050,40 @@
9611050
9621051 "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=="],
9631052
1053+ "mississippi/duplexify/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
1054+
1055+ "mississippi/duplexify/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
1056+
1057+ "pumpify/duplexify/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
1058+
1059+ "pumpify/duplexify/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
1060+
9641061 "purescript-installer/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
9651062
9661063 "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
9671064
9681065 "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
9691066
970− "wrap-ansi-cjs/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
1067+ "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
9711068
972− "wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
1069+ "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
9731070
974− "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1071+ "log-update/wrap-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
1072+
1073+ "make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
9751074
9761075 "make-fetch-happen/cacache/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
9771076
9781077 "make-fetch-happen/cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
9791078
1079+ "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
1080+
1081+ "@npmcli/move-file/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
1082+
1083+ "make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
1084+
9801085 "make-fetch-happen/cacache/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1086+
1087+ "make-fetch-happen/cacache/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
9811088 }
9821089}
compile-fail-tests/ClientBadBodyType.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: String
2+module CompileFail.ClientBadBodyType where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client)
5+
6+type UserApi =
7+ { createUser :: Route POST "users" { body :: JSON { name :: String } } (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.createUser { name: 42 }
compile-fail-tests/ClientBadCurriedBodyType.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: String
2+module CompileFail.ClientBadCurriedBodyType where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client, type (/), type (:))
5+
6+type UserApi =
7+ { updateUser :: Route POST ("users" / "id" : Int) { body :: JSON { name :: String } } (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.updateUser { id: 1 } { name: 42 }
compile-fail-tests/ClientBadHeaderType.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: BearerToken
2+module CompileFail.ClientBadHeaderType where
3+
4+import Yoga.Fetch.Om (GET, Route, client)
5+import Yoga.HTTP.API.Route (BearerToken)
6+
7+type UserApi =
8+ { me :: Route GET "me" { headers :: { authorization :: BearerToken } } (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.me { authorization: "secret" }
compile-fail-tests/ClientBadOptionalQueryType.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: limit
2+module CompileFail.ClientBadOptionalQueryType where
3+
4+import Justifill (justifill)
5+import Yoga.Fetch.Om (GET, Route, client, type (:?))
6+
7+type UserApi =
8+ { listUsers :: Route GET ("users" :? { limit :: Int, offset :: Int }) {} (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.listUsers (justifill { limit: "10" })
compile-fail-tests/ClientBadPathType.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: Int
2+module CompileFail.ClientBadPathType where
3+
4+import Yoga.Fetch.Om (GET, Route, client, type (/), type (:))
5+
6+type UserApi =
7+ { getUser :: Route GET ("users" / "id" : Int) {} (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.getUser { id: "1" }
compile-fail-tests/ClientExtraBodyField.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: extra
2+module CompileFail.ClientExtraBodyField where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client)
5+
6+type UserApi =
7+ { createUser :: Route POST "users" { body :: JSON { name :: String } } (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.createUser { name: "Alice", extra: true }
compile-fail-tests/ClientExtraHeaderField.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: extra
2+module CompileFail.ClientExtraHeaderField where
3+
4+import Yoga.Fetch.Om (GET, Route, client)
5+import Yoga.HTTP.API.Route (BearerToken(..))
6+
7+type UserApi =
8+ { me :: Route GET "me" { headers :: { authorization :: BearerToken } } (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.me { authorization: BearerToken "secret", extra: true }
compile-fail-tests/ClientExtraOptionalQueryField.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: extra
2+module CompileFail.ClientExtraOptionalQueryField where
3+
4+import Justifill (justifill)
5+import Yoga.Fetch.Om (GET, Route, client, type (:?))
6+
7+type UserApi =
8+ { listUsers :: Route GET ("users" :? { limit :: Int, offset :: Int }) {} (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.listUsers (justifill { limit: 1, extra: "nope" })
compile-fail-tests/ClientExtraPathField.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: extra
2+module CompileFail.ClientExtraPathField where
3+
4+import Yoga.Fetch.Om (GET, Route, client, type (/), type (:))
5+
6+type UserApi =
7+ { getUser :: Route GET ("users" / "id" : Int) {} (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.getUser { id: 1, extra: true }
compile-fail-tests/ClientMissingBodyArgument.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: Function
2+module CompileFail.ClientMissingBodyArgument where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client, type (/), type (:))
5+import Yoga.Om (Om)
6+
7+type UserApi =
8+ { updateUser :: Route POST ("users" / "id" : Int) { body :: JSON { token :: String } } (ok :: { body :: {} })
9+ }
10+
11+api :: forall ctx err. { updateUser :: { id :: Int } -> Om { | ctx } err {} }
12+api = client @UserApi ""
compile-fail-tests/ClientMissingBodyField.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: name :: String
2+module CompileFail.ClientMissingBodyField where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client)
5+
6+type UserApi =
7+ { createUser :: Route POST "users" { body :: JSON { name :: String, age :: Int } } (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.createUser { age: 42 }
compile-fail-tests/ClientMissingHeaderArgument.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: Function
2+module CompileFail.ClientMissingHeaderArgument where
3+
4+import Yoga.Fetch.Om (GET, Route, client, type (/), type (:))
5+import Yoga.HTTP.API.Route (BearerToken)
6+import Yoga.Om (Om)
7+
8+type UserApi =
9+ { getUser :: Route GET ("users" / "id" : Int) { headers :: { authorization :: BearerToken } } (ok :: { body :: {} })
10+ }
11+
12+api :: forall ctx err. { getUser :: { id :: Int } -> Om { | ctx } err {} }
13+api = client @UserApi ""
compile-fail-tests/ClientMissingHeaderField.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: authorization :: BearerToken
2+module CompileFail.ClientMissingHeaderField where
3+
4+import Yoga.Fetch.Om (GET, Route, client)
5+import Yoga.HTTP.API.Route (BearerToken)
6+
7+type UserApi =
8+ { me :: Route GET "me" { headers :: { authorization :: BearerToken } } (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.me {}
compile-fail-tests/ClientMissingPathField.pursnew file+12
@@ -0,0 +1,12 @@
1+-- EXPECT: id :: Int
2+module CompileFail.ClientMissingPathField where
3+
4+import Yoga.Fetch.Om (GET, Route, client, type (/), type (:))
5+
6+type UserApi =
7+ { getUser :: Route GET ("users" / "id" : Int) {} (ok :: { body :: {} })
8+ }
9+
10+api = client @UserApi ""
11+
12+bad = api.getUser {}
compile-fail-tests/ClientMissingRequiredQuery.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: id :: Int
2+module CompileFail.ClientMissingRequiredQuery where
3+
4+import Justifill (justifill)
5+import Yoga.Fetch.Om (GET, Required, Route, client, type (:?))
6+
7+type UserApi =
8+ { getUser :: Route GET ("users" :? { id :: Required Int, token :: Required String }) {} (ok :: { body :: {} })
9+ }
10+
11+api = client @UserApi ""
12+
13+bad = api.getUser (justifill { token: "secret" })
compile-fail-tests/ClientMissingThirdArgument.pursnew file+13
@@ -0,0 +1,13 @@
1+-- EXPECT: Function
2+module CompileFail.ClientMissingThirdArgument where
3+
4+import Yoga.Fetch.Om (JSON, POST, Route, client, type (/), type (:))
5+import Yoga.HTTP.API.Route (BearerToken)
6+import Yoga.Om (Om)
7+
8+type UserApi =
9+ { updateUser :: Route POST ("users" / "id" : Int) { headers :: { authorization :: BearerToken }, body :: JSON { token :: String } } (ok :: { body :: {} })
10+ }
11+
12+api :: forall ctx err. { updateUser :: { id :: Int } -> { authorization :: BearerToken } -> Om { | ctx } err {} }
13+api = client @UserApi ""
compile-fail-tests/run.jsnew file+101
@@ -0,0 +1,101 @@
1+import { mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
2+import { tmpdir } from "node:os";
3+import { basename, join } from "node:path";
4+
5+const cwd = new URL("..", import.meta.url).pathname;
6+const casesDir = join(cwd, "compile-fail-tests");
7+const decoder = new TextDecoder();
8+
9+function run(args, options = {}) {
10+ const proc = Bun.spawnSync(args, {
11+ cwd,
12+ stdout: options.stdout ?? "pipe",
13+ stderr: options.stderr ?? "pipe",
14+ });
15+ return {
16+ code: proc.exitCode,
17+ stdout: proc.stdout ? decoder.decode(proc.stdout) : "",
18+ stderr: proc.stderr ? decoder.decode(proc.stderr) : "",
19+ };
20+}
21+
22+function printResult(label, result) {
23+ console.error(`\n${label} failed with exit code ${result.code}`);
24+ if (result.stdout) console.error(result.stdout);
25+ if (result.stderr) console.error(result.stderr);
26+}
27+
28+function expectedSubstring(file) {
29+ const firstLine = readFileSync(file, "utf8").split("\n", 1)[0];
30+ return firstLine.startsWith("-- EXPECT: ") ? firstLine.slice("-- EXPECT: ".length) : null;
31+}
32+
33+const sourcesResult = run(["bunx", "spago", "sources", "--quiet", "--json"]);
34+if (sourcesResult.code !== 0) {
35+ printResult("spago sources", sourcesResult);
36+ process.exit(1);
37+}
38+
39+const excludedSourceGlobs = new Set([
40+ "test/**/*.purs",
41+ "../purescript-yoga-fastify-om/src/**/*.purs",
42+ "../purescript-yoga-fastify-om/local-packages/yoga-fastify/src/**/*.purs",
43+]);
44+
45+const librarySources = JSON.parse(sourcesResult.stdout)
46+ .filter((source) => !excludedSourceGlobs.has(source));
47+
48+const cases = readdirSync(casesDir)
49+ .filter((file) => file.endsWith(".purs"))
50+ .sort()
51+ .map((file) => join("compile-fail-tests", file));
52+
53+const libOutput = mkdtempSync(join(tmpdir(), "yoga-fetch-om-lib-"));
54+
55+try {
56+ const libResult = run(["bunx", "purs", "compile", ...librarySources, "--output", libOutput], { stdout: "ignore" });
57+ if (libResult.code !== 0) {
58+ printResult("library compile", libResult);
59+ process.exit(1);
60+ }
61+
62+ let failed = 0;
63+
64+ for (const testCase of cases) {
65+ const failOutput = mkdtempSync(join(tmpdir(), "yoga-fetch-om-compile-fail-"));
66+ try {
67+ const result = run([
68+ "bunx",
69+ "purs",
70+ "compile",
71+ ...librarySources,
72+ testCase,
73+ "--output",
74+ failOutput,
75+ ]);
76+
77+ const output = `${result.stdout}\n${result.stderr}`;
78+ const expected = expectedSubstring(join(cwd, testCase));
79+ const name = basename(testCase, ".purs");
80+
81+ if (result.code === 0) {
82+ console.error(`FAIL ${name} - compiled successfully (should have failed)`);
83+ failed++;
84+ } else if (expected && !output.includes(expected)) {
85+ printResult(`FAIL ${name}`, result);
86+ console.error(`Expected failure to contain: ${expected}`);
87+ failed++;
88+ } else {
89+ console.log(`PASS ${name}`);
90+ }
91+ } finally {
92+ rmSync(failOutput, { recursive: true, force: true });
93+ }
94+ }
95+
96+ if (failed > 0) {
97+ process.exit(1);
98+ }
99+} finally {
100+ rmSync(libOutput, { recursive: true, force: true });
101+}
package.json+15−3
@@ -1,12 +1,14 @@
11{
22 "name": "purescript-yoga-fetch-om",
3− "version": "0.4.0",
3+ "version": "0.8.1",
44 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
55 "type": "module",
66 "scripts": {
77 "build": "spago build",
88 "test": "spago test",
9− "clean": "rm -rf output .spago",
9+ "coverage": "spago build && c8 spago test",
10+ "test:compile-fail": "bun compile-fail-tests/run.js",
11+ "clean": "rm -rf output .spago coverage",
1012 "watch": "spago build --watch",
1113 "repl": "spago repl",
1214 "docs": "spago docs",
@@ -24,10 +26,20 @@
2426 "author": "",
2527 "license": "MIT",
2628 "devDependencies": {
29+ "@fastify/cookie": "^11.1.2",
30+ "@fastify/cors": "^11.3.0",
31+ "@fastify/formbody": "^9.0.0",
32+ "@fastify/helmet": "^13.1.0",
33+ "@fastify/multipart": "^10.1.0",
34+ "@fastify/rate-limit": "^11.2.0",
35+ "@fastify/websocket": "^11.3.0",
36+ "argon2": "^0.45.1",
37+ "c8": "^12.0.0",
2738 "fastify": "^5.7.4",
39+ "jose": "^6.2.8",
2840 "jsdom": "^28.0.0",
2941 "purescript": "^0.15.15",
30− "spago": "0.93.42",
42+ "spago": "^1.0.3",
3143 "vitest": "^2.1.8"
3244 },
3345 "dependencies": {
spago.lock+112−200
@@ -44,6 +44,9 @@
4444 {
4545 "strings": ">=6.0.1 <7.0.0"
4646 },
47+ {
48+ "tailrec": ">=6.1.0 <7.0.0"
49+ },
4750 {
4851 "transformers": ">=6.1.0 <7.0.0"
4952 },
@@ -60,7 +63,7 @@
6063 "variant": ">=8.0.0 <9.0.0"
6164 },
6265 {
63− "yoga-http-api": ">=0.3.2 <0.4.0"
66+ "yoga-http-api": ">=0.3.4 <0.4.0"
6467 },
6568 {
6669 "yoga-json": ">=5.2.1 <6.0.0"
@@ -71,97 +74,28 @@
7174 {
7275 "yoga-om-strom": ">=0.4.3 <0.5.0"
7376 }
74− ],
75− "build_plan": [
76− "aff",
77− "aff-retry",
78− "ansi",
79− "arraybuffer-types",
80− "arrays",
81− "avar",
82− "bifunctors",
83− "catenable-lists",
84− "console",
85− "const",
86− "contravariant",
87− "control",
88− "datetime",
89− "distributive",
90− "effect",
91− "either",
77+ ]
78+ },
79+ "test": {
80+ "dependencies": [
81+ {
82+ "console": ">=6.0.0 <7.0.0"
83+ },
9284 "enums",
93− "exceptions",
94− "exists",
9585 "foldable-traversable",
9686 "foreign",
9787 "foreign-object",
98− "fork",
99− "free",
100− "functions",
101− "functors",
102− "gen",
103− "heterogeneous",
104− "http-methods",
105− "identity",
10688 "integers",
107− "invariant",
108− "js-bigints",
109− "js-date",
110− "js-fetch",
11189 "js-promise",
112− "js-promise-aff",
113− "lazy",
114− "lists",
115− "literals",
116− "maybe",
117− "media-types",
118− "mmorph",
119− "newtype",
120− "nonempty",
121− "now",
122− "nullable",
123− "numbers",
124− "ordered-collections",
125− "orders",
126− "parallel",
90+ {
91+ "js-uri": ">=3.1.0 <4.0.0"
92+ },
93+ {
94+ "justifill": ">=0.5.0 <0.6.0"
95+ },
12796 "partial",
128− "pipes",
129− "prelude",
130− "profunctor",
131− "random",
132− "record",
133− "record-studio",
134− "refs",
135− "safe-coerce",
136− "spec",
137− "st",
138− "strings",
139− "tailrec",
140− "transformers",
141− "tuples",
142− "type-equality",
143− "typelevel-prelude",
144− "uncurried-transformers",
145− "undefined-is-not-a-problem",
146− "unfoldable",
147− "unsafe-coerce",
148− "untagged-union",
149− "variant",
150− "web-events",
151− "web-file",
152− "web-streams",
153− "yoga-http-api",
154− "yoga-json",
155− "yoga-om",
156− "yoga-om-strom",
157− "yoga-options",
158− "yoga-tree"
159− ]
160− },
161− "test": {
162− "dependencies": [
16397 {
164− "console": ">=6.0.0 <7.0.0"
98+ "quickcheck": ">=8.0.1 <9.0.0"
16599 },
166100 {
167101 "refs": ">=6.0.0 <7.0.0"
@@ -173,121 +107,17 @@
173107 "spec-discovery": ">=8.0.0 <9.0.0"
174108 },
175109 {
176− "yoga-fastify-om": ">=0.4.5 <0.5.0"
177− }
178− ],
179− "build_plan": [
180− "aff",
181− "aff-retry",
182− "ansi",
183− "argonaut-codecs",
184− "argonaut-core",
185− "arraybuffer-types",
186− "arrays",
187− "avar",
188− "bifunctors",
189− "catenable-lists",
190− "console",
191− "const",
192− "contravariant",
193− "control",
194− "datetime",
195− "distributive",
196− "effect",
197− "either",
198− "enums",
199− "exceptions",
200− "exists",
201− "exitcodes",
202− "foldable-traversable",
203− "foreign",
204− "foreign-object",
205− "fork",
206− "free",
207− "functions",
208− "functors",
209− "gen",
210− "heterogeneous",
211− "identity",
212− "integers",
213− "invariant",
214− "js-bigints",
215− "js-date",
216− "js-promise",
217− "js-promise-aff",
218− "js-uri",
219− "lazy",
220− "lists",
221− "literals",
222− "maybe",
223− "mmorph",
224− "newtype",
225− "node-buffer",
226− "node-event-emitter",
227− "node-fs",
228− "node-path",
229− "node-process",
230− "node-streams",
231− "nonempty",
232− "now",
233− "nullable",
234− "numbers",
235− "open-memoize",
236− "optparse",
237− "ordered-collections",
238− "orders",
239− "parallel",
240− "partial",
241− "pipes",
242− "posix-types",
243− "prelude",
244− "profunctor",
245− "random",
246− "record",
247− "record-studio",
248− "refs",
249− "routing-duplex",
250− "safe-coerce",
251− "spec",
252− "spec-discovery",
253− "spec-node",
254− "st",
255− "strings",
256− "tailrec",
257− "transformers",
258− "tuples",
259− "type-equality",
260− "typelevel-prelude",
261− "uncurried-transformers",
262− "undefined-is-not-a-problem",
263− "unfoldable",
264− "unsafe-coerce",
265− "untagged-union",
266− "variant",
110+ "spec-quickcheck": ">=5.0.2 <6.0.0"
111+ },
267112 "yoga-fastify",
268− "yoga-fastify-om",
269− "yoga-http-api",
270− "yoga-json",
271− "yoga-om",
272− "yoga-om-layer",
273− "yoga-om-strom",
274− "yoga-options",
275− "yoga-tree"
113+ {
114+ "yoga-fastify-om": ">=0.4.7 <0.5.0"
115+ }
276116 ]
277117 }
278118 }
279119 },
280− "extra_packages": {
281− "yoga-fastify": {
282− "path": "../purescript-yoga-fastify-om/local-packages/yoga-fastify"
283− },
284− "yoga-fastify-om": {
285− "path": "../purescript-yoga-fastify-om"
286− },
287− "yoga-http-api": {
288− "path": "../purescript-yoga-fastify-om/local-packages/yoga-http-api"
289− }
290− }
120+ "extra_packages": {}
291121 },
292122 "packages": {
293123 "aff": {
@@ -864,6 +694,17 @@
864694 "prelude"
865695 ]
866696 },
697+ "justifill": {
698+ "type": "registry",
699+ "version": "0.5.0",
700+ "integrity": "sha256-IwoDO72+IROcUvFIwnronb/ny8SepssgTUG3Qp2TEKg=",
701+ "dependencies": [
702+ "maybe",
703+ "prelude",
704+ "record",
705+ "typelevel-prelude"
706+ ]
707+ },
867708 "lazy": {
868709 "type": "registry",
869710 "version": "6.0.0",
@@ -875,6 +716,20 @@
875716 "prelude"
876717 ]
877718 },
719+ "lcg": {
720+ "type": "registry",
721+ "version": "4.0.0",
722+ "integrity": "sha256-bUYyfXYmjTn7zSpj2hTiePywzsuD80fCjkonsrpdGOw=",
723+ "dependencies": [
724+ "effect",
725+ "integers",
726+ "maybe",
727+ "numbers",
728+ "partial",
729+ "prelude",
730+ "random"
731+ ]
732+ },
878733 "lists": {
879734 "type": "registry",
880735 "version": "7.0.0",
@@ -1254,6 +1109,39 @@
12541109 "tuples"
12551110 ]
12561111 },
1112+ "quickcheck": {
1113+ "type": "registry",
1114+ "version": "8.0.1",
1115+ "integrity": "sha256-SfW+maesCt0jnnFhfrgJZGPcWLj8oFcGbcDIVklmdzc=",
1116+ "dependencies": [
1117+ "arrays",
1118+ "console",
1119+ "control",
1120+ "effect",
1121+ "either",
1122+ "enums",
1123+ "exceptions",
1124+ "foldable-traversable",
1125+ "gen",
1126+ "identity",
1127+ "integers",
1128+ "lazy",
1129+ "lcg",
1130+ "lists",
1131+ "maybe",
1132+ "newtype",
1133+ "nonempty",
1134+ "numbers",
1135+ "partial",
1136+ "prelude",
1137+ "record",
1138+ "st",
1139+ "strings",
1140+ "tailrec",
1141+ "transformers",
1142+ "tuples"
1143+ ]
1144+ },
12571145 "random": {
12581146 "type": "registry",
12591147 "version": "6.0.0",
@@ -1404,6 +1292,22 @@
14041292 "tuples"
14051293 ]
14061294 },
1295+ "spec-quickcheck": {
1296+ "type": "registry",
1297+ "version": "5.0.2",
1298+ "integrity": "sha256-pmnLu9e9Yk+Yx4hi71XwbyuPS27ya4hjTOKk2KFK/Ns=",
1299+ "dependencies": [
1300+ "aff",
1301+ "arrays",
1302+ "effect",
1303+ "foldable-traversable",
1304+ "lists",
1305+ "maybe",
1306+ "prelude",
1307+ "quickcheck",
1308+ "tuples"
1309+ ]
1310+ },
14071311 "st": {
14081312 "type": "registry",
14091313 "version": "6.2.0",
@@ -1640,8 +1544,8 @@
16401544 },
16411545 "yoga-fastify": {
16421546 "type": "registry",
1643− "version": "0.5.3",
1644− "integrity": "sha256-KeAoEYXXNH2gHEl5Y7j9uMo5ipByLZp/p7u6pgQn6KU=",
1547+ "version": "0.5.4",
1548+ "integrity": "sha256-YenjePRdiApMPpcvkXlzre7c4fIuvk5T19Z2uEAWhR4=",
16451549 "dependencies": [
16461550 "aff",
16471551 "arrays",
@@ -1667,8 +1571,8 @@
16671571 },
16681572 "yoga-fastify-om": {
16691573 "type": "registry",
1670− "version": "0.4.5",
1671− "integrity": "sha256-fPDRni+HjddpI7GPad5U2kVHxMPOZIsvTPFZtOPpiWI=",
1574+ "version": "0.4.7",
1575+ "integrity": "sha256-KGQ7lHI7pcSiEQoEq6j6K+3XilcPy8wQ/M9C1y+uaEk=",
16721576 "dependencies": [
16731577 "aff",
16741578 "console",
@@ -1680,10 +1584,12 @@
16801584 "integers",
16811585 "maybe",
16821586 "node-buffer",
1587+ "numbers",
16831588 "prelude",
16841589 "record",
16851590 "refs",
16861591 "routing-duplex",
1592+ "strings",
16871593 "transformers",
16881594 "typelevel-prelude",
16891595 "unsafe-coerce",
@@ -1698,19 +1604,25 @@
16981604 },
16991605 "yoga-http-api": {
17001606 "type": "registry",
1701− "version": "0.3.2",
1702− "integrity": "sha256-SiL07TYVjHPiQjMhnkYdoI3KrL6gNn1W1LigCN+80jA=",
1607+ "version": "0.3.4",
1608+ "integrity": "sha256-DSIJ8YWr5vB5IMpyaBALM+fx57lz3AAAR0MGtfHeWxo=",
17031609 "dependencies": [
17041610 "aff",
1611+ "arraybuffer-types",
17051612 "arrays",
1613+ "effect",
17061614 "either",
17071615 "foldable-traversable",
17081616 "foreign",
17091617 "foreign-object",
17101618 "identity",
17111619 "integers",
1620+ "js-promise",
1621+ "js-promise-aff",
17121622 "maybe",
17131623 "newtype",
1624+ "nullable",
1625+ "numbers",
17141626 "prelude",
17151627 "record",
17161628 "strings",
spago.yaml+19−14
@@ -1,15 +1,8 @@
1−workspace:
2− extraPackages:
3− yoga-fastify-om:
4− path: ../purescript-yoga-fastify-om
5− yoga-http-api:
6− path: ../purescript-yoga-fastify-om/local-packages/yoga-http-api
7− yoga-fastify:
8− path: ../purescript-yoga-fastify-om/local-packages/yoga-fastify
1+workspace: {}
92package:
103 name: yoga-fetch-om
114 publish:
12− version: 0.7.0
5+ version: 0.8.1
136 license: MIT
147 location:
158 githubOwner: rowtype-yoga
@@ -17,7 +10,6 @@ package:
1710 include:
1811 - LICENSE
1912 - README.md
20− - CHANGELOG.md
2113 exclude:
2214 - .github
2315 dependencies:
@@ -34,20 +26,33 @@ package:
3426 - prelude: ">=6.0.2 <7.0.0"
3527 - record: ">=4.0.0 <5.0.0"
3628 - strings: ">=6.0.1 <7.0.0"
29+ - tailrec: ">=6.1.0 <7.0.0"
3730 - transformers: ">=6.1.0 <7.0.0"
3831 - tuples: ">=7.0.0 <8.0.0"
3932 - typelevel-prelude: ">=7.0.0 <8.0.0"
4033 - unsafe-coerce: ">=6.0.0 <7.0.0"
4134 - variant: ">=8.0.0 <9.0.0"
42− - yoga-http-api: ">=0.3.2 <0.4.0"
35+ - yoga-http-api: ">=0.3.4 <0.4.0"
4336 - yoga-json: ">=5.2.1 <6.0.0"
4437 - yoga-om: ">=1.5.2 <2.0.0"
4538 - yoga-om-strom: ">=0.4.3 <0.5.0"
4639 test:
4740 main: Test.Main
4841 dependencies:
49− - spec: ">=8.0.0 <9.0.0"
50− - spec-discovery: ">=8.0.0 <9.0.0"
42+ - enums
43+ - foldable-traversable
44+ - foreign
45+ - foreign-object
46+ - integers
47+ - js-promise
48+ - partial
49+ - yoga-fastify
5150 - console: ">=6.0.0 <7.0.0"
52− - yoga-fastify-om: ">=0.4.5 <0.5.0"
51+ - js-uri: ">=3.1.0 <4.0.0"
52+ - justifill: ">=0.5.0 <0.6.0"
53+ - quickcheck: ">=8.0.1 <9.0.0"
5354 - refs: ">=6.0.0 <7.0.0"
55+ - spec: ">=8.0.0 <9.0.0"
56+ - spec-discovery: ">=8.0.0 <9.0.0"
57+ - spec-quickcheck: ">=5.0.2 <6.0.0"
58+ - yoga-fastify-om: ">=0.4.7 <0.5.0"
src/Yoga/Fetch/Om.jsnew file+1
@@ -0,0 +1 @@
1+export const mergeRecords = (left) => (right) => ({ ...right, ...left });
src/Yoga/Fetch/Om.purs+135−20
@@ -1,5 +1,8 @@
11module Yoga.Fetch.Om
22 ( client
3+ , clientWith
4+ , ClientInputs
5+ , ClientWithConfig
36 , deriveClient
47 , class DeriveClient
58 , deriveClientImpl
@@ -7,13 +10,17 @@ module Yoga.Fetch.Om
710 , deriveClientRL
811 , class DeriveClientFn
912 , deriveClientFn
13+ , class DeriveClientFnWith
14+ , deriveClientFnWith
15+ , class DeriveClientWith
16+ , deriveClientWithImpl
17+ , class DeriveClientWithRL
18+ , deriveClientWithRL
1019 , class RecordRow
1120 , class ToHeaders
1221 , toHeaders
1322 , module Yoga.HTTP.API.Route
1423 , module Yoga.HTTP.API.Path
15− , plainText
16− , opt
1724 , module Yoga.Fetch.Om.StreamDecode
1825 , module Yoga.Fetch.Om.Simple
1926 ) where
@@ -35,16 +42,27 @@ import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
3542import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
3643import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
3744import Yoga.Fetch.Om.ClientFunction (class BuildClientFn, class CheckBodyIsUnit, buildClientFn)
38−import Yoga.Fetch.Om.QueryDefaults (class BuildQueryFromPartial, buildQueryFromPartial)
3945import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractBodyEncoding, class ExtractRequestHeaders)
4046import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class BodyEncoding, makeRequest, encodingContentType, encodeBody)
4147import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
4248import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
4349import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
4450import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
45−import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, PlainText, Streaming, NoBody, class HeaderValue, printHeader)
51+import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, QUERY, Response(..), JSON, FormData, PlainText, Streaming, NoBody, class HeaderValue, printHeader)
4652import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
47−import Yoga.Om (class ToOm, Om, toOm)
53+import Yoga.Om (Om, toOm)
54+
55+type ClientInputs providedHeaders providedQuery =
56+ { headers :: Record providedHeaders
57+ , query :: Record providedQuery
58+ }
59+
60+type ClientWithConfig ctx err providedHeaders providedQuery =
61+ { baseUrl :: Om { | ctx } err String
62+ , provide :: Om { | ctx } err (ClientInputs providedHeaders providedQuery)
63+ }
64+
65+foreign import mergeRecords :: forall left right out. Record left -> Record right -> Record out
4866
4967-- | Extract row type from Record type
5068class RecordRow :: Type -> Row Type -> Constraint
@@ -102,11 +120,11 @@ instance
102120 , ToHeaders headersRL headers
103121 , CheckBodyIsUnit body bodyFlag
104122 , Row.Union routeErrors extraErr errRow
105− , BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers { | ctx } errRow result fn
123+ , BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers (Record pathQuery) { | ctx } errRow result fn
106124 ) =>
107125 DeriveClientFn method segments request response ctx extraErr routeErrors result fn where
108126 deriveClientFn baseUrl _ =
109− buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersRL) (Proxy :: _ bodyFlag) impl
127+ buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersRL) (Proxy :: _ bodyFlag) identity impl
110128 where
111129 impl :: Record pathQuery -> Record headers -> body -> Om { | ctx } errRow result
112130 impl pathQueryRec headersRec bodyVal = widenOm do
@@ -119,18 +137,88 @@ instance
119137 where
120138 pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
121139 queryParamsRec = unsafeCoerce pathQueryRec :: Record queryParams
140+
122141 widenOm :: Om (Record ()) routeErrors ~> Om { | ctx } errRow
123142 widenOm = unsafeCoerce
124143
144+class DeriveClientFnWith
145+ :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
146+class
147+ DeriveClientFnWith method segments request response providedHeaders providedQuery ctx extraErr routeErrors result fn
148+ | method segments request response -> routeErrors result
149+ , method segments request response providedHeaders providedQuery ctx extraErr -> fn where
150+ deriveClientFnWith
151+ :: ClientWithConfig ctx extraErr providedHeaders providedQuery
152+ -> Proxy (Route method segments request response)
153+ -> fn
154+
155+instance
156+ ( SegmentPathParams segments pathParams
157+ , SegmentQueryParams segments queryParams
158+ , ExtractRequestBody request body
159+ , ExtractBodyEncoding request encoding
160+ , ExtractRequestHeaders request headers
161+ , PathPattern segments
162+ , BuildUrl segments pathParams queryParams
163+ , MakeRequest method
164+ , BodyEncoding encoding body
165+ , SplitResponses response successRow routeErrors
166+ , Row.Union pathParams queryParams pathQuery
167+ , Row.Nub pathQuery pathQuery
168+ , ParseResponse routeErrors successRow
169+ , RowToList successRow successRL
170+ , VariantOrValue successRL successRow result
171+ , Row.Union routeErrors extraErr errRow
172+ , Row.Union providedQuery remainingQuery queryParams
173+ , Row.Union pathParams remainingQuery callerPathQuery
174+ , Row.Nub callerPathQuery callerPathQuery
175+ , RowToList callerPathQuery callerPathQueryRL
176+ , RowToList headers headersRL
177+ , ToHeaders headersRL headers
178+ , Row.Union providedHeaders remainingHeaders headers
179+ , Row.Nub headers headers
180+ , RowToList remainingHeaders remainingHeadersRL
181+ , CheckBodyIsUnit body bodyFlag
182+ , BuildClientFn callerPathQueryRL remainingHeadersRL bodyFlag body callerPathQuery remainingHeaders (Record callerPathQuery) { | ctx } errRow result fn
183+ ) =>
184+ DeriveClientFnWith method segments request response providedHeaders providedQuery ctx extraErr routeErrors result fn where
185+ deriveClientFnWith config _ =
186+ buildClientFn (Proxy :: _ callerPathQueryRL) (Proxy :: _ remainingHeadersRL) (Proxy :: _ bodyFlag) identity impl
187+ where
188+ impl :: Record callerPathQuery -> Record remainingHeaders -> body -> Om { | ctx } errRow result
189+ impl pathQueryRec headersRec bodyVal = do
190+ baseUrl <- widenExtra config.baseUrl
191+ provided <- widenExtra config.provide
192+ let pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
193+ let queryParamsRec = mergeRecords provided.query (unsafeCoerce pathQueryRec :: Record remainingQuery) :: Record queryParams
194+ let fullHeaders = mergeRecords provided.headers headersRec :: Record headers
195+ let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
196+ let hdrs = toHeaders (Proxy :: _ headersRL) fullHeaders
197+ let ct = encodingContentType (Proxy :: _ encoding)
198+ fetchResp <- makeRequest (Proxy :: _ method) url hdrs ct (encodeBody @encoding bodyVal) # toOm # widenRoute
199+ variant <- parseResponse fetchResp # widenRoute :: Om { | ctx } errRow (Variant successRow)
200+ variantOrValue (Proxy :: _ successRL) variant # pure
201+ where
202+ widenExtra :: Om { | ctx } extraErr ~> Om { | ctx } errRow
203+ widenExtra = unsafeCoerce
204+
205+ widenRoute :: Om (Record ()) routeErrors ~> Om { | ctx } errRow
206+ widenRoute = unsafeCoerce
207+
125208class DeriveClient :: Row Type -> Row Type -> Row Type -> Row Type -> Constraint
126209class DeriveClient ctx extraErr routesRow clientsRow | routesRow ctx extraErr -> clientsRow where
127210 deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
128211
129−-- | Derive API client functions from route definitions using VTA
212+-- | Derive API client functions from route definitions using VTA.
213+-- | Generated fields use ordinary function application. Path parameters are
214+-- | plain records; optional query parameters are explicit `Maybe` fields.
130215-- |
131216-- | ```purescript
132217-- | type UserAPI = { getUser :: Route ... }
133218-- | api = client @UserAPI "https://api.example.com"
219+-- | user <- api.getUser { id: 42 }
220+-- | users <- api.listUsers { limit: Just 10, offset: Nothing }
221+-- | health <- api.health
134222-- | ```
135223client :: forall @routes routesRow ctx extraErr clientsRow. RecordRow routes routesRow => DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
136224client baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesRow })
@@ -143,18 +231,13 @@ client baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesR
143231deriveClient :: forall @routesRow ctx extraErr clientsRow. DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
144232deriveClient baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesRow })
145233
146−plainText :: PlainText -> String
147−plainText = unsafeCoerce
148−
149−-- | Convert a partial record to a full query params record.
150−-- | Provided fields get `Just`, missing fields get `Nothing`.
151−-- |
152−-- | ```purescript
153−-- | api.listUsers $ opt { limit: 10 }
154−-- | -- instead of: api.listUsers { limit: Just 10, offset: Nothing }
155−-- | ```
156−opt :: forall @queryParams provided. BuildQueryFromPartial queryParams provided => Record provided -> Record queryParams
157−opt = buildQueryFromPartial
234+clientWith
235+ :: forall @routes @providedHeaders @providedQuery routesRow ctx extraErr clientsRow
236+ . RecordRow routes routesRow
237+ => DeriveClientWith providedHeaders providedQuery ctx extraErr routesRow clientsRow
238+ => ClientWithConfig ctx extraErr providedHeaders providedQuery
239+ -> Record clientsRow
240+clientWith config = deriveClientWithImpl @providedHeaders @providedQuery @ctx @extraErr config (Proxy :: _ { | routesRow })
158241
159242instance
160243 ( RowToList routesRow rl
@@ -163,6 +246,38 @@ instance
163246 DeriveClient ctx extraErr routesRow clientsRow where
164247 deriveClientImpl baseUrl _ = deriveClientRL @ctx @extraErr baseUrl (Proxy :: _ rl) {}
165248
249+class DeriveClientWith :: Row Type -> Row Type -> Row Type -> Row Type -> Row Type -> Row Type -> Constraint
250+class DeriveClientWith providedHeaders providedQuery ctx extraErr routesRow clientsRow | providedHeaders providedQuery routesRow ctx extraErr -> clientsRow where
251+ deriveClientWithImpl :: ClientWithConfig ctx extraErr providedHeaders providedQuery -> Proxy (Record routesRow) -> Record clientsRow
252+
253+instance
254+ ( RowToList routesRow rl
255+ , DeriveClientWithRL providedHeaders providedQuery ctx extraErr rl () clientsRow
256+ ) =>
257+ DeriveClientWith providedHeaders providedQuery ctx extraErr routesRow clientsRow where
258+ deriveClientWithImpl config _ = deriveClientWithRL @providedHeaders @providedQuery @ctx @extraErr config (Proxy :: _ rl) {}
259+
260+class DeriveClientWithRL :: Row Type -> Row Type -> Row Type -> Row Type -> RowList Type -> Row Type -> Row Type -> Constraint
261+class DeriveClientWithRL providedHeaders providedQuery ctx extraErr rl acc out | providedHeaders providedQuery ctx extraErr rl acc -> out where
262+ deriveClientWithRL :: ClientWithConfig ctx extraErr providedHeaders providedQuery -> Proxy rl -> Record acc -> Record out
263+
264+instance DeriveClientWithRL providedHeaders providedQuery ctx extraErr RL.Nil acc acc where
265+ deriveClientWithRL _ _ acc = acc
266+
267+instance
268+ ( IsSymbol label
269+ , DeriveClientFnWith method segments request response providedHeaders providedQuery ctx extraErr routeErrors result fn
270+ , DeriveClientWithRL providedHeaders providedQuery ctx extraErr tail acc1 acc2
271+ , Row.Cons label fn acc2 out
272+ , Row.Lacks label acc2
273+ , Row.Cons label (Route method segments request response) routeTail routeRow
274+ ) =>
275+ DeriveClientWithRL providedHeaders providedQuery ctx extraErr (RL.Cons label (Route method segments request response) tail) acc1 out where
276+ deriveClientWithRL config _ acc = Record.insert (Proxy :: _ label) clientFn rest
277+ where
278+ clientFn = deriveClientFnWith @_ @_ @_ @_ @providedHeaders @providedQuery @ctx @extraErr config (Proxy :: _ (Route method segments request response))
279+ rest = deriveClientWithRL @providedHeaders @providedQuery @ctx @extraErr config (Proxy :: _ tail) acc
280+
166281class DeriveClientRL :: Row Type -> Row Type -> RowList Type -> Row Type -> Row Type -> Constraint
167282class DeriveClientRL ctx extraErr rl acc out | ctx extraErr rl acc -> out where
168283 deriveClientRL :: String -> Proxy rl -> Record acc -> Record out
src/Yoga/Fetch/Om/BuildUrl.js+86−2
@@ -1,3 +1,87 @@
1−export function encodeURIComponent_(s) {
2− return encodeURIComponent(s);
1+export function encodeURIComponent_(value) {
2+ try {
3+ return encodeURIComponent(value);
4+ } catch (error) {
5+ if (!(error instanceof URIError)) throw error;
6+ return encodeURIComponent(toWellFormed(value));
7+ }
8+}
9+
10+function toWellFormed(value) {
11+ if (typeof value.toWellFormed === "function") return value.toWellFormed();
12+
13+ let result = "";
14+ let sliceStart = 0;
15+ for (let index = 0; index < value.length; index += 1) {
16+ const codeUnit = value.charCodeAt(index);
17+ const previous = index === 0 ? 0 : value.charCodeAt(index - 1);
18+ const next =
19+ index + 1 === value.length ? 0 : value.charCodeAt(index + 1);
20+ const loneHigh =
21+ codeUnit >= 0xd800 &&
22+ codeUnit <= 0xdbff &&
23+ (next < 0xdc00 || next > 0xdfff);
24+ const loneLow =
25+ codeUnit >= 0xdc00 &&
26+ codeUnit <= 0xdfff &&
27+ (previous < 0xd800 || previous > 0xdbff);
28+
29+ if (!loneHigh && !loneLow) continue;
30+ result += value.slice(sliceStart, index) + "\ufffd";
31+ sliceStart = index + 1;
32+ }
33+
34+ return sliceStart === 0 ? value : result + value.slice(sliceStart);
35+}
36+
37+function urlSuffixIndex(url) {
38+ const queryIndex = url.indexOf("?");
39+ const fragmentIndex = url.indexOf("#");
40+ return Math.min(
41+ queryIndex === -1 ? url.length : queryIndex,
42+ fragmentIndex === -1 ? url.length : fragmentIndex,
43+ );
44+}
45+
46+export function appendPath_(base) {
47+ return function (path) {
48+ const suffixIndex = urlSuffixIndex(base);
49+ const prefix = base.slice(0, suffixIndex).replace(/\/+$/, "");
50+ return prefix + path + base.slice(suffixIndex);
51+ };
52+}
53+
54+
55+export function substitutePathParam_(name) {
56+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57+ const segment = new RegExp(`(^|/):${escapedName}(?=/|[?#]|$)`, "g");
58+ return function (value) {
59+ const encodedValue = encodeURIComponent_(value);
60+ return function (url) {
61+ const suffixIndex = urlSuffixIndex(url);
62+ const path = url.slice(0, suffixIndex);
63+ return (
64+ path.replace(segment, (_, prefix) => prefix + encodedValue) +
65+ url.slice(suffixIndex)
66+ );
67+ };
68+ };
69+}
70+
71+export function appendQueryString_(url) {
72+ return function (queryString) {
73+ if (queryString === "") return url;
74+
75+ const fragmentIndex = url.indexOf("#");
76+ const beforeFragment =
77+ fragmentIndex === -1 ? url : url.slice(0, fragmentIndex);
78+ const fragment = fragmentIndex === -1 ? "" : url.slice(fragmentIndex);
79+ const separator = beforeFragment.includes("?")
80+ ? beforeFragment.endsWith("?") || beforeFragment.endsWith("&")
81+ ? ""
82+ : "&"
83+ : "?";
84+
85+ return beforeFragment + separator + queryString + fragment;
86+ };
387}
src/Yoga/Fetch/Om/BuildUrl.purs+43−13
@@ -19,7 +19,6 @@ import Prelude
1919
2020import Data.Array as Array
2121import Data.Maybe (Maybe(..))
22−import Data.String (Pattern(..), Replacement(..))
2322import Data.String as String
2423import Data.Symbol (class IsSymbol, reflectSymbol)
2524import Prim.Row as Row
@@ -28,8 +27,13 @@ import Prim.RowList as RL
2827import Record as Record
2928import Type.Proxy (Proxy(..))
3029import Yoga.HTTP.API.Path (class PathPattern, pathPattern)
30+import Yoga.Fetch.Om.ReadOptional (readOptional)
3131
3232foreign import encodeURIComponent_ :: String -> String
33+foreign import appendPath_ :: String -> String -> String
34+foreign import substitutePathParam_ :: String -> String -> String -> String
35+
36+foreign import appendQueryString_ :: String -> String -> String
3337
3438class BuildUrl :: forall k. k -> Row Type -> Row Type -> Constraint
3539class BuildUrl segments pathParams queryParams | segments -> pathParams queryParams where
@@ -46,10 +50,7 @@ instance
4650 pattern = pathPattern segmentsProxy
4751 withBase = case baseUrl of
4852 "" -> pattern
49− base -> dropTrailingSlash base <> pattern
50− dropTrailingSlash s = case String.stripSuffix (Pattern "/") s of
51− Just s' -> dropTrailingSlash s'
52− Nothing -> s
53+ base -> appendPath_ base pattern
5354 withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
5455 withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
5556
@@ -84,8 +85,8 @@ instance
8485 substitutePathParamsRL _ rec url = substitutePathParamsRL (Proxy :: _ tail) rec replaced
8586 where
8687 value = Record.get (Proxy :: _ name) rec
87− pattern = Pattern (":" <> reflectSymbol (Proxy :: _ name))
88− replaced = String.replaceAll pattern (Replacement (serializeParam value)) url
88+ name = reflectSymbol (Proxy :: _ name)
89+ replaced = substitutePathParam_ name (serializeParam value) url
8990
9091class AppendQueryParams :: Row Type -> Constraint
9192class AppendQueryParams params where
@@ -99,12 +100,11 @@ instance
99100 , AppendQueryParamsRL rl params
100101 ) =>
101102 AppendQueryParams params where
102− appendQueryParamsImpl _ rec url = url <> queryString
103+ appendQueryParamsImpl _ rec url = case pairs of
104+ [] -> url
105+ _ -> appendQueryString_ url (String.joinWith "&" pairs)
103106 where
104107 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
105− queryString = case pairs of
106− [] -> ""
107− _ -> "?" <> String.joinWith "&" pairs
108108
109109class AppendQueryParamsRL :: RowList Type -> Row Type -> Constraint
110110class AppendQueryParamsRL rl r where
@@ -113,19 +113,49 @@ class AppendQueryParamsRL rl r where
113113instance AppendQueryParamsRL RL.Nil r where
114114 appendQueryParamsRL _ _ acc = acc
115115
116+-- Maybe (Array ty): optional repeated query param — missing/empty field → no pairs
116117instance
117118 ( IsSymbol name
118119 , SerializeParam ty
119120 , AppendQueryParamsRL tail r
120− , Row.Cons name (Maybe ty) tailRow r
121+ ) =>
122+ AppendQueryParamsRL (RL.Cons name (Maybe (Array ty)) tail) r where
123+ appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
124+ where
125+ encName = encodeURIComponent_ (reflectSymbol (Proxy :: _ name))
126+ acc' = case (readOptional @name rec :: Maybe (Array ty)) of
127+ Nothing -> acc
128+ Just values -> acc <> map (\v -> encName <> "=" <> encodeURIComponent_ (serializeParam v)) values
129+
130+-- Maybe ty: read with undefined-awareness — missing field → skip
131+else instance
132+ ( IsSymbol name
133+ , SerializeParam ty
134+ , AppendQueryParamsRL tail r
121135 ) =>
122136 AppendQueryParamsRL (RL.Cons name (Maybe ty) tail) r where
123137 appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
124138 where
125− acc' = case Record.get (Proxy :: _ name) rec of
139+ acc' = case (readOptional @name rec :: Maybe ty) of
126140 Nothing -> acc
127141 Just value -> Array.snoc acc (encodeURIComponent_ (reflectSymbol (Proxy :: _ name)) <> "=" <> encodeURIComponent_ (serializeParam value))
128142
143+-- Array ty: emit one `name=value` pair per element (repeated keys)
144+else instance
145+ ( IsSymbol name
146+ , SerializeParam ty
147+ , AppendQueryParamsRL tail r
148+ , Row.Cons name (Array ty) tailRow r
149+ ) =>
150+ AppendQueryParamsRL (RL.Cons name (Array ty) tail) r where
151+ appendQueryParamsRL _ rec acc =
152+ appendQueryParamsRL (Proxy :: _ tail) rec (acc <> pairs)
153+ where
154+ encName = encodeURIComponent_ (reflectSymbol (Proxy :: _ name))
155+ values = Record.get (Proxy :: _ name) rec
156+ pairs = map (\v -> encName <> "=" <> encodeURIComponent_ (serializeParam v)) values
157+
158+-- bare ty (Required query param): always present
129159else instance
130160 ( IsSymbol name
131161 , SerializeParam ty
src/Yoga/Fetch/Om/ClientFunction.purs+24−15
@@ -22,16 +22,17 @@ instance CheckBodyIsUnit Unit IsUnit
2222else instance CheckBodyIsUnit body IsNotUnit
2323
2424class BuildClientFn
25− :: RowList Type -> RowList Type -> Type -> Type -> Row Type -> Row Type -> Type -> Row Type -> Type -> Type -> Constraint
25+ :: RowList Type -> RowList Type -> Type -> Type -> Row Type -> Row Type -> Type -> Type -> Row Type -> Type -> Type -> Constraint
2626class
27− BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers ctx errorRow result fn
27+ BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers arg ctx errorRow result fn
2828 | pathQueryRL -> pathQuery
2929 , headersRL -> headers
30− , pathQueryRL headersRL bodyFlag body ctx errorRow result -> fn where
30+ , pathQueryRL headersRL bodyFlag body arg ctx errorRow result -> fn where
3131 buildClientFn
3232 :: Proxy pathQueryRL
3333 -> Proxy headersRL
3434 -> Proxy bodyFlag
35+ -> (arg -> Record pathQuery)
3536 -> (Record pathQuery -> Record headers -> body -> Om ctx errorRow result)
3637 -> fn
3738
@@ -43,11 +44,12 @@ instance
4344 Unit
4445 ()
4546 ()
47+ arg
4648 ctx
4749 errorRow
4850 result
4951 (Om ctx errorRow result) where
50− buildClientFn _ _ _ f = f {} {} unit
52+ buildClientFn _ _ _ _ f = f {} {} unit
5153
5254-- No path/query, no headers, with body
5355instance
@@ -57,11 +59,12 @@ instance
5759 body
5860 ()
5961 ()
62+ arg
6063 ctx
6164 errorRow
6265 result
6366 (body -> Om ctx errorRow result) where
64− buildClientFn _ _ _ f b = f {} {} b
67+ buildClientFn _ _ _ _ f b = f {} {} b
6568
6669-- No path/query, with headers, no body
6770instance
@@ -71,11 +74,12 @@ instance
7174 Unit
7275 ()
7376 h
77+ arg
7478 ctx
7579 errorRow
7680 result
7781 (Record h -> Om ctx errorRow result) where
78− buildClientFn _ _ _ f hdrs = f {} hdrs unit
82+ buildClientFn _ _ _ _ f hdrs = f {} hdrs unit
7983
8084-- No path/query, with headers, with body
8185instance
@@ -85,11 +89,12 @@ instance
8589 body
8690 ()
8791 h
92+ arg
8893 ctx
8994 errorRow
9095 result
9196 (Record h -> body -> Om ctx errorRow result) where
92− buildClientFn _ _ _ f hdrs b = f {} hdrs b
97+ buildClientFn _ _ _ _ f hdrs b = f {} hdrs b
9398
9499-- With path/query, no headers, no body
95100instance
@@ -99,11 +104,12 @@ instance
99104 Unit
100105 pq
101106 ()
107+ arg
102108 ctx
103109 errorRow
104110 result
105− (Record pq -> Om ctx errorRow result) where
106− buildClientFn _ _ _ f pqr = f pqr {} unit
111+ (arg -> Om ctx errorRow result) where
112+ buildClientFn _ _ _ convert f pqr = f (convert pqr) {} unit
107113
108114-- With path/query, no headers, with body
109115instance
@@ -113,11 +119,12 @@ instance
113119 body
114120 pq
115121 ()
122+ arg
116123 ctx
117124 errorRow
118125 result
119− (Record pq -> body -> Om ctx errorRow result) where
120− buildClientFn _ _ _ f pqr b = f pqr {} b
126+ (arg -> body -> Om ctx errorRow result) where
127+ buildClientFn _ _ _ convert f pqr b = f (convert pqr) {} b
121128
122129-- With path/query, with headers, no body
123130instance
@@ -127,11 +134,12 @@ instance
127134 Unit
128135 pq
129136 h
137+ arg
130138 ctx
131139 errorRow
132140 result
133− (Record pq -> Record h -> Om ctx errorRow result) where
134− buildClientFn _ _ _ f pqr hdrs = f pqr hdrs unit
141+ (arg -> Record h -> Om ctx errorRow result) where
142+ buildClientFn _ _ _ convert f pqr hdrs = f (convert pqr) hdrs unit
135143
136144-- With path/query, with headers, with body
137145instance
@@ -141,8 +149,9 @@ instance
141149 body
142150 pq
143151 h
152+ arg
144153 ctx
145154 errorRow
146155 result
147− (Record pq -> Record h -> body -> Om ctx errorRow result) where
148− buildClientFn _ _ _ f = f
156+ (arg -> Record h -> body -> Om ctx errorRow result) where
157+ buildClientFn _ _ _ convert f pqr hdrs b = f (convert pqr) hdrs b
src/Yoga/Fetch/Om/MakeRequest.purs+6−13
@@ -2,8 +2,6 @@ module Yoga.Fetch.Om.MakeRequest
22 ( class MakeRequest
33 , httpMethod
44 , makeRequest
5− , class SerializeBody
6− , serializeBody
75 , class BodyEncoding
86 , encodingContentType
97 , encodeBody
@@ -32,8 +30,8 @@ import JS.Fetch.Integrity (Integrity(..))
3230import JS.Fetch.Duplex as Duplex
3331import JS.Fetch.Response (Response) as FetchResponse
3432import Promise.Aff as Promise
35−import Type.Proxy (Proxy(..))
36−import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody)
33+import Type.Proxy (Proxy)
34+import Yoga.HTTP.API.Route.Encoding (JSON, FormData, PlainText, NoBody)
3735import Yoga.HTTP.API.Route.Method as Method
3836import Yoga.JSON (class WriteForeign, writeJSON)
3937
@@ -96,15 +94,6 @@ makeRequest proxy url customHeaders bodyContentType maybeBody = do
9694 , cache: Cache.Default
9795 }
9896
99−class SerializeBody :: Type -> Constraint
100−class SerializeBody body where
101− serializeBody :: body -> Maybe String
102−
103−instance SerializeBody Unit where
104− serializeBody _ = Nothing
105−else instance WriteForeign body => SerializeBody body where
106− serializeBody b = Just (writeJSON b)
107−
10897class BodyEncoding :: Type -> Type -> Constraint
10998class BodyEncoding encoding body | encoding -> body where
11099 encodingContentType :: Proxy encoding -> String
@@ -114,6 +103,10 @@ instance WriteForeign ty => BodyEncoding (JSON ty) ty where
114103 encodingContentType _ = "application/json"
115104 encodeBody b = Just (writeJSON b)
116105
106+else instance BodyEncoding PlainText String where
107+ encodingContentType _ = "text/plain"
108+ encodeBody = Just
109+
117110else instance BodyEncoding (FormData ty) ty where
118111 encodingContentType _ = "application/x-www-form-urlencoded"
119112 encodeBody b = Just (toUrlEncoded b)
src/Yoga/Fetch/Om/ParseResponse.purs+11−30
@@ -29,11 +29,10 @@ import Promise.Aff as Promise
2929import Type.Proxy (Proxy(..))
3030import Unsafe.Coerce (unsafeCoerce)
3131import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
32−import Yoga.HTTP.API.Route.Encoding (PlainText, Streaming)
3332import Yoga.HTTP.API.Route.StatusCode (class StatusCodeMap, statusCodeFor, StatusCode(..))
3433import Yoga.JSON (class ReadForeign, readJSON)
3534import Yoga.JSON.Error (withStringErrors)
36−import Yoga.Om (class ToOm, Om, toOm)
35+import Yoga.Om (Om, toOm)
3736import Yoga.Om.Error (Exception)
3837import Yoga.Om.Strom (Strom)
3938import Yoga.Om.Strom.WebStream as WebStream
@@ -75,37 +74,35 @@ instance ParseSuccessRL RL.Nil successRow where
7574else instance
7675 ( IsSymbol label
7776 , StatusCodeMap label
78− , Row.Cons label PlainText tailRow successRow
77+ , StreamDecode a
78+ , Row.Cons label (Strom streamContext streamError a) tailRow successRow
7979 , Row.Lacks label tailRow
8080 , ParseSuccessRL tailRL successRow
8181 ) =>
82− ParseSuccessRL (RL.Cons label PlainText tailRL) successRow where
82+ ParseSuccessRL (RL.Cons label (Strom streamContext streamError a) tailRL) successRow where
8383 parseSuccessRL _ actualStatus fetchResp = do
8484 let StatusCode expected = statusCodeFor (Proxy :: _ label)
8585 if actualStatus == expected then do
86− text <- liftEffect (Fetch.text fetchResp) >>= Promise.toAff
87− pure (Variant.inj (Proxy :: _ label) (unsafeCoerce text))
86+ webStream <- liftEffect (Fetch.body fetchResp)
87+ let strom = decodeStream (WebStream.fromReadableStream (unsafeCoerce webStream)) :: Strom {} () a
88+ pure (Variant.inj (Proxy :: _ label) (unsafeCoerce strom))
8889 else
8990 parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
9091
9192else instance
9293 ( IsSymbol label
9394 , StatusCodeMap label
94− , StreamDecode a
95− , Row.Cons label (Streaming a) tailRow successRow
95+ , Row.Cons label Unit tailRow successRow
9696 , Row.Lacks label tailRow
9797 , ParseSuccessRL tailRL successRow
9898 ) =>
99− ParseSuccessRL (RL.Cons label (Streaming a) tailRL) successRow where
99+ ParseSuccessRL (RL.Cons label Unit tailRL) successRow where
100100 parseSuccessRL _ actualStatus fetchResp = do
101101 let StatusCode expected = statusCodeFor (Proxy :: _ label)
102− if actualStatus == expected then do
103− webStream <- liftEffect (Fetch.body fetchResp)
104− let strom = decodeStream (WebStream.fromReadableStream (unsafeCoerce webStream)) :: Strom {} () a
105− pure (Variant.inj (Proxy :: _ label) (unsafeCoerce strom))
102+ if actualStatus == expected then
103+ pure (Variant.inj (Proxy :: _ label) unit)
106104 else
107105 parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
108−
109106else instance
110107 ( IsSymbol label
111108 , StatusCodeMap label
@@ -138,22 +135,6 @@ instance ParseErrorRL RL.Nil errorRow where
138135 parseErrorRL _ _ status _ =
139136 throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Unexpected error status code: " <> show status)))
140137
141−else instance
142− ( IsSymbol label
143− , StatusCodeMap label
144− , Row.Cons label PlainText tailRow errorRow
145− , Row.Lacks label tailRow
146− , Row.Cons label PlainText tailExc (Exception errorRow)
147− , ParseErrorRL tailRL errorRow
148− ) =>
149− ParseErrorRL (RL.Cons label PlainText tailRL) errorRow where
150− parseErrorRL _ errorProxy actualStatus text = do
151− let StatusCode expected = statusCodeFor (Proxy :: _ label)
152− if actualStatus == expected then
153− throwError (Variant.inj (Proxy :: _ label) (unsafeCoerce text))
154− else
155− parseErrorRL (Proxy :: _ tailRL) errorProxy actualStatus text
156−
157138else instance
158139 ( IsSymbol label
159140 , StatusCodeMap label
src/Yoga/Fetch/Om/QueryDefaults.pursdeleted−135
@@ -1,135 +0,0 @@
1−module Yoga.Fetch.Om.QueryDefaults
2− ( class BuildMaybeQuery
3− , buildMaybeQuery
4− , class HasField
5− , class BuildMaybeQueryField
6− , buildMaybeQueryField
7− , class SplitQueryRow
8− , class SplitQueryRowRL
9− , class BuildQueryFromPartial
10− , buildQueryFromPartial
11− , class BuildQueryFromPartialRL
12− , buildQueryFromPartialRL
13− ) where
14−
15−import Data.Maybe (Maybe(..))
16−import Data.Symbol (class IsSymbol)
17−import Prim.Boolean (True, False)
18−import Prim.Row as Row
19−import Prim.RowList (class RowToList, RowList)
20−import Prim.RowList as RL
21−import Record as Record
22−import Type.Proxy (Proxy(..))
23−
24−-- | Check whether a label exists in a RowList
25−class HasField (name :: Symbol) (rl :: RowList Type) (has :: Boolean) | name rl -> has
26−
27−instance HasField name RL.Nil False
28−instance HasField name (RL.Cons name ty tail) True
29−else instance HasField name tail has => HasField name (RL.Cons other ty tail) has
30−
31−-- | Get a Maybe-wrapped value from a record, based on whether the field exists
32−class BuildMaybeQueryField (has :: Boolean) (name :: Symbol) (ty :: Type) (provided :: Row Type) where
33− buildMaybeQueryField :: Proxy has -> Proxy name -> Record provided -> Maybe ty
34−
35−instance (IsSymbol name, Row.Cons name ty rest provided) =>
36− BuildMaybeQueryField True name ty provided where
37− buildMaybeQueryField _ _ provided = Just (Record.get (Proxy :: _ name) provided)
38−
39−instance BuildMaybeQueryField False name ty provided where
40− buildMaybeQueryField _ _ _ = Nothing
41−
42−-- | Build a full Maybe-wrapped query record from a partial provided record.
43−-- | Fields present in provided get Just, missing fields get Nothing.
44−class BuildMaybeQuery (rl :: RowList Type) (providedRL :: RowList Type) (provided :: Row Type) (queryParams :: Row Type) | rl -> queryParams where
45− buildMaybeQuery :: Proxy rl -> Proxy providedRL -> Record provided -> Record queryParams
46−
47−instance BuildMaybeQuery RL.Nil providedRL provided () where
48− buildMaybeQuery _ _ _ = {}
49−
50−instance
51− ( IsSymbol name
52− , HasField name providedRL has
53− , BuildMaybeQueryField has name ty provided
54− , BuildMaybeQuery tail providedRL provided tailQuery
55− , Row.Cons name (Maybe ty) tailQuery queryParams
56− , Row.Lacks name tailQuery
57− ) =>
58− BuildMaybeQuery (RL.Cons name ty tail) providedRL provided queryParams where
59− buildMaybeQuery _ prl provided = Record.insert (Proxy :: _ name) val rest
60− where
61− val = buildMaybeQueryField (Proxy :: _ has) (Proxy :: _ name) provided
62− rest = buildMaybeQuery (Proxy :: _ tail) prl provided
63−
64−-- | Split a query params row into optional raw types and required raw types.
65−-- | Maybe ty → optional with raw type ty
66−-- | ty (no Maybe) → required with raw type ty (from Required query params)
67−class SplitQueryRow (queryParams :: Row Type) (optionalRaw :: Row Type) (requiredRaw :: Row Type) | queryParams -> optionalRaw requiredRaw
68−
69−instance (RowToList queryParams rl, SplitQueryRowRL rl optionalRaw requiredRaw) => SplitQueryRow queryParams optionalRaw requiredRaw
70−
71−class SplitQueryRowRL (rl :: RowList Type) (optionalRaw :: Row Type) (requiredRaw :: Row Type) | rl -> optionalRaw requiredRaw
72−
73−instance SplitQueryRowRL RL.Nil () ()
74−
75−-- Maybe ty field → optional
76−instance
77− ( SplitQueryRowRL tail optTail reqTail
78− , Row.Cons name ty optTail optionalRaw
79− , Row.Lacks name optTail
80− ) =>
81− SplitQueryRowRL (RL.Cons name (Maybe ty) tail) optionalRaw reqTail
82−
83−-- Non-Maybe field → required (from Required query params)
84−else instance
85− ( SplitQueryRowRL tail optTail reqTail
86− , Row.Cons name ty reqTail requiredRaw
87− , Row.Lacks name reqTail
88− ) =>
89− SplitQueryRowRL (RL.Cons name ty tail) optTail requiredRaw
90−
91−-- | Build full queryParams from a partial record that contains path params,
92−-- | required query params, and optionally some optional query params.
93−-- | Walks the queryParams RowList:
94−-- | Maybe ty field → check if raw value provided, wrap in Just or Nothing
95−-- | bare ty field → copy from provided (required, always present)
96−class BuildQueryFromPartial (queryParams :: Row Type) (provided :: Row Type) where
97− buildQueryFromPartial :: Record provided -> Record queryParams
98−
99−instance (RowToList queryParams rl, RowToList provided prl, BuildQueryFromPartialRL rl prl provided queryParams) => BuildQueryFromPartial queryParams provided where
100− buildQueryFromPartial = buildQueryFromPartialRL (Proxy :: _ rl) (Proxy :: _ prl)
101−
102−class BuildQueryFromPartialRL (rl :: RowList Type) (providedRL :: RowList Type) (provided :: Row Type) (queryParams :: Row Type) | rl -> queryParams where
103− buildQueryFromPartialRL :: Proxy rl -> Proxy providedRL -> Record provided -> Record queryParams
104−
105−instance BuildQueryFromPartialRL RL.Nil providedRL provided () where
106− buildQueryFromPartialRL _ _ _ = {}
107−
108−-- Maybe ty: check if field is provided, wrap in Just or use Nothing
109−instance
110− ( IsSymbol name
111− , HasField name providedRL has
112− , BuildMaybeQueryField has name ty provided
113− , BuildQueryFromPartialRL tail providedRL provided tailQuery
114− , Row.Cons name (Maybe ty) tailQuery queryParams
115− , Row.Lacks name tailQuery
116− ) =>
117− BuildQueryFromPartialRL (RL.Cons name (Maybe ty) tail) providedRL provided queryParams where
118− buildQueryFromPartialRL _ prl provided = Record.insert (Proxy :: _ name) val rest
119− where
120− val = buildMaybeQueryField (Proxy :: _ has) (Proxy :: _ name) provided
121− rest = buildQueryFromPartialRL (Proxy :: _ tail) prl provided
122−
123−-- bare ty (Required): always present in provided, copy directly
124−else instance
125− ( IsSymbol name
126− , Row.Cons name ty rest provided
127− , BuildQueryFromPartialRL tail providedRL provided tailQuery
128− , Row.Cons name ty tailQuery queryParams
129− , Row.Lacks name tailQuery
130− ) =>
131− BuildQueryFromPartialRL (RL.Cons name ty tail) providedRL provided queryParams where
132− buildQueryFromPartialRL _ prl provided = Record.insert (Proxy :: _ name) val rest
133− where
134− val = Record.get (Proxy :: _ name) provided
135− rest = buildQueryFromPartialRL (Proxy :: _ tail) prl provided
src/Yoga/Fetch/Om/ReadOptional.js+4−1
@@ -3,7 +3,10 @@ import * as Maybe from "../Data.Maybe/index.js";
33export function readOptionalImpl(key) {
44 return function (obj) {
55 const val = obj[key];
6− if (val === undefined) return Maybe.Nothing.value;
6+ if (val === undefined || val instanceof Maybe.Nothing) {
7+ return Maybe.Nothing.value;
8+ }
9+ if (val instanceof Maybe.Just) return val;
710 return Maybe.Just.create(val);
811 };
912}
src/Yoga/Fetch/Om/ReadOptional.purs+3−2
@@ -4,16 +4,17 @@ module Yoga.Fetch.Om.ReadOptional
44 , readPartialQuery
55 ) where
66
7−import Data.Maybe (Maybe(..))
7+import Data.Maybe (Maybe)
88import Data.Symbol (class IsSymbol, reflectSymbol)
99import Prim.Row as Row
10−import Prim.RowList (class RowToList, RowList)
10+import Prim.RowList (RowList)
1111import Prim.RowList as RL
1212import Record as Record
1313import Type.Proxy (Proxy(..))
1414
1515foreign import readOptionalImpl :: forall a r. String -> Record r -> Maybe a
1616
17+-- | Accept explicit `Maybe` fields as well as undefined-aware partial records.
1718readOptional :: forall @name ty r. IsSymbol name => Record r -> Maybe ty
1819readOptional rec = readOptionalImpl (reflectSymbol (Proxy :: _ name)) rec
1920
src/Yoga/Fetch/Om/Simple.purs+6−15
@@ -32,21 +32,14 @@ import JS.Fetch as Fetch
3232import JS.Fetch.Duplex as Duplex
3333import JS.Fetch.Headers (Headers)
3434import JS.Fetch.Headers as Headers
35−import JS.Fetch.Integrity (Integrity(..))
36−import JS.Fetch.Referrer as Referrer
37−import JS.Fetch.ReferrerPolicy as ReferrerPolicy
3835import JS.Fetch.Request as Request
3936import JS.Fetch.RequestBody as Body
40−import JS.Fetch.RequestCache as Cache
41−import JS.Fetch.RequestCredentials as Credentials
42−import JS.Fetch.RequestMode as Mode
4337import JS.Fetch.Response as Resp
4438import Promise.Aff as Promise
4539import Type.Row.Homogeneous (class Homogeneous)
46−-- PlainText removed, using local phantom type
4740import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
4841import Yoga.JSON.Error (withStringErrors)
49−import Yoga.Om (class ToOm, Om, toOm, throw)
42+import Yoga.Om (Om, toOm, throw)
5043
5144type FetchError = { status :: Int, body :: String }
5245
@@ -56,9 +49,7 @@ class DecodeResponse :: forall k. k -> Type -> Constraint
5649class DecodeResponse a result | a -> result where
5750 decodeResponse :: String -> Either String result
5851
59−data PlainTextResponse
60−
61−instance DecodeResponse PlainTextResponse String where
52+instance DecodeResponse String String where
6253 decodeResponse = Right
6354
6455else instance ReadForeign a => DecodeResponse a a where
@@ -147,7 +138,7 @@ delete_
147138 => String
148139 -> Record headers
149140 -> Om ctx (fetchError :: FetchError | err) Unit
150−delete_ url headers = void $ simpleFetch @PlainTextResponse DELETE headers url Nothing
141+delete_ url headers = void $ simpleFetch @String DELETE headers url Nothing
151142
152143post
153144 :: forall @a result headers body ctx err
@@ -179,7 +170,7 @@ post_
179170 -> Record headers
180171 -> body
181172 -> Om ctx (fetchError :: FetchError | err) Unit
182−post_ url headers body = void $ simpleFetch @PlainTextResponse POST headers url (Just (writeJSON body))
173+post_ url headers body = void $ simpleFetch @String POST headers url (Just (writeJSON body))
183174
184175put
185176 :: forall @a result headers body ctx err
@@ -211,7 +202,7 @@ put_
211202 -> Record headers
212203 -> body
213204 -> Om ctx (fetchError :: FetchError | err) Unit
214−put_ url headers body = void $ simpleFetch @PlainTextResponse PUT headers url (Just (writeJSON body))
205+put_ url headers body = void $ simpleFetch @String PUT headers url (Just (writeJSON body))
215206
216207patch
217208 :: forall @a result headers body ctx err
@@ -243,4 +234,4 @@ patch_
243234 -> Record headers
244235 -> body
245236 -> Om ctx (fetchError :: FetchError | err) Unit
246−patch_ url headers body = void $ simpleFetch @PlainTextResponse PATCH headers url (Just (writeJSON body))
237+patch_ url headers body = void $ simpleFetch @String PATCH headers url (Just (writeJSON body))
src/Yoga/Fetch/Om/SplitResponses.purs+12−2
@@ -2,6 +2,7 @@ module Yoga.Fetch.Om.SplitResponses
22 ( class IsSuccessStatus
33 , class ExtractBodyRL
44 , class ExtractBody
5+ , class ClientResponseBody
56 , class SplitResponses
67 , class SplitResponsesRL
78 , class DispatchResponse
@@ -13,6 +14,8 @@ import Prim.RowList (RowList)
1314import Prim.RowList as RL
1415import Data.Unit (Unit)
1516import Yoga.HTTP.API.Route (Response)
17+import Yoga.HTTP.API.Route.Encoding (PlainText, Streaming)
18+import Yoga.Om.Strom (Strom)
1619
1720class IsSuccessStatus (label :: Symbol) (isSuccess :: Boolean) | label -> isSuccess
1821
@@ -37,13 +40,20 @@ instance ExtractBodyRL (RL.Cons "body" body tail) body
3740
3841else instance ExtractBodyRL tail body => ExtractBodyRL (RL.Cons label ty tail) body
3942
43+class ClientResponseBody (encoded :: Type) (decoded :: Type) | encoded -> decoded
44+
45+instance ClientResponseBody PlainText String
46+else instance ClientResponseBody (Streaming a) (Strom {} () a)
47+else instance ClientResponseBody body body
48+
4049class ExtractBody (responseType :: Type) (bodyType :: Type) | responseType -> bodyType
4150
42−instance ExtractBody (Response headers body) body
51+instance ClientResponseBody encoded body => ExtractBody (Response headers encoded) body
4352
4453else instance
4554 ( RL.RowToList row rl
46− , ExtractBodyRL rl body
55+ , ExtractBodyRL rl encoded
56+ , ClientResponseBody encoded body
4757 ) =>
4858 ExtractBody (Record row) body
4959
src/Yoga/Fetch/Om/StreamDecode.js+12−1
@@ -1 +1,12 @@
1−export const decodeUtf8 = (uint8array) => new TextDecoder().decode(uint8array);
1+export const newUtf8Decoder = () => new TextDecoder();
2+
3+export const decodeUtf8Chunks = (decoder) => (chunks) => () => {
4+ const decoded = [];
5+ for (const bytes of chunks) {
6+ const text = decoder.decode(bytes, { stream: true });
7+ if (text !== "") decoded.push(text);
8+ }
9+ return decoded;
10+};
11+
12+export const flushUtf8Decoder = (decoder) => () => decoder.decode();
src/Yoga/Fetch/Om/StreamDecode.purs+43−2
@@ -4,7 +4,15 @@ module Yoga.Fetch.Om.StreamDecode
44 ) where
55
66import Prelude
7+
8+import Control.Monad.Rec.Class (Step(..))
9+import Data.Array as Array
710import Data.ArrayBuffer.Types (Uint8Array)
11+import Data.Maybe (Maybe(..))
12+import Data.Tuple.Nested ((/\))
13+import Effect (Effect)
14+import Effect.Class (liftEffect)
15+import Yoga.Om (Om)
816import Yoga.Om.Strom (Strom)
917import Yoga.Om.Strom as Strom
1018
@@ -15,6 +23,39 @@ instance StreamDecode Uint8Array where
1523 decodeStream = identity
1624
1725else instance StreamDecode String where
18− decodeStream = Strom.mapStrom decodeUtf8
26+ decodeStream stream =
27+ Strom.fromOm (liftEffect newUtf8Decoder) >>= \decoder ->
28+ decodeUtf8Stream decoder stream
29+
30+data Utf8Decoder
31+
32+foreign import newUtf8Decoder :: Effect Utf8Decoder
33+
34+foreign import decodeUtf8Chunks :: Utf8Decoder -> Array Uint8Array -> Effect (Array String)
35+
36+foreign import flushUtf8Decoder :: Utf8Decoder -> Effect String
37+
38+decodeUtf8Stream :: Utf8Decoder -> Strom {} () Uint8Array -> Strom {} () String
39+decodeUtf8Stream decoder stream = Strom.mkStrom do
40+ step <- Strom.runStrom stream
41+ case step of
42+ Done maybeChunk -> do
43+ decoded <- decodeChunk decoder maybeChunk
44+ trailing <- liftEffect $ flushUtf8Decoder decoder
45+ let
46+ finalChunk =
47+ if trailing == "" then decoded
48+ else Array.snoc decoded trailing
49+ pure $ Done (nonEmpty finalChunk)
50+ Loop (maybeChunk /\ next) -> do
51+ decoded <- decodeChunk decoder maybeChunk
52+ pure $ Loop (nonEmpty decoded /\ decodeUtf8Stream decoder next)
53+
54+decodeChunk :: Utf8Decoder -> Maybe (Array Uint8Array) -> Om {} () (Array String)
55+decodeChunk _ Nothing = pure []
56+decodeChunk decoder (Just chunk) = liftEffect $ decodeUtf8Chunks decoder chunk
1957
20−foreign import decodeUtf8 :: Uint8Array -> String
58+nonEmpty :: forall a. Array a -> Maybe (Array a)
59+nonEmpty values
60+ | Array.null values = Nothing
61+ | otherwise = Just values
test/BodylessResponse.Example.purs+1−1
@@ -23,5 +23,5 @@ type ResourceAPI =
2323-- This must typecheck: the derived client exposes notModified/unauthorized as Unit errors.
2424api
2525 :: forall ctx err
26− . { getResource :: Om ctx (notModified :: Unit, unauthorized :: Unit | err) Resource }
26+ . { getResource :: Om (Record ctx) (notModified :: Unit, unauthorized :: Unit | err) Resource }
2727api = client @ResourceAPI "https://api.example.com"
test/BodylessResponse.Spec.purs+31−4
@@ -12,9 +12,9 @@ import Test.Spec (Spec, around, describe, it)
1212import Test.Spec.Assertions (shouldEqual)
1313import Yoga.Fastify.Fastify as F
1414import Yoga.Fastify.Fastify (Port(..), Host(..), RouteURL(..), StatusCode(..))
15−import Yoga.Fastify.Om (createOmFastify, getOm)
16−import Yoga.Fetch.Om (GET, Route, type (/), client)
17−import Yoga.Om (Om, runOm)
15+import Yoga.Fastify.Om (createOmFastify, deleteOm, getOm)
16+import Yoga.Fetch.Om (DELETE, GET, Route, type (/), type (:), client)
17+import Yoga.Om (runOm)
1818
1919type Resource = { id :: Int, name :: String }
2020
@@ -27,9 +27,15 @@ type ResourceAPI =
2727 , notModified :: {}
2828 , unauthorized :: {}
2929 )
30+ , deleteUser ::
31+ Route DELETE
32+ ("bodyless-test" / "users" / "id" : Int)
33+ {}
34+ ( noContent :: {}
35+ , notFound :: { body :: { error :: String } }
36+ )
3037 }
3138
32−api :: forall ctx err. { getResource :: Om ctx (notModified :: Unit, unauthorized :: Unit | err) Resource }
3339api = client @ResourceAPI "http://localhost:44932"
3440
3541spec :: Spec Unit
@@ -68,6 +74,15 @@ spec = around withServer $ describe "bodyless responses (notModified / unauthori
6874 api.getResource
6975 liftAff $ resource `shouldEqual` { id: 1, name: "test" }
7076
77+ it "204 No Content returns Unit for noContent shorthand" \serverMode -> do
78+ liftEffect $ Ref.write "deleted" serverMode
79+ result <- runOm {}
80+ { exception: \_ -> pure false
81+ , notFound: \_ -> pure false
82+ }
83+ (api.deleteUser { id: 1 } $> true)
84+ liftAff $ result `shouldEqual` true
85+
7186withServer :: forall a. (Ref String -> Aff a) -> Aff a
7287withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
7388 where
@@ -77,6 +92,7 @@ withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
7792 f <- F.fastify {}
7893 omApp <- createOmFastify {} f
7994 getOm (RouteURL "/bodyless-test/resource") (handler serverMode) omApp
95+ deleteOm (RouteURL "/bodyless-test/users/:id") (deleteHandler serverMode) omApp
8096 pure f
8197 void $ F.listen { port: Port 44932, host: Host "0.0.0.0" } fastify
8298 pure { fastify, serverMode }
@@ -96,3 +112,14 @@ withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
96112 void $ F.status (StatusCode 200) reply # liftEffect
97113 void $ F.header "content-type" "application/json" reply # liftEffect
98114 F.sendJson (unsafeToForeign { id: 1, name: "test" }) reply # liftAff
115+
116+ deleteHandler serverMode reply = do
117+ mode <- liftEffect $ Ref.read serverMode
118+ case mode of
119+ "deleted" -> do
120+ void $ F.status (StatusCode 204) reply # liftEffect
121+ F.send (unsafeToForeign "") reply # liftAff
122+ _ -> do
123+ void $ F.status (StatusCode 404) reply # liftEffect
124+ void $ F.header "content-type" "application/json" reply # liftEffect
125+ F.sendJson (unsafeToForeign { error: "Missing user" }) reply # liftAff
test/BuildUrl.Spec.purs+74−6
@@ -6,6 +6,7 @@ import Data.Maybe (Maybe(..))
66import Test.Spec (Spec, describe, it)
77import Test.Spec.Assertions (shouldEqual)
88import Type.Proxy (Proxy(..))
9+import Unsafe.Coerce (unsafeCoerce)
910import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
1011import Yoga.HTTP.API.Path (Path, type (/), type (:))
1112
@@ -33,6 +34,19 @@ spec = do
3334 { slug: "hello-world" }
3435 "/posts/:slug"
3536 result `shouldEqual` "/posts/hello-world"
37+ it "URL-encodes reserved characters in path params" do
38+ let
39+ result = substitutePathParams @(slug :: String)
40+ { slug: "a/b c?d#e" }
41+ "/users/:slug"
42+ result `shouldEqual` "/users/a%2Fb%20c%3Fd%23e"
43+
44+ it "distinguishes path param names with shared prefixes" do
45+ let
46+ result = substitutePathParams @(id :: Int, id2 :: Int)
47+ { id: 1, id2: 2 }
48+ "/pairs/:id/:id2"
49+ result `shouldEqual` "/pairs/1/2"
3650
3751 it "handles empty params" do
3852 let result = substitutePathParams @() {} "/users"
@@ -50,24 +64,32 @@ spec = do
5064 "/users"
5165 result `shouldEqual` "/users?limit=10&offset=20"
5266
53− it "handles Maybe params - Just" do
67+ it "handles optional params - provided via partial record" do
5468 let
5569 result = appendQueryParams @(limit :: Maybe Int)
56− { limit: Just 10 }
70+ (unsafeCoerce { limit: 10 } :: Record (limit :: Maybe Int))
5771 "/users"
5872 result `shouldEqual` "/users?limit=10"
5973
60− it "handles Maybe params - Nothing" do
74+ it "serializes a present explicit Maybe query param" do
75+ let result = appendQueryParams @(id :: Maybe Int) { id: Just 42 } "/users"
76+ result `shouldEqual` "/users?id=42"
77+
78+ it "omits an explicit Nothing query param" do
79+ let result = appendQueryParams @(id :: Maybe Int) { id: Nothing } "/users"
80+ result `shouldEqual` "/users"
81+
82+ it "handles optional params - omitted via partial record" do
6183 let
6284 result = appendQueryParams @(limit :: Maybe Int)
63− { limit: Nothing }
85+ (unsafeCoerce {} :: Record (limit :: Maybe Int))
6486 "/users"
6587 result `shouldEqual` "/users"
6688
67− it "handles mixed Maybe and required params" do
89+ it "handles mixed optional and required params" do
6890 let
6991 result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
70− { limit: Nothing, offset: 20 }
92+ (unsafeCoerce { offset: 20 } :: Record (limit :: Maybe Int, offset :: Int))
7193 "/users"
7294 result `shouldEqual` "/users?offset=20"
7395
@@ -78,6 +100,34 @@ spec = do
78100 it "URL-encodes special characters in values" do
79101 let result = appendQueryParams @(search :: String) { search: "hello world&more=yes" } "/users"
80102 result `shouldEqual` "/users?search=hello%20world%26more%3Dyes"
103+ it "appends query params to an existing query string" do
104+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users?token=old"
105+ result `shouldEqual` "/users?token=old&search=new%20value"
106+
107+ it "inserts query params before a URL fragment" do
108+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users#details"
109+ result `shouldEqual` "/users?search=new%20value#details"
110+
111+ it "repeats Array query params one pair per element" do
112+ let
113+ result = appendQueryParams @(categories :: Array Int)
114+ { categories: [ 3030, 103130 ] }
115+ "/users"
116+ result `shouldEqual` "/users?categories=3030&categories=103130"
117+
118+ it "omits empty Array query params" do
119+ let
120+ result = appendQueryParams @(categories :: Array Int)
121+ { categories: [] }
122+ "/users"
123+ result `shouldEqual` "/users"
124+
125+ it "mixes Array and bare query params" do
126+ let
127+ result = appendQueryParams @(query :: String, categories :: Array Int)
128+ { query: "foo", categories: [ 1, 2 ] }
129+ "/users"
130+ result `shouldEqual` "/users?categories=1&categories=2&query=foo"
81131
82132 describe "buildUrl" do
83133 it "builds URL with path params only" do
@@ -97,3 +147,21 @@ spec = do
97147 {}
98148 { limit: 10, offset: 20 }
99149 result `shouldEqual` "https://api.example.com/users?limit=10&offset=20"
150+
151+ it "appends route paths before base URL query strings and fragments" do
152+ let
153+ result = buildUrl
154+ "https://api.example.com/base/?token=old#details"
155+ (Proxy :: _ (Path "users"))
156+ {}
157+ { search: "new value" }
158+ result `shouldEqual` "https://api.example.com/base/users?token=old&search=new%20value#details"
159+
160+ it "does not substitute path captures inside a base URL query string" do
161+ let
162+ result = buildUrl
163+ "https://api.example.com?redirect=/:id#details"
164+ (Proxy :: _ (Path ("users" / "id" : Int)))
165+ { id: 42 }
166+ {}
167+ result `shouldEqual` "https://api.example.com/users/42?redirect=/:id#details"
test/BuildUrlProperty.Spec.pursnew file+256
@@ -0,0 +1,256 @@
1+module BuildUrlProperty.Spec where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Enum (toEnum)
8+import Data.Foldable (all)
9+import Data.Maybe (Maybe(..), fromJust, isJust)
10+import Data.String as String
11+import Data.String.CodeUnits as CodeUnits
12+import Data.String.Gen (genAsciiString)
13+import Data.String.CodePoints (CodePoint, fromCodePointArray)
14+import Data.String.Pattern (Pattern(..))
15+import JSURI (decodeURIComponent)
16+import Test.QuickCheck ((<?>), (===))
17+import Test.QuickCheck.Arbitrary (arbitrary)
18+import Test.QuickCheck.Gen (Gen, arrayOf1, chooseInt)
19+import Test.Spec (Spec, describe, it)
20+import Test.Spec.Assertions (shouldEqual)
21+import Test.Spec.QuickCheck (quickCheck)
22+import Partial.Unsafe (unsafePartial)
23+import Unsafe.Coerce (unsafeCoerce)
24+import Yoga.Fetch.Om.BuildUrl (appendQueryParams, substitutePathParams)
25+
26+spec :: Spec Unit
27+spec = describe "URL properties" do
28+ it "never substitutes path captures in the query or fragment" $ quickCheck do
29+ value <- (genAsciiString :: Gen String)
30+ let
31+ suffix = "?next=/:slug#/:slug"
32+ result = substitutePathParams @(slug :: String)
33+ { slug: value }
34+ ("/items/:slug" <> suffix)
35+ property = case String.stripSuffix (Pattern suffix) result of
36+ Nothing -> false
37+ Just path -> not $ String.contains (Pattern ":slug") path
38+ pure $ property <?> "unexpected path substitution result: " <> show result
39+
40+ it "round-trips percent-encoded path and query values" $ quickCheck do
41+ value <- (genAsciiString :: Gen String)
42+ let
43+ pathResult = substitutePathParams @(value :: String)
44+ { value }
45+ "/items/:value"
46+ queryResult = appendQueryParams @(value :: String)
47+ { value }
48+ "/items"
49+ property = case
50+ String.stripPrefix (Pattern "/items/") pathResult,
51+ String.stripPrefix (Pattern "/items?value=") queryResult
52+ of
53+ Just encodedPath, Just encodedQuery ->
54+ decodeURIComponent encodedPath == Just value
55+ && decodeURIComponent encodedQuery == Just value
56+ _, _ -> false
57+ pure $ property <?> "failed to round-trip value " <> show value
58+
59+ it "preserves existing query values and fragments" $ quickCheck do
60+ existing <- (genAsciiString :: Gen String)
61+ added <- (genAsciiString :: Gen String)
62+ let
63+ base = appendQueryParams @(token :: String)
64+ { token: existing }
65+ "/items#details"
66+ result = appendQueryParams @(search :: String)
67+ { search: added }
68+ base
69+ property = do
70+ withoutFragment <- String.stripSuffix (Pattern "#details") result
71+ query <- String.stripPrefix (Pattern "/items?token=") withoutFragment
72+ case String.split (Pattern "&search=") query of
73+ [ encodedExisting, encodedAdded ] ->
74+ pure
75+ $ decodeURIComponent encodedExisting == Just existing
76+ && decodeURIComponent encodedAdded == Just added
77+ _ -> Nothing
78+ pure
79+ $ (property == Just true)
80+ <?> "query or fragment changed unexpectedly: " <> show result
81+
82+ it "preserves repeated query parameter order and multiplicity" $ quickCheck do
83+ values <- arbitrary
84+ let
85+ result = appendQueryParams @(tag :: Array Int) { tag: values } "/items"
86+ expected = case values of
87+ [] -> "/items"
88+ _ -> "/items?" <> String.joinWith "&" (map (\value -> "tag=" <> show value) values)
89+ pure $ result === expected
90+
91+ it "normalizes absent, Nothing, and Just optional fields" $ quickCheck do
92+ missing <- arbitrary
93+ value <- arbitrary
94+ let
95+ params :: Record (id :: Maybe Int)
96+ params = if missing then unsafeCoerce {} else { id: value }
97+ result = appendQueryParams @(id :: Maybe Int) params "/items"
98+ expected = case missing, value of
99+ true, _ -> "/items"
100+ false, Nothing -> "/items"
101+ false, Just id -> "/items?id=" <> show id
102+ pure $ result === expected
103+
104+ it "never introduces malformed query separators" $ quickCheck do
105+ value <- arbitrary
106+ let
107+ bases =
108+ [ "/items"
109+ , "/items?existing=1"
110+ , "/items?"
111+ , "/items?existing=1&"
112+ , "/items#details"
113+ , "/items?existing=1#details"
114+ , "/items?#details"
115+ , "/items?existing=1&#details"
116+ ]
117+ results = map (appendQueryParams @(id :: Int) { id: value }) bases
118+ pure
119+ $ all isWellFormed results
120+ <?> "malformed URL generated from " <> show results
121+
122+ it "preserves repeated existing query keys" $ quickCheck do
123+ first <- (arbitrary :: Gen Int)
124+ second <- (arbitrary :: Gen Int)
125+ page <- (arbitrary :: Gen Int)
126+ let
127+ base = "/items?id=" <> show first <> "&id=" <> show second <> "#details"
128+ result = appendQueryParams @(page :: Int) { page } base
129+ expected =
130+ "/items?id=" <> show first <> "&id=" <> show second
131+ <> "&page="
132+ <> show page
133+ <> "#details"
134+ pure $ result === expected
135+
136+ it "appends duplicate query values without coalescing existing ones" $ quickCheck do
137+ first <- (arbitrary :: Gen Int)
138+ second <- (arbitrary :: Gen Int)
139+ added <- (arbitrary :: Gen (Array Int))
140+ let
141+ base = "/items?id=" <> show first <> "&id=" <> show second
142+ result = appendQueryParams @(id :: Array Int) { id: added } base
143+ expected = case added of
144+ [] -> base
145+ _ -> base <> "&" <> String.joinWith "&" (map (\value -> "id=" <> show value) added)
146+ pure $ result === expected
147+
148+ it "ignores query-looking text inside fragments" $ quickCheck do
149+ value <- (arbitrary :: Gen Int)
150+ let
151+ rendered = show (value :: Int)
152+ cases =
153+ [ { base: "/items#section?fake=1", expected: "/items?id=" <> rendered <> "#section?fake=1" }
154+ , { base: "/items?real=1#section?fake=1", expected: "/items?real=1&id=" <> rendered <> "#section?fake=1" }
155+ , { base: "/items#?fake=1&other=2", expected: "/items?id=" <> rendered <> "#?fake=1&other=2" }
156+ , { base: "/items#first#second?fake=1", expected: "/items?id=" <> rendered <> "#first#second?fake=1" }
157+ ]
158+ property = all
159+ (\entry -> appendQueryParams @(id :: Int) { id: value } entry.base == entry.expected)
160+ cases
161+ pure $ property <?> "fragment handling failed for id " <> rendered
162+
163+ it "encodes every query delimiter inside values" $ quickCheck do
164+ prefix <- (genAsciiString :: Gen String)
165+ suffix <- (genAsciiString :: Gen String)
166+ let
167+ value = prefix <> "?#&=%+ /" <> suffix
168+ result = appendQueryParams @(value :: String) { value } "/items"
169+ property = case String.stripPrefix (Pattern "/items?value=") result of
170+ Nothing -> false
171+ Just encoded ->
172+ decodeURIComponent encoded == Just value
173+ && all
174+ (\delimiter -> not $ String.contains (Pattern delimiter) encoded)
175+ [ "?", "#", "&", "=", "+", "/" ]
176+ && String.contains (Pattern "%25") encoded
177+ pure $ property <?> "delimiter encoding failed: " <> show result
178+
179+ it "substitutes repeated captures without matching overlapping names" $ quickCheck do
180+ id <- (arbitrary :: Gen Int)
181+ id2 <- (arbitrary :: Gen Int)
182+ let
183+ result = substitutePathParams @(id :: Int, id2 :: Int)
184+ { id, id2 }
185+ "/:id/:id/:id2/:id20?next=/:id#/:id2"
186+ expected =
187+ "/" <> show id <> "/" <> show id <> "/" <> show id2
188+ <> "/:id20?next=/:id#/:id2"
189+ pure $ result === expected
190+
191+ it "normalizes lone UTF-16 surrogates before percent-encoding" do
192+ let
193+ value = codeUnit 0xD800 <> "x" <> codeUnit 0xDFFF
194+ result = appendQueryParams @(value :: String) { value } "/items"
195+ result `shouldEqual` "/items?value=%EF%BF%BDx%EF%BF%BD"
196+
197+ it "percent-encodes arbitrary UTF-16 strings without throwing" $ quickCheck do
198+ value <- (arbitrary :: Gen String)
199+ let
200+ pathResult = substitutePathParams @(value :: String) { value } "/items/:value"
201+ queryResult = appendQueryParams @(value :: String) { value } "/items"
202+ property =
203+ isJust (String.stripPrefix (Pattern "/items/") pathResult)
204+ && isJust (String.stripPrefix (Pattern "/items?value=") queryResult)
205+ pure $ property <?> "encoding failed for UTF-16 input " <> show value
206+
207+ it "round-trips valid non-ASCII scalar values" $ quickCheck do
208+ value <- unicodeScalarString
209+ let
210+ pathResult = substitutePathParams @(value :: String) { value } "/items/:value"
211+ queryResult = appendQueryParams @(value :: String) { value } "/items"
212+ property = case
213+ String.stripPrefix (Pattern "/items/") pathResult,
214+ String.stripPrefix (Pattern "/items?value=") queryResult
215+ of
216+ Just encodedPath, Just encodedQuery ->
217+ decodeURIComponent encodedPath == Just value
218+ && decodeURIComponent encodedQuery == Just value
219+ _, _ -> false
220+ pure $ property <?> "Unicode round-trip failed for " <> show value
221+
222+isWellFormed :: String -> Boolean
223+isWellFormed url =
224+ occurrences "?" url == 1
225+ && occurrences "#" url <= 1
226+ && not (String.contains (Pattern "??") url)
227+ && not (String.contains (Pattern "&&") url)
228+ && not (String.contains (Pattern "?#") url)
229+ && not (String.contains (Pattern "&#") url)
230+ && queryPrecedesFragment url
231+
232+queryPrecedesFragment :: String -> Boolean
233+queryPrecedesFragment url = case String.indexOf (Pattern "#") url of
234+ Nothing -> true
235+ Just fragmentIndex -> case String.indexOf (Pattern "?") url of
236+ Nothing -> false
237+ Just queryIndex -> queryIndex < fragmentIndex
238+
239+occurrences :: String -> String -> Int
240+occurrences needle = (_ - 1) <<< Array.length <<< String.split (Pattern needle)
241+
242+unicodeScalarString :: Gen String
243+unicodeScalarString =
244+ fromCodePointArray <<< NEA.toArray <$> arrayOf1 unicodeScalar
245+
246+unicodeScalar :: Gen CodePoint
247+unicodeScalar = do
248+ useBasicMultilingualPlane <- arbitrary
249+ value <- if useBasicMultilingualPlane then
250+ chooseInt 0x80 0xD7FF
251+ else
252+ chooseInt 0xE000 0x10FFFF
253+ pure $ unsafePartial $ fromJust $ toEnum value
254+
255+codeUnit :: Int -> String
256+codeUnit value = CodeUnits.singleton $ unsafePartial $ fromJust $ toEnum value
test/Complete.Example.purs+14−19
@@ -3,9 +3,9 @@ module Complete.Example where
33import Prelude
44
55import Data.Array (length)
6−import Data.Maybe (Maybe(..))
6+import Justifill (justifill)
77import Effect.Class.Console (log)
8−import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), client)
8+import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, type (/), type (:), type (:?), client)
99import Yoga.HTTP.API.Route (BearerToken(..))
1010import Yoga.Om (Om, handleErrors)
1111
@@ -31,43 +31,37 @@ type ErrorMessage =
3131
3232type UserAPI =
3333 { getUser ::
34− Route GET
35− (Path ("users" / "id" : Int))
34+ Route GET ("users" / "id" : Int)
3635 {}
3736 ( ok :: { body :: User }
3837 , notFound :: { body :: ErrorMessage }
3938 )
4039 , listUsers ::
41− Route GET
42− (Path "users" :? { limit :: Int, offset :: Int })
40+ Route GET ("users" :? { limit :: Int, offset :: Int })
4341 {}
4442 ( ok :: { body :: Array User }
4543 )
4644 , createUser ::
47− Route POST
48− (Path "users")
45+ Route POST "users"
4946 { body :: JSON CreateUserRequest }
5047 ( created :: { body :: User }
5148 , badRequest :: { body :: ErrorMessage }
5249 )
5350 , updateUser ::
54− Route PUT
55− (Path ("users" / "id" : Int))
51+ Route PUT ("users" / "id" : Int)
5652 { body :: JSON UpdateUserRequest }
5753 ( ok :: { body :: User }
5854 , notFound :: { body :: ErrorMessage }
5955 , badRequest :: { body :: ErrorMessage }
6056 )
6157 , deleteUser ::
62− Route DELETE
63− (Path ("users" / "id" : Int))
58+ Route DELETE ("users" / "id" : Int)
6459 {}
65− ( noContent :: { body :: {} }
60+ ( noContent :: {}
6661 , notFound :: { body :: ErrorMessage }
6762 )
6863 , createUserAuth ::
69− Route POST
70− (Path "users")
64+ Route POST "users"
7165 { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
7266 ( created :: { body :: User }
7367 , badRequest :: { body :: ErrorMessage }
@@ -85,15 +79,15 @@ exampleGetUserHandled :: Om {} () Unit
8579exampleGetUserHandled = do
8680 user <- api.getUser { id: 42 }
8781 # handleErrors
88− { notFound: \err -> do
82+ { notFound: \err -> ado
8983 log $ "User not found: " <> err.error
90− pure { id: 0, name: "unknown", email: "" }
84+ in { id: 0, name: "unknown", email: "" }
9185 }
9286 log $ "Found user: " <> user.name
9387
9488exampleListUsers :: Om {} () Unit
9589exampleListUsers = do
96− users <- api.listUsers { limit: Just 10, offset: Just 0 }
90+ users <- api.listUsers (justifill { limit: 10, offset: 0 })
9791 log $ "Found " <> show (length users) <> " users"
9892
9993exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
@@ -106,7 +100,8 @@ exampleCreateUser = do
106100
107101exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
108102exampleUpdateUser = do
109− user <- api.updateUser { id: 42 }
103+ user <- api.updateUser
104+ { id: 42 }
110105 { name: "Alice Updated"
111106 , email: "alice.new@example.com"
112107 }
test/Example.purs+13−8
@@ -4,41 +4,46 @@ import Yoga.HTTP.API.Route (GET, POST, DELETE, Route, BearerToken)
44import Yoga.HTTP.API.Route.Encoding (JSON)
55import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
66
7−type User = { id :: Int, name :: String, email :: String }
8−type CreateUserRequest = { name :: String, email :: String }
7+type User = { id :: UserId, name :: UserName, email :: UserMail }
8+type CreateUserRequest = { name :: String, email :: UserMail }
99type ErrorMessage = { error :: String }
10+newtype UserId = UserId Int
11+newtype UserName = UserName String
12+newtype UserMail = UserMail String
13+newtype Limit = Limit Int
14+newtype Offset = Offset Int
1015
1116type UserAPI =
1217 { getUser ::
1318 Route GET
14− (Path ("users" / "id" : Int))
19+ ("users" / "id" : UserId)
1520 {}
1621 ( ok :: { body :: User }
1722 , notFound :: { body :: ErrorMessage }
1823 )
1924 , listUsers ::
2025 Route GET
21− (Path "users" :? { limit :: Int, offset :: Int })
26+ ("users" :? { limit :: Limit, offset :: Offset })
2227 {}
2328 ( ok :: { body :: Array User }
2429 )
2530 , createUser ::
2631 Route POST
27− (Path "users")
32+ "users"
2833 { body :: JSON CreateUserRequest }
2934 ( created :: { body :: User }
3035 , badRequest :: { body :: ErrorMessage }
3136 )
3237 , deleteUser ::
3338 Route DELETE
34− (Path ("users" / "id" : Int))
39+ ("users" / "id" : UserId)
3540 {}
36− ( noContent :: { body :: {} }
41+ ( noContent :: {}
3742 , notFound :: { body :: ErrorMessage }
3843 )
3944 , createUserAuth ::
4045 Route POST
41− (Path "users")
46+ "users"
4247 { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
4348 ( created :: { body :: User }
4449 , badRequest :: { body :: ErrorMessage }
test/MakeRequest.Spec.purs+15−5
@@ -3,22 +3,32 @@ module MakeRequest.Spec where
33import Prelude
44
55import Data.HTTP.Method as HTTP
6+import Data.Maybe (Maybe(..))
67import Data.Tuple.Nested ((/\))
78import JS.Fetch.Headers as Headers
89import Prim.RowList as RL
910import Test.Spec (Spec, describe, it)
1011import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
1112import Type.Proxy (Proxy(..))
12−import Yoga.Fetch.Om (toHeaders)
13−import Yoga.Fetch.Om.MakeRequest (httpMethod)
13+import Yoga.Fetch.Om (FormData, PlainText, QUERY, toHeaders)
14+import Yoga.Fetch.Om.MakeRequest (encodeBody, encodingContentType, httpMethod)
1415import Yoga.HTTP.API.Route (BearerToken(..))
15−import Yoga.HTTP.API.Route.Method as Route
1616
1717spec :: Spec Unit
1818spec = do
1919 describe "MakeRequest" do
20− it "maps a QUERY route method to the QUERY runtime method" do
21− httpMethod (Proxy :: Proxy Route.QUERY) `shouldEqual` HTTP.QUERY
20+ it "maps a QUERY route method imported from the public module" do
21+ httpMethod (Proxy :: Proxy QUERY) `shouldEqual` HTTP.QUERY
22+
23+ describe "BodyEncoding" do
24+ it "sends PlainText request bodies without JSON quoting" do
25+ encodingContentType (Proxy :: Proxy PlainText) `shouldEqual` "text/plain"
26+ encodeBody @PlainText "hello" `shouldEqual` Just "hello"
27+
28+ it "URL-encodes FormData request bodies" do
29+ encodingContentType (Proxy :: Proxy (FormData { username :: String })) `shouldEqual` "application/x-www-form-urlencoded"
30+ encodeBody @(FormData { username :: String }) { username: "Ada Lovelace" }
31+ `shouldEqual` Just "username=Ada+Lovelace"
2232
2333 describe "toHeaders" do
2434 it "empty headers produces no entries" do
test/PlainTextResponse.Spec.purs+49−8
@@ -1,42 +1,54 @@
11module PlainTextResponse.Spec where
22
33import Prelude
4+import Data.String as String
45
56import Effect.Aff (Aff, bracket)
67import Effect.Aff.Class (liftAff)
78import Effect.Class (liftEffect)
89import Effect.Ref (Ref)
10+import Effect.Exception as Exn
911import Effect.Ref as Ref
1012import Foreign (unsafeToForeign)
1113import Test.Spec (Spec, around, describe, it)
1214import Test.Spec.Assertions (shouldEqual)
1315import Yoga.Fastify.Fastify as F
1416import Yoga.Fastify.Fastify (Port(..), Host(..), RouteURL(..), StatusCode(..))
15−import Yoga.Fastify.Om (createOmFastify, postOm)
16−import Yoga.Fetch.Om (POST, Route, type (/), JSON, client)
17+import Yoga.Fastify.Om (createOmFastify, getOm, postOm)
18+import Yoga.Fetch.Om (GET, POST, Route, type (/), JSON, PlainText, Streaming, client)
19+import Yoga.Fetch.Om.Simple as Simple
1720import Yoga.Om (Om, runOm)
21+import Yoga.Om.Strom (Strom)
22+import Yoga.Om.Strom as Strom
1823
1924type Payload = { value :: Int }
20−type Result = { doubled :: Int }
25+type Result = { doubled :: Int }
2126
2227type TestAPI =
2328 { double ::
2429 Route POST
2530 ("plain-text-test" / "double")
2631 { body :: JSON Payload }
27− ( ok :: { body :: Result }
28− , badRequest :: { body :: String }
32+ ( ok :: { body :: Result }
33+ , badRequest :: { body :: PlainText }
2934 )
35+ , health :: Route GET ("plain-text-test" / "health") {} (ok :: { body :: PlainText })
36+ , stream :: Route GET ("plain-text-test" / "stream") {} (ok :: { body :: Streaming String })
3037 }
3138
32−api :: { double :: forall ctx err. Payload -> Om ctx (badRequest :: String | err) Result }
39+api
40+ :: forall ctx err
41+ . { double :: Payload -> Om (Record ctx) (badRequest :: String | err) Result
42+ , health :: Om (Record ctx) err String
43+ , stream :: Om (Record ctx) err (Strom {} () String)
44+ }
3345api = client @TestAPI "http://localhost:44933"
3446
3547spec :: Spec Unit
3648spec = around withServer $ describe "String body on error responses" do
3749 it "200 OK decodes the JSON response body" \_ -> do
3850 result <- runOm {}
39− { exception: \_ -> pure { doubled: -1 }
51+ { exception: \_ -> pure { doubled: -1 }
4052 , badRequest: \_ -> pure { doubled: -1 }
4153 }
4254 (api.double { value: 21 })
@@ -46,13 +58,35 @@ spec = around withServer $ describe "String body on error responses" do
4658 liftEffect $ Ref.write "badRequest" serverMode
4759 captured <- liftEffect $ Ref.new ""
4860 runOm {}
49− { exception: \_ -> pure unit
61+ { exception: \_ -> pure unit
5062 , badRequest: \b -> liftEffect $ Ref.write b captured
5163 }
5264 (void $ api.double { value: 0 })
5365 body <- liftEffect $ Ref.read captured
5466 liftAff $ body `shouldEqual` "invalid value"
5567
68+ it "Simple.get returns plain text for String responses" \_ -> do
69+ result <- runOm {}
70+ { exception: \e -> pure $ "exception: " <> Exn.message e
71+ , fetchError: \e -> pure $ "fetch error: " <> e.body
72+ }
73+ (Simple.get @String "http://localhost:44933/plain-text-test/health" {})
74+ liftAff $ result `shouldEqual` "healthy"
75+
76+ it "derived PlainText responses decode to String" \_ -> do
77+ result <- runOm {}
78+ { exception: \e -> pure $ "exception: " <> Exn.message e }
79+ api.health
80+ liftAff $ result `shouldEqual` "healthy"
81+
82+ it "derived Streaming responses decode to a usable Strom" \_ -> do
83+ chunks <- runOm {}
84+ { exception: \e -> pure [ "exception: " <> Exn.message e ] }
85+ do
86+ stream <- api.stream
87+ Strom.runCollect stream
88+ liftAff $ String.joinWith "" chunks `shouldEqual` "stream €"
89+
5690withServer :: forall a. (Ref String -> Aff a) -> Aff a
5791withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
5892 where
@@ -62,12 +96,19 @@ withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
6296 f <- F.fastify {}
6397 omApp <- createOmFastify {} f
6498 postOm (RouteURL "/plain-text-test/double") (handler serverMode) omApp
99+ getOm (RouteURL "/plain-text-test/health") (plainTextHandler "healthy") omApp
100+ getOm (RouteURL "/plain-text-test/stream") (plainTextHandler "stream €") omApp
65101 pure f
66102 void $ F.listen { port: Port 44933, host: Host "0.0.0.0" } fastify
67103 pure { fastify, serverMode }
68104
69105 release { fastify } = F.close fastify
70106
107+ plainTextHandler text reply = do
108+ void $ F.status (StatusCode 200) reply # liftEffect
109+ void $ F.header "content-type" "text/plain" reply # liftEffect
110+ F.send (unsafeToForeign text) reply # liftAff
111+
71112 handler serverMode reply = do
72113 mode <- liftEffect $ Ref.read serverMode
73114 case mode of
test/Polymorphic.Spec.purs+20−17
@@ -12,7 +12,8 @@ import Yoga.Fastify.Fastify (Port(..), Host(..))
1212import Yoga.Fastify.Om.API (registerAPI)
1313import Yoga.Fastify.Om.Route (Handler, handle, respond)
1414import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
15−import Yoga.Om (Om, ask, runOm)
15+import Yoga.Om (ask, runOm)
16+import Control.Monad.Error.Class (throwError)
1617
1718type User = { id :: Int, name :: String }
1819
@@ -27,26 +28,28 @@ api = client @TestAPI "http://localhost:44931"
2728spec :: Spec Unit
2829spec = around withServer $ describe "polymorphic client" do
2930 it "fetches user with { token :: String } context" \_ ->
30− runOm { token: "secret123" } { exception: \_ -> pure unit } do
31− ctx <- ask
32− user <- api.getUser { id: 42 }
33− liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "secret123:User 42"
31+ runOm { token: "secret123" } { exception: throwError } do
32+ { token } <- ask
33+ { name } <- api.getUser { id: 42 }
34+ liftAff $ (token <> ":" <> name) `shouldEqual` "secret123:User 42"
3435
3536 it "fetches user with { apiKey :: String, userId :: Int } context" \_ ->
36− runOm { apiKey: "mykey", userId: 7 } { exception: \_ -> pure unit } do
37− ctx <- ask
38− user <- api.getUser { id: ctx.userId }
39− liftAff $ (ctx.apiKey <> ":" <> user.name) `shouldEqual` "mykey:User 7"
37+ runOm { apiKey: "mykey", userId: 7 } { exception: throwError } do
38+ { apiKey, userId } <- ask
39+ { name } <- api.getUser { id: userId }
40+ liftAff $ (apiKey <> ":" <> name) `shouldEqual` "mykey:User 7"
4041
4142 it "same api.getUser works in two different contexts" \_ -> do
42− runOm { token: "tok1" } { exception: \_ -> pure unit } do
43− ctx <- ask
44− user <- api.getUser { id: 1 }
45− liftAff $ (ctx.token <> ":" <> user.name) `shouldEqual` "tok1:User 1"
46− runOm { endpoint: "prod", region: "eu" } { exception: \_ -> pure unit } do
47− ctx <- ask
48− user <- api.getUser { id: 2 }
49− liftAff $ (ctx.endpoint <> "-" <> ctx.region <> ":" <> user.name) `shouldEqual` "prod-eu:User 2"
43+
44+ runOm { token: "tok1" } { exception: throwError } do
45+ { token } <- ask
46+ { name } <- api.getUser { id: 1 }
47+ liftAff $ (token <> ":" <> name) `shouldEqual` "tok1:User 1"
48+
49+ runOm { endpoint: "prod", region: "eu" } { exception: throwError } do
50+ { endpoint, region } <- ask
51+ { name } <- api.getUser { id: 2 }
52+ liftAff $ (endpoint <> "-" <> region <> ":" <> name) `shouldEqual` "prod-eu:User 2"
5053
5154withServer :: forall a. (Unit -> _ a) -> _ a
5255withServer test = bracket acquire release (\_ -> test unit)
test/Roundtrip.Spec.purs+144−54
@@ -6,14 +6,17 @@ import Data.Maybe (Maybe(..))
66import Effect.Aff (bracket)
77import Effect.Aff.Class (liftAff)
88import Effect.Class (liftEffect)
9+import Effect.Ref as Ref
10+import Effect.Exception as Exn
11+import Justifill (justifill)
912import Test.Spec (Spec, around, describe, it)
1013import Test.Spec.Assertions (shouldEqual)
1114import Yoga.Fastify.Fastify as F
1215import Yoga.Fastify.Fastify (Port(..), Host(..))
1316import Yoga.Fastify.Om.API (registerAPI)
1417import Yoga.Fastify.Om.Route (Handler, handle, respond, reject, BearerToken(..))
15−import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, type (/), type (:), type (:?), client, opt)
16−import Yoga.Om (Om, ask, handleErrors, runOm)
18+import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Required, Route, JSON, type (/), type (:), type (:?), client, clientWith)
19+import Yoga.Om (ask, handleErrors, runOm)
1720
1821-- Types
1922
@@ -68,6 +71,34 @@ type SearchRoute = Route GET
6871 {}
6972 (ok :: { body :: { active :: Boolean, minScore :: Number } })
7073
74+type FindByQueryRoute = Route GET
75+ ("find-by-query" :? { id :: Int })
76+ {}
77+ (ok :: { body :: { id :: Int } })
78+
79+type InjectedTenantRoute = Route GET
80+ ("tenant-users" / "id" : Int :? { tenant :: Required String })
81+ { headers :: { authorization :: BearerToken, requestid :: String } }
82+ ( ok :: { body :: User }
83+ , unauthorized :: { body :: ErrorMsg }
84+ )
85+
86+type InjectedAPI =
87+ { getTenantUser :: InjectedTenantRoute
88+ }
89+
90+injectedApi tokenRef =
91+ clientWith @InjectedAPI
92+ { baseUrl: pure "http://localhost:44932"
93+ , provide: do
94+ ctx <- ask
95+ token <- liftEffect $ Ref.read tokenRef
96+ pure
97+ { headers: { authorization: BearerToken token, requestid: ctx.requestid }
98+ , query: { tenant: ctx.tenant }
99+ }
100+ }
101+
71102type API =
72103 { getUser :: GetUserRoute
73104 , listUsers :: ListUsersRoute
@@ -76,6 +107,7 @@ type API =
76107 , deleteUser :: DeleteUserRoute
77108 , me :: AuthRoute
78109 , search :: SearchRoute
110+ , findByQuery :: FindByQueryRoute
79111 }
80112
81113api = client @API "http://localhost:44932"
@@ -85,49 +117,50 @@ api = client @API "http://localhost:44932"
85117getUserHandler :: Handler GetUserRoute ()
86118getUserHandler = handle do
87119 { path } <- ask
88− if path.id == 42
89− then respond @"ok" { id: 42, name: "Alice", email: "alice@test.com" }
90− else reject @"notFound" { error: "User " <> show path.id <> " not found" }
120+ if path.id == 42 then respond @"ok" { id: 42, name: "Alice", email: "alice@test.com" }
121+ else reject @"notFound" { error: "User " <> show path.id <> " not found" }
91122
92123listUsersHandler :: Handler ListUsersRoute ()
93124listUsersHandler = handle do
94125 { query } <- ask
95− let users = [ { id: 1, name: "Alice", email: "a@t.com" }
96− , { id: 2, name: "Bob", email: "b@t.com" }
97− , { id: 3, name: "Charlie", email: "c@t.com" }
98− ]
99− let limited = case query.limit of
100− Just l -> take l users
101− Nothing -> users
126+ let
127+ users =
128+ [ { id: 1, name: "Alice", email: "a@t.com" }
129+ , { id: 2, name: "Bob", email: "b@t.com" }
130+ , { id: 3, name: "Charlie", email: "c@t.com" }
131+ ]
132+ let
133+ limited = case query.limit of
134+ Just l -> take l users
135+ Nothing -> users
102136 respond @"ok" limited
103137 where
104− take n xs = if n <= 0 then [] else case xs of
105− [] -> []
106− [a] -> [a]
107− [a, _] | n == 1 -> [a]
108− [a, b] -> [a, b]
109− _ -> xs
138+ take n xs =
139+ if n <= 0 then []
140+ else case xs of
141+ [] -> []
142+ [ a ] -> [ a ]
143+ [ a, _ ] | n == 1 -> [ a ]
144+ [ a, b ] -> [ a, b ]
145+ _ -> xs
110146
111147createUserHandler :: Handler CreateUserRoute ()
112148createUserHandler = handle do
113149 { body } <- ask
114− if body.name == ""
115− then reject @"badRequest" { error: "Name required" }
116− else respond @"created" { id: 99, name: body.name, email: body.email }
150+ if body.name == "" then reject @"badRequest" { error: "Name required" }
151+ else respond @"created" { id: 99, name: body.name, email: body.email }
117152
118153updateUserHandler :: Handler UpdateUserRoute ()
119154updateUserHandler = handle do
120155 { path, body } <- ask
121− if path.id /= 42
122− then reject @"notFound" { error: "Not found" }
123− else respond @"ok" { id: path.id, name: body.name, email: body.email }
156+ if path.id /= 42 then reject @"notFound" { error: "Not found" }
157+ else respond @"ok" { id: path.id, name: body.name, email: body.email }
124158
125159deleteUserHandler :: Handler DeleteUserRoute ()
126160deleteUserHandler = handle do
127161 { path } <- ask
128− if path.id /= 42
129− then reject @"notFound" { error: "Not found" }
130− else respond @"ok" {}
162+ if path.id /= 42 then reject @"notFound" { error: "Not found" }
163+ else respond @"ok" {}
131164
132165meHandler :: Handler AuthRoute ()
133166meHandler = handle do
@@ -139,14 +172,32 @@ meHandler = handle do
139172searchHandler :: Handler SearchRoute ()
140173searchHandler = handle do
141174 { query } <- ask
142− let active = case query.active of
143− Just a -> a
144− Nothing -> false
145− let minScore = case query.minScore of
146− Just s -> s
147− Nothing -> 0.0
175+ let
176+ active = case query.active of
177+ Just a -> a
178+ Nothing -> false
179+ let
180+ minScore = case query.minScore of
181+ Just s -> s
182+ Nothing -> 0.0
148183 respond @"ok" { active, minScore }
149184
185+findByQueryHandler :: Handler FindByQueryRoute ()
186+findByQueryHandler = handle do
187+ { query } <- ask
188+ let
189+ id = case query.id of
190+ Just value -> value
191+ Nothing -> -1
192+ respond @"ok" { id }
193+
194+tenantUserHandler :: Handler InjectedTenantRoute ()
195+tenantUserHandler = handle do
196+ { path, query, headers } <- ask
197+ case headers.authorization of
198+ BearerToken "secret" -> respond @"ok" { id: path.id, name: query.tenant <> ":" <> headers.requestid, email: "tenant@test.com" }
199+ _ -> reject @"unauthorized" { error: "Invalid token" }
200+
150201-- Test suite
151202
152203spec :: Spec Unit
@@ -168,13 +219,18 @@ spec = around withServer $ describe "server ↔ client roundtrip" do
168219 describe "GET with query params" do
169220 it "passes query params correctly" \_ ->
170221 runOm {} { exception: \_ -> pure unit } do
171− users <- api.listUsers { limit: Just 1, offset: Nothing }
172− liftAff $ (map _.name users) `shouldEqual` ["Alice"]
222+ users <- api.listUsers (justifill { limit: 1 })
223+ liftAff $ (map _.name users) `shouldEqual` [ "Alice" ]
224+
225+ it "serializes a single explicit Maybe query through the derived client" \_ ->
226+ runOm {} { exception: \_ -> pure unit } do
227+ result <- api.findByQuery { id: Just 42 }
228+ liftAff $ result.id `shouldEqual` 42
173229
174230 it "handles no query params" \_ ->
175231 runOm {} { exception: \_ -> pure unit } do
176− users <- api.listUsers { limit: Nothing, offset: Nothing }
177− liftAff $ (map _.name users) `shouldEqual` ["Alice", "Bob", "Charlie"]
232+ users <- api.listUsers (justifill {})
233+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob", "Charlie" ]
178234
179235 describe "POST with JSON body" do
180236 it "sends and receives JSON body" \_ ->
@@ -195,12 +251,24 @@ spec = around withServer $ describe "server ↔ client roundtrip" do
195251 describe "PUT with path params and JSON body" do
196252 it "sends path params and body together" \_ ->
197253 runOm {} { exception: \_ -> pure unit } do
198− user <- api.updateUser { id: 42 } { name: "Updated", email: "u@t.com" }
199− # handleErrors { notFound: \e -> pure { id: 0, name: e.error, email: "" } }
254+ user <-
255+ api.updateUser
256+ { id: 42 }
257+ { name: "Updated", email: "u@t.com" }
258+ # handleErrors { notFound: \e -> pure { id: 0, name: e.error, email: "" } }
200259 liftAff do
201260 user.id `shouldEqual` 42
202261 user.name `shouldEqual` "Updated"
203262
263+ it "supports partial application before the body argument" \_ ->
264+ runOm {} { exception: \_ -> pure unit } do
265+ let update42 = api.updateUser { id: 42 }
266+ user <- update42 { name: "Curried", email: "curried@t.com" }
267+ # handleErrors { notFound: \e -> pure { id: 0, name: e.error, email: "" } }
268+ liftAff do
269+ user.id `shouldEqual` 42
270+ user.name `shouldEqual` "Curried"
271+
204272 describe "DELETE with path params" do
205273 it "deletes successfully" \_ ->
206274 runOm {} { exception: \_ -> pure unit } do
@@ -230,41 +298,61 @@ spec = around withServer $ describe "server ↔ client roundtrip" do
230298 describe "GET with Boolean and Number query params" do
231299 it "passes Boolean and Number query params" \_ ->
232300 runOm {} { exception: \_ -> pure unit } do
233− result <- api.search { active: Just true, minScore: Just 3.14 }
301+ result <- api.search (justifill { active: true, minScore: 3.14 })
234302 liftAff do
235303 result.active `shouldEqual` true
236304 result.minScore `shouldEqual` 3.14
237305
238− it "handles Nothing for optional Boolean/Number" \_ ->
306+ it "handles missing optional query params" \_ ->
239307 runOm {} { exception: \_ -> pure unit } do
240− result <- api.search { active: Nothing, minScore: Nothing }
308+ result <- api.search (justifill {})
241309 liftAff do
242310 result.active `shouldEqual` false
243311 result.minScore `shouldEqual` 0.0
244312
245− describe "opt helper for partial query params" do
246− it "passes subset of query params via opt" \_ ->
313+ describe "optional query params" do
314+ it "accepts a selected optional query parameter" \_ ->
247315 runOm {} { exception: \_ -> pure unit } do
248− users <- api.listUsers $ opt { limit: 1 }
249− liftAff $ (map _.name users) `shouldEqual` ["Alice"]
316+ users <- api.listUsers (justifill { limit: 1 })
317+ liftAff $ (map _.name users) `shouldEqual` [ "Alice" ]
250318
251− it "passes all query params via opt" \_ ->
319+ it "accepts all optional query parameters" \_ ->
252320 runOm {} { exception: \_ -> pure unit } do
253− users <- api.listUsers $ opt { limit: 2, offset: 0 }
254− liftAff $ (map _.name users) `shouldEqual` ["Alice", "Bob"]
321+ users <- api.listUsers (justifill { limit: 2, offset: 0 })
322+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob" ]
255323
256− it "passes empty record via opt" \_ ->
324+ it "fills omitted optional query parameters with Nothing" \_ ->
257325 runOm {} { exception: \_ -> pure unit } do
258− users <- api.listUsers $ opt {}
259− liftAff $ (map _.name users) `shouldEqual` ["Alice", "Bob", "Charlie"]
326+ users <- api.listUsers (justifill {})
327+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob", "Charlie" ]
260328
261− it "passes Boolean via opt" \_ ->
329+ it "passes selected Boolean query param" \_ ->
262330 runOm {} { exception: \_ -> pure unit } do
263− result <- api.search $ opt { active: true }
331+ result <- api.search (justifill { active: true })
264332 liftAff do
265333 result.active `shouldEqual` true
266334 result.minScore `shouldEqual` 0.0
267335
336+ describe "clientWith injected inputs" do
337+ it "reads provided headers and query params from Om context for each call" \_ -> do
338+ tokenRef <- liftEffect $ Ref.new "secret"
339+ let injected = injectedApi tokenRef
340+ user <- runOm { tenant: "acme", requestid: "req-1" }
341+ { exception: \e -> pure { id: -1, name: Exn.message e, email: "" }
342+ , unauthorized: \e -> pure { id: -2, name: e.error, email: "" }
343+ }
344+ (injected.getTenantUser { id: 7 })
345+ liftAff do
346+ user.id `shouldEqual` 7
347+ user.name `shouldEqual` "acme:req-1"
348+ liftEffect $ Ref.write "wrong" tokenRef
349+ rejected <- runOm { tenant: "acme", requestid: "req-2" }
350+ { exception: \e -> pure { id: -1, name: Exn.message e, email: "" }
351+ , unauthorized: \e -> pure { id: -2, name: e.error, email: "" }
352+ }
353+ (injected.getTenantUser { id: 8 })
354+ liftAff $ rejected.name `shouldEqual` "Invalid token"
355+
268356-- Server setup
269357
270358withServer :: forall a. (Unit -> _ a) -> _ a
@@ -281,8 +369,10 @@ withServer test = bracket acquire release (\_ -> test unit)
281369 , deleteUser: deleteUserHandler
282370 , me: meHandler
283371 , search: searchHandler
372+ , findByQuery: findByQueryHandler
284373 }
285374 f
375+ registerAPI @InjectedAPI { getTenantUser: tenantUserHandler } f
286376 pure f
287377 void $ F.listen { port: Port 44932, host: Host "0.0.0.0" } fastify
288378 pure fastify
test/StreamDecode.Spec.jsnew file+1
@@ -0,0 +1 @@
1+export const uint8Array = (values) => new Uint8Array(values);
test/StreamDecode.Spec.pursnew file+32
@@ -0,0 +1,32 @@
1+module StreamDecode.Spec where
2+
3+import Prelude
4+
5+import Data.ArrayBuffer.Types (Uint8Array)
6+import Data.String as String
7+import Effect.Aff (Aff)
8+import Effect.Class (liftEffect)
9+import Effect.Exception (throwException)
10+import Test.Spec (Spec, describe, it)
11+import Test.Spec.Assertions (shouldEqual)
12+import Yoga.Fetch.Om.StreamDecode (decodeStream)
13+import Yoga.Om (Om)
14+import Yoga.Om as Om
15+import Yoga.Om.Strom as Strom
16+
17+foreign import uint8Array :: Array Int -> Uint8Array
18+
19+runOm :: forall a. Om {} () a -> Aff a
20+runOm om = Om.runOm {} { exception: \err -> liftEffect (throwException err) } om
21+
22+spec :: Spec Unit
23+spec = describe "StreamDecode" do
24+ it "preserves UTF-8 code points split across chunks" do
25+ chunks <- runOm $
26+ Strom.fromArray
27+ [ uint8Array [ 0xe2 ]
28+ , uint8Array [ 0x82, 0xac ]
29+ ]
30+ # decodeStream @String
31+ # Strom.runCollect
32+ String.joinWith "" chunks `shouldEqual` "€"