git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
.c8rc.jsonnew file+22
since splitsnapshots
unifiedsplit
@@ -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:
12 steps:
12 steps:
13 - uses: actions/checkout@v4
13 - uses: actions/checkout@v4
14 - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
14 - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
15− - run: spago build
15+ - run: bun run coverage
16− - run: spago test
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 @@
1output/
1output/
2output-compile-fail/
2output-compile-fail/
3+coverage/
4+generated-docs/
3node_modules/
5node_modules/
4.spago/
6.spago/
5.psc-ide-port
7.psc-ide-port
.psci_modules/Data.Int.Bits/externs.cbornew file

Binary file not shown.

.psci_modules/Data.Int.Bits/foreign.jsnew file+48
@@ -0,0 +1,48 @@
1+// module Data.Int.Bits
2+
3+export const and = function (n1) {
4+ return function (n2) {
5+ /* jshint bitwise: false */
6+ return n1 & n2;
7+ };
8+};
9+
10+export const or = function (n1) {
11+ return function (n2) {
12+ /* jshint bitwise: false */
13+ return n1 | n2;
14+ };
15+};
16+
17+export const xor = function (n1) {
18+ return function (n2) {
19+ /* jshint bitwise: false */
20+ return n1 ^ n2;
21+ };
22+};
23+
24+export const shl = function (n1) {
25+ return function (n2) {
26+ /* jshint bitwise: false */
27+ return n1 << n2;
28+ };
29+};
30+
31+export const shr = function (n1) {
32+ return function (n2) {
33+ /* jshint bitwise: false */
34+ return n1 >> n2;
35+ };
36+};
37+
38+export const zshr = function (n1) {
39+ return function (n2) {
40+ /* jshint bitwise: false */
41+ return n1 >>> n2;
42+ };
43+};
44+
45+export const complement = function (n) {
46+ /* jshint bitwise: false */
47+ return ~n;
48+};
.psci_modules/Data.Int.Bits/index.jsnew file+12
@@ -0,0 +1,12 @@
1+// | This module defines bitwise operations for the `Int` type.
2+import * as $foreign from "./foreign.js";
3+
4+export {
5+ and,
6+ or,
7+ xor,
8+ shl,
9+ shr,
10+ zshr,
11+ complement
12+} from "./foreign.js";
.psci_modules/Data.NaturalTransformation/externs.cbornew file

Binary file not shown.

.psci_modules/Data.NaturalTransformation/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Data.String.Unsafe/externs.cbornew file

Binary file not shown.

.psci_modules/Data.String.Unsafe/foreign.jsnew file+11
@@ -0,0 +1,11 @@
1+export const charAt = function (i) {
2+ return function (s) {
3+ if (i >= 0 && i < s.length) return s.charAt(i);
4+ throw new Error("Data.String.Unsafe.charAt: Invalid index.");
5+ };
6+};
7+
8+export const char = function (s) {
9+ if (s.length === 1) return s.charAt(0);
10+ throw new Error("Data.String.Unsafe.char: Expected string of length 1.");
11+};
.psci_modules/Data.String.Unsafe/index.jsnew file+7
@@ -0,0 +1,7 @@
1+// | Unsafe string and character functions.
2+import * as $foreign from "./foreign.js";
3+
4+export {
5+ char,
6+ charAt
7+} from "./foreign.js";
.psci_modules/Record.Unsafe/externs.cbornew file

Binary file not shown.

.psci_modules/Record.Unsafe/foreign.jsnew file+38
@@ -0,0 +1,38 @@
1+export const unsafeHas = function (label) {
2+ return function (rec) {
3+ return {}.hasOwnProperty.call(rec, label);
4+ };
5+};
6+
7+export const unsafeGet = function (label) {
8+ return function (rec) {
9+ return rec[label];
10+ };
11+};
12+
13+export const unsafeSet = function (label) {
14+ return function (value) {
15+ return function (rec) {
16+ var copy = {};
17+ for (var key in rec) {
18+ if ({}.hasOwnProperty.call(rec, key)) {
19+ copy[key] = rec[key];
20+ }
21+ }
22+ copy[label] = value;
23+ return copy;
24+ };
25+ };
26+};
27+
28+export const unsafeDelete = function (label) {
29+ return function (rec) {
30+ var copy = {};
31+ for (var key in rec) {
32+ if (key !== label && {}.hasOwnProperty.call(rec, key)) {
33+ copy[key] = rec[key];
34+ }
35+ }
36+ return copy;
37+ };
38+};
.psci_modules/Record.Unsafe/index.jsnew file+14
@@ -0,0 +1,14 @@
1+// | The functions in this module are highly unsafe as they treat records like
2+// | stringly-keyed maps and can coerce the row of labels that a record has.
3+// |
4+// | These function are intended for situations where there is some other way of
5+// | proving things about the structure of the record - for example, when using
6+// | `RowToList`. **They should never be used for general record manipulation.**
7+import * as $foreign from "./foreign.js";
8+
9+export {
10+ unsafeHas,
11+ unsafeGet,
12+ unsafeSet,
13+ unsafeDelete
14+} from "./foreign.js";
.psci_modules/Spago.Generated.BuildInfo/externs.cbornew file

Binary file not shown.

.psci_modules/Spago.Generated.BuildInfo/index.jsnew file+13
@@ -0,0 +1,13 @@
1+// @inline export packages always
2+// @inline export pursVersion always
3+// @inline export spagoVersion always
4+var spagoVersion = "1.0.3";
5+var pursVersion = "0.15.15";
6+var packages = {
7+ "yoga-fetch-om": "0.3.0"
8+};
9+export {
10+ packages,
11+ pursVersion,
12+ spagoVersion
13+};
.psci_modules/Type.Equality/externs.cbornew file

Binary file not shown.

.psci_modules/Type.Equality/index.jsnew file+35
@@ -0,0 +1,35 @@
1+var To = function (x) {
2+ return x;
3+};
4+var From = function (x) {
5+ return x;
6+};
7+var refl = {
8+ proof: function (a) {
9+ return a;
10+ },
11+ Coercible0: function () {
12+ return undefined;
13+ }
14+};
15+var proof = function (dict) {
16+ return dict.proof;
17+};
18+var to = function (dictTypeEquals) {
19+ var v = proof(dictTypeEquals)(function (a) {
20+ return a;
21+ });
22+ return v;
23+};
24+var from = function (dictTypeEquals) {
25+ var v = proof(dictTypeEquals)(function (a) {
26+ return a;
27+ });
28+ return v;
29+};
30+export {
31+ proof,
32+ to,
33+ from,
34+ refl
35+};
.psci_modules/Type.Function/externs.cbornew file

Binary file not shown.

.psci_modules/Type.Function/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Type.Proxy/externs.cbornew file

Binary file not shown.

.psci_modules/Type.Proxy/index.jsnew file+58
@@ -0,0 +1,58 @@
1+// | The `Proxy` type and values are for situations where type information is
2+// | required for an input to determine the type of an output, but where it is
3+// | not possible or convenient to provide a _value_ for the input.
4+// |
5+// | A hypothetical example: if you have a class that is used to handle the
6+// | result of an AJAX request, you may want to use this information to set the
7+// | expected content type of the request, so you might have a class something
8+// | like this:
9+// |
10+// | ``` purescript
11+// | class AjaxResponse a where
12+// | responseType :: a -> ResponseType
13+// | fromResponse :: Foreign -> a
14+// | ```
15+// |
16+// | The problem here is `responseType` requires a value of type `a`, but we
17+// | won't have a value of that type until the request has been completed. The
18+// | solution is to use a `Proxy` type instead:
19+// |
20+// | ``` purescript
21+// | class AjaxResponse a where
22+// | responseType :: Proxy a -> ResponseType
23+// | fromResponse :: Foreign -> a
24+// | ```
25+// |
26+// | We can now call `responseType (Proxy :: Proxy SomeContentType)` to produce
27+// | a `ResponseType` for `SomeContentType` without having to construct some
28+// | empty version of `SomeContentType` first. In situations like this where
29+// | the `Proxy` type can be statically determined, it is recommended to pull
30+// | out the definition to the top level and make a declaration like:
31+// |
32+// | ``` purescript
33+// | _SomeContentType :: Proxy SomeContentType
34+// | _SomeContentType = Proxy
35+// | ```
36+// |
37+// | That way the proxy value can be used as `responseType _SomeContentType`
38+// | for improved readability. However, this is not always possible, sometimes
39+// | the type required will be determined by a type variable. As PureScript has
40+// | scoped type variables, we can do things like this:
41+// |
42+// | ``` purescript
43+// | makeRequest :: URL -> ResponseType -> Aff _ Foreign
44+// | makeRequest = ...
45+// |
46+// | fetchData :: forall a. (AjaxResponse a) => URL -> Aff _ a
47+// | fetchData url = fromResponse <$> makeRequest url (responseType (Proxy :: Proxy a))
48+// | ```
49+var $$Proxy = /* #__PURE__ */ (function () {
50+ function $$Proxy() {
51+
52+ };
53+ $$Proxy.value = new $$Proxy();
54+ return $$Proxy;
55+})();
56+export {
57+ $$Proxy as Proxy
58+};
.psci_modules/Type.Row/externs.cbornew file

Binary file not shown.

.psci_modules/Type.Row/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Unsafe.Coerce/externs.cbornew file

Binary file not shown.

.psci_modules/Unsafe.Coerce/foreign.jsnew file+5
@@ -0,0 +1,5 @@
1+// module Unsafe.Coerce
2+
3+export const unsafeCoerce = function (x) {
4+ return x;
5+};
.psci_modules/Unsafe.Coerce/index.jsnew file+5
@@ -0,0 +1,5 @@
1+import * as $foreign from "./foreign.js";
2+
3+export {
4+ unsafeCoerce
5+} from "./foreign.js";
.psci_modules/Web.DOM.Internal.Types/externs.cbornew file

Binary file not shown.

.psci_modules/Web.DOM.Internal.Types/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Web.Event.Internal.Types/externs.cbornew file

Binary file not shown.

.psci_modules/Web.Event.Internal.Types/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Yoga.HTTP.API.Route.Encoding/externs.cbornew file

Binary file not shown.

.psci_modules/Yoga.HTTP.API.Route.Encoding/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/Yoga.HTTP.API.Route.Method/externs.cbornew file

Binary file not shown.

.psci_modules/Yoga.HTTP.API.Route.Method/index.jsnew file+1
@@ -0,0 +1 @@
1+
.psci_modules/cache-db.jsonnew file+1
@@ -0,0 +1 @@
1+{"Data.Int.Bits":{".spago/p/integers-6.0.0/src/Data/Int/Bits.js":["1970-01-01T06:00:00Z","bdbabdb4bf0d318a750d7ff7e1542735731877a33803a87011e670bafa8d59c8c3ef42107be1ca1b86dc7b8222c11008895341e36760cf7989a0da098d848100"],".spago/p/integers-6.0.0/src/Data/Int/Bits.purs":["1970-01-01T06:00:00Z","61f10b047f013a5b4505df8cc49e566ce827bce1272b471cdcc2a2b849e39db49f0850613e036931a4d2efd7a2cc727bbd8a8a310498ea28552cc4d6547fc800"]},"Data.NaturalTransformation":{".spago/p/prelude-6.0.2/src/Data/NaturalTransformation.purs":["1970-01-01T00:00:00Z","6b9fc42ec524a517d464dea99867c64345cdbcbada4260a772373a956961ad1c9a4a3a4f8ed4a0c7c7f3e36120068954141841c5d6bdc4e5898ea06435104bb7"]},"Data.String.Unsafe":{".spago/p/strings-6.0.1/src/Data/String/Unsafe.js":["1970-01-01T06:00:00Z","81c79d607f04aaa23089d717e10d358971deec3254ee25837a9d14e947c4d28444e4057abd2cdc1c114327a43584c3cef2cbec957b011ab4ed412e1a25596812"],".spago/p/strings-6.0.1/src/Data/String/Unsafe.purs":["1970-01-01T06:00:00Z","c3bddfdcefc7c46316a573e2af253df77fae08a64ad096f692391d33266d0f77623c0a6c8008a15bcae0d440670734399edd547ed335f5beecf9042b49fa2d9b"]},"Record.Unsafe":{".spago/p/prelude-6.0.2/src/Record/Unsafe.js":["1970-01-01T00:00:00Z","667d613a0e1265a710e98c9f6e64ff791b9e9b7227cdf699c3a25724077c02e42726c807c0387ca4484454fd853d5b41522bf2e048887f22a894413ef80cbb92"],".spago/p/prelude-6.0.2/src/Record/Unsafe.purs":["1970-01-01T00:00:00Z","af8a7c83127f5e61853ad0e572be799c04e321562a375d51ab133706ac6a6777e169b057f01a709ad7aae2d8c7a5ae8daa28eae8793c4fe92e2e8f0cfaecf870"]},"Spago.Generated.BuildInfo":{".spago/BuildInfo.purs":["2026-02-09T12:42:13.813485312Z","abfa99370d332ed1138a8d9f7ec91fe0377758e01f2520a6d390bdec7c3b5bacd6778fca0e5571642cde24de431c68071562941dde9fadc7a1dcb8de511370d8"]},"Type.Equality":{".spago/p/type-equality-4.0.1/src/Type/Equality.purs":["1970-01-01T05:00:00Z","2713b8d5621912fe0dad9cd588cf4579181566459fe200f5089de472d00e29ab4a1c020f73a8e89f04f3ef02b94ec28082d17d395b89349d6c5f8016426a009e"]},"Type.Function":{".spago/p/typelevel-prelude-7.0.0/src/Type/Function.purs":["1970-01-01T06:00:00Z","d1b8eb136c5e23ded6dcd622ef352200ea9a323dd4f3dec0328fe0dbcf673ee4c959a495efecc5605fedcb7824bb086faaf8684fa30afe0625b1d03fc921b149"]},"Type.Proxy":{".spago/p/prelude-6.0.2/src/Type/Proxy.purs":["1970-01-01T00:00:00Z","333d0ae90b05098eada860ad476ed9e1eacd67d969dc1573c74375610cf8de22e85a358a7783a0a461d7ffa6fe322d8c3e90696be02555a02793ac99aba687f2"]},"Type.Row":{".spago/p/typelevel-prelude-7.0.0/src/Type/Row.purs":["1970-01-01T06:00:00Z","cee5ad6bca731f76da8388f32d1e2f644b0dd976734261a01d2a5dd1671924e4534168e085f843f1576c754c917ccdf7fb0e68925fd23a2e121a1bed33d9a4c0"]},"Unsafe.Coerce":{".spago/p/unsafe-coerce-6.0.0/src/Unsafe/Coerce.js":["1970-01-01T05:00:00Z","031c3187f7ce65110a359260041309506e1ff39a97b6eb22c9a8f2e5c14b35db87d509bb5929f245c5d80df6ccd6dadf83579ebe4ac46650acdfa34a493cfc0d"],".spago/p/unsafe-coerce-6.0.0/src/Unsafe/Coerce.purs":["1970-01-01T05:00:00Z","04b214d9cbbf0c438bb2af4b25f8db5d04f247059241982f718c6d83322c3180f0eced0eb5c410860dcdb35650e9129a1cffe87f7279308cc4cf6c6570bba74f"]},"Web.DOM.Internal.Types":{".spago/p/web-dom-6.0.0/src/Web/DOM/Internal/Types.purs":["1970-01-01T05:00:00Z","c113e0d6553a99ad7dc85145336ddf709dd07893a49db715d8ab460be7ed42f6a17df038966e06b02fe7896b6347893fab0caf216dcd4262dd53a15d9d4b6596"]},"Web.Event.Internal.Types":{".spago/p/web-events-4.0.0/src/Web/Event/Internal/Types.purs":["1970-01-01T06:00:00Z","f7497de5e0b09a66fc33a8959bdbe979d56bd185d5a032894a357db7195e82454e55d4a672311d5a4cfb0fed2f55897d3bda655c9a7a68b39e84d7a04619b9ba"]},"Yoga.HTTP.API.Route.Encoding":{".spago/p/yoga-http-api-0.1.0/src/Yoga/HTTP/API/Route/Encoding.purs":["1970-01-01T00:00:00Z","7f41a999eace3d73aacf6ea69c91f9a49a6948959fcfb0fb784ff83479345f88c6e5970783ce7fb4d38e2648caf506e68f889fc185a80a91b029ccfd2c269c0b"]},"Yoga.HTTP.API.Route.Method":{".spago/p/yoga-http-api-0.1.0/src/Yoga/HTTP/API/Route/Method.purs":["1970-01-01T00:00:00Z","344b6850269f52ff385b364d9bebb1e884269d46a900dda3d451028770793c7ec6308ccaaec047664c6b6db9be3861a1039a6af5230c98a588ea47e62bdbea7c"]}}
.psci_modules/package.jsonnew file+1
@@ -0,0 +1 @@
1+{"type":"module"}
.purs-replnew file+1
@@ -0,0 +1 @@
1+import Prelude
.purslint-cache/props.dbnew file

Binary file not shown.

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+74−17
@@ -8,6 +8,34 @@ Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions
8spago install yoga-fetch-om
8spago install yoga-fetch-om
9```
9```
10
10
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+
11## Quick Start
39## Quick Start
12
40
13### Example 1: Simple GET and POST Requests
41### Example 1: Simple GET and POST Requests
@@ -30,8 +58,13 @@ main = do
30### Example 2: Type-Safe API Client with Full CRUD
58### Example 2: Type-Safe API Client with Full CRUD
31
59
32```purescript
60```purescript
61+import Justifill (justifill)
62+
33type UserAPI =
63type UserAPI =
64+ { health ::
65+ Route GET "health" {}
66+ ( ok :: { body :: { status :: String } } )
34− { getUser ::
67+ , getUser ::
35 Route GET ("users" / "id" : Int) {}
68 Route GET ("users" / "id" : Int) {}
36 ( ok :: { body :: User }
69 ( ok :: { body :: User }
37 , notFound :: { body :: ErrorMessage }
70 , notFound :: { body :: ErrorMessage }
@@ -59,7 +92,8 @@ type UserAPI =
59
92
60api = client @UserAPI "https://api.example.com"
93api = client @UserAPI "https://api.example.com"
61
94
62−-- Create a user
95+-- Generated methods use ordinary function application.
96+health <- api.health
63user <- api.createUser { name: "Alice", email: "alice@example.com" }
97user <- api.createUser { name: "Alice", email: "alice@example.com" }
64 # handleErrors
98 # handleErrors
65 { badRequest: \err -> do
99 { badRequest: \err -> do
@@ -67,15 +101,21 @@ user <- api.createUser { name: "Alice", email: "alice@example.com" }
67 throw err
101 throw err
68 }
102 }
69
103
70−-- Update with path param + body
104+-- Path parameters and request bodies remain separate arguments.
105+updated <- api.updateUser
106+ { id: user.id }
71−updated <- api.updateUser { id: user.id } { name: "Alice Updated", email: user.email }
107+ { name: "Alice Updated", email: user.email }
72 # handleErrors
108 # handleErrors
73 { notFound: \_ -> throw userNotFound
109 { notFound: \_ -> throw userNotFound
74 , badRequest: \err -> throw validationError
110 , badRequest: \err -> throw validationError
75 }
111 }
76
112
77−-- Query with pagination
113+-- Optional query parameters are explicit `Maybe` fields. Justifill can wrap
114+-- supplied values in `Just` and fill omitted fields with `Nothing`.
78−users <- api.listUsers { limit: 10, offset: 0 }
115+users <- api.listUsers (justifill { limit: 10 })
116+
117+-- The equivalent call without Justifill:
118+allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
79```
119```
80
120
81## More Examples
121## More Examples
@@ -92,10 +132,10 @@ See the test files for complete, runnable examples with all imports:
92
132
93### ✅ Type-Safe Everything
133### ✅ Type-Safe Everything
94
134
95−- **Path parameters**: `/users/:id` requires `{ id: Int }`
135+- **Path parameters**: `/users/:id` requires `{ id :: Int }`; names and values are percent-encoded
96−- **Query parameters**: Type-safe query strings with `?`
136+- **Query parameters**: Type-safe, percent-encoded query strings with explicit `Maybe` fields for optional values
97−- **Request bodies**: Automatic JSON serialization
137+- **Request bodies**: `JSON`, `PlainText`, and URL-encoded `FormData`
98−- **Response bodies**: Automatic JSON parsing
138+- **Response bodies**: Parsed JSON, `PlainText` as `String`, and `Streaming a` as `Strom {} () a`
99- **Error handling**: Exhaustive pattern matching on variants
139- **Error handling**: Exhaustive pattern matching on variants
100
140
101### ✅ Single Source of Truth
141### ✅ Single Source of Truth
@@ -115,12 +155,12 @@ Changes to routes automatically update both client and server!
115### ✅ Automatic Derivation
155### ✅ Automatic Derivation
116
156
117No manual client code:
157No manual client code:
118−- ✅ URL building with path parameter substitution
158+- ✅ URL building with segment-safe path substitution
119−- ✅ Query string construction
159+- ✅ Query construction that preserves existing queries and fragments
120−- ✅ JSON request serialization
160+- ✅ JSON, plain-text, and URL-encoded request serialization
121−- ✅ JSON response parsing
161+- ✅ JSON and plain-text response decoding
162+- ✅ Incremental UTF-8 and binary response streams
122- ✅ Status code → variant mapping
163- ✅ Status code → variant mapping
123−- ✅ CORS and credentials handling
124
164
125### ✅ Integration with yoga-om
165### ✅ Integration with yoga-om
126
166
@@ -140,8 +180,9 @@ The library uses PureScript's type system to:
140
180
1411. **Extract parameters** from route definitions at compile time
1811. **Extract parameters** from route definitions at compile time
142 - Path params: `"users" / "id" : Int` → `{ id :: Int }`
182 - Path params: `"users" / "id" : Int` → `{ id :: Int }`
183+ - Optional query params: `:? { limit :: Int }` → `{ limit :: Maybe Int }`
143− - Query params: `:? { limit :: Int }` → `{ limit :: Int }`
184+ - Required query params: `:? { limit :: Required Int }` → `{ limit :: Int }`
144− - Body params: `{ body :: JSON User }` → request body
185+ - Body params: `{ body :: JSON User }` → `User`
145
186
1462. **Build URLs** automatically
1872. **Build URLs** automatically
147 - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
188 - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
@@ -186,6 +227,21 @@ api = client @UserAPI "https://api.example.com"
186- ✅ Variant response handling
227- ✅ Variant response handling
187- ✅ Om monad integration
228- ✅ Om monad integration
188
229
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+
189## License
245## License
190
246
191MIT
247MIT
@@ -196,3 +252,4 @@ MIT
196- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
252- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
197- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
253- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
198- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad
254- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad
255+
bun.locknew file+1089
@@ -0,0 +1,1089 @@
1+{
2+ "lockfileVersion": 1,
3+ "workspaces": {
4+ "": {
5+ "name": "purescript-yoga-fetch-om",
6+ "dependencies": {
7+ "pino": "^10.3.1",
8+ },
9+ "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",
19+ "fastify": "^5.7.4",
20+ "jose": "^6.2.8",
21+ "jsdom": "^28.0.0",
22+ "purescript": "^0.15.15",
23+ "spago": "^1.0.3",
24+ "vitest": "^2.1.8",
25+ },
26+ },
27+ },
28+ "packages": {
29+ "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="],
30+
31+ "@asamuzakjp/css-color": ["@asamuzakjp/css-color@4.1.2", "", { "dependencies": { "@csstools/css-calc": "^3.0.0", "@csstools/css-color-parser": "^4.0.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.2.5" } }, "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg=="],
32+
33+ "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.7.8", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.5" } }, "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ=="],
34+
35+ "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="],
36+
37+ "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="],
38+
39+ "@csstools/color-helpers": ["@csstools/color-helpers@6.0.1", "", {}, "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ=="],
40+
41+ "@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=="],
42+
43+ "@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.1", "", { "dependencies": { "@csstools/color-helpers": "^6.0.1", "@csstools/css-calc": "^3.0.0" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw=="],
44+
45+ "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="],
46+
47+ "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.0.26", "", {}, "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA=="],
48+
49+ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="],
50+
51+ "@epic-web/invariant": ["@epic-web/invariant@1.0.0", "", {}, "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA=="],
52+
53+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
54+
55+ "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
56+
57+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
58+
59+ "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
60+
61+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
62+
63+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
64+
65+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
66+
67+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
68+
69+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
70+
71+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
72+
73+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
74+
75+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
76+
77+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
78+
79+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
80+
81+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
82+
83+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
84+
85+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
86+
87+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
88+
89+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
90+
91+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
92+
93+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
94+
95+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
96+
97+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
98+
99+ "@exodus/bytes": ["@exodus/bytes@1.11.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA=="],
100+
101+ "@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=="],
102+
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+
111+ "@fastify/error": ["@fastify/error@4.2.0", "", {}, "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ=="],
112+
113+ "@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=="],
114+
115+ "@fastify/formbody": ["@fastify/formbody@9.0.0", "", { "dependencies": { "fast-querystring": "^1.1.2", "fastify-plugin": "^6.0.0" } }, "sha512-T/af26CSrUARBCvsEmv+DJLPfZlrRKESzqironxP1j7qzuLyKcoZtj6MuTGShuKx1THXugoie2oFbUJxXfGFzA=="],
116+
117+ "@fastify/forwarded": ["@fastify/forwarded@3.0.1", "", {}, "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw=="],
118+
119+ "@fastify/helmet": ["@fastify/helmet@13.1.0", "", { "dependencies": { "fastify-plugin": "^6.0.0", "helmet": "^8.0.0" } }, "sha512-SvVOU0IrzYJW1BvSkfq9G1WUdW3dnaRUvg6m0BtgGMBmML62No0VmSu087jecH58SFbicbREgZTPJ89mAguupA=="],
120+
121+ "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.2.1", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A=="],
122+
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+
125+ "@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=="],
126+
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+
131+ "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
132+
133+ "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
134+
135+ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
136+
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+
141+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
142+
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=="],
146+
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=="],
148+
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=="],
152+
153+ "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="],
154+
155+ "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="],
156+
157+ "@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
158+
159+ "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
160+
161+ "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="],
162+
163+ "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="],
164+
165+ "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="],
166+
167+ "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="],
168+
169+ "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="],
170+
171+ "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="],
172+
173+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="],
174+
175+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="],
176+
177+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="],
178+
179+ "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="],
180+
181+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="],
182+
183+ "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="],
184+
185+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="],
186+
187+ "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="],
188+
189+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="],
190+
191+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="],
192+
193+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="],
194+
195+ "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="],
196+
197+ "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="],
198+
199+ "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="],
200+
201+ "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="],
202+
203+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="],
204+
205+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="],
206+
207+ "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="],
208+
209+ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="],
210+
211+ "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="],
212+
213+ "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
214+
215+ "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
216+
217+ "@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=="],
218+
219+ "@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=="],
220+
221+ "@vitest/pretty-format": ["@vitest/pretty-format@2.1.9", "", { "dependencies": { "tinyrainbow": "^1.2.0" } }, "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ=="],
222+
223+ "@vitest/runner": ["@vitest/runner@2.1.9", "", { "dependencies": { "@vitest/utils": "2.1.9", "pathe": "^1.1.2" } }, "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g=="],
224+
225+ "@vitest/snapshot": ["@vitest/snapshot@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "magic-string": "^0.30.12", "pathe": "^1.1.2" } }, "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ=="],
226+
227+ "@vitest/spy": ["@vitest/spy@2.1.9", "", { "dependencies": { "tinyspy": "^3.0.2" } }, "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ=="],
228+
229+ "@vitest/utils": ["@vitest/utils@2.1.9", "", { "dependencies": { "@vitest/pretty-format": "2.1.9", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" } }, "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ=="],
230+
231+ "abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
232+
233+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
234+
235+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
236+
237+ "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
238+
239+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
240+
241+ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
242+
243+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
244+
245+ "ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
246+
247+ "ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
248+
249+ "aproba": ["aproba@1.2.0", "", {}, "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="],
250+
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+
253+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
254+
255+ "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
256+
257+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
258+
259+ "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
260+
261+ "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
262+
263+ "avvio": ["avvio@9.1.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw=="],
264+
265+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
266+
267+ "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
268+
269+ "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
270+
271+ "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
272+
273+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
274+
275+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
276+
277+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
278+
279+ "buildcheck": ["buildcheck@0.0.7", "", {}, "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA=="],
280+
281+ "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
282+
283+ "byline": ["byline@5.0.0", "", {}, "sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q=="],
284+
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+
287+ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
288+
289+ "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=="],
290+
291+ "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
292+
293+ "chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
294+
295+ "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
296+
297+ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
298+
299+ "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
300+
301+ "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
302+
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+
305+ "color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
306+
307+ "color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
308+
309+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
310+
311+ "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=="],
312+
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=="],
316+
317+ "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=="],
318+
319+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
320+
321+ "cpu-features": ["cpu-features@0.0.10", "", { "dependencies": { "buildcheck": "~0.0.6", "nan": "^2.19.0" } }, "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA=="],
322+
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+
325+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
326+
327+ "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
328+
329+ "cssstyle": ["cssstyle@5.3.7", "", { "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", "css-tree": "^3.1.0", "lru-cache": "^11.2.4" } }, "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ=="],
330+
331+ "cyclist": ["cyclist@1.0.2", "", {}, "sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA=="],
332+
333+ "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
334+
335+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
336+
337+ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
338+
339+ "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
340+
341+ "default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
342+
343+ "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
344+
345+ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
346+
347+ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
348+
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=="],
350+
351+ "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
352+
353+ "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="],
354+
355+ "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
356+
357+ "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
358+
359+ "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
360+
361+ "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="],
362+
363+ "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
364+
365+ "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=="],
366+
367+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
368+
369+ "escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
370+
371+ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
372+
373+ "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=="],
374+
375+ "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
376+
377+ "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
378+
379+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
380+
381+ "fast-json-stringify": ["fast-json-stringify@6.3.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="],
382+
383+ "fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
384+
385+ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
386+
387+ "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=="],
388+
389+ "fastify-plugin": ["fastify-plugin@6.0.0", "", {}, "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg=="],
390+
391+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
392+
393+ "figgy-pudding": ["figgy-pudding@3.5.2", "", {}, "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw=="],
394+
395+ "filesize": ["filesize@4.2.1", "", {}, "sha512-bP82Hi8VRZX/TUBKfE24iiUGsB/sfm2WUrwTQyAzQrhO3V9IhcBBNBXMyzLY5orACxRyYJ3d2HeRVX+eFv4lmA=="],
396+
397+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
398+
399+ "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=="],
400+
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+
403+ "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=="],
404+
405+ "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
406+
407+ "from2": ["from2@2.3.0", "", { "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" } }, "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g=="],
408+
409+ "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=="],
410+
411+ "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="],
412+
413+ "fs-write-stream-atomic": ["fs-write-stream-atomic@1.0.10", "", { "dependencies": { "graceful-fs": "^4.1.2", "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" } }, "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA=="],
414+
415+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
416+
417+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
418+
419+ "fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
420+
421+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
422+
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=="],
426+
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=="],
428+
429+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
430+
431+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
432+
433+ "helmet": ["helmet@8.3.0", "", {}, "sha512-Qgpiaws3Sm30Av8Eah6sjMCZZwjlBu+E68rhpCWBshY1lb09HtLwj5GviX0OyQIn+ulUS0iX0AxN5n3tLZzz1w=="],
434+
435+ "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
436+
437+ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
438+
439+ "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
440+
441+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
442+
443+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
444+
445+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
446+
447+ "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
448+
449+ "iferr": ["iferr@0.1.5", "", {}, "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA=="],
450+
451+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
452+
453+ "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
454+
455+ "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="],
456+
457+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
458+
459+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
460+
461+ "ip-address": ["ip-address@10.5.0", "", {}, "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g=="],
462+
463+ "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
464+
465+ "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
466+
467+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
468+
469+ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
470+
471+ "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
472+
473+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
474+
475+ "is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
476+
477+ "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="],
478+
479+ "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
480+
481+ "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="],
482+
483+ "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
484+
485+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
486+
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=="],
496+
497+ "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=="],
498+
499+ "json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="],
500+
501+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
502+
503+ "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
504+
505+ "light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
506+
507+ "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
508+
509+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
510+
511+ "log-symbols": ["log-symbols@3.0.0", "", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="],
512+
513+ "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=="],
514+
515+ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
516+
517+ "lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="],
518+
519+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
520+
521+ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
522+
523+ "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=="],
524+
525+ "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=="],
526+
527+ "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
528+
529+ "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
530+
531+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
532+
533+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
534+
535+ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
536+
537+ "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
538+
539+ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
540+
541+ "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
542+
543+ "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="],
544+
545+ "minipass-fetch": ["minipass-fetch@2.1.2", "", { "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA=="],
546+
547+ "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="],
548+
549+ "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="],
550+
551+ "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
552+
553+ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
554+
555+ "mississippi": ["mississippi@3.0.0", "", { "dependencies": { "concat-stream": "^1.5.0", "duplexify": "^3.4.2", "end-of-stream": "^1.1.0", "flush-write-stream": "^1.0.0", "from2": "^2.1.0", "parallel-transform": "^1.1.0", "pump": "^3.0.0", "pumpify": "^1.3.3", "stream-each": "^1.1.0", "through2": "^2.0.0" } }, "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA=="],
556+
557+ "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
558+
559+ "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=="],
560+
561+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
562+
563+ "nan": ["nan@2.25.0", "", {}, "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g=="],
564+
565+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
566+
567+ "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
568+
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=="],
572+
573+ "npm-run-path": ["npm-run-path@3.1.0", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg=="],
574+
575+ "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
576+
577+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
578+
579+ "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
580+
581+ "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
582+
583+ "p-finally": ["p-finally@2.0.1", "", {}, "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw=="],
584+
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+
589+ "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
590+
591+ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
592+
593+ "parallel-transform": ["parallel-transform@1.2.0", "", { "dependencies": { "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" } }, "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg=="],
594+
595+ "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
596+
597+ "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
598+
599+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
600+
601+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
602+
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=="],
604+
605+ "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
606+
607+ "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
608+
609+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
610+
611+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
612+
613+ "pino": ["pino@10.3.1", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^3.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^4.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg=="],
614+
615+ "pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
616+
617+ "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
618+
619+ "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=="],
620+
621+ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
622+
623+ "process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
624+
625+ "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="],
626+
627+ "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
628+
629+ "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
630+
631+ "pumpify": ["pumpify@1.5.1", "", { "dependencies": { "duplexify": "^3.6.0", "inherits": "^2.0.3", "pump": "^2.0.0" } }, "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ=="],
632+
633+ "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
634+
635+ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
636+
637+ "purescript": ["purescript@0.15.15", "", { "dependencies": { "purescript-installer": "^0.3.5" }, "bin": { "purs": "purs.bin" } }, "sha512-4gwLNJa4KRiV+Wl+lyMY4MUFM6cSnIqvz7MvSTdLdvXJN2P9CrABnNbSspaokkNRFd9soWHBFDlYsAtRchqHtg=="],
638+
639+ "purescript-installer": ["purescript-installer@0.3.5", "", { "dependencies": { "byline": "^5.0.0", "cacache": "^11.3.2", "chalk": "^2.4.2", "env-paths": "^2.2.0", "execa": "^2.0.3", "filesize": "^4.1.2", "is-plain-obj": "^2.0.0", "log-symbols": "^3.0.0", "log-update": "^4.0.0", "make-fetch-happen": "^10.0.0", "minimist": "^1.2.0", "ms": "^2.1.2", "once": "^1.4.0", "rimraf": "^2.6.3", "semver": "^7.3.7", "tar": "^6.1.11", "which": "^1.3.1", "zen-observable": "^0.8.14" }, "bin": { "install-purescript": "index.js" } }, "sha512-w04BBvW4BSQlspLsZ9Bs5vtUBZqvC4bC+FizC5GUp2/rpmYvXJ0CTf79Q3MKo2A8p1ZjqbhCI+SqdTQ30UZUIQ=="],
640+
641+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
642+
643+ "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
644+
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=="],
646+
647+ "readline-sync": ["readline-sync@1.4.10", "", {}, "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw=="],
648+
649+ "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
650+
651+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
652+
653+ "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
654+
655+ "ret": ["ret@0.5.0", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],
656+
657+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
658+
659+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
660+
661+ "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
662+
663+ "rimraf": ["rimraf@2.7.1", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
664+
665+ "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="],
666+
667+ "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
668+
669+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
670+
671+ "run-queue": ["run-queue@1.0.3", "", { "dependencies": { "aproba": "^1.1.1" } }, "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg=="],
672+
673+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
674+
675+ "safe-regex2": ["safe-regex2@5.0.0", "", { "dependencies": { "ret": "~0.5.0" } }, "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw=="],
676+
677+ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
678+
679+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
680+
681+ "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="],
682+
683+ "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="],
684+
685+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
686+
687+ "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
688+
689+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
690+
691+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
692+
693+ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
694+
695+ "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
696+
697+ "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=="],
698+
699+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
700+
701+ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
702+
703+ "socks-proxy-agent": ["socks-proxy-agent@7.0.0", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww=="],
704+
705+ "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
706+
707+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
708+
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=="],
710+
711+ "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
712+
713+ "spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="],
714+
715+ "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="],
716+
717+ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
718+
719+ "ssh2": ["ssh2@1.17.0", "", { "dependencies": { "asn1": "^0.2.6", "bcrypt-pbkdf": "^1.0.2" }, "optionalDependencies": { "cpu-features": "~0.0.10", "nan": "^2.23.0" } }, "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ=="],
720+
721+ "ssri": ["ssri@6.0.2", "", { "dependencies": { "figgy-pudding": "^3.5.1" } }, "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q=="],
722+
723+ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
724+
725+ "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
726+
727+ "stream-each": ["stream-each@1.2.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "stream-shift": "^1.0.0" } }, "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw=="],
728+
729+ "stream-shift": ["stream-shift@1.0.3", "", {}, "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="],
730+
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=="],
732+
733+ "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
734+
735+ "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
736+
737+ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
738+
739+ "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
740+
741+ "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="],
742+
743+ "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=="],
744+
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=="],
746+
747+ "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="],
748+
749+ "through2": ["through2@2.0.5", "", { "dependencies": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" } }, "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ=="],
750+
751+ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
752+
753+ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
754+
755+ "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
756+
757+ "tinyrainbow": ["tinyrainbow@1.2.0", "", {}, "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ=="],
758+
759+ "tinyspy": ["tinyspy@3.0.2", "", {}, "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q=="],
760+
761+ "tldts": ["tldts@7.0.22", "", { "dependencies": { "tldts-core": "^7.0.22" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw=="],
762+
763+ "tldts-core": ["tldts-core@7.0.22", "", {}, "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw=="],
764+
765+ "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
766+
767+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
768+
769+ "toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
770+
771+ "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="],
772+
773+ "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
774+
775+ "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
776+
777+ "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
778+
779+ "typedarray": ["typedarray@0.0.6", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
780+
781+ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
782+
783+ "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="],
784+
785+ "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="],
786+
787+ "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="],
788+
789+ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
790+
791+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
792+
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+
795+ "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=="],
796+
797+ "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=="],
798+
799+ "vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="],
800+
801+ "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
802+
803+ "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
804+
805+ "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
806+
807+ "whatwg-url": ["whatwg-url@16.0.0", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ=="],
808+
809+ "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
810+
811+ "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=="],
812+
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=="],
814+
815+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
816+
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+
819+ "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
820+
821+ "xhr2": ["xhr2@0.2.1", "", {}, "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw=="],
822+
823+ "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="],
824+
825+ "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="],
826+
827+ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
828+
829+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
830+
831+ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
832+
833+ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
834+
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=="],
836+
837+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
838+
839+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
840+
841+ "zen-observable": ["zen-observable@0.8.15", "", {}, "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="],
842+
843+ "@npmcli/move-file/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
844+
845+ "@npmcli/move-file/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
846+
847+ "cacache/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
848+
849+ "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=="],
850+
851+ "cacache/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
852+
853+ "cacache/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
854+
855+ "chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
856+
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+
861+ "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
862+
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=="],
868+
869+ "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
870+
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+
877+ "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
878+
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+
881+ "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=="],
882+
883+ "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=="],
884+
885+ "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
886+
887+ "make-fetch-happen/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
888+
889+ "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
890+
891+ "make-fetch-happen/ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="],
892+
893+ "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
894+
895+ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
896+
897+ "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
898+
899+ "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
900+
901+ "minipass-fetch/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
902+
903+ "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
904+
905+ "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
906+
907+ "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
908+
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=="],
914+
915+ "pumpify/pump": ["pump@2.0.1", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA=="],
916+
917+ "purescript-installer/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
918+
919+ "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=="],
920+
921+ "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
922+
923+ "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=="],
924+
925+ "slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
926+
927+ "socks/ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
928+
929+ "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
930+
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=="],
932+
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=="],
934+
935+ "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
936+
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=="],
938+
939+ "yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
940+
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=="],
942+
943+ "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
944+
945+ "cacache/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
946+
947+ "chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
948+
949+ "concat-stream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
950+
951+ "concat-stream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
952+
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=="],
960+
961+ "fs-minipass/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
962+
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+
973+ "make-fetch-happen/cacache/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
974+
975+ "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=="],
976+
977+ "make-fetch-happen/cacache/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
978+
979+ "make-fetch-happen/cacache/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
980+
981+ "make-fetch-happen/cacache/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
982+
983+ "make-fetch-happen/cacache/unique-filename": ["unique-filename@2.0.1", "", { "dependencies": { "unique-slug": "^3.0.0" } }, "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A=="],
984+
985+ "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
986+
987+ "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
988+
989+ "make-fetch-happen/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
990+
991+ "minipass-collect/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
992+
993+ "minipass-fetch/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
994+
995+ "minipass-fetch/minizlib/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
996+
997+ "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
998+
999+ "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1000+
1001+ "minipass-sized/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1002+
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+
1011+ "purescript-installer/tar/chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
1012+
1013+ "purescript-installer/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
1014+
1015+ "purescript-installer/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
1016+
1017+ "purescript-installer/tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
1018+
1019+ "purescript-installer/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1020+
1021+ "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
1022+
1023+ "slice-ansi/ansi-styles/color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
1024+
1025+ "test-exclude/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1026+
1027+ "through2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
1028+
1029+ "through2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
1030+
1031+ "@npmcli/move-file/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
1032+
1033+ "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=="],
1034+
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+
1041+ "make-fetch-happen/cacache/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
1042+
1043+ "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=="],
1044+
1045+ "make-fetch-happen/cacache/tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
1046+
1047+ "make-fetch-happen/cacache/tar/minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="],
1048+
1049+ "make-fetch-happen/cacache/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
1050+
1051+ "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=="],
1052+
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+
1061+ "purescript-installer/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1062+
1063+ "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=="],
1064+
1065+ "slice-ansi/ansi-styles/color-convert/color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
1066+
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=="],
1068+
1069+ "cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
1070+
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=="],
1074+
1075+ "make-fetch-happen/cacache/rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
1076+
1077+ "make-fetch-happen/cacache/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
1078+
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+
1085+ "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=="],
1088+ }
1089+}
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+20−5
@@ -1,12 +1,14 @@
1{
1{
2 "name": "purescript-yoga-fetch-om",
2 "name": "purescript-yoga-fetch-om",
3− "version": "0.2.0",
3+ "version": "0.8.1",
4 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
4 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
5 "type": "module",
5 "type": "module",
6 "scripts": {
6 "scripts": {
7 "build": "spago build",
7 "build": "spago build",
8− "test": "spago build && bunx vitest run",
8+ "test": "spago test",
9+ "coverage": "spago build && c8 spago test",
10+ "test:compile-fail": "bun compile-fail-tests/run.js",
9− "clean": "rm -rf output .spago",
11+ "clean": "rm -rf output .spago coverage",
10 "watch": "spago build --watch",
12 "watch": "spago build --watch",
11 "repl": "spago repl",
13 "repl": "spago repl",
12 "docs": "spago docs",
14 "docs": "spago docs",
@@ -24,10 +26,23 @@
24 "author": "",
26 "author": "",
25 "license": "MIT",
27 "license": "MIT",
26 "devDependencies": {
28 "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",
38+ "fastify": "^5.7.4",
39+ "jose": "^6.2.8",
27 "jsdom": "^28.0.0",
40 "jsdom": "^28.0.0",
28 "purescript": "^0.15.15",
41 "purescript": "^0.15.15",
29− "spago": "^0.93.42",
42+ "spago": "^1.0.3",
30 "vitest": "^2.1.8"
43 "vitest": "^2.1.8"
31 },
44 },
32− "dependencies": {}
45+ "dependencies": {
46+ "pino": "^10.3.1"
47+ }
33}
48}
spago.lock+721−98
@@ -8,21 +8,30 @@
8 {
8 {
9 "aff": ">=8.0.0 <9.0.0"
9 "aff": ">=8.0.0 <9.0.0"
10 },
10 },
11+ {
12+ "arraybuffer-types": ">=3.0.2 <4.0.0"
13+ },
11 {
14 {
12 "arrays": ">=7.3.0 <8.0.0"
15 "arrays": ">=7.3.0 <8.0.0"
13 },
16 },
14 {
17 {
15 "effect": ">=4.0.0 <5.0.0"
18 "effect": ">=4.0.0 <5.0.0"
16 },
19 },
20+ {
21+ "either": ">=6.1.0 <7.0.0"
22+ },
17 {
23 {
18 "exceptions": ">=6.1.0 <7.0.0"
24 "exceptions": ">=6.1.0 <7.0.0"
19 },
25 },
20 {
26 {
21− "http-methods": ">=6.0.0 <7.0.0"
27+ "http-methods": ">=6.1.0 <7.0.0"
22 },
28 },
23 {
29 {
24 "js-fetch": ">=0.2.1 <0.3.0"
30 "js-fetch": ">=0.2.1 <0.3.0"
25 },
31 },
32+ {
33+ "js-promise-aff": ">=1.0.0 <2.0.0"
34+ },
26 {
35 {
27 "maybe": ">=6.0.0 <7.0.0"
36 "maybe": ">=6.0.0 <7.0.0"
28 },
37 },
@@ -35,6 +44,9 @@
35 {
44 {
36 "strings": ">=6.0.1 <7.0.0"
45 "strings": ">=6.0.1 <7.0.0"
37 },
46 },
47+ {
48+ "tailrec": ">=6.1.0 <7.0.0"
49+ },
38 {
50 {
39 "transformers": ">=6.1.0 <7.0.0"
51 "transformers": ">=6.1.0 <7.0.0"
40 },
52 },
@@ -51,13 +63,16 @@
51 "variant": ">=8.0.0 <9.0.0"
63 "variant": ">=8.0.0 <9.0.0"
52 },
64 },
53 {
65 {
54− "yoga-http-api": ">=0.1.0 <0.2.0"
66+ "yoga-http-api": ">=0.3.4 <0.4.0"
55 },
67 },
56 {
68 {
57− "yoga-json": ">=5.2.0 <6.0.0"
69+ "yoga-json": ">=5.2.1 <6.0.0"
58 },
70 },
59 {
71 {
60− "yoga-om": ">=0.1.0 <0.2.0"
72+ "yoga-om": ">=1.5.2 <2.0.0"
73+ },
74+ {
75+ "yoga-om-strom": ">=0.4.3 <0.5.0"
61 }
76 }
62 ]
77 ]
63 },
78 },
@@ -66,8 +81,37 @@
66 {
81 {
67 "console": ">=6.0.0 <7.0.0"
82 "console": ">=6.0.0 <7.0.0"
68 },
83 },
84+ "enums",
85+ "foldable-traversable",
86+ "foreign",
87+ "foreign-object",
88+ "integers",
89+ "js-promise",
90+ {
91+ "js-uri": ">=3.1.0 <4.0.0"
92+ },
93+ {
94+ "justifill": ">=0.5.0 <0.6.0"
95+ },
96+ "partial",
97+ {
98+ "quickcheck": ">=8.0.1 <9.0.0"
99+ },
69 {
100 {
70− "vitest": ">=1.0.0 <2.0.0"
101+ "refs": ">=6.0.0 <7.0.0"
102+ },
103+ {
104+ "spec": ">=8.0.0 <9.0.0"
105+ },
106+ {
107+ "spec-discovery": ">=8.0.0 <9.0.0"
108+ },
109+ {
110+ "spec-quickcheck": ">=5.0.2 <6.0.0"
111+ },
112+ "yoga-fastify",
113+ {
114+ "yoga-fastify-om": ">=0.4.7 <0.5.0"
71 }
115 }
72 ]
116 ]
73 }
117 }
@@ -97,6 +141,77 @@
97 "unsafe-coerce"
141 "unsafe-coerce"
98 ]
142 ]
99 },
143 },
144+ "aff-retry": {
145+ "type": "registry",
146+ "version": "2.0.0",
147+ "integrity": "sha256-rKZNcP4IKSyBFQBVzrCvE0ULoJt0N/uDLLJ0dPotr4k=",
148+ "dependencies": [
149+ "aff",
150+ "arrays",
151+ "datetime",
152+ "effect",
153+ "either",
154+ "exceptions",
155+ "integers",
156+ "maybe",
157+ "newtype",
158+ "numbers",
159+ "prelude",
160+ "random",
161+ "transformers"
162+ ]
163+ },
164+ "ansi": {
165+ "type": "registry",
166+ "version": "7.0.0",
167+ "integrity": "sha256-tOUaXJFzPSfBVAqLI8hc+Tz2M1rg16cXqyHaK/3SmFg=",
168+ "dependencies": [
169+ "foldable-traversable",
170+ "lists",
171+ "prelude"
172+ ]
173+ },
174+ "argonaut-codecs": {
175+ "type": "registry",
176+ "version": "9.1.0",
177+ "integrity": "sha256-K910SBrmYallESsm8pxJYThs7lig6hxX3M4PW33Cgew=",
178+ "dependencies": [
179+ "argonaut-core",
180+ "arrays",
181+ "bifunctors",
182+ "either",
183+ "foldable-traversable",
184+ "foreign-object",
185+ "identity",
186+ "integers",
187+ "lists",
188+ "maybe",
189+ "nonempty",
190+ "ordered-collections",
191+ "prelude",
192+ "record",
193+ "strings",
194+ "tuples"
195+ ]
196+ },
197+ "argonaut-core": {
198+ "type": "registry",
199+ "version": "7.0.0",
200+ "integrity": "sha256-gYZihtBIKthVuXXoiPphMookSf8M5aHyFYkcIWqlxTM=",
201+ "dependencies": [
202+ "arrays",
203+ "control",
204+ "either",
205+ "foreign-object",
206+ "functions",
207+ "gen",
208+ "maybe",
209+ "nonempty",
210+ "prelude",
211+ "strings",
212+ "tailrec"
213+ ]
214+ },
100 "arraybuffer-types": {
215 "arraybuffer-types": {
101 "type": "registry",
216 "type": "registry",
102 "version": "3.0.2",
217 "version": "3.0.2",
@@ -294,6 +409,16 @@
294 "unsafe-coerce"
409 "unsafe-coerce"
295 ]
410 ]
296 },
411 },
412+ "exitcodes": {
413+ "type": "registry",
414+ "version": "4.0.0",
415+ "integrity": "sha256-xBl4aFSEQ05Owp1ZvO6OxxvLK3LVzU35RSbj79SsePQ=",
416+ "dependencies": [
417+ "enums",
418+ "maybe",
419+ "prelude"
420+ ]
421+ },
297 "foldable-traversable": {
422 "foldable-traversable": {
298 "type": "registry",
423 "type": "registry",
299 "version": "6.0.0",
424 "version": "6.0.0",
@@ -348,6 +473,16 @@
348 "unsafe-coerce"
473 "unsafe-coerce"
349 ]
474 ]
350 },
475 },
476+ "fork": {
477+ "type": "registry",
478+ "version": "6.0.0",
479+ "integrity": "sha256-0keBVeZZg1eCZEy/En1CohVW829gQUqKtqBNIRa93/A=",
480+ "dependencies": [
481+ "aff",
482+ "prelude",
483+ "transformers"
484+ ]
485+ },
351 "free": {
486 "free": {
352 "type": "registry",
487 "type": "registry",
353 "version": "7.1.0",
488 "version": "7.1.0",
@@ -430,8 +565,8 @@
430 },
565 },
431 "http-methods": {
566 "http-methods": {
432 "type": "registry",
567 "type": "registry",
433− "version": "6.0.0",
568+ "version": "6.1.0",
434− "integrity": "sha256-S0UjrJSgwJxEsFHM/pX77dM8V4HfvvDqdHPckQrtxMs=",
569+ "integrity": "sha256-WbjEmCv6rysY/aKwlrcspyzShi5bIQy+AFpDzPMnSP4=",
435 "dependencies": [
570 "dependencies": [
436 "either",
571 "either",
437 "prelude",
572 "prelude",
@@ -549,6 +684,27 @@
549 "transformers"
684 "transformers"
550 ]
685 ]
551 },
686 },
687+ "js-uri": {
688+ "type": "registry",
689+ "version": "3.1.0",
690+ "integrity": "sha256-7waHOctAfqhhw540FNJ7C/YsDMQW9TmA+SpHhduH6Uw=",
691+ "dependencies": [
692+ "functions",
693+ "maybe",
694+ "prelude"
695+ ]
696+ },
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+ },
552 "lazy": {
708 "lazy": {
553 "type": "registry",
709 "type": "registry",
554 "version": "6.0.0",
710 "version": "6.0.0",
@@ -560,6 +716,20 @@
560 "prelude"
716 "prelude"
561 ]
717 ]
562 },
718 },
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+ },
563 "lists": {
733 "lists": {
564 "type": "registry",
734 "type": "registry",
565 "version": "7.0.0",
735 "version": "7.0.0",
@@ -613,6 +783,23 @@
613 "prelude"
783 "prelude"
614 ]
784 ]
615 },
785 },
786+ "mmorph": {
787+ "type": "registry",
788+ "version": "7.0.0",
789+ "integrity": "sha256-Cfltaq+tfYjuSoSBN/b8cSQZbvZTqIRs6ebQewbIkGs=",
790+ "dependencies": [
791+ "bifunctors",
792+ "either",
793+ "free",
794+ "functors",
795+ "identity",
796+ "maybe",
797+ "newtype",
798+ "prelude",
799+ "transformers",
800+ "tuples"
801+ ]
802+ },
616 "newtype": {
803 "newtype": {
617 "type": "registry",
804 "type": "registry",
618 "version": "5.0.0",
805 "version": "5.0.0",
@@ -622,6 +809,107 @@
622 "safe-coerce"
809 "safe-coerce"
623 ]
810 ]
624 },
811 },
812+ "node-buffer": {
813+ "type": "registry",
814+ "version": "9.0.0",
815+ "integrity": "sha256-RdQjilmEHqsT4fwpCr8Mavw8KTafYrS3ZM6zeAAVAl8=",
816+ "dependencies": [
817+ "arraybuffer-types",
818+ "effect",
819+ "functions",
820+ "maybe",
821+ "nullable",
822+ "partial",
823+ "prelude",
824+ "st",
825+ "unsafe-coerce"
826+ ]
827+ },
828+ "node-event-emitter": {
829+ "type": "registry",
830+ "version": "3.0.0",
831+ "integrity": "sha256-wodx71NxJBPXOaawFn01V1ByYB2vT+I3RuV2giOVKMg=",
832+ "dependencies": [
833+ "effect",
834+ "either",
835+ "functions",
836+ "maybe",
837+ "nullable",
838+ "prelude",
839+ "unsafe-coerce"
840+ ]
841+ },
842+ "node-fs": {
843+ "type": "registry",
844+ "version": "9.2.0",
845+ "integrity": "sha256-fxioTSm9y47WuYs9F/7nxCh/RVDCl3Kr3Hg779J4SJA=",
846+ "dependencies": [
847+ "aff",
848+ "datetime",
849+ "effect",
850+ "either",
851+ "enums",
852+ "exceptions",
853+ "functions",
854+ "integers",
855+ "js-date",
856+ "maybe",
857+ "node-buffer",
858+ "node-path",
859+ "node-streams",
860+ "nullable",
861+ "partial",
862+ "prelude",
863+ "strings"
864+ ]
865+ },
866+ "node-path": {
867+ "type": "registry",
868+ "version": "5.0.1",
869+ "integrity": "sha256-j7n/SmpVz0FOMJl4XZop3ofJ+MeIPkMNqUEUKHEL6Us=",
870+ "dependencies": [
871+ "effect"
872+ ]
873+ },
874+ "node-process": {
875+ "type": "registry",
876+ "version": "11.2.0",
877+ "integrity": "sha256-onzKeNmaeYfN3HSDPqSFTrrP2Yl9tnVsfKKhnf2plxM=",
878+ "dependencies": [
879+ "effect",
880+ "exceptions",
881+ "foreign",
882+ "foreign-object",
883+ "maybe",
884+ "node-event-emitter",
885+ "node-streams",
886+ "nullable",
887+ "posix-types",
888+ "prelude",
889+ "strings"
890+ ]
891+ },
892+ "node-streams": {
893+ "type": "registry",
894+ "version": "9.0.1",
895+ "integrity": "sha256-yuVGm/R/tBr4Nphi+a1a984Z0fkmN9Q5wWtSmbmJTpA=",
896+ "dependencies": [
897+ "aff",
898+ "arrays",
899+ "effect",
900+ "either",
901+ "exceptions",
902+ "maybe",
903+ "node-buffer",
904+ "node-event-emitter",
905+ "nullable",
906+ "prelude",
907+ "refs",
908+ "st",
909+ "tailrec",
910+ "unsafe-coerce"
911+ ]
912+ },
625 "nonempty": {
913 "nonempty": {
626 "type": "registry",
914 "type": "registry",
627 "version": "7.0.0",
915 "version": "7.0.0",
@@ -635,6 +923,16 @@
635 "unfoldable"
923 "unfoldable"
636 ]
924 ]
637 },
925 },
926+ "now": {
927+ "type": "registry",
928+ "version": "6.0.0",
929+ "integrity": "sha256-mTyG4s8tps3XIf0jjV/lME2/QlwMzrQly2wqfSSUM3o=",
930+ "dependencies": [
931+ "datetime",
932+ "effect",
933+ "prelude"
934+ ]
935+ },
638 "nullable": {
936 "nullable": {
639 "type": "registry",
937 "type": "registry",
640 "version": "6.0.0",
938 "version": "6.0.0",
@@ -655,39 +953,89 @@
655 "prelude"
953 "prelude"
656 ]
954 ]
657 },
955 },
658− "ordered-collections": {
956+ "open-memoize": {
659 "type": "registry",
957 "type": "registry",
660− "version": "3.2.0",
958+ "version": "6.2.0",
661− "integrity": "sha256-NaEXc2cz/7lJfxPWEja4XlZbGxuRqJwFKQJjsJf51kQ=",
959+ "integrity": "sha256-yJLuVYX8FocHIJ+6q7efH/Pj4S+e1Y57DZ5Dn7qf4ww=",
662 "dependencies": [
960 "dependencies": [
663− "arrays",
961+ "either",
664− "control",
962+ "integers",
665− "foldable-traversable",
963+ "lazy",
666− "functions",
667− "gen",
668 "lists",
964 "lists",
669 "maybe",
965 "maybe",
670− "newtype",
671 "partial",
966 "partial",
672 "prelude",
967 "prelude",
673− "safe-coerce",
968+ "strings",
674− "tailrec",
675− "tuples",
969+ "tuples"
676− "unfoldable"
677 ]
970 ]
678 },
971 },
679− "orders": {
972+ "optparse": {
680 "type": "registry",
973 "type": "registry",
681− "version": "6.0.0",
974+ "version": "5.0.1",
682− "integrity": "sha256-nSMxZK7wxyDULkjZqvMyB71mVo6b0AyLLHosDVbv1XM=",
975+ "integrity": "sha256-2btDjHPRR0uujUQJuZAN9AzvVoXe7yG6IwnY5evERhQ=",
683 "dependencies": [
976 "dependencies": [
977+ "arrays",
978+ "bifunctors",
979+ "control",
980+ "effect",
981+ "either",
982+ "enums",
983+ "exists",
984+ "exitcodes",
985+ "foldable-traversable",
986+ "free",
987+ "integers",
988+ "lazy",
989+ "lists",
990+ "maybe",
991+ "newtype",
992+ "node-buffer",
993+ "node-process",
994+ "node-streams",
995+ "nonempty",
996+ "numbers",
997+ "open-memoize",
998+ "partial",
999+ "prelude",
1000+ "strings",
1001+ "tailrec",
1002+ "transformers",
1003+ "tuples"
1004+ ]
1005+ },
1006+ "ordered-collections": {
1007+ "type": "registry",
1008+ "version": "3.2.0",
1009+ "integrity": "sha256-NaEXc2cz/7lJfxPWEja4XlZbGxuRqJwFKQJjsJf51kQ=",
1010+ "dependencies": [
1011+ "arrays",
1012+ "control",
1013+ "foldable-traversable",
1014+ "functions",
1015+ "gen",
1016+ "lists",
1017+ "maybe",
1018+ "newtype",
1019+ "partial",
1020+ "prelude",
1021+ "safe-coerce",
1022+ "tailrec",
1023+ "tuples",
1024+ "unfoldable"
1025+ ]
1026+ },
1027+ "orders": {
1028+ "type": "registry",
1029+ "version": "6.0.0",
1030+ "integrity": "sha256-nSMxZK7wxyDULkjZqvMyB71mVo6b0AyLLHosDVbv1XM=",
1031+ "dependencies": [
684− "newtype",
1032+ "newtype",
685− "prelude"
1033+ "prelude"
686− ]
1034+ ]
687− },
1035+ },
688− "parallel": {
1036+ "parallel": {
689− "type": "registry",
1037+ "type": "registry",
690− "version": "7.0.0",
1038+ "version": "7.0.0",
691 "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=",
1039 "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=",
692 "dependencies": [
1040 "dependencies": [
693 "control",
1041 "control",
@@ -709,6 +1057,37 @@
709 "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1057 "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
710 "dependencies": []
1058 "dependencies": []
711 },
1059 },
1060+ "pipes": {
1061+ "type": "registry",
1062+ "version": "8.0.0",
1063+ "integrity": "sha256-dtUNpG8BGVr7pu6mQZyAnUAftUHgNS7f0QH3Kw5UTpQ=",
1064+ "dependencies": [
1065+ "aff",
1066+ "control",
1067+ "effect",
1068+ "either",
1069+ "foldable-traversable",
1070+ "identity",
1071+ "lists",
1072+ "maybe",
1073+ "mmorph",
1074+ "newtype",
1075+ "prelude",
1076+ "tailrec",
1077+ "transformers",
1078+ "tuples"
1079+ ]
1080+ },
1081+ "posix-types": {
1082+ "type": "registry",
1083+ "version": "6.0.0",
1084+ "integrity": "sha256-Je5jG439F/SFO/IvVevZnWVxDIonrehlKuJkefP2lHE=",
1085+ "dependencies": [
1086+ "maybe",
1087+ "newtype",
1088+ "prelude"
1089+ ]
1090+ },
712 "prelude": {
1091 "prelude": {
713 "type": "registry",
1092 "type": "registry",
714 "version": "6.0.2",
1093 "version": "6.0.2",
@@ -730,6 +1109,49 @@
730 "tuples"
1109 "tuples"
731 ]
1110 ]
732 },
1111 },
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+ },
1145+ "random": {
1146+ "type": "registry",
1147+ "version": "6.0.0",
1148+ "integrity": "sha256-7sZRo6P9UIiXDZ9s2QL9T8h3wlIwLdQrR4+oZKOijQw=",
1149+ "dependencies": [
1150+ "effect",
1151+ "integers",
1152+ "prelude"
1153+ ]
1154+ },
733 "record": {
1155 "record": {
734 "type": "registry",
1156 "type": "registry",
735 "version": "4.0.0",
1157 "version": "4.0.0",
@@ -762,6 +1184,27 @@
762 "prelude"
1184 "prelude"
763 ]
1185 ]
764 },
1186 },
1187+ "routing-duplex": {
1188+ "type": "registry",
1189+ "version": "0.7.0",
1190+ "integrity": "sha256-rQw9yC01qE/LS134zucIhLD6GFcVUzHX5HyzwosdGXM=",
1191+ "dependencies": [
1192+ "arrays",
1193+ "control",
1194+ "either",
1195+ "foldable-traversable",
1196+ "integers",
1197+ "js-uri",
1198+ "lazy",
1199+ "maybe",
1200+ "newtype",
1201+ "prelude",
1202+ "profunctor",
1203+ "record",
1204+ "strings",
1205+ "tuples"
1206+ ]
1207+ },
765 "safe-coerce": {
1208 "safe-coerce": {
766 "type": "registry",
1209 "type": "registry",
767 "version": "2.0.0",
1210 "version": "2.0.0",
@@ -770,6 +1213,101 @@
770 "unsafe-coerce"
1213 "unsafe-coerce"
771 ]
1214 ]
772 },
1215 },
1216+ "spec": {
1217+ "type": "registry",
1218+ "version": "8.1.2",
1219+ "integrity": "sha256-klPPJ6lBQI1CGOAdLJ+ZoGRt8XZippkTKjn53BrSkL0=",
1220+ "dependencies": [
1221+ "aff",
1222+ "ansi",
1223+ "arrays",
1224+ "avar",
1225+ "bifunctors",
1226+ "control",
1227+ "datetime",
1228+ "effect",
1229+ "either",
1230+ "exceptions",
1231+ "foldable-traversable",
1232+ "fork",
1233+ "identity",
1234+ "integers",
1235+ "lists",
1236+ "maybe",
1237+ "newtype",
1238+ "now",
1239+ "ordered-collections",
1240+ "parallel",
1241+ "pipes",
1242+ "prelude",
1243+ "refs",
1244+ "strings",
1245+ "tailrec",
1246+ "transformers",
1247+ "tuples"
1248+ ]
1249+ },
1250+ "spec-discovery": {
1251+ "type": "registry",
1252+ "version": "8.4.1",
1253+ "integrity": "sha256-guvY+NtSOBQmM+HqNJ7woX6taN8hS1wEz7JDmdKF11E=",
1254+ "dependencies": [
1255+ "aff",
1256+ "effect",
1257+ "foldable-traversable",
1258+ "prelude",
1259+ "spec",
1260+ "spec-node"
1261+ ]
1262+ },
1263+ "spec-node": {
1264+ "type": "registry",
1265+ "version": "0.0.2",
1266+ "integrity": "sha256-HDW02z3Mj+daux0j9487ApDBX32/gX0od6YR4zvDRZI=",
1267+ "dependencies": [
1268+ "aff",
1269+ "argonaut-codecs",
1270+ "argonaut-core",
1271+ "arrays",
1272+ "control",
1273+ "datetime",
1274+ "effect",
1275+ "either",
1276+ "foldable-traversable",
1277+ "identity",
1278+ "integers",
1279+ "maybe",
1280+ "newtype",
1281+ "node-buffer",
1282+ "node-fs",
1283+ "node-process",
1284+ "now",
1285+ "numbers",
1286+ "optparse",
1287+ "ordered-collections",
1288+ "partial",
1289+ "prelude",
1290+ "spec",
1291+ "strings",
1292+ "tuples"
1293+ ]
1294+ },
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+ },
773 "st": {
1311 "st": {
774 "type": "registry",
1312 "type": "registry",
775 "version": "6.2.0",
1313 "version": "6.2.0",
@@ -884,6 +1422,22 @@
884 "tuples"
1422 "tuples"
885 ]
1423 ]
886 },
1424 },
1425+ "undefined-is-not-a-problem": {
1426+ "type": "registry",
1427+ "version": "1.1.0",
1428+ "integrity": "sha256-0YkVzpcKi40T43go7/f8uLVIYgir6lGzOytL6LPfiog=",
1429+ "dependencies": [
1430+ "effect",
1431+ "either",
1432+ "foreign",
1433+ "maybe",
1434+ "newtype",
1435+ "prelude",
1436+ "tuples",
1437+ "type-equality",
1438+ "unsafe-coerce"
1439+ ]
1440+ },
887 "unfoldable": {
1441 "unfoldable": {
888 "type": "registry",
1442 "type": "registry",
889 "version": "6.0.0",
1443 "version": "6.0.0",
@@ -934,40 +1488,6 @@
934 "unsafe-coerce"
1488 "unsafe-coerce"
935 ]
1489 ]
936 },
1490 },
937− "vitest": {
938− "type": "registry",
939− "version": "1.0.0",
940− "integrity": "sha256-grMOE1y+Ld5u8NZQ5OL32Rk+MzYt0DQI3Wi4L8b9hm8=",
941− "dependencies": [
942− "aff",
943− "effect",
944− "exceptions",
945− "js-promise",
946− "js-promise-aff",
947− "maybe",
948− "nullable",
949− "prelude",
950− "transformers",
951− "unsafe-coerce",
952− "web-dom",
953− "web-html"
954− ]
955− },
956− "web-dom": {
957− "type": "registry",
958− "version": "6.0.0",
959− "integrity": "sha256-rVeqkxChkjqisDvOujy9BMEgGqJkwX3t6g21bXfiG5o=",
960− "dependencies": [
961− "effect",
962− "enums",
963− "maybe",
964− "newtype",
965− "nullable",
966− "prelude",
967− "unsafe-coerce",
968− "web-events"
969− ]
970− },
971 "web-events": {
1491 "web-events": {
972 "type": "registry",
1492 "type": "registry",
973 "version": "4.0.0",
1493 "version": "4.0.0",
@@ -1007,86 +1527,117 @@
1007 "web-events"
1527 "web-events"
1008 ]
1528 ]
1009 },
1529 },
1010− "web-html": {
1530+ "web-streams": {
1011 "type": "registry",
1531 "type": "registry",
1012− "version": "4.1.1",
1532+ "version": "4.0.0",
1013− "integrity": "sha256-yOuZJGUxFrivHKpo/jKuKzFZaq6PPPE64ftSysXFssk=",
1533+ "integrity": "sha256-iKQbHh/3wNOWyGTZmWJZ1Yuri9J/wQIJKXcxrELaHVo=",
1014 "dependencies": [
1534 "dependencies": [
1535+ "arraybuffer-types",
1015 "effect",
1536 "effect",
1016− "enums",
1537+ "exceptions",
1017− "foreign",
1538+ "js-promise",
1018− "functions",
1019− "js-date",
1020 "maybe",
1539 "maybe",
1021− "media-types",
1022− "newtype",
1023 "nullable",
1540 "nullable",
1024 "prelude",
1541 "prelude",
1025− "unsafe-coerce",
1542+ "tuples"
1026− "web-dom",
1027− "web-events",
1028− "web-file",
1029− "web-storage"
1030 ]
1543 ]
1031 },
1544 },
1032− "web-storage": {
1545+ "yoga-fastify": {
1033 "type": "registry",
1546 "type": "registry",
1034− "version": "5.0.0",
1547+ "version": "0.5.4",
1035− "integrity": "sha256-YJfos55oeKtBStNfoWfeflSy8j+4IDym5eaZtIY7YM4=",
1548+ "integrity": "sha256-YenjePRdiApMPpcvkXlzre7c4fIuvk5T19Z2uEAWhR4=",
1036 "dependencies": [
1549 "dependencies": [
1550+ "aff",
1551+ "arrays",
1552+ "bifunctors",
1037 "effect",
1553 "effect",
1554+ "either",
1555+ "foldable-traversable",
1556+ "foreign",
1557+ "foreign-object",
1558+ "js-promise",
1559+ "js-promise-aff",
1038 "maybe",
1560 "maybe",
1561+ "newtype",
1039 "nullable",
1562 "nullable",
1040 "prelude",
1563 "prelude",
1564+ "record",
1565+ "strings",
1041 "unsafe-coerce",
1566 "unsafe-coerce",
1042− "web-events"
1567+ "variant",
1568+ "yoga-http-api",
1569+ "yoga-json"
1043 ]
1570 ]
1044 },
1571 },
1045− "web-streams": {
1572+ "yoga-fastify-om": {
1046 "type": "registry",
1573 "type": "registry",
1047− "version": "4.0.0",
1574+ "version": "0.4.7",
1048− "integrity": "sha256-iKQbHh/3wNOWyGTZmWJZ1Yuri9J/wQIJKXcxrELaHVo=",
1575+ "integrity": "sha256-KGQ7lHI7pcSiEQoEq6j6K+3XilcPy8wQ/M9C1y+uaEk=",
1049 "dependencies": [
1576 "dependencies": [
1050− "arraybuffer-types",
1577+ "aff",
1578+ "console",
1051 "effect",
1579 "effect",
1580+ "either",
1052 "exceptions",
1581 "exceptions",
1053− "js-promise",
1582+ "foreign",
1583+ "foreign-object",
1584+ "integers",
1054 "maybe",
1585 "maybe",
1055− "nullable",
1586+ "node-buffer",
1587+ "numbers",
1056 "prelude",
1588 "prelude",
1057− "tuples"
1589+ "record",
1590+ "refs",
1591+ "routing-duplex",
1592+ "strings",
1593+ "transformers",
1594+ "typelevel-prelude",
1595+ "unsafe-coerce",
1596+ "variant",
1597+ "yoga-fastify",
1598+ "yoga-http-api",
1599+ "yoga-json",
1600+ "yoga-om",
1601+ "yoga-om-layer",
1602+ "yoga-om-strom"
1058 ]
1603 ]
1059 },
1604 },
1060 "yoga-http-api": {
1605 "yoga-http-api": {
1061 "type": "registry",
1606 "type": "registry",
1062− "version": "0.1.0",
1607+ "version": "0.3.4",
1063− "integrity": "sha256-Afkd2lCW98V0hAj68sGJwCsqHI5FgYI+IeNBa7mENWY=",
1608+ "integrity": "sha256-DSIJ8YWr5vB5IMpyaBALM+fx57lz3AAAR0MGtfHeWxo=",
1064 "dependencies": [
1609 "dependencies": [
1065 "aff",
1610 "aff",
1611+ "arraybuffer-types",
1066 "arrays",
1612 "arrays",
1613+ "effect",
1067 "either",
1614 "either",
1068 "foldable-traversable",
1615 "foldable-traversable",
1069 "foreign",
1616 "foreign",
1070 "foreign-object",
1617 "foreign-object",
1071 "identity",
1618 "identity",
1072 "integers",
1619 "integers",
1073− "literals",
1620+ "js-promise",
1621+ "js-promise-aff",
1074 "maybe",
1622 "maybe",
1075 "newtype",
1623 "newtype",
1624+ "nullable",
1625+ "numbers",
1076 "prelude",
1626 "prelude",
1077 "record",
1627 "record",
1078 "strings",
1628 "strings",
1079 "tuples",
1629 "tuples",
1630+ "undefined-is-not-a-problem",
1080 "unsafe-coerce",
1631 "unsafe-coerce",
1081− "untagged-union",
1082 "variant",
1632 "variant",
1083− "yoga-json"
1633+ "yoga-json",
1634+ "yoga-options"
1084 ]
1635 ]
1085 },
1636 },
1086 "yoga-json": {
1637 "yoga-json": {
1087 "type": "registry",
1638 "type": "registry",
1088− "version": "5.2.0",
1639+ "version": "5.2.1",
1089− "integrity": "sha256-M+kkz6E1Gf+C4bKnhqoBFIoVcc74loi3rLULePbB204=",
1640+ "integrity": "sha256-12I7qIf27oKV/D/YRcHtGpN8n7GQ0Bm3uzvI4ZjOQrs=",
1090 "dependencies": [
1641 "dependencies": [
1091 "arrays",
1642 "arrays",
1092 "bifunctors",
1643 "bifunctors",
@@ -1124,10 +1675,11 @@
1124 },
1675 },
1125 "yoga-om": {
1676 "yoga-om": {
1126 "type": "registry",
1677 "type": "registry",
1127− "version": "0.1.0",
1678+ "version": "1.5.2",
1128− "integrity": "sha256-0EGWo/UOVLzZ14DPtdv0C+bTvNO49wNgtTBIXI6QHqU=",
1679+ "integrity": "sha256-2ys14BF6NPgzFDFUIFk0EDxmqkA8+yghIA9TXHlOmE0=",
1129 "dependencies": [
1680 "dependencies": [
1130 "aff",
1681 "aff",
1682+ "aff-retry",
1131 "avar",
1683 "avar",
1132 "console",
1684 "console",
1133 "control",
1685 "control",
@@ -1136,12 +1688,14 @@
1136 "either",
1688 "either",
1137 "exceptions",
1689 "exceptions",
1138 "functions",
1690 "functions",
1691+ "heterogeneous",
1139 "maybe",
1692 "maybe",
1140 "newtype",
1693 "newtype",
1141 "parallel",
1694 "parallel",
1142 "prelude",
1695 "prelude",
1143 "record",
1696 "record",
1144 "record-studio",
1697 "record-studio",
1698+ "refs",
1145 "tailrec",
1699 "tailrec",
1146 "transformers",
1700 "transformers",
1147 "tuples",
1701 "tuples",
@@ -1151,6 +1705,75 @@
1151 "variant"
1705 "variant"
1152 ]
1706 ]
1153 },
1707 },
1708+ "yoga-om-layer": {
1709+ "type": "registry",
1710+ "version": "1.2.2",
1711+ "integrity": "sha256-Usw2ZysteSYNkEG3CDmVVXUk7cgr1mntkmhbR33nF6M=",
1712+ "dependencies": [
1713+ "aff",
1714+ "aff-retry",
1715+ "arrays",
1716+ "effect",
1717+ "exceptions",
1718+ "foldable-traversable",
1719+ "maybe",
1720+ "nullable",
1721+ "ordered-collections",
1722+ "parallel",
1723+ "prelude",
1724+ "record",
1725+ "record-studio",
1726+ "refs",
1727+ "transformers",
1728+ "type-equality",
1729+ "typelevel-prelude",
1730+ "unsafe-coerce",
1731+ "variant",
1732+ "yoga-om"
1733+ ]
1734+ },
1735+ "yoga-om-strom": {
1736+ "type": "registry",
1737+ "version": "0.4.3",
1738+ "integrity": "sha256-dxj1zveEES6lF+iiMHx+qOhUG3zmiJJnlNpwiAfUBRE=",
1739+ "dependencies": [
1740+ "aff",
1741+ "arrays",
1742+ "avar",
1743+ "catenable-lists",
1744+ "console",
1745+ "control",
1746+ "datetime",
1747+ "effect",
1748+ "either",
1749+ "exceptions",
1750+ "foldable-traversable",
1751+ "integers",
1752+ "lists",
1753+ "maybe",
1754+ "now",
1755+ "prelude",
1756+ "refs",
1757+ "spec",
1758+ "st",
1759+ "tailrec",
1760+ "transformers",
1761+ "tuples",
1762+ "variant",
1763+ "yoga-om"
1764+ ]
1765+ },
1766+ "yoga-options": {
1767+ "type": "registry",
1768+ "version": "0.1.1",
1769+ "integrity": "sha256-INtZKI2rw7KcDikzmyXlglsEujSMqHT5jz3ho9dVYC8=",
1770+ "dependencies": [
1771+ "identity",
1772+ "literals",
1773+ "unsafe-coerce",
1774+ "untagged-union"
1775+ ]
1776+ },
1154 "yoga-tree": {
1777 "yoga-tree": {
1155 "type": "registry",
1778 "type": "registry",
1156 "version": "1.0.0",
1779 "version": "1.0.0",
spago.yaml+26−8
@@ -1,9 +1,8 @@
1workspace: {}
1workspace: {}
2−
3package:
2package:
4 name: yoga-fetch-om
3 name: yoga-fetch-om
5 publish:
4 publish:
6− version: 0.2.0
5+ version: 0.8.1
7 license: MIT
6 license: MIT
8 location:
7 location:
9 githubOwner: rowtype-yoga
8 githubOwner: rowtype-yoga
@@ -11,30 +10,49 @@ package:
11 include:
10 include:
12 - LICENSE
11 - LICENSE
13 - README.md
12 - README.md
14− - CHANGELOG.md
15 exclude:
13 exclude:
16 - .github
14 - .github
17 dependencies:
15 dependencies:
18 - aff: ">=8.0.0 <9.0.0"
16 - aff: ">=8.0.0 <9.0.0"
17+ - arraybuffer-types: ">=3.0.2 <4.0.0"
19 - arrays: ">=7.3.0 <8.0.0"
18 - arrays: ">=7.3.0 <8.0.0"
20 - effect: ">=4.0.0 <5.0.0"
19 - effect: ">=4.0.0 <5.0.0"
20+ - either: ">=6.1.0 <7.0.0"
21 - exceptions: ">=6.1.0 <7.0.0"
21 - exceptions: ">=6.1.0 <7.0.0"
22− - http-methods: ">=6.0.0 <7.0.0"
22+ - http-methods: ">=6.1.0 <7.0.0"
23 - js-fetch: ">=0.2.1 <0.3.0"
23 - js-fetch: ">=0.2.1 <0.3.0"
24+ - js-promise-aff: ">=1.0.0 <2.0.0"
24 - maybe: ">=6.0.0 <7.0.0"
25 - maybe: ">=6.0.0 <7.0.0"
25 - prelude: ">=6.0.2 <7.0.0"
26 - prelude: ">=6.0.2 <7.0.0"
26 - record: ">=4.0.0 <5.0.0"
27 - record: ">=4.0.0 <5.0.0"
27 - strings: ">=6.0.1 <7.0.0"
28 - strings: ">=6.0.1 <7.0.0"
29+ - tailrec: ">=6.1.0 <7.0.0"
28 - transformers: ">=6.1.0 <7.0.0"
30 - transformers: ">=6.1.0 <7.0.0"
29 - tuples: ">=7.0.0 <8.0.0"
31 - tuples: ">=7.0.0 <8.0.0"
30 - typelevel-prelude: ">=7.0.0 <8.0.0"
32 - typelevel-prelude: ">=7.0.0 <8.0.0"
31 - unsafe-coerce: ">=6.0.0 <7.0.0"
33 - unsafe-coerce: ">=6.0.0 <7.0.0"
32 - variant: ">=8.0.0 <9.0.0"
34 - variant: ">=8.0.0 <9.0.0"
33− - yoga-http-api: ">=0.1.0 <0.2.0"
35+ - yoga-http-api: ">=0.3.4 <0.4.0"
34− - yoga-json: ">=5.2.0 <6.0.0"
36+ - yoga-json: ">=5.2.1 <6.0.0"
35− - yoga-om: ">=0.1.0 <0.2.0"
37+ - yoga-om: ">=1.5.2 <2.0.0"
38+ - yoga-om-strom: ">=0.4.3 <0.5.0"
36 test:
39 test:
37 main: Test.Main
40 main: Test.Main
38 dependencies:
41 dependencies:
39− - vitest: ">=1.0.0 <2.0.0"
42+ - enums
43+ - foldable-traversable
44+ - foreign
45+ - foreign-object
46+ - integers
47+ - js-promise
48+ - partial
49+ - yoga-fastify
40 - console: ">=6.0.0 <7.0.0"
50 - console: ">=6.0.0 <7.0.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"
54+ - 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+206−38
@@ -1,5 +1,8 @@
1module Yoga.Fetch.Om
1module Yoga.Fetch.Om
2 ( client
2 ( client
3+ , clientWith
4+ , ClientInputs
5+ , ClientWithConfig
3 , deriveClient
6 , deriveClient
4 , class DeriveClient
7 , class DeriveClient
5 , deriveClientImpl
8 , deriveClientImpl
@@ -7,15 +10,25 @@ module Yoga.Fetch.Om
7 , deriveClientRL
10 , deriveClientRL
8 , class DeriveClientFn
11 , class DeriveClientFn
9 , deriveClientFn
12 , deriveClientFn
13+ , class DeriveClientFnWith
14+ , deriveClientFnWith
15+ , class DeriveClientWith
16+ , deriveClientWithImpl
17+ , class DeriveClientWithRL
18+ , deriveClientWithRL
10 , class RecordRow
19 , class RecordRow
20+ , class ToHeaders
21+ , toHeaders
11 , module Yoga.HTTP.API.Route
22 , module Yoga.HTTP.API.Route
12 , module Yoga.HTTP.API.Path
23 , module Yoga.HTTP.API.Path
24+ , module Yoga.Fetch.Om.StreamDecode
13 , module Yoga.Fetch.Om.Simple
25 , module Yoga.Fetch.Om.Simple
14 ) where
26 ) where
15
27
16import Prelude
28import Prelude
17
29
18−import Data.Symbol (class IsSymbol)
30+import Data.Symbol (class IsSymbol, reflectSymbol)
31+import Data.Tuple.Nested ((/\))
19import Data.Variant (Variant)
32import Data.Variant (Variant)
20import Prim.Row as Row
33import Prim.Row as Row
21import Prim.RowList (class RowToList, RowList)
34import Prim.RowList (class RowToList, RowList)
@@ -23,18 +36,33 @@ import Prim.RowList as RL
23import Record as Record
36import Record as Record
24import Type.Proxy (Proxy(..))
37import Type.Proxy (Proxy(..))
25import Unsafe.Coerce (unsafeCoerce)
38import Unsafe.Coerce (unsafeCoerce)
39+import JS.Fetch.Headers (Headers)
40+import JS.Fetch.Headers as Headers
26import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
41import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
27import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
42import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
43+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
28−import Yoga.Fetch.Om.ClientFn (class BuildClientFn, class CheckBodyIsUnit, class CheckRowEmpty, buildClientFn)
44+import Yoga.Fetch.Om.ClientFunction (class BuildClientFn, class CheckBodyIsUnit, buildClientFn)
29−import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractRequestHeaders)
45+import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractBodyEncoding, class ExtractRequestHeaders)
30−import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class SerializeBody, makeRequest, serializeBody)
46+import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class BodyEncoding, makeRequest, encodingContentType, encodeBody)
31import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
47import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
32import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
48import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
33import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
49import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
34import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
50import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
35−import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, Response(..), JSON, FormData, NoBody)
51+import Yoga.HTTP.API.Route (Route(..), GET, POST, PUT, DELETE, PATCH, QUERY, Response(..), JSON, FormData, PlainText, Streaming, NoBody, class HeaderValue, printHeader)
36import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
52import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
37−import Yoga.Om (Om, fromAff)
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
38
66
39-- | Extract row type from Record type
67-- | Extract row type from Record type
40class RecordRow :: Type -> Row Type -> Constraint
68class RecordRow :: Type -> Row Type -> Constraint
@@ -42,91 +70,231 @@ class RecordRow t r | t -> r
42
70
43instance RecordRow (Record r) r
71instance RecordRow (Record r) r
44
72
73+class ToHeaders :: RowList Type -> Row Type -> Constraint
74+class ToHeaders headersRL headers where
75+ toHeaders :: Proxy headersRL -> Record headers -> Headers
76+
77+instance ToHeaders RL.Nil headers where
78+ toHeaders _ _ = Headers.empty
79+
80+else instance
81+ ( IsSymbol name
82+ , HeaderValue ty
83+ , Row.Cons name ty tailRow headers
84+ , Row.Lacks name tailRow
85+ , ToHeaders tail tailRow
86+ ) =>
87+ ToHeaders (RL.Cons name ty tail) headers where
88+ toHeaders _ headers = Headers.fromFoldable ([ headerName /\ headerValue ] <> Headers.toArray rest)
89+ where
90+ headerName = reflectSymbol (Proxy :: Proxy name)
91+ headerValue = printHeader (Record.get (Proxy :: Proxy name) headers)
92+ rest = toHeaders (Proxy :: Proxy tail) (unsafeCoerce headers :: Record tailRow)
93+
94+class DeriveClientFn
45−class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
95+ :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
46class
96class
47− DeriveClientFn method segments request response errorRow successRow result fn
97+ DeriveClientFn method segments request response ctx extraErr routeErrors result fn
48− | method segments request response -> errorRow successRow result fn where
98+ | method segments request response -> routeErrors result
99+ , method segments request response ctx extraErr -> fn where
49 deriveClientFn :: String -> Proxy (Route method segments request response) -> fn
100 deriveClientFn :: String -> Proxy (Route method segments request response) -> fn
50
101
51instance
102instance
52 ( SegmentPathParams segments pathParams
103 ( SegmentPathParams segments pathParams
53 , SegmentQueryParams segments queryParams
104 , SegmentQueryParams segments queryParams
54 , ExtractRequestBody request body
105 , ExtractRequestBody request body
106+ , ExtractBodyEncoding request encoding
55 , ExtractRequestHeaders request headers
107 , ExtractRequestHeaders request headers
56 , PathPattern segments
108 , PathPattern segments
57 , BuildUrl segments pathParams queryParams
109 , BuildUrl segments pathParams queryParams
58 , MakeRequest method
110 , MakeRequest method
59− , SerializeBody body
111+ , BodyEncoding encoding body
60− , SplitResponses response successRow errorRow
112+ , SplitResponses response successRow routeErrors
61− , ParseResponse errorRow successRow
113+ , ParseResponse routeErrors successRow
62 , RowToList successRow successRL
114 , RowToList successRow successRL
63 , VariantOrValue successRL successRow result
115 , VariantOrValue successRL successRow result
64 , Row.Union pathParams queryParams pathQuery
116 , Row.Union pathParams queryParams pathQuery
65 , Row.Nub pathQuery pathQuery
117 , Row.Nub pathQuery pathQuery
66 , RowToList pathQuery pathQueryRL
118 , RowToList pathQuery pathQueryRL
67− , CheckRowEmpty headers headersFlag
119+ , RowToList headers headersRL
120+ , ToHeaders headersRL headers
68 , CheckBodyIsUnit body bodyFlag
121 , CheckBodyIsUnit body bodyFlag
122+ , Row.Union routeErrors extraErr errRow
69− , BuildClientFn pathQueryRL headersFlag bodyFlag body pathQuery headers errorRow result fn
123+ , BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers (Record pathQuery) { | ctx } errRow result fn
70 ) =>
124 ) =>
71− DeriveClientFn method segments request response errorRow successRow result fn where
125+ DeriveClientFn method segments request response ctx extraErr routeErrors result fn where
72 deriveClientFn baseUrl _ =
126 deriveClientFn baseUrl _ =
73− buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersFlag) (Proxy :: _ bodyFlag) impl
127+ buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersRL) (Proxy :: _ bodyFlag) identity impl
74 where
128 where
75− impl :: Record pathQuery -> Record headers -> body -> Om {} errorRow result
129+ impl :: Record pathQuery -> Record headers -> body -> Om { | ctx } errRow result
76− impl pathQueryRec _headers bodyVal = do
130+ impl pathQueryRec headersRec bodyVal = widenOm do
77 let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
131 let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
132+ let hdrs = toHeaders (Proxy :: _ headersRL) headersRec
133+ let ct = encodingContentType (Proxy :: _ encoding)
78− fetchResp <- makeRequest (Proxy :: _ method) url (serializeBody bodyVal) # fromAff
134+ fetchResp <- makeRequest (Proxy :: _ method) url hdrs ct (encodeBody @encoding bodyVal) # toOm
79− variant <- parseResponse fetchResp :: Om {} errorRow (Variant successRow)
135+ variant <- parseResponse fetchResp :: Om (Record ()) routeErrors (Variant successRow)
80 variantOrValue (Proxy :: _ successRL) variant # pure
136 variantOrValue (Proxy :: _ successRL) variant # pure
81 where
137 where
82 pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
138 pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
83 queryParamsRec = unsafeCoerce pathQueryRec :: Record queryParams
139 queryParamsRec = unsafeCoerce pathQueryRec :: Record queryParams
84
140
141+ widenOm :: Om (Record ()) routeErrors ~> Om { | ctx } errRow
142+ widenOm = unsafeCoerce
143+
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+
85−class DeriveClient :: Row Type -> Row Type -> Constraint
208+class DeriveClient :: Row Type -> Row Type -> Row Type -> Row Type -> Constraint
86−class DeriveClient routesRow clientsRow | routesRow -> clientsRow where
209+class DeriveClient ctx extraErr routesRow clientsRow | routesRow ctx extraErr -> clientsRow where
87 deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
210 deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
88
211
89−-- | 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.
90-- |
215-- |
91-- | ```purescript
216-- | ```purescript
92-- | type UserAPI = { getUser :: Route ... }
217-- | type UserAPI = { getUser :: Route ... }
93-- | api = client @UserAPI "https://api.example.com"
218-- | 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
94-- | ```
222-- | ```
95−client :: forall @routes routesRow clientsRow. RecordRow routes routesRow => DeriveClient routesRow clientsRow => String -> Record clientsRow
223+client :: forall @routes routesRow ctx extraErr clientsRow. RecordRow routes routesRow => DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
96−client baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
224+client baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesRow })
97
225
98-- | Deprecated: Use `client` with VTA instead
226-- | Deprecated: Use `client` with VTA instead
99-- |
227-- |
100-- | ```purescript
228-- | ```purescript
101-- | api = deriveClient @UserAPI "https://api.example.com"
229-- | api = deriveClient @UserAPI "https://api.example.com"
102-- | ```
230-- | ```
103−deriveClient :: forall @routesRow clientsRow. DeriveClient routesRow clientsRow => String -> Record clientsRow
231+deriveClient :: forall @routesRow ctx extraErr clientsRow. DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
104−deriveClient baseUrl = deriveClientImpl baseUrl (Proxy :: _ { | routesRow })
232+deriveClient baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesRow })
233+
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 })
105
241
106instance
242instance
107 ( RowToList routesRow rl
243 ( RowToList routesRow rl
108− , DeriveClientRL rl () clientsRow
244+ , DeriveClientRL ctx extraErr rl () clientsRow
109 ) =>
245 ) =>
110− DeriveClient routesRow clientsRow where
246+ DeriveClient ctx extraErr routesRow clientsRow where
111− deriveClientImpl baseUrl _ = deriveClientRL baseUrl (Proxy :: _ rl) {}
247+ deriveClientImpl baseUrl _ = deriveClientRL @ctx @extraErr baseUrl (Proxy :: _ rl) {}
248+
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
112
280
113−class DeriveClientRL :: RowList Type -> Row Type -> Row Type -> Constraint
281+class DeriveClientRL :: Row Type -> Row Type -> RowList Type -> Row Type -> Row Type -> Constraint
114−class DeriveClientRL rl acc out | rl acc -> out where
282+class DeriveClientRL ctx extraErr rl acc out | ctx extraErr rl acc -> out where
115 deriveClientRL :: String -> Proxy rl -> Record acc -> Record out
283 deriveClientRL :: String -> Proxy rl -> Record acc -> Record out
116
284
117−instance DeriveClientRL RL.Nil acc acc where
285+instance DeriveClientRL ctx extraErr RL.Nil acc acc where
118 deriveClientRL _ _ acc = acc
286 deriveClientRL _ _ acc = acc
119
287
120instance
288instance
121 ( IsSymbol label
289 ( IsSymbol label
122− , DeriveClientFn method segments request response errorRow successRow result fn
290+ , DeriveClientFn method segments request response ctx extraErr routeErrors result fn
123− , DeriveClientRL tail acc1 acc2
291+ , DeriveClientRL ctx extraErr tail acc1 acc2
124 , Row.Cons label fn acc2 out
292 , Row.Cons label fn acc2 out
125 , Row.Lacks label acc2
293 , Row.Lacks label acc2
126 , Row.Cons label (Route method segments request response) routeTail routeRow
294 , Row.Cons label (Route method segments request response) routeTail routeRow
127 ) =>
295 ) =>
128− DeriveClientRL (RL.Cons label (Route method segments request response) tail) acc1 out where
296+ DeriveClientRL ctx extraErr (RL.Cons label (Route method segments request response) tail) acc1 out where
129 deriveClientRL baseUrl _ acc = Record.insert (Proxy :: _ label) clientFn rest
297 deriveClientRL baseUrl _ acc = Record.insert (Proxy :: _ label) clientFn rest
130 where
298 where
131− clientFn = deriveClientFn baseUrl (Proxy :: _ (Route method segments request response))
299+ clientFn = deriveClientFn @_ @_ @_ @_ @ctx @extraErr baseUrl (Proxy :: _ (Route method segments request response))
132− rest = deriveClientRL baseUrl (Proxy :: _ tail) acc
300+ rest = deriveClientRL @ctx @extraErr baseUrl (Proxy :: _ tail) acc
src/Yoga/Fetch/Om/BuildUrl.jsnew file+87
@@ -0,0 +1,87 @@
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+ };
87+}
src/Yoga/Fetch/Om/BuildUrl.purs+47−12
@@ -19,7 +19,6 @@ import Prelude
19
19
20import Data.Array as Array
20import Data.Array as Array
21import Data.Maybe (Maybe(..))
21import Data.Maybe (Maybe(..))
22−import Data.String (Pattern(..), Replacement(..))
23import Data.String as String
22import Data.String as String
24import Data.Symbol (class IsSymbol, reflectSymbol)
23import Data.Symbol (class IsSymbol, reflectSymbol)
25import Prim.Row as Row
24import Prim.Row as Row
@@ -28,6 +27,13 @@ import Prim.RowList as RL
28import Record as Record
27import Record as Record
29import Type.Proxy (Proxy(..))
28import Type.Proxy (Proxy(..))
30import Yoga.HTTP.API.Path (class PathPattern, pathPattern)
29import Yoga.HTTP.API.Path (class PathPattern, pathPattern)
30+import Yoga.Fetch.Om.ReadOptional (readOptional)
31+
32+foreign 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
31
37
32class BuildUrl :: forall k. k -> Row Type -> Row Type -> Constraint
38class BuildUrl :: forall k. k -> Row Type -> Row Type -> Constraint
33class BuildUrl segments pathParams queryParams | segments -> pathParams queryParams where
39class BuildUrl segments pathParams queryParams | segments -> pathParams queryParams where
@@ -44,7 +50,7 @@ instance
44 pattern = pathPattern segmentsProxy
50 pattern = pathPattern segmentsProxy
45 withBase = case baseUrl of
51 withBase = case baseUrl of
46 "" -> pattern
52 "" -> pattern
47− base -> base <> pattern
53+ base -> appendPath_ base pattern
48 withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
54 withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
49 withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
55 withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
50
56
@@ -79,8 +85,8 @@ instance
79 substitutePathParamsRL _ rec url = substitutePathParamsRL (Proxy :: _ tail) rec replaced
85 substitutePathParamsRL _ rec url = substitutePathParamsRL (Proxy :: _ tail) rec replaced
80 where
86 where
81 value = Record.get (Proxy :: _ name) rec
87 value = Record.get (Proxy :: _ name) rec
82− pattern = Pattern (":" <> reflectSymbol (Proxy :: _ name))
88+ name = reflectSymbol (Proxy :: _ name)
83− replaced = String.replaceAll pattern (Replacement (serializeParam value)) url
89+ replaced = substitutePathParam_ name (serializeParam value) url
84
90
85class AppendQueryParams :: Row Type -> Constraint
91class AppendQueryParams :: Row Type -> Constraint
86class AppendQueryParams params where
92class AppendQueryParams params where
@@ -94,12 +100,11 @@ instance
94 , AppendQueryParamsRL rl params
100 , AppendQueryParamsRL rl params
95 ) =>
101 ) =>
96 AppendQueryParams params where
102 AppendQueryParams params where
97− appendQueryParamsImpl _ rec url = url <> queryString
103+ appendQueryParamsImpl _ rec url = case pairs of
104+ [] -> url
105+ _ -> appendQueryString_ url (String.joinWith "&" pairs)
98 where
106 where
99 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
107 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
100− queryString = case pairs of
101− [] -> ""
102− _ -> "?" <> String.joinWith "&" pairs
103
108
104class AppendQueryParamsRL :: RowList Type -> Row Type -> Constraint
109class AppendQueryParamsRL :: RowList Type -> Row Type -> Constraint
105class AppendQueryParamsRL rl r where
110class AppendQueryParamsRL rl r where
@@ -108,19 +113,49 @@ class AppendQueryParamsRL rl r where
108instance AppendQueryParamsRL RL.Nil r where
113instance AppendQueryParamsRL RL.Nil r where
109 appendQueryParamsRL _ _ acc = acc
114 appendQueryParamsRL _ _ acc = acc
110
115
116+-- Maybe (Array ty): optional repeated query param — missing/empty field → no pairs
111instance
117instance
112 ( IsSymbol name
118 ( IsSymbol name
113 , SerializeParam ty
119 , SerializeParam ty
114 , AppendQueryParamsRL tail r
120 , AppendQueryParamsRL tail r
115− , 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
116 ) =>
135 ) =>
117 AppendQueryParamsRL (RL.Cons name (Maybe ty) tail) r where
136 AppendQueryParamsRL (RL.Cons name (Maybe ty) tail) r where
118 appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
137 appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
119 where
138 where
120− acc' = case Record.get (Proxy :: _ name) rec of
139+ acc' = case (readOptional @name rec :: Maybe ty) of
121 Nothing -> acc
140 Nothing -> acc
122− Just value -> Array.snoc acc (reflectSymbol (Proxy :: _ name) <> "=" <> serializeParam value)
141+ Just value -> Array.snoc acc (encodeURIComponent_ (reflectSymbol (Proxy :: _ name)) <> "=" <> encodeURIComponent_ (serializeParam value))
142+
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
123
157
158+-- bare ty (Required query param): always present
124else instance
159else instance
125 ( IsSymbol name
160 ( IsSymbol name
126 , SerializeParam ty
161 , SerializeParam ty
@@ -131,7 +166,7 @@ else instance
131 appendQueryParamsRL _ rec acc =
166 appendQueryParamsRL _ rec acc =
132 appendQueryParamsRL (Proxy :: _ tail) rec (Array.snoc acc pair)
167 appendQueryParamsRL (Proxy :: _ tail) rec (Array.snoc acc pair)
133 where
168 where
134− pair = reflectSymbol (Proxy :: _ name) <> "=" <> serializeParam (Record.get (Proxy :: _ name) rec)
169+ pair = encodeURIComponent_ (reflectSymbol (Proxy :: _ name)) <> "=" <> encodeURIComponent_ (serializeParam (Record.get (Proxy :: _ name) rec))
135
170
136class SerializeParam :: Type -> Constraint
171class SerializeParam :: Type -> Constraint
137class SerializeParam a where
172class SerializeParam a where
src/Yoga/Fetch/Om/ClientFn.pursdeleted−142
@@ -1,142 +0,0 @@
1−module Yoga.Fetch.Om.ClientFn
2− ( class BuildClientFn
3− , buildClientFn
4− , class CheckBodyIsUnit
5− , class CheckRowEmpty
6− , IsUnit
7− , IsNotUnit
8− , IsEmpty
9− , IsNonEmpty
10− ) where
11−
12−import Data.Unit (Unit, unit)
13−import Prim.RowList (class RowToList, RowList)
14−import Prim.RowList as RL
15−import Type.Proxy (Proxy)
16−import Unsafe.Coerce (unsafeCoerce)
17−import Yoga.Om (Om)
18−
19−data IsUnit
20−data IsNotUnit
21−data IsEmpty
22−data IsNonEmpty
23−
24−class CheckBodyIsUnit :: Type -> Type -> Constraint
25−class CheckBodyIsUnit body flag | body -> flag
26−
27−instance CheckBodyIsUnit Unit IsUnit
28−else instance CheckBodyIsUnit body IsNotUnit
29−
30−class CheckRowEmpty :: Row Type -> Type -> Constraint
31−class CheckRowEmpty row flag | row -> flag
32−
33−instance RowToList row RL.Nil => CheckRowEmpty row IsEmpty
34−else instance CheckRowEmpty row IsNonEmpty
35−
36−class BuildClientFn
37− :: RowList Type -> Type -> Type -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
38−class
39− BuildClientFn pathQueryRL headersFlag bodyFlag body pathQuery headers errorRow result fn
40− | pathQueryRL headersFlag bodyFlag body pathQuery headers errorRow result -> fn where
41− buildClientFn
42− :: Proxy pathQueryRL
43− -> Proxy headersFlag
44− -> Proxy bodyFlag
45− -> (Record pathQuery -> Record headers -> body -> Om {} errorRow result)
46− -> fn
47−
48−instance
49− BuildClientFn RL.Nil
50− IsEmpty
51− IsUnit
52− Unit
53− pq
54− h
55− errorRow
56− result
57− (Om {} errorRow result) where
58− buildClientFn _ _ _ f = f (unsafeCoerce {}) (unsafeCoerce {}) unit
59−
60−instance
61− BuildClientFn RL.Nil
62− IsEmpty
63− IsNotUnit
64− body
65− pq
66− h
67− errorRow
68− result
69− (body -> Om {} errorRow result) where
70− buildClientFn _ _ _ f = \b -> f (unsafeCoerce {}) (unsafeCoerce {}) b
71−
72−instance
73− BuildClientFn RL.Nil
74− IsNonEmpty
75− IsUnit
76− Unit
77− pq
78− h
79− errorRow
80− result
81− (Record h -> Om {} errorRow result) where
82− buildClientFn _ _ _ f = \hdrs -> f (unsafeCoerce {}) hdrs unit
83−
84−instance
85− BuildClientFn RL.Nil
86− IsNonEmpty
87− IsNotUnit
88− body
89− pq
90− h
91− errorRow
92− result
93− (Record h -> body -> Om {} errorRow result) where
94− buildClientFn _ _ _ f = \hdrs b -> f (unsafeCoerce {}) hdrs b
95−
96−instance
97− BuildClientFn (RL.Cons n t tl)
98− IsEmpty
99− IsUnit
100− Unit
101− pq
102− h
103− errorRow
104− result
105− (Record pq -> Om {} errorRow result) where
106− buildClientFn _ _ _ f = \pqr -> f pqr (unsafeCoerce {}) unit
107−
108−instance
109− BuildClientFn (RL.Cons n t tl)
110− IsEmpty
111− IsNotUnit
112− body
113− pq
114− h
115− errorRow
116− result
117− (Record pq -> body -> Om {} errorRow result) where
118− buildClientFn _ _ _ f = \pqr b -> f pqr (unsafeCoerce {}) b
119−
120−instance
121− BuildClientFn (RL.Cons n t tl)
122− IsNonEmpty
123− IsUnit
124− Unit
125− pq
126− h
127− errorRow
128− result
129− (Record pq -> Record h -> Om {} errorRow result) where
130− buildClientFn _ _ _ f = \pqr hdrs -> f pqr hdrs unit
131−
132−instance
133− BuildClientFn (RL.Cons n t tl)
134− IsNonEmpty
135− IsNotUnit
136− body
137− pq
138− h
139− errorRow
140− result
141− (Record pq -> Record h -> body -> Om {} errorRow result) where
142− buildClientFn _ _ _ f = f
src/Yoga/Fetch/Om/ClientFunction.pursnew file+157
@@ -0,0 +1,157 @@
1+module Yoga.Fetch.Om.ClientFunction
2+ ( class BuildClientFn
3+ , buildClientFn
4+ , class CheckBodyIsUnit
5+ , IsUnit
6+ , IsNotUnit
7+ ) where
8+
9+import Data.Unit (Unit, unit)
10+import Prim.RowList (RowList)
11+import Prim.RowList as RL
12+import Type.Proxy (Proxy)
13+import Yoga.Om (Om)
14+
15+data IsUnit
16+data IsNotUnit
17+
18+class CheckBodyIsUnit :: Type -> Type -> Constraint
19+class CheckBodyIsUnit body flag | body -> flag
20+
21+instance CheckBodyIsUnit Unit IsUnit
22+else instance CheckBodyIsUnit body IsNotUnit
23+
24+class BuildClientFn
25+ :: RowList Type -> RowList Type -> Type -> Type -> Row Type -> Row Type -> Type -> Type -> Row Type -> Type -> Type -> Constraint
26+class
27+ BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers arg ctx errorRow result fn
28+ | pathQueryRL -> pathQuery
29+ , headersRL -> headers
30+ , pathQueryRL headersRL bodyFlag body arg ctx errorRow result -> fn where
31+ buildClientFn
32+ :: Proxy pathQueryRL
33+ -> Proxy headersRL
34+ -> Proxy bodyFlag
35+ -> (arg -> Record pathQuery)
36+ -> (Record pathQuery -> Record headers -> body -> Om ctx errorRow result)
37+ -> fn
38+
39+-- No path/query, no headers, no body
40+instance
41+ BuildClientFn RL.Nil
42+ RL.Nil
43+ IsUnit
44+ Unit
45+ ()
46+ ()
47+ arg
48+ ctx
49+ errorRow
50+ result
51+ (Om ctx errorRow result) where
52+ buildClientFn _ _ _ _ f = f {} {} unit
53+
54+-- No path/query, no headers, with body
55+instance
56+ BuildClientFn RL.Nil
57+ RL.Nil
58+ IsNotUnit
59+ body
60+ ()
61+ ()
62+ arg
63+ ctx
64+ errorRow
65+ result
66+ (body -> Om ctx errorRow result) where
67+ buildClientFn _ _ _ _ f b = f {} {} b
68+
69+-- No path/query, with headers, no body
70+instance
71+ BuildClientFn RL.Nil
72+ (RL.Cons n t tl)
73+ IsUnit
74+ Unit
75+ ()
76+ h
77+ arg
78+ ctx
79+ errorRow
80+ result
81+ (Record h -> Om ctx errorRow result) where
82+ buildClientFn _ _ _ _ f hdrs = f {} hdrs unit
83+
84+-- No path/query, with headers, with body
85+instance
86+ BuildClientFn RL.Nil
87+ (RL.Cons n t tl)
88+ IsNotUnit
89+ body
90+ ()
91+ h
92+ arg
93+ ctx
94+ errorRow
95+ result
96+ (Record h -> body -> Om ctx errorRow result) where
97+ buildClientFn _ _ _ _ f hdrs b = f {} hdrs b
98+
99+-- With path/query, no headers, no body
100+instance
101+ BuildClientFn (RL.Cons n t tl)
102+ RL.Nil
103+ IsUnit
104+ Unit
105+ pq
106+ ()
107+ arg
108+ ctx
109+ errorRow
110+ result
111+ (arg -> Om ctx errorRow result) where
112+ buildClientFn _ _ _ convert f pqr = f (convert pqr) {} unit
113+
114+-- With path/query, no headers, with body
115+instance
116+ BuildClientFn (RL.Cons n t tl)
117+ RL.Nil
118+ IsNotUnit
119+ body
120+ pq
121+ ()
122+ arg
123+ ctx
124+ errorRow
125+ result
126+ (arg -> body -> Om ctx errorRow result) where
127+ buildClientFn _ _ _ convert f pqr b = f (convert pqr) {} b
128+
129+-- With path/query, with headers, no body
130+instance
131+ BuildClientFn (RL.Cons n t tl)
132+ (RL.Cons hn ht htl)
133+ IsUnit
134+ Unit
135+ pq
136+ h
137+ arg
138+ ctx
139+ errorRow
140+ result
141+ (arg -> Record h -> Om ctx errorRow result) where
142+ buildClientFn _ _ _ convert f pqr hdrs = f (convert pqr) hdrs unit
143+
144+-- With path/query, with headers, with body
145+instance
146+ BuildClientFn (RL.Cons n t tl)
147+ (RL.Cons hn ht htl)
148+ IsNotUnit
149+ body
150+ pq
151+ h
152+ arg
153+ ctx
154+ errorRow
155+ result
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/ExtractParams.purs+22−1
@@ -1,6 +1,7 @@
1module Yoga.Fetch.Om.ExtractParams
1module Yoga.Fetch.Om.ExtractParams
2 ( class ExtractRequestHeaders
2 ( class ExtractRequestHeaders
3 , class ExtractRequestBody
3 , class ExtractRequestBody
4+ , class ExtractBodyEncoding
4 , class FindBodyType
5 , class FindBodyType
5 , class UnwrapEncoding
6 , class UnwrapEncoding
6 , class FindHeaders
7 , class FindHeaders
@@ -9,7 +10,8 @@ module Yoga.Fetch.Om.ExtractParams
9import Data.Unit (Unit)
10import Data.Unit (Unit)
10import Prim.RowList (class RowToList, RowList)
11import Prim.RowList (class RowToList, RowList)
11import Prim.RowList as RL
12import Prim.RowList as RL
12−import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody)
13+import Yoga.HTTP.API.Route.Encoding (JSON, FormData, NoBody, PlainText)
14+import Yoga.HTTP.API.Route.Handler (Request)
13
15
14class ExtractRequestBody :: Type -> Type -> Constraint
16class ExtractRequestBody :: Type -> Type -> Constraint
15class ExtractRequestBody request body | request -> body
17class ExtractRequestBody request body | request -> body
@@ -20,6 +22,21 @@ instance
20 , UnwrapEncoding encoding body
22 , UnwrapEncoding encoding body
21 ) =>
23 ) =>
22 ExtractRequestBody (Record row) body
24 ExtractRequestBody (Record row) body
25+else instance
26+ ExtractRequestBody (Record row) body =>
27+ ExtractRequestBody (Request (Record row)) body
28+
29+class ExtractBodyEncoding :: Type -> Type -> Constraint
30+class ExtractBodyEncoding request encoding | request -> encoding
31+
32+instance
33+ ( RowToList row rl
34+ , FindBodyType rl encoding
35+ ) =>
36+ ExtractBodyEncoding (Record row) encoding
37+else instance
38+ ExtractBodyEncoding (Record row) encoding =>
39+ ExtractBodyEncoding (Request (Record row)) encoding
23
40
24class ExtractRequestHeaders :: Type -> Row Type -> Constraint
41class ExtractRequestHeaders :: Type -> Row Type -> Constraint
25class ExtractRequestHeaders request headers | request -> headers
42class ExtractRequestHeaders request headers | request -> headers
@@ -29,6 +46,9 @@ instance
29 , FindHeaders rl headers
46 , FindHeaders rl headers
30 ) =>
47 ) =>
31 ExtractRequestHeaders (Record row) headers
48 ExtractRequestHeaders (Record row) headers
49+else instance
50+ ExtractRequestHeaders (Record row) headers =>
51+ ExtractRequestHeaders (Request (Record row)) headers
32
52
33class FindBodyType :: RowList Type -> Type -> Constraint
53class FindBodyType :: RowList Type -> Type -> Constraint
34class FindBodyType rl encoding | rl -> encoding
54class FindBodyType rl encoding | rl -> encoding
@@ -43,6 +63,7 @@ class UnwrapEncoding encoding body | encoding -> body
43instance UnwrapEncoding (JSON ty) ty
63instance UnwrapEncoding (JSON ty) ty
44instance UnwrapEncoding (FormData ty) ty
64instance UnwrapEncoding (FormData ty) ty
45instance UnwrapEncoding NoBody Unit
65instance UnwrapEncoding NoBody Unit
66+instance UnwrapEncoding PlainText String
46
67
47class FindHeaders :: RowList Type -> Row Type -> Constraint
68class FindHeaders :: RowList Type -> Row Type -> Constraint
48class FindHeaders rl headers | rl -> headers
69class FindHeaders rl headers | rl -> headers
src/Yoga/Fetch/Om/MakeRequest.jsnew file+1
@@ -0,0 +1 @@
1+export const toUrlEncoded = (obj) => new URLSearchParams(obj).toString();
src/Yoga/Fetch/Om/MakeRequest.purs+44−16
@@ -2,18 +2,22 @@ module Yoga.Fetch.Om.MakeRequest
2 ( class MakeRequest
2 ( class MakeRequest
3 , httpMethod
3 , httpMethod
4 , makeRequest
4 , makeRequest
5− , class SerializeBody
5+ , class BodyEncoding
6− , serializeBody
6+ , encodingContentType
7+ , encodeBody
7 ) where
8 ) where
8
9
9import Prelude
10import Prelude
10
11
11import Data.HTTP.Method (Method(..))
12import Data.HTTP.Method (Method(..))
13+import Data.Array as Array
12import Data.Maybe (Maybe(..))
14import Data.Maybe (Maybe(..))
15+import Data.Tuple (fst)
13import Data.Tuple.Nested ((/\))
16import Data.Tuple.Nested ((/\))
14import Effect.Aff (Aff)
17import Effect.Aff (Aff)
15import Effect.Class (liftEffect)
18import Effect.Class (liftEffect)
16import JS.Fetch as Fetch
19import JS.Fetch as Fetch
20+import JS.Fetch.Headers (Headers)
17import JS.Fetch.Headers as Headers
21import JS.Fetch.Headers as Headers
18import JS.Fetch.Request as Request
22import JS.Fetch.Request as Request
19import JS.Fetch.RequestBody as Body
23import JS.Fetch.RequestBody as Body
@@ -27,6 +31,7 @@ import JS.Fetch.Duplex as Duplex
27import JS.Fetch.Response (Response) as FetchResponse
31import JS.Fetch.Response (Response) as FetchResponse
28import Promise.Aff as Promise
32import Promise.Aff as Promise
29import Type.Proxy (Proxy)
33import Type.Proxy (Proxy)
34+import Yoga.HTTP.API.Route.Encoding (JSON, FormData, PlainText, NoBody)
30import Yoga.HTTP.API.Route.Method as Method
35import Yoga.HTTP.API.Route.Method as Method
31import Yoga.JSON (class WriteForeign, writeJSON)
36import Yoga.JSON (class WriteForeign, writeJSON)
32
37
@@ -49,42 +54,65 @@ instance MakeRequest Method.DELETE where
49instance MakeRequest Method.PATCH where
54instance MakeRequest Method.PATCH where
50 httpMethod _ = PATCH
55 httpMethod _ = PATCH
51
56
57+instance MakeRequest Method.QUERY where
58+ httpMethod _ = QUERY
59+
52makeRequest
60makeRequest
53 :: forall method
61 :: forall method
54 . MakeRequest method
62 . MakeRequest method
55 => Proxy method
63 => Proxy method
56 -> String
64 -> String
65+ -> Headers
66+ -> String
57 -> Maybe String
67 -> Maybe String
58 -> Aff FetchResponse.Response
68 -> Aff FetchResponse.Response
59−makeRequest proxy url maybeBody = do
69+makeRequest proxy url customHeaders bodyContentType maybeBody = do
60 request <- Request.new url options # liftEffect
70 request <- Request.new url options # liftEffect
61 Promise.toAffE $ Fetch.fetch request
71 Promise.toAffE $ Fetch.fetch request
62 where
72 where
63 method = httpMethod proxy
73 method = httpMethod proxy
74+ customArr = Headers.toArray customHeaders
75+ hasContentType = Array.any (\t -> fst t == "content-type") customArr
64− headers = case maybeBody of
76+ contentTypeArr = case maybeBody of
65− Nothing -> Headers.empty
77+ Nothing -> []
66− Just _ -> Headers.fromFoldable [ "Content-Type" /\ "application/json" ]
78+ Just _ | hasContentType -> []
79+ Just _ -> [ "Content-Type" /\ bodyContentType ]
80+ allHeaders = Headers.fromFoldable (contentTypeArr <> customArr)
67 body = case maybeBody of
81 body = case maybeBody of
68 Nothing -> Body.empty
82 Nothing -> Body.empty
69 Just b -> Body.fromString b
83 Just b -> Body.fromString b
70 options =
84 options =
71 { method
85 { method
72− , headers
86+ , headers: allHeaders
73 , body
87 , body
74− , credentials: Credentials.Include
88+ , credentials: Credentials.SameOrigin
75 , mode: Mode.Cors
89 , mode: Mode.Cors
76− , referrer: Referrer.ReferrerClient
90+ , referrer: Referrer.ReferrerUrl ""
77 , referrerPolicy: ReferrerPolicy.NoReferrer
91 , referrerPolicy: ReferrerPolicy.NoReferrer
78 , integrity: Integrity ""
92 , integrity: Integrity ""
79 , duplex: Duplex.Half
93 , duplex: Duplex.Half
80 , cache: Cache.Default
94 , cache: Cache.Default
81 }
95 }
82
96
83−class SerializeBody :: Type -> Constraint
97+class BodyEncoding :: Type -> Type -> Constraint
84−class SerializeBody body where
98+class BodyEncoding encoding body | encoding -> body where
99+ encodingContentType :: Proxy encoding -> String
85− serializeBody :: body -> Maybe String
100+ encodeBody :: body -> Maybe String
101+
102+instance WriteForeign ty => BodyEncoding (JSON ty) ty where
103+ encodingContentType _ = "application/json"
104+ encodeBody b = Just (writeJSON b)
105+
106+else instance BodyEncoding PlainText String where
107+ encodingContentType _ = "text/plain"
108+ encodeBody = Just
109+
110+else instance BodyEncoding (FormData ty) ty where
111+ encodingContentType _ = "application/x-www-form-urlencoded"
112+ encodeBody b = Just (toUrlEncoded b)
113+
114+else instance BodyEncoding NoBody Unit where
115+ encodingContentType _ = ""
116+ encodeBody _ = Nothing
86
117
87−instance SerializeBody Unit where
118+foreign import toUrlEncoded :: forall a. a -> String
88− serializeBody _ = Nothing
89−else instance WriteForeign body => SerializeBody body where
90− serializeBody b = Just (writeJSON b)
src/Yoga/Fetch/Om/ParseResponse.purs+83−20
@@ -5,17 +5,20 @@ module Yoga.Fetch.Om.ParseResponse
5 , parseSuccessRL
5 , parseSuccessRL
6 , class ParseErrorRL
6 , class ParseErrorRL
7 , parseErrorRL
7 , parseErrorRL
8+ , class ParseResponseBody
9+ , parseResponseBody
8 ) where
10 ) where
9
11
10import Prelude
12import Prelude
11
13
12import Control.Monad.Error.Class (throwError)
14import Control.Monad.Error.Class (throwError)
13−import Data.Maybe (Maybe(..))
15+import Data.Either (Either(..))
14import Data.Symbol (class IsSymbol)
16import Data.Symbol (class IsSymbol)
15import Data.Variant (Variant)
17import Data.Variant (Variant)
16import Data.Variant as Variant
18import Data.Variant as Variant
17import Effect.Aff (Aff)
19import Effect.Aff (Aff)
18import Effect.Aff as Aff
20import Effect.Aff as Aff
21+import Effect.Class (liftEffect)
19import Effect.Exception as Exception
22import Effect.Exception as Exception
20import JS.Fetch.Response (Response) as FetchResponse
23import JS.Fetch.Response (Response) as FetchResponse
21import JS.Fetch.Response as Fetch
24import JS.Fetch.Response as Fetch
@@ -24,10 +27,24 @@ import Prim.RowList (class RowToList, RowList)
24import Prim.RowList as RL
27import Prim.RowList as RL
25import Promise.Aff as Promise
28import Promise.Aff as Promise
26import Type.Proxy (Proxy(..))
29import Type.Proxy (Proxy(..))
30+import Unsafe.Coerce (unsafeCoerce)
31+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
27import Yoga.HTTP.API.Route.StatusCode (class StatusCodeMap, statusCodeFor, StatusCode(..))
32import Yoga.HTTP.API.Route.StatusCode (class StatusCodeMap, statusCodeFor, StatusCode(..))
28−import Yoga.JSON (class ReadForeign, readJSON_)
33+import Yoga.JSON (class ReadForeign, readJSON)
34+import Yoga.JSON.Error (withStringErrors)
29−import Yoga.Om (Om, fromAff)
35+import Yoga.Om (Om, toOm)
30import Yoga.Om.Error (Exception)
36import Yoga.Om.Error (Exception)
37+import Yoga.Om.Strom (Strom)
38+import Yoga.Om.Strom.WebStream as WebStream
39+
40+class ParseResponseBody (body :: Type) where
41+ parseResponseBody :: String -> Either String body
42+
43+instance ParseResponseBody String where
44+ parseResponseBody = Right
45+
46+else instance ReadForeign body => ParseResponseBody body where
47+ parseResponseBody = withStringErrors <<< readJSON
31
48
32class ParseResponse (errorRow :: Row Type) (successRow :: Row Type) where
49class ParseResponse (errorRow :: Row Type) (successRow :: Row Type) where
33 parseResponse :: FetchResponse.Response -> Om {} errorRow (Variant successRow)
50 parseResponse :: FetchResponse.Response -> Om {} errorRow (Variant successRow)
@@ -41,16 +58,14 @@ instance
41 ParseResponse errorRow successRow where
58 ParseResponse errorRow successRow where
42 parseResponse fetchResp = do
59 parseResponse fetchResp = do
43 let status = Fetch.status fetchResp
60 let status = Fetch.status fetchResp
44− jsonText <- Promise.toAffE (Fetch.text fetchResp) # fromAff
45 if status >= 200 && status < 300 then
61 if status >= 200 && status < 300 then
46− parseSuccessRL (Proxy :: _ successRL) status jsonText # fromAff
62+ parseSuccessRL (Proxy :: _ successRL) status fetchResp # toOm
47− else if status >= 400 then
63+ else do
64+ errorText <- Promise.toAffE (Fetch.text fetchResp) # toOm
48− parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status jsonText
65+ parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status errorText
49− else
50− throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Unexpected HTTP status code: " <> show status)))
51
66
52class ParseSuccessRL (rl :: RowList Type) (successRow :: Row Type) | rl -> successRow where
67class ParseSuccessRL (rl :: RowList Type) (successRow :: Row Type) | rl -> successRow where
53− parseSuccessRL :: Proxy rl -> Int -> String -> Aff (Variant successRow)
68+ parseSuccessRL :: Proxy rl -> Int -> FetchResponse.Response -> Aff (Variant successRow)
54
69
55instance ParseSuccessRL RL.Nil successRow where
70instance ParseSuccessRL RL.Nil successRow where
56 parseSuccessRL _ status _ =
71 parseSuccessRL _ status _ =
@@ -59,19 +74,53 @@ instance ParseSuccessRL RL.Nil successRow where
59else instance
74else instance
60 ( IsSymbol label
75 ( IsSymbol label
61 , StatusCodeMap label
76 , StatusCodeMap label
62− , ReadForeign body
77+ , StreamDecode a
78+ , Row.Cons label (Strom streamContext streamError a) tailRow successRow
79+ , Row.Lacks label tailRow
80+ , ParseSuccessRL tailRL successRow
81+ ) =>
82+ ParseSuccessRL (RL.Cons label (Strom streamContext streamError a) tailRL) successRow where
83+ parseSuccessRL _ actualStatus fetchResp = do
84+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
85+ if actualStatus == expected then do
86+ webStream <- liftEffect (Fetch.body fetchResp)
87+ let strom = decodeStream (WebStream.fromReadableStream (unsafeCoerce webStream)) :: Strom {} () a
88+ pure (Variant.inj (Proxy :: _ label) (unsafeCoerce strom))
89+ else
90+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
91+
92+else instance
93+ ( IsSymbol label
94+ , StatusCodeMap label
95+ , Row.Cons label Unit tailRow successRow
96+ , Row.Lacks label tailRow
97+ , ParseSuccessRL tailRL successRow
98+ ) =>
99+ ParseSuccessRL (RL.Cons label Unit tailRL) successRow where
100+ parseSuccessRL _ actualStatus fetchResp = do
101+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
102+ if actualStatus == expected then
103+ pure (Variant.inj (Proxy :: _ label) unit)
104+ else
105+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
106+else instance
107+ ( IsSymbol label
108+ , StatusCodeMap label
109+ , ParseResponseBody body
63 , Row.Cons label body tailRow successRow
110 , Row.Cons label body tailRow successRow
64 , Row.Lacks label tailRow
111 , Row.Lacks label tailRow
65 , ParseSuccessRL tailRL successRow
112 , ParseSuccessRL tailRL successRow
66 ) =>
113 ) =>
67 ParseSuccessRL (RL.Cons label body tailRL) successRow where
114 ParseSuccessRL (RL.Cons label body tailRL) successRow where
68− parseSuccessRL _ actualStatus jsonText = do
115+ parseSuccessRL _ actualStatus fetchResp = do
69 let StatusCode expected = statusCodeFor (Proxy :: _ label)
116 let StatusCode expected = statusCodeFor (Proxy :: _ label)
70− if actualStatus == expected then case readJSON_ jsonText of
117+ if actualStatus == expected then do
118+ jsonText <- liftEffect (Fetch.text fetchResp) >>= Promise.toAff
119+ case parseResponseBody jsonText of
71− Nothing -> Aff.throwError (Exception.error "JSON parse error")
120+ Left errs -> Aff.throwError (Exception.error ("Failed to parse response (status " <> show actualStatus <> "):\n" <> errs))
72− Just parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
121+ Right parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
73 else
122 else
74− parseSuccessRL (Proxy :: _ tailRL) actualStatus jsonText
123+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
75
124
76class ParseErrorRL (rl :: RowList Type) (errorRow :: Row Type) | rl -> errorRow where
125class ParseErrorRL (rl :: RowList Type) (errorRow :: Row Type) | rl -> errorRow where
77 parseErrorRL
126 parseErrorRL
@@ -89,7 +138,21 @@ instance ParseErrorRL RL.Nil errorRow where
89else instance
138else instance
90 ( IsSymbol label
139 ( IsSymbol label
91 , StatusCodeMap label
140 , StatusCodeMap label
92− , ReadForeign body
141+ , Row.Cons label Unit tailRow errorRow
142+ , Row.Lacks label tailRow
143+ , Row.Cons label Unit tailExc (Exception errorRow)
144+ , ParseErrorRL tailRL errorRow
145+ ) =>
146+ ParseErrorRL (RL.Cons label Unit tailRL) errorRow where
147+ parseErrorRL _ errorProxy actualStatus jsonText = do
148+ let StatusCode expected = statusCodeFor (Proxy :: _ label)
149+ if actualStatus == expected then throwError (Variant.inj (Proxy :: _ label) unit)
150+ else parseErrorRL (Proxy :: _ tailRL) errorProxy actualStatus jsonText
151+
152+else instance
153+ ( IsSymbol label
154+ , StatusCodeMap label
155+ , ParseResponseBody body
93 , Row.Cons label body tailRow errorRow
156 , Row.Cons label body tailRow errorRow
94 , Row.Lacks label tailRow
157 , Row.Lacks label tailRow
95 , Row.Cons label body tailExc (Exception errorRow)
158 , Row.Cons label body tailExc (Exception errorRow)
@@ -98,8 +161,8 @@ else instance
98 ParseErrorRL (RL.Cons label body tailRL) errorRow where
161 ParseErrorRL (RL.Cons label body tailRL) errorRow where
99 parseErrorRL _ errorProxy actualStatus jsonText = do
162 parseErrorRL _ errorProxy actualStatus jsonText = do
100 let StatusCode expected = statusCodeFor (Proxy :: _ label)
163 let StatusCode expected = statusCodeFor (Proxy :: _ label)
101− if actualStatus == expected then case readJSON_ jsonText of
164+ if actualStatus == expected then case parseResponseBody jsonText of
102− Nothing -> throwError (Variant.inj (Proxy :: _ "exception") (Exception.error "JSON parse error"))
165+ Left errs -> throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Failed to parse error response (status " <> show actualStatus <> "):\n" <> errs)))
103− Just parsed -> throwError (Variant.inj (Proxy :: _ label) parsed)
166+ Right parsed -> throwError (Variant.inj (Proxy :: _ label) parsed)
104 else
167 else
105 parseErrorRL (Proxy :: _ tailRL) errorProxy actualStatus jsonText
168 parseErrorRL (Proxy :: _ tailRL) errorProxy actualStatus jsonText
src/Yoga/Fetch/Om/ReadOptional.jsnew file+18
@@ -0,0 +1,18 @@
1+import * as Maybe from "../Data.Maybe/index.js";
2+
3+export function readOptionalImpl(key) {
4+ return function (obj) {
5+ const val = obj[key];
6+ if (val === undefined || val instanceof Maybe.Nothing) {
7+ return Maybe.Nothing.value;
8+ }
9+ if (val instanceof Maybe.Just) return val;
10+ return Maybe.Just.create(val);
11+ };
12+}
13+
14+export function readRequiredField(key) {
15+ return function (obj) {
16+ return obj[key];
17+ };
18+}
src/Yoga/Fetch/Om/ReadOptional.pursnew file+56
@@ -0,0 +1,56 @@
1+module Yoga.Fetch.Om.ReadOptional
2+ ( readOptional
3+ , class ReadPartialQuery
4+ , readPartialQuery
5+ ) where
6+
7+import Data.Maybe (Maybe)
8+import Data.Symbol (class IsSymbol, reflectSymbol)
9+import Prim.Row as Row
10+import Prim.RowList (RowList)
11+import Prim.RowList as RL
12+import Record as Record
13+import Type.Proxy (Proxy(..))
14+
15+foreign import readOptionalImpl :: forall a r. String -> Record r -> Maybe a
16+
17+-- | Accept explicit `Maybe` fields as well as undefined-aware partial records.
18+readOptional :: forall @name ty r. IsSymbol name => Record r -> Maybe ty
19+readOptional rec = readOptionalImpl (reflectSymbol (Proxy :: _ name)) rec
20+
21+-- | Read query params from a potentially-partial record.
22+-- | Walks the query params RowList:
23+-- | Maybe ty → read with undefined-awareness (present → Just, absent → Nothing)
24+-- | bare ty → read directly (required, always present)
25+class ReadPartialQuery (rl :: RowList Type) (queryParams :: Row Type) | rl -> queryParams where
26+ readPartialQuery :: forall r. Record r -> Record queryParams
27+
28+instance ReadPartialQuery RL.Nil () where
29+ readPartialQuery _ = {}
30+
31+-- Maybe ty: optional field, may be undefined at runtime
32+instance
33+ ( IsSymbol name
34+ , ReadPartialQuery tail tailQuery
35+ , Row.Cons name (Maybe ty) tailQuery queryParams
36+ , Row.Lacks name tailQuery
37+ ) =>
38+ ReadPartialQuery (RL.Cons name (Maybe ty) tail) queryParams where
39+ readPartialQuery rec = Record.insert (Proxy :: _ name) (readOptional @name rec) rest
40+ where
41+ rest = readPartialQuery @tail rec
42+
43+-- bare ty: required field (from Required query params), always present
44+else instance
45+ ( IsSymbol name
46+ , ReadPartialQuery tail tailQuery
47+ , Row.Cons name ty tailQuery queryParams
48+ , Row.Lacks name tailQuery
49+ ) =>
50+ ReadPartialQuery (RL.Cons name ty tail) queryParams where
51+ readPartialQuery rec = Record.insert (Proxy :: _ name) val rest
52+ where
53+ val = readRequiredField (reflectSymbol (Proxy :: _ name)) rec
54+ rest = readPartialQuery @tail rec
55+
56+foreign import readRequiredField :: forall a r. String -> Record r -> a
src/Yoga/Fetch/Om/Simple.purs+25−34
@@ -22,27 +22,24 @@ module Yoga.Fetch.Om.Simple
22import Prelude
22import Prelude
23
23
24import Data.HTTP.Method (Method(..))
24import Data.HTTP.Method (Method(..))
25+import Data.Array as Array
26+import Data.Either (Either(..))
25import Data.Maybe (Maybe(..))
27import Data.Maybe (Maybe(..))
28+import Data.Tuple (fst)
26import Data.Tuple.Nested ((/\))
29import Data.Tuple.Nested ((/\))
27import Effect.Class (liftEffect)
30import Effect.Class (liftEffect)
28import JS.Fetch as Fetch
31import JS.Fetch as Fetch
29import JS.Fetch.Duplex as Duplex
32import JS.Fetch.Duplex as Duplex
30import JS.Fetch.Headers (Headers)
33import JS.Fetch.Headers (Headers)
31import JS.Fetch.Headers as Headers
34import JS.Fetch.Headers as Headers
32−import JS.Fetch.Integrity (Integrity(..))
33−import JS.Fetch.Referrer as Referrer
34−import JS.Fetch.ReferrerPolicy as ReferrerPolicy
35import JS.Fetch.Request as Request
35import JS.Fetch.Request as Request
36import JS.Fetch.RequestBody as Body
36import JS.Fetch.RequestBody as Body
37−import JS.Fetch.RequestCache as Cache
38−import JS.Fetch.RequestCredentials as Credentials
39−import JS.Fetch.RequestMode as Mode
40import JS.Fetch.Response as Resp
37import JS.Fetch.Response as Resp
41import Promise.Aff as Promise
38import Promise.Aff as Promise
42import Type.Row.Homogeneous (class Homogeneous)
39import Type.Row.Homogeneous (class Homogeneous)
43−-- PlainText removed, using local phantom type
44−import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON_, writeJSON)
40+import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
45−import Yoga.Om (Om, fromAff, throw)
41+import Yoga.JSON.Error (withStringErrors)
42+import Yoga.Om (Om, toOm, throw)
46
43
47type FetchError = { status :: Int, body :: String }
44type FetchError = { status :: Int, body :: String }
48
45
@@ -50,15 +47,13 @@ type FetchResponse a = { headers :: Headers, body :: a }
50
47
51class DecodeResponse :: forall k. k -> Type -> Constraint
48class DecodeResponse :: forall k. k -> Type -> Constraint
52class DecodeResponse a result | a -> result where
49class DecodeResponse a result | a -> result where
53− decodeResponse :: String -> Maybe result
50+ decodeResponse :: String -> Either String result
54
51
55−data PlainTextResponse
56−
57−instance DecodeResponse PlainTextResponse String where
52+instance DecodeResponse String String where
58− decodeResponse = Just
53+ decodeResponse = Right
59
54
60else instance ReadForeign a => DecodeResponse a a where
55else instance ReadForeign a => DecodeResponse a a where
61− decodeResponse = readJSON_
56+ decodeResponse = withStringErrors <<< readJSON
62
57
63simpleFetch
58simpleFetch
64 :: forall @a result headers ctx err
59 :: forall @a result headers ctx err
@@ -70,25 +65,27 @@ simpleFetch
70 -> Maybe String
65 -> Maybe String
71 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
66 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
72simpleFetch method headers url maybeBody = do
67simpleFetch method headers url maybeBody = do
73− resp <- doFetch # fromAff
68+ resp <- doFetch # toOm
74− respText <- Promise.toAffE (Resp.text resp) # fromAff
69+ respText <- Promise.toAffE (Resp.text resp) # toOm
75 let status = Resp.status resp
70 let status = Resp.status resp
76 let respHeaders = Resp.headers resp
71 let respHeaders = Resp.headers resp
77 if status >= 200 && status < 300 then
72 if status >= 200 && status < 300 then
78 case decodeResponse @a respText of
73 case decodeResponse @a respText of
79− Nothing -> throw { fetchError: { status, body: respText } }
74+ Left errs -> throw { fetchError: { status, body: "Failed to parse response:\n" <> errs <> "\nBody: " <> respText } }
80− Just body -> pure { headers: respHeaders, body }
75+ Right body -> pure { headers: respHeaders, body }
81 else
76 else
82 throw { fetchError: { status, body: respText } }
77 throw { fetchError: { status, body: respText } }
83 where
78 where
84 doFetch = do
79 doFetch = do
85 request <- Request.new url options # liftEffect
80 request <- Request.new url options # liftEffect
86 Promise.toAffE $ Fetch.fetch request
81 Promise.toAffE $ Fetch.fetch request
82+ customArr = Headers.toArray (Headers.fromRecord headers)
83+ hasContentType = Array.any (\t -> fst t == "content-type") customArr
87− contentTypeHeaders = case maybeBody of
84+ contentTypeArr = case maybeBody of
88− Nothing -> Headers.empty
85+ Nothing -> []
86+ Just _ | hasContentType -> []
89− Just _ -> Headers.fromFoldable [ "Content-Type" /\ "application/json" ]
87+ Just _ -> [ "Content-Type" /\ "application/json" ]
90− allHeaders = Headers.fromFoldable
88+ allHeaders = Headers.fromFoldable (contentTypeArr <> customArr)
91− (Headers.toArray contentTypeHeaders <> Headers.toArray (Headers.fromRecord headers))
92 reqBody = case maybeBody of
89 reqBody = case maybeBody of
93 Nothing -> Body.empty
90 Nothing -> Body.empty
94 Just b -> Body.fromString b
91 Just b -> Body.fromString b
@@ -96,13 +93,7 @@ simpleFetch method headers url maybeBody = do
96 { method
93 { method
97 , headers: allHeaders
94 , headers: allHeaders
98 , body: reqBody
95 , body: reqBody
99− , credentials: Credentials.Include
100− , mode: Mode.Cors
101− , referrer: Referrer.ReferrerClient
102− , referrerPolicy: ReferrerPolicy.NoReferrer
103− , integrity: Integrity ""
104 , duplex: Duplex.Half
96 , duplex: Duplex.Half
105− , cache: Cache.Default
106 }
97 }
107
98
108get
99get
@@ -147,7 +138,7 @@ delete_
147 => String
138 => String
148 -> Record headers
139 -> Record headers
149 -> Om ctx (fetchError :: FetchError | err) Unit
140 -> 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
151
142
152post
143post
153 :: forall @a result headers body ctx err
144 :: forall @a result headers body ctx err
@@ -179,7 +170,7 @@ post_
179 -> Record headers
170 -> Record headers
180 -> body
171 -> body
181 -> Om ctx (fetchError :: FetchError | err) Unit
172 -> 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))
183
174
184put
175put
185 :: forall @a result headers body ctx err
176 :: forall @a result headers body ctx err
@@ -211,7 +202,7 @@ put_
211 -> Record headers
202 -> Record headers
212 -> body
203 -> body
213 -> Om ctx (fetchError :: FetchError | err) Unit
204 -> 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))
215
206
216patch
207patch
217 :: forall @a result headers body ctx err
208 :: forall @a result headers body ctx err
@@ -243,4 +234,4 @@ patch_
243 -> Record headers
234 -> Record headers
244 -> body
235 -> body
245 -> Om ctx (fetchError :: FetchError | err) Unit
236 -> 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+24−2
@@ -1,6 +1,8 @@
1module Yoga.Fetch.Om.SplitResponses
1module Yoga.Fetch.Om.SplitResponses
2 ( class IsSuccessStatus
2 ( class IsSuccessStatus
3+ , class ExtractBodyRL
3 , class ExtractBody
4 , class ExtractBody
5+ , class ClientResponseBody
4 , class SplitResponses
6 , class SplitResponses
5 , class SplitResponsesRL
7 , class SplitResponsesRL
6 , class DispatchResponse
8 , class DispatchResponse
@@ -10,7 +12,10 @@ import Prim.Boolean (True, False)
10import Prim.Row as Row
12import Prim.Row as Row
11import Prim.RowList (RowList)
13import Prim.RowList (RowList)
12import Prim.RowList as RL
14import Prim.RowList as RL
15+import Data.Unit (Unit)
13import Yoga.HTTP.API.Route (Response)
16import Yoga.HTTP.API.Route (Response)
17+import Yoga.HTTP.API.Route.Encoding (PlainText, Streaming)
18+import Yoga.Om.Strom (Strom)
14
19
15class IsSuccessStatus (label :: Symbol) (isSuccess :: Boolean) | label -> isSuccess
20class IsSuccessStatus (label :: Symbol) (isSuccess :: Boolean) | label -> isSuccess
16
21
@@ -26,12 +31,29 @@ else instance IsSuccessStatus "alreadyReported" True
26else instance IsSuccessStatus "imUsed" True
31else instance IsSuccessStatus "imUsed" True
27else instance IsSuccessStatus label False
32else instance IsSuccessStatus label False
28
33
34+-- Traverse a RowList to find the "body" field; fall back to Unit if absent.
35+class ExtractBodyRL (rl :: RowList Type) (body :: Type) | rl -> body
36+
37+instance ExtractBodyRL RL.Nil Unit
38+
39+instance ExtractBodyRL (RL.Cons "body" body tail) body
40+
41+else instance ExtractBodyRL tail body => ExtractBodyRL (RL.Cons label ty tail) body
42+
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+
29class ExtractBody (responseType :: Type) (bodyType :: Type) | responseType -> bodyType
49class ExtractBody (responseType :: Type) (bodyType :: Type) | responseType -> bodyType
30
50
31−instance ExtractBody (Response headers body) body
51+instance ClientResponseBody encoded body => ExtractBody (Response headers encoded) body
32
52
33else instance
53else instance
34− ( Row.Cons "body" body rest row
54+ ( RL.RowToList row rl
55+ , ExtractBodyRL rl encoded
56+ , ClientResponseBody encoded body
35 ) =>
57 ) =>
36 ExtractBody (Record row) body
58 ExtractBody (Record row) body
37
59
src/Yoga/Fetch/Om/StreamDecode.jsnew file+12
@@ -0,0 +1,12 @@
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.pursnew file+61
@@ -0,0 +1,61 @@
1+module Yoga.Fetch.Om.StreamDecode
2+ ( class StreamDecode
3+ , decodeStream
4+ ) where
5+
6+import Prelude
7+
8+import Control.Monad.Rec.Class (Step(..))
9+import Data.Array as Array
10+import 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)
16+import Yoga.Om.Strom (Strom)
17+import Yoga.Om.Strom as Strom
18+
19+class StreamDecode a where
20+ decodeStream :: Strom {} () Uint8Array -> Strom {} () a
21+
22+instance StreamDecode Uint8Array where
23+ decodeStream = identity
24+
25+else instance StreamDecode String where
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
57+
58+nonEmpty :: forall a. Array a -> Maybe (Array a)
59+nonEmpty values
60+ | Array.null values = Nothing
61+ | otherwise = Just values
test-compile-fail/cases/ClientBogusSignature.pursnew file+9
@@ -0,0 +1,9 @@
1+module Test.CompileFail.ClientBogusSignature where
2+
3+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
4+
5+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: { id :: Int, name :: String } })
6+type TestAPI = { getUser :: GetUserRoute }
7+
8+wrong :: { getUser :: String -> Int }
9+wrong = client @TestAPI "http://localhost:3000"
test-compile-fail/cases/ClientExtraField.pursnew file+12
@@ -0,0 +1,12 @@
1+module Test.CompileFail.ClientExtraField where
2+
3+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
4+import Yoga.Om (Om)
5+
6+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: { id :: Int, name :: String } })
7+type TestAPI = { getUser :: GetUserRoute }
8+
9+api = client @TestAPI "http://localhost:3000"
10+
11+wrong :: forall ctx err. Om ctx err { id :: Int, name :: String }
12+wrong = api.getUser { id: 42, extra: "oops" }
test-compile-fail/cases/ClientMissingField.pursnew file+12
@@ -0,0 +1,12 @@
1+module Test.CompileFail.ClientMissingField where
2+
3+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
4+import Yoga.Om (Om)
5+
6+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: { id :: Int, name :: String } })
7+type TestAPI = { getUser :: GetUserRoute }
8+
9+api = client @TestAPI "http://localhost:3000"
10+
11+wrong :: forall ctx err. Om ctx err { id :: Int, name :: String }
12+wrong = api.getUser {}
test-compile-fail/cases/ClientNoArgsPassedArg.pursnew file+12
@@ -0,0 +1,12 @@
1+module Test.CompileFail.ClientNoArgsPassedArg where
2+
3+import Yoga.Fetch.Om (GET, Route, client)
4+import Yoga.Om (Om)
5+
6+type HealthRoute = Route GET "health" {} (ok :: { body :: String })
7+type TestAPI = { health :: HealthRoute }
8+
9+api = client @TestAPI "http://localhost:3000"
10+
11+wrong :: forall ctx err. Om ctx err String
12+wrong = api.health "should not accept this"
test-compile-fail/cases/ClientWrongFieldName.pursnew file+12
@@ -0,0 +1,12 @@
1+module Test.CompileFail.ClientWrongFieldName where
2+
3+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
4+import Yoga.Om (Om)
5+
6+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: { id :: Int, name :: String } })
7+type TestAPI = { getUser :: GetUserRoute }
8+
9+api = client @TestAPI "http://localhost:3000"
10+
11+wrong :: forall ctx err. Om ctx err { id :: Int, name :: String }
12+wrong = api.getUser { userId: 42 }
test-compile-fail/cases/ClientWrongFieldType.pursnew file+12
@@ -0,0 +1,12 @@
1+module Test.CompileFail.ClientWrongFieldType where
2+
3+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
4+import Yoga.Om (Om)
5+
6+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: { id :: Int, name :: String } })
7+type TestAPI = { getUser :: GetUserRoute }
8+
9+api = client @TestAPI "http://localhost:3000"
10+
11+wrong :: forall ctx err. Om ctx err { id :: Int, name :: String }
12+wrong = api.getUser { id: "not-an-int" }
test-compile-fail/run.shnew file+26
@@ -0,0 +1,26 @@
1+#!/usr/bin/env bash
2+set -euo pipefail
3+
4+# Compile-fail tests for client derivation type safety.
5+# Each .purs file in cases/ must FAIL to compile.
6+
7+SOURCES=$(bunx spago sources 2>/dev/null | grep -v 'test/')
8+OUTPUT_DIR="output-compile-fail"
9+CASES_DIR="test-compile-fail/cases"
10+PASS=0
11+FAIL=0
12+
13+for f in "$CASES_DIR"/*.purs; do
14+ name=$(basename "$f" .purs)
15+ if bunx purs compile --output "$OUTPUT_DIR" $SOURCES "$f" >/dev/null 2>&1; then
16+ echo "FAIL: $name compiled but should not have"
17+ FAIL=$((FAIL + 1))
18+ else
19+ echo " ok: $name correctly rejected"
20+ PASS=$((PASS + 1))
21+ fi
22+done
23+
24+echo ""
25+echo "$PASS passed, $FAIL failed"
26+[ "$FAIL" -eq 0 ]
test/BodylessResponse.Example.pursnew file+27
@@ -0,0 +1,27 @@
1+-- Compile-time test: bodyless responses (e.g. 304, 401) can use {} without
2+-- wrapping in { body :: {} }.
3+module BodylessResponse.Example where
4+
5+import Prelude
6+
7+import Yoga.Fetch.Om (GET, Route, type (/), client)
8+import Yoga.Om (Om)
9+
10+type Resource = { id :: Int, name :: String }
11+
12+type ResourceAPI =
13+ { getResource ::
14+ Route GET
15+ ("resources" / "id")
16+ {}
17+ ( ok :: { body :: Resource }
18+ , notModified :: {}
19+ , unauthorized :: {}
20+ )
21+ }
22+
23+-- This must typecheck: the derived client exposes notModified/unauthorized as Unit errors.
24+api
25+ :: forall ctx err
26+ . { getResource :: Om (Record ctx) (notModified :: Unit, unauthorized :: Unit | err) Resource }
27+api = client @ResourceAPI "https://api.example.com"
test/BodylessResponse.Spec.pursnew file+125
@@ -0,0 +1,125 @@
1+module BodylessResponse.Spec where
2+
3+import Prelude
4+
5+import Effect.Ref (Ref)
6+import Effect.Ref as Ref
7+import Effect.Aff (Aff, bracket)
8+import Effect.Aff.Class (liftAff)
9+import Effect.Class (liftEffect)
10+import Foreign (unsafeToForeign)
11+import Test.Spec (Spec, around, describe, it)
12+import Test.Spec.Assertions (shouldEqual)
13+import Yoga.Fastify.Fastify as F
14+import Yoga.Fastify.Fastify (Port(..), Host(..), RouteURL(..), StatusCode(..))
15+import Yoga.Fastify.Om (createOmFastify, deleteOm, getOm)
16+import Yoga.Fetch.Om (DELETE, GET, Route, type (/), type (:), client)
17+import Yoga.Om (runOm)
18+
19+type Resource = { id :: Int, name :: String }
20+
21+type ResourceAPI =
22+ { getResource ::
23+ Route GET
24+ ("bodyless-test" / "resource")
25+ {}
26+ ( ok :: { body :: Resource }
27+ , notModified :: {}
28+ , unauthorized :: {}
29+ )
30+ , deleteUser ::
31+ Route DELETE
32+ ("bodyless-test" / "users" / "id" : Int)
33+ {}
34+ ( noContent :: {}
35+ , notFound :: { body :: { error :: String } }
36+ )
37+ }
38+
39+api = client @ResourceAPI "http://localhost:44932"
40+
41+spec :: Spec Unit
42+spec = around withServer $ describe "bodyless responses (notModified / unauthorized)" do
43+ it "304 Not Modified triggers the notModified handler" \serverMode -> do
44+ liftEffect $ Ref.write "notModified" serverMode
45+ triggered <- liftEffect $ Ref.new false
46+ runOm {}
47+ { exception: \_ -> pure unit
48+ , notModified: \_ -> liftEffect $ Ref.write true triggered
49+ , unauthorized: \_ -> pure unit
50+ }
51+ (void api.getResource)
52+ result <- liftEffect $ Ref.read triggered
53+ liftAff $ result `shouldEqual` true
54+
55+ it "401 Unauthorized triggers the unauthorized handler" \serverMode -> do
56+ liftEffect $ Ref.write "unauthorized" serverMode
57+ triggered <- liftEffect $ Ref.new false
58+ runOm {}
59+ { exception: \_ -> pure unit
60+ , notModified: \_ -> pure unit
61+ , unauthorized: \_ -> liftEffect $ Ref.write true triggered
62+ }
63+ (void api.getResource)
64+ result <- liftEffect $ Ref.read triggered
65+ liftAff $ result `shouldEqual` true
66+
67+ it "200 OK returns the resource body" \serverMode -> do
68+ liftEffect $ Ref.write "ok" serverMode
69+ resource <- runOm {}
70+ { exception: \_ -> pure { id: -1, name: "" }
71+ , notModified: \_ -> pure { id: -1, name: "" }
72+ , unauthorized: \_ -> pure { id: -1, name: "" }
73+ }
74+ api.getResource
75+ liftAff $ resource `shouldEqual` { id: 1, name: "test" }
76+
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+
86+withServer :: forall a. (Ref String -> Aff a) -> Aff a
87+withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
88+ where
89+ acquire = do
90+ serverMode <- liftEffect $ Ref.new "ok"
91+ fastify <- liftEffect do
92+ f <- F.fastify {}
93+ omApp <- createOmFastify {} f
94+ getOm (RouteURL "/bodyless-test/resource") (handler serverMode) omApp
95+ deleteOm (RouteURL "/bodyless-test/users/:id") (deleteHandler serverMode) omApp
96+ pure f
97+ void $ F.listen { port: Port 44932, host: Host "0.0.0.0" } fastify
98+ pure { fastify, serverMode }
99+
100+ release { fastify } = F.close fastify
101+
102+ handler serverMode reply = do
103+ mode <- liftEffect $ Ref.read serverMode
104+ case mode of
105+ "notModified" -> do
106+ void $ F.status (StatusCode 304) reply # liftEffect
107+ F.send (unsafeToForeign "") reply # liftAff
108+ "unauthorized" -> do
109+ void $ F.status (StatusCode 401) reply # liftEffect
110+ F.send (unsafeToForeign "") reply # liftAff
111+ _ -> do
112+ void $ F.status (StatusCode 200) reply # liftEffect
113+ void $ F.header "content-type" "application/json" reply # liftEffect
114+ 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+154−100
@@ -3,111 +3,165 @@ module BuildUrl.Spec where
3import Prelude
3import Prelude
4
4
5import Data.Maybe (Maybe(..))
5import Data.Maybe (Maybe(..))
6−import Effect (Effect)
6+import Test.Spec (Spec, describe, it)
7−import Effect.Aff (Aff)
7+import Test.Spec.Assertions (shouldEqual)
8import Type.Proxy (Proxy(..))
8import Type.Proxy (Proxy(..))
9+import Unsafe.Coerce (unsafeCoerce)
9import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
10import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
10import Yoga.HTTP.API.Path (Path, type (/), type (:))
11import Yoga.HTTP.API.Path (Path, type (/), type (:))
11−import ViTest (ViTest, describe, test)
12−import ViTest.Expect (expectToBe)
13−
14−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
15−expectToEqual expected actual = expectToBe true (expected == actual)
16
12
17--------------------------------------------------------------------------------
13--------------------------------------------------------------------------------
18-- SubstitutePathParams Tests
14-- SubstitutePathParams Tests
19--------------------------------------------------------------------------------
15--------------------------------------------------------------------------------
20
16
21−testSubstitutePathParams :: Effect ViTest
17+spec :: Spec Unit
18+spec = do
22−testSubstitutePathParams = describe "substitutePathParams" $ do
19+ describe "substitutePathParams" do
23− _ <- test "substitutes single path param" do
20+ it "substitutes single path param" do
24− let
25− result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
21+ let result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
26− expectToEqual "/users/42" result
22+ result `shouldEqual` "/users/42"
27−
23+
28− _ <- test "substitutes multiple path params" do
24+ it "substitutes multiple path params" do
29− let
25+ let
30− result = substitutePathParams @(userId :: Int, postId :: Int)
26+ result = substitutePathParams @(userId :: Int, postId :: Int)
31− { userId: 1, postId: 99 }
27+ { userId: 1, postId: 99 }
32− "/users/:userId/posts/:postId"
28+ "/users/:userId/posts/:postId"
33− expectToEqual "/users/1/posts/99" result
29+ result `shouldEqual` "/users/1/posts/99"
34−
30+
35− _ <- test "handles String params" do
31+ it "handles String params" do
36− let
32+ let
37− result = substitutePathParams @(slug :: String)
33+ result = substitutePathParams @(slug :: String)
38− { slug: "hello-world" }
34+ { slug: "hello-world" }
39− "/posts/:slug"
35+ "/posts/:slug"
40− expectToEqual "/posts/hello-world" result
36+ result `shouldEqual` "/posts/hello-world"
41−
37+ it "URL-encodes reserved characters in path params" do
42− test "handles empty params" do
43− let
38+ let
44− result = substitutePathParams @() {} "/users"
39+ result = substitutePathParams @(slug :: String)
45− expectToEqual "/users" result
40+ { slug: "a/b c?d#e" }
46−
41+ "/users/:slug"
47−--------------------------------------------------------------------------------
42+ result `shouldEqual` "/users/a%2Fb%20c%3Fd%23e"
48−-- AppendQueryParams Tests
43+
49−--------------------------------------------------------------------------------
44+ it "distinguishes path param names with shared prefixes" do
50−
45+ let
51−testAppendQueryParams :: Effect ViTest
46+ result = substitutePathParams @(id :: Int, id2 :: Int)
52−testAppendQueryParams = describe "appendQueryParams" $ do
47+ { id: 1, id2: 2 }
48+ "/pairs/:id/:id2"
49+ result `shouldEqual` "/pairs/1/2"
50+
51+ it "handles empty params" do
52+ let result = substitutePathParams @() {} "/users"
53+ result `shouldEqual` "/users"
54+
55+ describe "appendQueryParams" do
53− _ <- test "appends single query param" do
56+ it "appends single query param" do
54− let
55− result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
57+ let result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
56− expectToEqual "/users?limit=10" result
58+ result `shouldEqual` "/users?limit=10"
57−
59+
58− _ <- test "appends multiple query params" do
60+ it "appends multiple query params" do
59− let
61+ let
60− result = appendQueryParams @(limit :: Int, offset :: Int)
62+ result = appendQueryParams @(limit :: Int, offset :: Int)
61− { limit: 10, offset: 20 }
63+ { limit: 10, offset: 20 }
62− "/users"
64+ "/users"
63− expectToEqual "/users?limit=10&offset=20" result
65+ result `shouldEqual` "/users?limit=10&offset=20"
64−
66+
65− _ <- test "handles Maybe params - Just" do
67+ it "handles optional params - provided via partial record" do
66− let
68+ let
67− result = appendQueryParams @(limit :: Maybe Int)
69+ result = appendQueryParams @(limit :: Maybe Int)
68− { limit: Just 10 }
70+ (unsafeCoerce { limit: 10 } :: Record (limit :: Maybe Int))
69− "/users"
71+ "/users"
70− expectToEqual "/users?limit=10" result
72+ result `shouldEqual` "/users?limit=10"
71−
73+
72− _ <- test "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
73− let
83+ let
74− result = appendQueryParams @(limit :: Maybe Int)
84+ result = appendQueryParams @(limit :: Maybe Int)
75− { limit: Nothing }
85+ (unsafeCoerce {} :: Record (limit :: Maybe Int))
76− "/users"
86+ "/users"
77− expectToEqual "/users" result
87+ result `shouldEqual` "/users"
78−
88+
79− _ <- test "handles mixed Maybe and required params" do
89+ it "handles mixed optional and required params" do
80− let
90+ let
81− result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
91+ result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
82− { limit: Nothing, offset: 20 }
92+ (unsafeCoerce { offset: 20 } :: Record (limit :: Maybe Int, offset :: Int))
83− "/users"
93+ "/users"
84− expectToEqual "/users?offset=20" result
94+ result `shouldEqual` "/users?offset=20"
85−
95+
86− test "handles empty query params" do
96+ it "handles empty query params" do
87− let
97+ let result = appendQueryParams @() {} "/users"
98+ result `shouldEqual` "/users"
99+
100+ it "URL-encodes special characters in values" do
101+ let result = appendQueryParams @(search :: String) { search: "hello world&more=yes" } "/users"
102+ result `shouldEqual` "/users?search=hello%20world%26more%3Dyes"
103+ it "appends query params to an existing query string" do
88− result = appendQueryParams @() {} "/users"
104+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users?token=old"
89− expectToEqual "/users" result
105+ result `shouldEqual` "/users?token=old&search=new%20value"
90−
106+
91−--------------------------------------------------------------------------------
107+ it "inserts query params before a URL fragment" do
92−-- BuildUrl Tests
108+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users#details"
93−--------------------------------------------------------------------------------
109+ result `shouldEqual` "/users?search=new%20value#details"
94−
110+
95−testBuildUrl :: Effect ViTest
111+ it "repeats Array query params one pair per element" do
96−testBuildUrl = describe "buildUrl" $ do
112+ let
97− _ <- test "builds URL with path params only" do
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"
131+
132+ describe "buildUrl" do
133+ it "builds URL with path params only" do
134+ let
135+ result = buildUrl
136+ "https://api.example.com"
137+ (Proxy :: _ (Path ("users" / "id" : Int)))
138+ { id: 42 }
139+ {}
140+ result `shouldEqual` "https://api.example.com/users/42"
141+
142+ it "builds URL with query params only" do
143+ let
144+ result = buildUrl
145+ "https://api.example.com"
146+ (Proxy :: _ (Path "users"))
147+ {}
148+ { limit: 10, offset: 20 }
149+ 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
98− let
152+ let
99− result = buildUrl
153+ result = buildUrl
100− "https://api.example.com"
154+ "https://api.example.com/base/?token=old#details"
101− (Proxy :: _ (Path ("users" / "id" : Int)))
155+ (Proxy :: _ (Path "users"))
102− { id: 42 }
156+ {}
103− {}
157+ { search: "new value" }
104− expectToEqual "https://api.example.com/users/42" result
158+ result `shouldEqual` "https://api.example.com/base/users?token=old&search=new%20value#details"
105−
159+
106− test "builds URL with query params only" do
160+ it "does not substitute path captures inside a base URL query string" do
107− let
161+ let
108− result = buildUrl
162+ result = buildUrl
109− "https://api.example.com"
163+ "https://api.example.com?redirect=/:id#details"
110− (Proxy :: _ (Path "users"))
164+ (Proxy :: _ (Path ("users" / "id" : Int)))
111− {}
165+ { id: 42 }
112− { limit: 10, offset: 20 }
166+ {}
113− expectToEqual "https://api.example.com/users?limit=10&offset=20" result
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+28−24
@@ -3,9 +3,10 @@ module Complete.Example where
3import Prelude
3import Prelude
4
4
5import Data.Array (length)
5import Data.Array (length)
6−import Data.Maybe (Maybe(..))
6+import Justifill (justifill)
7import Effect.Class.Console (log)
7import 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)
9+import Yoga.HTTP.API.Route (BearerToken(..))
9import Yoga.Om (Om, handleErrors)
10import Yoga.Om (Om, handleErrors)
10
11
11type User =
12type User =
@@ -30,49 +31,43 @@ type ErrorMessage =
30
31
31type UserAPI =
32type UserAPI =
32 { getUser ::
33 { getUser ::
33− Route GET
34− (Path ("users" / "id" : Int))
34+ Route GET ("users" / "id" : Int)
35 {}
35 {}
36 ( ok :: { body :: User }
36 ( ok :: { body :: User }
37 , notFound :: { body :: ErrorMessage }
37 , notFound :: { body :: ErrorMessage }
38 )
38 )
39 , listUsers ::
39 , listUsers ::
40− Route GET
41− (Path "users" :? { limit :: Int, offset :: Int })
40+ Route GET ("users" :? { limit :: Int, offset :: Int })
42 {}
41 {}
43 ( ok :: { body :: Array User }
42 ( ok :: { body :: Array User }
44 )
43 )
45 , createUser ::
44 , createUser ::
46− Route POST
45+ Route POST "users"
47− (Path "users")
48 { body :: JSON CreateUserRequest }
46 { body :: JSON CreateUserRequest }
49 ( created :: { body :: User }
47 ( created :: { body :: User }
50 , badRequest :: { body :: ErrorMessage }
48 , badRequest :: { body :: ErrorMessage }
51 )
49 )
52 , updateUser ::
50 , updateUser ::
53− Route PUT
54− (Path ("users" / "id" : Int))
51+ Route PUT ("users" / "id" : Int)
55 { body :: JSON UpdateUserRequest }
52 { body :: JSON UpdateUserRequest }
56 ( ok :: { body :: User }
53 ( ok :: { body :: User }
57 , notFound :: { body :: ErrorMessage }
54 , notFound :: { body :: ErrorMessage }
58 , badRequest :: { body :: ErrorMessage }
55 , badRequest :: { body :: ErrorMessage }
59 )
56 )
60 , deleteUser ::
57 , deleteUser ::
61− Route DELETE
58+ Route DELETE ("users" / "id" : Int)
62− (Path ("users" / "id" : Int))
63 {}
59 {}
64− ( noContent :: { body :: {} }
60+ ( noContent :: {}
65 , notFound :: { body :: ErrorMessage }
61 , notFound :: { body :: ErrorMessage }
66 )
62 )
63+ , createUserAuth ::
64+ Route POST "users"
65+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
66+ ( created :: { body :: User }
67+ , badRequest :: { body :: ErrorMessage }
68+ )
67 }
69 }
68
70
69−api
70− :: { createUser :: CreateUserRequest -> Om {} (badRequest :: ErrorMessage) User
71− , deleteUser :: { id :: Int } -> Om {} (notFound :: ErrorMessage) {}
72− , getUser :: { id :: Int } -> Om {} (notFound :: ErrorMessage) User
73− , listUsers :: { limit :: Maybe Int, offset :: Maybe Int } -> Om {} () (Array User)
74− , updateUser :: { id :: Int } -> UpdateUserRequest -> Om {} (badRequest :: ErrorMessage, notFound :: ErrorMessage) User
75− }
76api = client @UserAPI "https://api.example.com"
71api = client @UserAPI "https://api.example.com"
77
72
78exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
73exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
@@ -84,15 +79,15 @@ exampleGetUserHandled :: Om {} () Unit
84exampleGetUserHandled = do
79exampleGetUserHandled = do
85 user <- api.getUser { id: 42 }
80 user <- api.getUser { id: 42 }
86 # handleErrors
81 # handleErrors
87− { notFound: \err -> do
82+ { notFound: \err -> ado
88 log $ "User not found: " <> err.error
83 log $ "User not found: " <> err.error
89− pure { id: 0, name: "unknown", email: "" }
84+ in { id: 0, name: "unknown", email: "" }
90 }
85 }
91 log $ "Found user: " <> user.name
86 log $ "Found user: " <> user.name
92
87
93exampleListUsers :: Om {} () Unit
88exampleListUsers :: Om {} () Unit
94exampleListUsers = do
89exampleListUsers = do
95− users <- api.listUsers { limit: Just 10, offset: Just 0 }
90+ users <- api.listUsers (justifill { limit: 10, offset: 0 })
96 log $ "Found " <> show (length users) <> " users"
91 log $ "Found " <> show (length users) <> " users"
97
92
98exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
93exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
@@ -105,7 +100,8 @@ exampleCreateUser = do
105
100
106exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
101exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
107exampleUpdateUser = do
102exampleUpdateUser = do
108− user <- api.updateUser { id: 42 }
103+ user <- api.updateUser
104+ { id: 42 }
109 { name: "Alice Updated"
105 { name: "Alice Updated"
110 , email: "alice.new@example.com"
106 , email: "alice.new@example.com"
111 }
107 }
@@ -115,3 +111,11 @@ exampleDeleteUser :: Om {} (notFound :: ErrorMessage) Unit
115exampleDeleteUser = do
111exampleDeleteUser = do
116 _ <- api.deleteUser { id: 42 }
112 _ <- api.deleteUser { id: 42 }
117 log "User deleted successfully"
113 log "User deleted successfully"
114+
115+exampleCreateUserAuth :: Om {} (badRequest :: ErrorMessage) Unit
116+exampleCreateUserAuth = do
117+ user <- api.createUserAuth { authorization: BearerToken "abc123" }
118+ { name: "Alice"
119+ , email: "alice@example.com"
120+ }
121+ log $ "Created user with ID: " <> show user.id
test/Example.purs+20−8
@@ -1,39 +1,51 @@
1module Example where
1module Example where
2
2
3−import Yoga.HTTP.API.Route (GET, POST, DELETE, Route)
3+import Yoga.HTTP.API.Route (GET, POST, DELETE, Route, BearerToken)
4import Yoga.HTTP.API.Route.Encoding (JSON)
4import Yoga.HTTP.API.Route.Encoding (JSON)
5import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
5import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
6
6
7−type User = { id :: Int, name :: String, email :: String }
7+type User = { id :: UserId, name :: UserName, email :: UserMail }
8−type CreateUserRequest = { name :: String, email :: String }
8+type CreateUserRequest = { name :: String, email :: UserMail }
9type ErrorMessage = { error :: String }
9type 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
10
15
11type UserAPI =
16type UserAPI =
12 { getUser ::
17 { getUser ::
13 Route GET
18 Route GET
14− (Path ("users" / "id" : Int))
19+ ("users" / "id" : UserId)
15 {}
20 {}
16 ( ok :: { body :: User }
21 ( ok :: { body :: User }
17 , notFound :: { body :: ErrorMessage }
22 , notFound :: { body :: ErrorMessage }
18 )
23 )
19 , listUsers ::
24 , listUsers ::
20 Route GET
25 Route GET
21− (Path "users" :? { limit :: Int, offset :: Int })
26+ ("users" :? { limit :: Limit, offset :: Offset })
22 {}
27 {}
23 ( ok :: { body :: Array User }
28 ( ok :: { body :: Array User }
24 )
29 )
25 , createUser ::
30 , createUser ::
26 Route POST
31 Route POST
27− (Path "users")
32+ "users"
28 { body :: JSON CreateUserRequest }
33 { body :: JSON CreateUserRequest }
29 ( created :: { body :: User }
34 ( created :: { body :: User }
30 , badRequest :: { body :: ErrorMessage }
35 , badRequest :: { body :: ErrorMessage }
31 )
36 )
32 , deleteUser ::
37 , deleteUser ::
33 Route DELETE
38 Route DELETE
34− (Path ("users" / "id" : Int))
39+ ("users" / "id" : UserId)
35 {}
40 {}
36− ( noContent :: { body :: {} }
41+ ( noContent :: {}
37 , notFound :: { body :: ErrorMessage }
42 , notFound :: { body :: ErrorMessage }
38 )
43 )
44+ , createUserAuth ::
45+ Route POST
46+ "users"
47+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
48+ ( created :: { body :: User }
49+ , badRequest :: { body :: ErrorMessage }
50+ )
39 }
51 }
test/MakeRequest.Spec.pursnew file+54
@@ -0,0 +1,54 @@
1+module MakeRequest.Spec where
2+
3+import Prelude
4+
5+import Data.HTTP.Method as HTTP
6+import Data.Maybe (Maybe(..))
7+import Data.Tuple.Nested ((/\))
8+import JS.Fetch.Headers as Headers
9+import Prim.RowList as RL
10+import Test.Spec (Spec, describe, it)
11+import Test.Spec.Assertions (shouldEqual, shouldSatisfy)
12+import Type.Proxy (Proxy(..))
13+import Yoga.Fetch.Om (FormData, PlainText, QUERY, toHeaders)
14+import Yoga.Fetch.Om.MakeRequest (encodeBody, encodingContentType, httpMethod)
15+import Yoga.HTTP.API.Route (BearerToken(..))
16+
17+spec :: Spec Unit
18+spec = do
19+ describe "MakeRequest" do
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"
32+
33+ describe "toHeaders" do
34+ it "empty headers produces no entries" do
35+ let hdrs = toHeaders (Proxy :: _ RL.Nil) {}
36+ Headers.toArray hdrs `shouldEqual` []
37+
38+ it "single header is included" do
39+ let hdrs = toHeaders (Proxy :: _ (RL.Cons "authorization" String RL.Nil)) { authorization: "Bearer abc123" }
40+ Headers.toArray hdrs `shouldEqual` [ "authorization" /\ "Bearer abc123" ]
41+
42+ it "multiple headers are included" do
43+ let
44+ hdrs = toHeaders
45+ (Proxy :: _ (RL.Cons "authorization" String (RL.Cons "x-custom" String RL.Nil)))
46+ { authorization: "Bearer abc123", "x-custom": "value" }
47+ arr = Headers.toArray hdrs
48+ arr `shouldSatisfy` \a ->
49+ a == [ "authorization" /\ "Bearer abc123", "x-custom" /\ "value" ]
50+ || a == [ "x-custom" /\ "value", "authorization" /\ "Bearer abc123" ]
51+
52+ it "typed header values are rendered with HeaderValue" do
53+ let hdrs = toHeaders (Proxy :: _ (RL.Cons "authorization" BearerToken RL.Nil)) { authorization: BearerToken "abc123" }
54+ Headers.toArray hdrs `shouldEqual` [ "authorization" /\ "Bearer abc123" ]
test/PlainTextResponse.Spec.pursnew file+122
@@ -0,0 +1,122 @@
1+module PlainTextResponse.Spec where
2+
3+import Prelude
4+import Data.String as String
5+
6+import Effect.Aff (Aff, bracket)
7+import Effect.Aff.Class (liftAff)
8+import Effect.Class (liftEffect)
9+import Effect.Ref (Ref)
10+import Effect.Exception as Exn
11+import Effect.Ref as Ref
12+import Foreign (unsafeToForeign)
13+import Test.Spec (Spec, around, describe, it)
14+import Test.Spec.Assertions (shouldEqual)
15+import Yoga.Fastify.Fastify as F
16+import Yoga.Fastify.Fastify (Port(..), Host(..), RouteURL(..), StatusCode(..))
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
20+import Yoga.Om (Om, runOm)
21+import Yoga.Om.Strom (Strom)
22+import Yoga.Om.Strom as Strom
23+
24+type Payload = { value :: Int }
25+type Result = { doubled :: Int }
26+
27+type TestAPI =
28+ { double ::
29+ Route POST
30+ ("plain-text-test" / "double")
31+ { body :: JSON Payload }
32+ ( ok :: { body :: Result }
33+ , badRequest :: { body :: PlainText }
34+ )
35+ , health :: Route GET ("plain-text-test" / "health") {} (ok :: { body :: PlainText })
36+ , stream :: Route GET ("plain-text-test" / "stream") {} (ok :: { body :: Streaming String })
37+ }
38+
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+ }
45+api = client @TestAPI "http://localhost:44933"
46+
47+spec :: Spec Unit
48+spec = around withServer $ describe "String body on error responses" do
49+ it "200 OK decodes the JSON response body" \_ -> do
50+ result <- runOm {}
51+ { exception: \_ -> pure { doubled: -1 }
52+ , badRequest: \_ -> pure { doubled: -1 }
53+ }
54+ (api.double { value: 21 })
55+ liftAff $ result `shouldEqual` { doubled: 42 }
56+
57+ it "400 badRequest delivers the raw text body as a String" \serverMode -> do
58+ liftEffect $ Ref.write "badRequest" serverMode
59+ captured <- liftEffect $ Ref.new ""
60+ runOm {}
61+ { exception: \_ -> pure unit
62+ , badRequest: \b -> liftEffect $ Ref.write b captured
63+ }
64+ (void $ api.double { value: 0 })
65+ body <- liftEffect $ Ref.read captured
66+ liftAff $ body `shouldEqual` "invalid value"
67+
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+
90+withServer :: forall a. (Ref String -> Aff a) -> Aff a
91+withServer test = bracket acquire release (\{ serverMode } -> test serverMode)
92+ where
93+ acquire = do
94+ serverMode <- liftEffect $ Ref.new "ok"
95+ fastify <- liftEffect do
96+ f <- F.fastify {}
97+ omApp <- createOmFastify {} f
98+ 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
101+ pure f
102+ void $ F.listen { port: Port 44933, host: Host "0.0.0.0" } fastify
103+ pure { fastify, serverMode }
104+
105+ release { fastify } = F.close fastify
106+
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+
112+ handler serverMode reply = do
113+ mode <- liftEffect $ Ref.read serverMode
114+ case mode of
115+ "badRequest" -> do
116+ void $ F.status (StatusCode 400) reply # liftEffect
117+ void $ F.header "content-type" "text/plain" reply # liftEffect
118+ F.send (unsafeToForeign "invalid value") reply # liftAff
119+ _ -> do
120+ void $ F.status (StatusCode 200) reply # liftEffect
121+ void $ F.header "content-type" "application/json" reply # liftEffect
122+ F.sendJson (unsafeToForeign { doubled: 42 }) reply # liftAff
test/Polymorphic.Spec.pursnew file+70
@@ -0,0 +1,70 @@
1+module Polymorphic.Spec where
2+
3+import Prelude
4+
5+import Effect.Aff (bracket)
6+import Effect.Aff.Class (liftAff)
7+import Effect.Class (liftEffect)
8+import Test.Spec (Spec, around, describe, it)
9+import Test.Spec.Assertions (shouldEqual)
10+import Yoga.Fastify.Fastify as F
11+import Yoga.Fastify.Fastify (Port(..), Host(..))
12+import Yoga.Fastify.Om.API (registerAPI)
13+import Yoga.Fastify.Om.Route (Handler, handle, respond)
14+import Yoga.Fetch.Om (GET, Route, type (/), type (:), client)
15+import Yoga.Om (ask, runOm)
16+import Control.Monad.Error.Class (throwError)
17+
18+type User = { id :: Int, name :: String }
19+
20+type GetUserRoute = Route GET ("users" / "id" : Int) {} (ok :: { body :: User })
21+
22+type TestAPI =
23+ { getUser :: GetUserRoute
24+ }
25+
26+api = client @TestAPI "http://localhost:44931"
27+
28+spec :: Spec Unit
29+spec = around withServer $ describe "polymorphic client" do
30+ it "fetches user with { token :: String } context" \_ ->
31+ runOm { token: "secret123" } { exception: throwError } do
32+ { token } <- ask
33+ { name } <- api.getUser { id: 42 }
34+ liftAff $ (token <> ":" <> name) `shouldEqual` "secret123:User 42"
35+
36+ it "fetches user with { apiKey :: String, userId :: Int } context" \_ ->
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"
41+
42+ it "same api.getUser works in two different contexts" \_ -> do
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"
53+
54+withServer :: forall a. (Unit -> _ a) -> _ a
55+withServer test = bracket acquire release (\_ -> test unit)
56+ where
57+ acquire = do
58+ fastify <- liftEffect do
59+ f <- F.fastify {}
60+ registerAPI @TestAPI { getUser: getUserHandler } f
61+ pure f
62+ void $ F.listen { port: Port 44931, host: Host "0.0.0.0" } fastify
63+ pure fastify
64+
65+ release = F.close
66+
67+getUserHandler :: Handler GetUserRoute ()
68+getUserHandler = handle do
69+ { path } <- ask
70+ respond @"ok" { id: path.id, name: "User " <> show path.id }
test/Roundtrip.Spec.pursnew file+380
@@ -0,0 +1,380 @@
1+module Roundtrip.Spec where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Effect.Aff (bracket)
7+import Effect.Aff.Class (liftAff)
8+import Effect.Class (liftEffect)
9+import Effect.Ref as Ref
10+import Effect.Exception as Exn
11+import Justifill (justifill)
12+import Test.Spec (Spec, around, describe, it)
13+import Test.Spec.Assertions (shouldEqual)
14+import Yoga.Fastify.Fastify as F
15+import Yoga.Fastify.Fastify (Port(..), Host(..))
16+import Yoga.Fastify.Om.API (registerAPI)
17+import Yoga.Fastify.Om.Route (Handler, handle, respond, reject, BearerToken(..))
18+import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Required, Route, JSON, type (/), type (:), type (:?), client, clientWith)
19+import Yoga.Om (ask, handleErrors, runOm)
20+
21+-- Types
22+
23+type User = { id :: Int, name :: String, email :: String }
24+type CreateUserReq = { name :: String, email :: String }
25+type ErrorMsg = { error :: String }
26+
27+-- Route definitions shared by server and client
28+
29+type GetUserRoute = Route GET
30+ ("users" / "id" : Int)
31+ {}
32+ ( ok :: { body :: User }
33+ , notFound :: { body :: ErrorMsg }
34+ )
35+
36+type ListUsersRoute = Route GET
37+ ("users" :? { limit :: Int, offset :: Int })
38+ {}
39+ (ok :: { body :: Array User })
40+
41+type CreateUserRoute = Route POST
42+ "users"
43+ { body :: JSON CreateUserReq }
44+ ( created :: { body :: User }
45+ , badRequest :: { body :: ErrorMsg }
46+ )
47+
48+type UpdateUserRoute = Route PUT
49+ ("users" / "id" : Int)
50+ { body :: JSON CreateUserReq }
51+ ( ok :: { body :: User }
52+ , notFound :: { body :: ErrorMsg }
53+ )
54+
55+type DeleteUserRoute = Route DELETE
56+ ("users" / "id" : Int)
57+ {}
58+ ( ok :: { body :: {} }
59+ , notFound :: { body :: ErrorMsg }
60+ )
61+
62+type AuthRoute = Route GET
63+ "me"
64+ { headers :: { authorization :: BearerToken } }
65+ ( ok :: { body :: User }
66+ , unauthorized :: { body :: ErrorMsg }
67+ )
68+
69+type SearchRoute = Route GET
70+ ("search" :? { active :: Boolean, minScore :: Number })
71+ {}
72+ (ok :: { body :: { active :: Boolean, minScore :: Number } })
73+
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+
102+type API =
103+ { getUser :: GetUserRoute
104+ , listUsers :: ListUsersRoute
105+ , createUser :: CreateUserRoute
106+ , updateUser :: UpdateUserRoute
107+ , deleteUser :: DeleteUserRoute
108+ , me :: AuthRoute
109+ , search :: SearchRoute
110+ , findByQuery :: FindByQueryRoute
111+ }
112+
113+api = client @API "http://localhost:44932"
114+
115+-- Handlers
116+
117+getUserHandler :: Handler GetUserRoute ()
118+getUserHandler = handle do
119+ { path } <- ask
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" }
122+
123+listUsersHandler :: Handler ListUsersRoute ()
124+listUsersHandler = handle do
125+ { query } <- ask
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
136+ respond @"ok" limited
137+ where
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
146+
147+createUserHandler :: Handler CreateUserRoute ()
148+createUserHandler = handle do
149+ { body } <- ask
150+ if body.name == "" then reject @"badRequest" { error: "Name required" }
151+ else respond @"created" { id: 99, name: body.name, email: body.email }
152+
153+updateUserHandler :: Handler UpdateUserRoute ()
154+updateUserHandler = handle do
155+ { path, body } <- ask
156+ if path.id /= 42 then reject @"notFound" { error: "Not found" }
157+ else respond @"ok" { id: path.id, name: body.name, email: body.email }
158+
159+deleteUserHandler :: Handler DeleteUserRoute ()
160+deleteUserHandler = handle do
161+ { path } <- ask
162+ if path.id /= 42 then reject @"notFound" { error: "Not found" }
163+ else respond @"ok" {}
164+
165+meHandler :: Handler AuthRoute ()
166+meHandler = handle do
167+ { headers } <- ask
168+ case headers.authorization of
169+ BearerToken "secret" -> respond @"ok" { id: 1, name: "Admin", email: "admin@test.com" }
170+ _ -> reject @"unauthorized" { error: "Invalid token" }
171+
172+searchHandler :: Handler SearchRoute ()
173+searchHandler = handle do
174+ { query } <- ask
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
183+ respond @"ok" { active, minScore }
184+
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+
201+-- Test suite
202+
203+spec :: Spec Unit
204+spec = around withServer $ describe "server ↔ client roundtrip" do
205+
206+ describe "GET with path params" do
207+ it "returns user for valid id" \_ ->
208+ runOm {} { exception: \_ -> pure unit } do
209+ user <- api.getUser { id: 42 }
210+ # handleErrors { notFound: \e -> pure { id: 0, name: e.error, email: "" } }
211+ liftAff $ user.name `shouldEqual` "Alice"
212+
213+ it "returns notFound for invalid id" \_ ->
214+ runOm {} { exception: \_ -> pure unit } do
215+ user <- api.getUser { id: 999 }
216+ # handleErrors { notFound: \e -> pure { id: 0, name: e.error, email: "" } }
217+ liftAff $ user.name `shouldEqual` "User 999 not found"
218+
219+ describe "GET with query params" do
220+ it "passes query params correctly" \_ ->
221+ runOm {} { exception: \_ -> pure unit } do
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
229+
230+ it "handles no query params" \_ ->
231+ runOm {} { exception: \_ -> pure unit } do
232+ users <- api.listUsers (justifill {})
233+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob", "Charlie" ]
234+
235+ describe "POST with JSON body" do
236+ it "sends and receives JSON body" \_ ->
237+ runOm {} { exception: \_ -> pure unit } do
238+ user <- api.createUser { name: "Dave", email: "d@t.com" }
239+ # handleErrors { badRequest: \e -> pure { id: 0, name: e.error, email: "" } }
240+ liftAff do
241+ user.name `shouldEqual` "Dave"
242+ user.email `shouldEqual` "d@t.com"
243+ user.id `shouldEqual` 99
244+
245+ it "rejects with badRequest for empty name" \_ ->
246+ runOm {} { exception: \_ -> pure unit } do
247+ user <- api.createUser { name: "", email: "e@t.com" }
248+ # handleErrors { badRequest: \e -> pure { id: 0, name: e.error, email: "" } }
249+ liftAff $ user.name `shouldEqual` "Name required"
250+
251+ describe "PUT with path params and JSON body" do
252+ it "sends path params and body together" \_ ->
253+ runOm {} { exception: \_ -> pure unit } do
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: "" } }
259+ liftAff do
260+ user.id `shouldEqual` 42
261+ user.name `shouldEqual` "Updated"
262+
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+
272+ describe "DELETE with path params" do
273+ it "deletes successfully" \_ ->
274+ runOm {} { exception: \_ -> pure unit } do
275+ result <- api.deleteUser { id: 42 }
276+ # handleErrors { notFound: \_ -> pure {} }
277+ liftAff $ result `shouldEqual` {}
278+
279+ it "returns notFound for wrong id" \_ ->
280+ runOm {} { exception: \_ -> pure unit } do
281+ result <- api.deleteUser { id: 1 }
282+ # handleErrors { notFound: \_ -> pure {} }
283+ liftAff $ result `shouldEqual` {}
284+
285+ describe "GET with authorization header" do
286+ it "passes bearer token in header" \_ ->
287+ runOm {} { exception: \_ -> pure unit } do
288+ user <- api.me { authorization: BearerToken "secret" }
289+ # handleErrors { unauthorized: \e -> pure { id: 0, name: e.error, email: "" } }
290+ liftAff $ user.name `shouldEqual` "Admin"
291+
292+ it "rejects unauthorized" \_ ->
293+ runOm {} { exception: \_ -> pure unit } do
294+ user <- api.me { authorization: BearerToken "wrong" }
295+ # handleErrors { unauthorized: \e -> pure { id: 0, name: e.error, email: "" } }
296+ liftAff $ user.name `shouldEqual` "Invalid token"
297+
298+ describe "GET with Boolean and Number query params" do
299+ it "passes Boolean and Number query params" \_ ->
300+ runOm {} { exception: \_ -> pure unit } do
301+ result <- api.search (justifill { active: true, minScore: 3.14 })
302+ liftAff do
303+ result.active `shouldEqual` true
304+ result.minScore `shouldEqual` 3.14
305+
306+ it "handles missing optional query params" \_ ->
307+ runOm {} { exception: \_ -> pure unit } do
308+ result <- api.search (justifill {})
309+ liftAff do
310+ result.active `shouldEqual` false
311+ result.minScore `shouldEqual` 0.0
312+
313+ describe "optional query params" do
314+ it "accepts a selected optional query parameter" \_ ->
315+ runOm {} { exception: \_ -> pure unit } do
316+ users <- api.listUsers (justifill { limit: 1 })
317+ liftAff $ (map _.name users) `shouldEqual` [ "Alice" ]
318+
319+ it "accepts all optional query parameters" \_ ->
320+ runOm {} { exception: \_ -> pure unit } do
321+ users <- api.listUsers (justifill { limit: 2, offset: 0 })
322+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob" ]
323+
324+ it "fills omitted optional query parameters with Nothing" \_ ->
325+ runOm {} { exception: \_ -> pure unit } do
326+ users <- api.listUsers (justifill {})
327+ liftAff $ (map _.name users) `shouldEqual` [ "Alice", "Bob", "Charlie" ]
328+
329+ it "passes selected Boolean query param" \_ ->
330+ runOm {} { exception: \_ -> pure unit } do
331+ result <- api.search (justifill { active: true })
332+ liftAff do
333+ result.active `shouldEqual` true
334+ result.minScore `shouldEqual` 0.0
335+
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+
356+-- Server setup
357+
358+withServer :: forall a. (Unit -> _ a) -> _ a
359+withServer test = bracket acquire release (\_ -> test unit)
360+ where
361+ acquire = do
362+ fastify <- liftEffect do
363+ f <- F.fastify {}
364+ registerAPI @API
365+ { getUser: getUserHandler
366+ , listUsers: listUsersHandler
367+ , createUser: createUserHandler
368+ , updateUser: updateUserHandler
369+ , deleteUser: deleteUserHandler
370+ , me: meHandler
371+ , search: searchHandler
372+ , findByQuery: findByQueryHandler
373+ }
374+ f
375+ registerAPI @InjectedAPI { getTenantUser: tenantUserHandler } f
376+ pure f
377+ void $ F.listen { port: Port 44932, host: Host "0.0.0.0" } fastify
378+ pure fastify
379+
380+ release = F.close
test/SplitParams.Spec.purs+22−31
@@ -2,22 +2,13 @@ module SplitParams.Spec where
2
2
3import Prelude
3import Prelude
4
4
5−import Effect (Effect)
5+import Test.Spec (Spec, describe, it)
6−import Effect.Aff (Aff)
6+import Test.Spec.Assertions (shouldEqual)
7import Yoga.Fetch.Om.SplitParams (splitParams)
7import Yoga.Fetch.Om.SplitParams (splitParams)
8−import ViTest (ViTest, describe, test)
9−import ViTest.Expect (expectToBe)
10
8
11−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
9+spec :: Spec Unit
12−expectToEqual expected actual = expectToBe true (expected == actual)
13−
14−--------------------------------------------------------------------------------
15−-- SplitParams Tests
16−--------------------------------------------------------------------------------
17−
18−testSplitParams :: Effect ViTest
19−testSplitParams = describe "splitParams" $ do
10+spec = describe "splitParams" do
20− _ <- test "splits path and query params" do
11+ it "splits path and query params" do
21 let
12 let
22 allParams = { id: 42, limit: 10, offset: 20 }
13 allParams = { id: 42, limit: 10, offset: 20 }
23 result =
14 result =
@@ -26,12 +17,12 @@ testSplitParams = describe "splitParams" $ do
26 , query :: { limit :: Int, offset :: Int }
17 , query :: { limit :: Int, offset :: Int }
27 , body :: {}
18 , body :: {}
28 }
19 }
29− expectToEqual 42 result.path.id
20+ result.path.id `shouldEqual` 42
30− expectToEqual 10 result.query.limit
21+ result.query.limit `shouldEqual` 10
31− expectToEqual 20 result.query.offset
22+ result.query.offset `shouldEqual` 20
32− expectToEqual {} result.body
23+ result.body `shouldEqual` {}
33
24
34− _ <- test "splits path and body params" do
25+ it "splits path and body params" do
35 let
26 let
36 allParams = { id: 42, name: "Alice", email: "alice@example.com" }
27 allParams = { id: 42, name: "Alice", email: "alice@example.com" }
37 result =
28 result =
@@ -40,12 +31,12 @@ testSplitParams = describe "splitParams" $ do
40 , query :: {}
31 , query :: {}
41 , body :: { name :: String, email :: String }
32 , body :: { name :: String, email :: String }
42 }
33 }
43− expectToEqual 42 result.path.id
34+ result.path.id `shouldEqual` 42
44− expectToEqual {} result.query
35+ result.query `shouldEqual` {}
45− expectToEqual "Alice" result.body.name
36+ result.body.name `shouldEqual` "Alice"
46− expectToEqual "alice@example.com" result.body.email
37+ result.body.email `shouldEqual` "alice@example.com"
47
38
48− _ <- test "splits all three param types" do
39+ it "splits all three param types" do
49 let
40 let
50 allParams =
41 allParams =
51 { id: 42
42 { id: 42
@@ -58,11 +49,11 @@ testSplitParams = describe "splitParams" $ do
58 , query :: { limit :: Int }
49 , query :: { limit :: Int }
59 , body :: { name :: String }
50 , body :: { name :: String }
60 }
51 }
61− expectToEqual 42 result.path.id
52+ result.path.id `shouldEqual` 42
62− expectToEqual 10 result.query.limit
53+ result.query.limit `shouldEqual` 10
63− expectToEqual "Bob" result.body.name
54+ result.body.name `shouldEqual` "Bob"
64
55
65− test "handles empty splits" do
56+ it "handles empty splits" do
66 let
57 let
67 allParams = { id: 42 }
58 allParams = { id: 42 }
68 result =
59 result =
@@ -71,6 +62,6 @@ testSplitParams = describe "splitParams" $ do
71 , query :: {}
62 , query :: {}
72 , body :: {}
63 , body :: {}
73 }
64 }
74− expectToEqual 42 result.path.id
65+ result.path.id `shouldEqual` 42
75− expectToEqual {} result.query
66+ result.query `shouldEqual` {}
76− expectToEqual {} result.body
67+ result.body `shouldEqual` {}
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` "€"
test/Test.Main.pursdeleted−20
@@ -1,20 +0,0 @@
1−module Test.Main where
2−
3−import Prelude
4−
5−import BuildUrl.Spec as BuildUrl
6−import SplitParams.Spec as SplitParams
7−import Variant.Spec as Variant
8−import Effect (Effect)
9−import ViTest (ViTest, viTest)
10−
11−spec :: Effect ViTest
12−spec = do
13− _ <- BuildUrl.testSubstitutePathParams
14− _ <- BuildUrl.testAppendQueryParams
15− _ <- BuildUrl.testBuildUrl
16− _ <- SplitParams.testSplitParams
17− Variant.testOnly
18−
19−main :: ViTest
20−main = viTest spec
test/Test/Main.pursnew file+14
@@ -0,0 +1,14 @@
1+module Test.Main where
2+
3+import Prelude
4+
5+import Effect (Effect)
6+import Effect.Aff (launchAff_)
7+import Test.Spec.Discovery (discover)
8+import Test.Spec.Reporter (consoleReporter)
9+import Test.Spec.Runner (runSpec)
10+
11+main :: Effect Unit
12+main = launchAff_ do
13+ specs <- discover ".*\\.Spec"
14+ runSpec [ consoleReporter ] specs
test/TestServer.pursnew file+38
@@ -0,0 +1,38 @@
1+module TestServer where
2+
3+import Prelude
4+
5+import Data.Int as Int
6+import Data.Maybe (fromMaybe)
7+import Effect (Effect)
8+import Effect.Aff.Class (liftAff)
9+import Effect.Class (liftEffect)
10+import Foreign (unsafeToForeign)
11+import Foreign.Object as FObject
12+import Promise (Promise)
13+import Promise.Aff (fromAff)
14+import Yoga.Fastify.Fastify as F
15+import Yoga.Fastify.Fastify (Fastify, Port(..), Host(..), RouteURL(..), StatusCode(..))
16+import Yoga.Fastify.Om (createOmFastify, getOm, requestParams)
17+
18+mkServer :: Effect Fastify
19+mkServer = do
20+ fastify <- F.fastify {}
21+ omApp <- createOmFastify {} fastify
22+ getOm (RouteURL "/users/:id") handleGetUser omApp
23+ pure fastify
24+ where
25+ handleGetUser reply = do
26+ params <- requestParams
27+ let idStr = fromMaybe "0" (FObject.lookup "id" params)
28+ let id = fromMaybe 0 (Int.fromString idStr)
29+ let body = unsafeToForeign { id, name: "User " <> idStr }
30+ void $ F.status (StatusCode 200) reply # liftEffect
31+ void $ F.header "content-type" "application/json" reply # liftEffect
32+ F.sendJson body reply # liftAff
33+
34+listenServer :: Fastify -> Effect (Promise String)
35+listenServer fastify = fromAff $ F.listen { port: Port 44931, host: Host "0.0.0.0" } fastify
36+
37+closeServer :: Fastify -> Effect (Promise Unit)
38+closeServer fastify = fromAff $ F.close fastify
test/Variant.Spec.purs+6−11
@@ -4,21 +4,16 @@ import Prelude
4
4
5import Data.Variant (Variant)
5import Data.Variant (Variant)
6import Data.Variant as Variant
6import Data.Variant as Variant
7−import Effect (Effect)
7+import Test.Spec (Spec, describe, it)
8−import Effect.Aff (Aff)
8+import Test.Spec.Assertions (shouldEqual)
9import Type.Proxy (Proxy(..))
9import Type.Proxy (Proxy(..))
10−import ViTest (ViTest, describe, test)
11−import ViTest.Expect (expectToBe)
12import Yoga.Fetch.Om.Variant (only)
10import Yoga.Fetch.Om.Variant (only)
13
11
14−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
12+spec :: Spec Unit
15−expectToEqual expected actual = expectToBe true (expected == actual)
16−
17−testOnly :: Effect ViTest
18−testOnly = describe "only" do
13+spec = describe "only" do
19− test "extracts single variant value - record" do
14+ it "extracts single variant value - record" do
20 let
15 let
21 variant :: Variant (ok :: { id :: Int, name :: String })
16 variant :: Variant (ok :: { id :: Int, name :: String })
22 variant = Variant.inj (Proxy :: _ "ok") { id: 42, name: "Alice" }
17 variant = Variant.inj (Proxy :: _ "ok") { id: 42, name: "Alice" }
23 result = only variant
18 result = only variant
24− expectToEqual { id: 42, name: "Alice" } result
19+ result `shouldEqual` { id: 42, name: "Alice" }
vitest.config.jsdeleted−9
@@ -1,9 +0,0 @@
1−import { defineConfig } from "vitest/config";
2−
3−export default defineConfig({
4− test: {
5− include: ["output/Test.Main/index.js"],
6− globals: true,
7− environment: "jsdom",
8− },
9−});