git.iko.soy/rowtype-yoga/purescript-yoga-fetch-om
on main help sign in
104 files changed+6070−670
since splitsnapshots
unifiedsplit
.c8rc.json+22 .github/workflows/ci.yml+7−2 .gitignore+4−1 .psci_modules/Data.Int.Bits/externs.cbor .psci_modules/Data.Int.Bits/foreign.js+48 .psci_modules/Data.Int.Bits/index.js+12 .psci_modules/Data.NaturalTransformation/externs.cbor .psci_modules/Data.NaturalTransformation/index.js+1 .psci_modules/Data.String.Unsafe/externs.cbor .psci_modules/Data.String.Unsafe/foreign.js+11 .psci_modules/Data.String.Unsafe/index.js+7 .psci_modules/Record.Unsafe/externs.cbor .psci_modules/Record.Unsafe/foreign.js+38 .psci_modules/Record.Unsafe/index.js+14 .psci_modules/Spago.Generated.BuildInfo/externs.cbor .psci_modules/Spago.Generated.BuildInfo/index.js+13 .psci_modules/Type.Equality/externs.cbor .psci_modules/Type.Equality/index.js+35 .psci_modules/Type.Function/externs.cbor .psci_modules/Type.Function/index.js+1 .psci_modules/Type.Proxy/externs.cbor .psci_modules/Type.Proxy/index.js+58 .psci_modules/Type.Row/externs.cbor .psci_modules/Type.Row/index.js+1 .psci_modules/Unsafe.Coerce/externs.cbor .psci_modules/Unsafe.Coerce/foreign.js+5 .psci_modules/Unsafe.Coerce/index.js+5 .psci_modules/Web.DOM.Internal.Types/externs.cbor .psci_modules/Web.DOM.Internal.Types/index.js+1 .psci_modules/Web.Event.Internal.Types/externs.cbor .psci_modules/Web.Event.Internal.Types/index.js+1 .psci_modules/Yoga.HTTP.API.Route.Encoding/externs.cbor .psci_modules/Yoga.HTTP.API.Route.Encoding/index.js+1 .psci_modules/Yoga.HTTP.API.Route.Method/externs.cbor .psci_modules/Yoga.HTTP.API.Route.Method/index.js+1 .psci_modules/cache-db.json+1 .psci_modules/package.json+1 .purs-repl+1 .purslint-cache/props.db LICENSE+21 README.md+153−127 bun.lock+1089 compile-fail-tests/ClientBadBodyType.purs+12 compile-fail-tests/ClientBadCurriedBodyType.purs+12 compile-fail-tests/ClientBadHeaderType.purs+13 compile-fail-tests/ClientBadOptionalQueryType.purs+13 compile-fail-tests/ClientBadPathType.purs+12 compile-fail-tests/ClientExtraBodyField.purs+12 compile-fail-tests/ClientExtraHeaderField.purs+13 compile-fail-tests/ClientExtraOptionalQueryField.purs+13 compile-fail-tests/ClientExtraPathField.purs+12 compile-fail-tests/ClientMissingBodyArgument.purs+12 compile-fail-tests/ClientMissingBodyField.purs+12 compile-fail-tests/ClientMissingHeaderArgument.purs+13 compile-fail-tests/ClientMissingHeaderField.purs+13 compile-fail-tests/ClientMissingPathField.purs+12 compile-fail-tests/ClientMissingRequiredQuery.purs+13 compile-fail-tests/ClientMissingThirdArgument.purs+13 compile-fail-tests/run.js+101 package.json+19−4 spago.lock+1792 spago.yaml+25−8 src/Yoga/Fetch/Om.js+1 src/Yoga/Fetch/Om.purs+231−37 src/Yoga/Fetch/Om/BuildUrl.js+87 src/Yoga/Fetch/Om/BuildUrl.purs+60−17 src/Yoga/Fetch/Om/ClientFn.purs−142 src/Yoga/Fetch/Om/ClientFunction.purs+157 src/Yoga/Fetch/Om/ExtractParams.purs+22−1 src/Yoga/Fetch/Om/MakeRequest.js+1 src/Yoga/Fetch/Om/MakeRequest.purs+44−16 src/Yoga/Fetch/Om/ParseResponse.purs+83−20 src/Yoga/Fetch/Om/ReadOptional.js+18 src/Yoga/Fetch/Om/ReadOptional.purs+56 src/Yoga/Fetch/Om/Simple.purs+63−72 src/Yoga/Fetch/Om/SplitResponses.purs+24−2 src/Yoga/Fetch/Om/StreamDecode.js+12 src/Yoga/Fetch/Om/StreamDecode.purs+61 test-compile-fail/cases/ClientBogusSignature.purs+9 test-compile-fail/cases/ClientExtraField.purs+12 test-compile-fail/cases/ClientMissingField.purs+12 test-compile-fail/cases/ClientNoArgsPassedArg.purs+12 test-compile-fail/cases/ClientWrongFieldName.purs+12 test-compile-fail/cases/ClientWrongFieldType.purs+12 test-compile-fail/run.sh+26 test/BodylessResponse.Example.purs+27 test/BodylessResponse.Spec.purs+125 test/BuildUrl.Spec.purs+154−100 test/BuildUrlProperty.Spec.purs+256 test/Complete.Example.purs+29−26 test/Example.purs+20−8 test/MakeRequest.Spec.purs+54 test/PlainTextResponse.Spec.purs+122 test/Polymorphic.Spec.purs+70 test/Roundtrip.Spec.purs+380 test/Simple.Spec.purs+16−16 test/SplitParams.Spec.purs+22−31 test/StreamDecode.Spec.js+1 test/StreamDecode.Spec.purs+32 test/Test.Main.purs−20 test/Test/Main.purs+14 test/TestServer.purs+38 test/Variant.Spec.purs+6−11 vitest.config.js−9
.c8rc.jsonnew file+22
@@ -0,0 +1,22 @@
1+{
2+ "all": true,
3+ "include": [
4+ "output/Yoga.Fetch.Om*/index.js",
5+ "output/Yoga.Fetch.Om*/foreign.js"
6+ ],
7+ "exclude": [
8+ "output/Yoga.Fetch.Om.ExtractParams/index.js",
9+ "output/Yoga.Fetch.Om.SplitResponses/index.js"
10+ ],
11+ "reporter": [
12+ "text",
13+ "html",
14+ "lcov"
15+ ],
16+ "reports-dir": "coverage",
17+ "check-coverage": true,
18+ "statements": 95,
19+ "branches": 90,
20+ "functions": 85,
21+ "lines": 95
22+}
.github/workflows/ci.yml+7−2
@@ -12,5 +12,10 @@ jobs:
1212 steps:
1313 - uses: actions/checkout@v4
1414 - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
15− - run: spago build
16− - run: spago test
15+ - run: bun run coverage
16+ - uses: actions/upload-artifact@v4
17+ if: always()
18+ with:
19+ name: coverage
20+ path: coverage/
21+ - run: bun compile-fail-tests/run.js
.gitignore+4−1
@@ -1,6 +1,9 @@
11output/
22output-compile-fail/
3+coverage/
4+generated-docs/
35node_modules/
46.spago/
57.psc-ide-port
6−*.lock
8+package-lock.json
9+bun.lockb
.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+153−127
@@ -2,97 +2,142 @@
22
33Derive type-safe fetch clients from `purescript-yoga-http-api` route definitions.
44
5−## Concept
6−
7−Similar to how `purescript-yoga-fastify-om` derives servers from HTTP API definitions, this library derives **clients** from the same definitions using the Fetch API.
8−
95## Installation
106
117```bash
128spago install yoga-fetch-om
139```
1410
15−## Quick Start
16−
17−### 1. Define your API using yoga-http-api
11+Optional query examples below use [`justifill`](https://pursuit.purescript.org/packages/purescript-justifill):
1812
19−```purescript
20−import Yoga.Fetch.Om
21−
22−type UserAPI =
23− { getUser ::
24− Route GET
25− (Path ("users" / "id" : Int))
26− (Request {})
27− ( ok :: { body :: User }
28− , notFound :: { body :: ErrorMessage }
29− )
30− , createUser ::
31− Route POST
32− (Path "users")
33− (Request { body :: JSON CreateUserRequest })
34− ( created :: { body :: User }
35− , badRequest :: { body :: ErrorMessage }
36− )
37− }
13+```bash
14+spago install justifill
3815```
3916
40−### 2. Derive a type-safe client
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:
4120
4221```purescript
43−api = deriveAPI "https://api.example.com" (Proxy :: _ UserAPI)
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 }
4428```
4529
46−### 3. Use it!
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`:
4731
4832```purescript
49−main = do
50− -- Compiler ensures you pass the right parameters
51− result <- api.getUser { id: 42 }
52−
53− -- Pattern match on all possible responses
54− result # Variant.match
55− { ok: \(Response { body: user }) ->
56− log $ "User: " <> user.name
57− , notFound: \_ ->
58− log "User not found"
59− }
33+-- offset remains omitted at the call site
34+users <- api.listUsers (justifill { limit: 10 })
6035```
6136
62−## Features
37+Path parameters and request bodies keep their existing argument shapes.
6338
64−### ✅ Type-Safe Parameters
39+## Quick Start
6540
66−The compiler ensures you provide exactly the right parameters:
41+### Example 1: Simple GET and POST Requests
6742
6843```purescript
69−-- Path parameters
70−api.getUser { id: 42 }
44+import Yoga.Fetch.Om.Simple (get, post)
7145
72−-- Query parameters
73−api.listUsers { limit: 10, offset: 20 }
46+type User = { id :: Int, name :: String, email :: String }
7447
75−-- Request body
76−api.createUser { body: { name: "Alice", email: "alice@example.com" } }
77−
78−-- Mixed parameters
79−api.updateUser { id: 42, body: { name: "Alice Updated" } }
48+main = do
49+ -- Simple GET request
50+ user <- get @User "https://api.example.com/users/42" {}
51+ log user.name
52+
53+ -- Simple POST request with JSON body
54+ newUser <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
55+ log $ "Created user with ID: " <> show newUser.id
8056```
8157
82−### ✅ Exhaustive Response Handling
83−
84−Variant types ensure you handle all possible responses:
58+### Example 2: Type-Safe API Client with Full CRUD
8559
8660```purescript
87−result <- api.createUser { body: newUser }
61+import Justifill (justifill)
8862
89−result # Variant.match
90− { created: \(Response { body }) -> log $ "Created: " <> show body.id
91− , badRequest: \(Response { body }) -> log $ "Error: " <> body.error
92− -- Forgot a case? Compile error!
63+type UserAPI =
64+ { health ::
65+ Route GET "health" {}
66+ ( ok :: { body :: { status :: String } } )
67+ , getUser ::
68+ Route GET ("users" / "id" : Int) {}
69+ ( ok :: { body :: User }
70+ , notFound :: { body :: ErrorMessage }
71+ )
72+ , listUsers ::
73+ Route GET ("users" :? { limit :: Int, offset :: Int }) {}
74+ ( ok :: { body :: Array User } )
75+ , createUser ::
76+ Route POST "users" { body :: JSON CreateUserRequest }
77+ ( created :: { body :: User }
78+ , badRequest :: { body :: ErrorMessage }
79+ )
80+ , updateUser ::
81+ Route PUT ("users" / "id" : Int) { body :: JSON UpdateUserRequest }
82+ ( ok :: { body :: User }
83+ , notFound :: { body :: ErrorMessage }
84+ , badRequest :: { body :: ErrorMessage }
85+ )
86+ , deleteUser ::
87+ Route DELETE ("users" / "id" : Int) {}
88+ ( noContent :: { body :: {} }
89+ , notFound :: { body :: ErrorMessage }
90+ )
9391 }
92+
93+api = client @UserAPI "https://api.example.com"
94+
95+-- Generated methods use ordinary function application.
96+health <- api.health
97+user <- api.createUser { name: "Alice", email: "alice@example.com" }
98+ # handleErrors
99+ { badRequest: \err -> do
100+ log $ "Validation error: " <> err.error
101+ throw err
102+ }
103+
104+-- Path parameters and request bodies remain separate arguments.
105+updated <- api.updateUser
106+ { id: user.id }
107+ { name: "Alice Updated", email: user.email }
108+ # handleErrors
109+ { notFound: \_ -> throw userNotFound
110+ , badRequest: \err -> throw validationError
111+ }
112+
113+-- Optional query parameters are explicit `Maybe` fields. Justifill can wrap
114+-- supplied values in `Just` and fill omitted fields with `Nothing`.
115+users <- api.listUsers (justifill { limit: 10 })
116+
117+-- The equivalent call without Justifill:
118+allUsers <- api.listUsers { limit: Nothing, offset: Nothing }
94119```
95120
121+## More Examples
122+
123+See the test files for complete, runnable examples with all imports:
124+
125+- **[`test/Simple.Spec.purs`](test/Simple.Spec.purs)** - Basic GET/POST/PUT/PATCH/DELETE requests
126+- **[`test/Complete.Example.purs`](test/Complete.Example.purs)** - Full CRUD API with error handling
127+- **[`test/BuildUrl.Spec.purs`](test/BuildUrl.Spec.purs)** - Path parameters and query strings
128+- **[`test/Variant.Spec.purs`](test/Variant.Spec.purs)** - Response variant handling
129+- **[`test/SplitParams.Spec.purs`](test/SplitParams.Spec.purs)** - Parameter extraction patterns
130+
131+## Features
132+
133+### ✅ Type-Safe Everything
134+
135+- **Path parameters**: `/users/:id` requires `{ id :: Int }`; names and values are percent-encoded
136+- **Query parameters**: Type-safe, percent-encoded query strings with explicit `Maybe` fields for optional values
137+- **Request bodies**: `JSON`, `PlainText`, and URL-encoded `FormData`
138+- **Response bodies**: Parsed JSON, `PlainText` as `String`, and `Streaming a` as `Strom {} () a`
139+- **Error handling**: Exhaustive pattern matching on variants
140+
96141### ✅ Single Source of Truth
97142
98143Define your API once, use it everywhere:
@@ -102,20 +147,20 @@ Define your API once, use it everywhere:
102147server = buildServer apiRoutes handlers
103148
104149-- Client (yoga-fetch-om)
105−client = deriveAPI baseUrl (Proxy :: _ apiRoutes)
150+apiClient = client @apiRoutes baseUrl
106151```
107152
108153Changes to routes automatically update both client and server!
109154
110−### ✅ Automatic Everything
155+### ✅ Automatic Derivation
111156
112−No manual client code needed:
113−- ✅ URL building with path parameter substitution
114−- ✅ Query string construction
115−- ✅ JSON request serialization
116−- ✅ JSON response parsing
157+No manual client code:
158+- ✅ URL building with segment-safe path substitution
159+- ✅ Query construction that preserves existing queries and fragments
160+- ✅ JSON, plain-text, and URL-encoded request serialization
161+- ✅ JSON and plain-text response decoding
162+- ✅ Incremental UTF-8 and binary response streams
117163- ✅ Status code → variant mapping
118−- ✅ CORS and credentials handling
119164
120165### ✅ Integration with yoga-om
121166
@@ -125,97 +170,77 @@ Works seamlessly with the Om monad:
125170getUserProfile :: Om AppContext AppErrors User
126171getUserProfile = do
127172 { api, userId } <- ask
128− result <- liftAff $ api.getUser { id: userId }
129− case result of
130− ok -> pure result.body
131− notFound -> throw { userNotFound: userId }
173+ api.getUser { id: userId }
174+ # handleErrors { notFound: \_ -> throw { userNotFound: userId } }
132175```
133176
134−## Complete Example
135−
136−See [`test/Complete.Example.purs`](test/Complete.Example.purs) for a full working example with:
137−- GET requests with path parameters
138−- GET requests with query parameters
139−- POST/PUT requests with JSON bodies
140−- DELETE requests
141−- Complete error handling
142−
143177## How It Works
144178
145179The library uses PureScript's type system to:
146180
1471811. **Extract parameters** from route definitions at compile time
148− - Path params from segments: `"users" / "id" : Int`
149− - Query params from `:?` operator: `:? { limit :: Int }`
150− - Body params from Request type: `{ body :: JSON User }`
182+ - Path params: `"users" / "id" : Int` → `{ id :: Int }`
183+ - Optional query params: `:? { limit :: Int }` → `{ limit :: Maybe Int }`
184+ - Required query params: `:? { limit :: Required Int }` → `{ limit :: Int }`
185+ - Body params: `{ body :: JSON User }` → `User`
151186
152−2. **Build URLs** by:
153− - Getting the path pattern: `/users/:id`
154− - Substituting path params: `/users/42`
155− - Appending query params: `?limit=10&offset=20`
187+2. **Build URLs** automatically
188+ - Pattern: `/users/:id` + params: `{ id: 42 }` → `/users/42`
189+ - Query: `?limit=10&offset=20`
156190
157−3. **Make requests** using:
158− - `js-fetch` for the fetch API
159− - `js-promise-aff` to convert Promises → Aff
160− - `yoga-json` for JSON serialization
191+3. **Make requests** with `js-fetch` and `js-promise-aff`
161192
162−4. **Parse responses** by:
163− - Mapping status codes to variant labels (200 → "ok", 404 → "notFound")
164− - Parsing JSON response bodies
165− - Constructing typed Response values
193+4. **Parse responses** by mapping status codes to variant labels
194+ - `200` → `"ok"`, `404` → `"notFound"`, etc.
166195
167−All of this happens automatically based on your route types!
196+All automatically based on your route types!
168197
169198## API Reference
170199
171−### `deriveAPI`
200+### `client`
172201
173202```purescript
174−deriveAPI :: String -> Proxy (Record routes) -> Record clients
203+client :: forall @routes. String -> Record clients
175204```
176205
177−Derives a record of client functions from a record of routes.
206+Derives a record of client functions from a record of routes using Visible Type Application.
178207
179208**Parameters:**
180−- `baseUrl` - The base URL of the API (e.g., `"https://api.example.com"`)
181−- `routes` - Proxy of your API route definitions
182−
183−**Returns:** A record where each route becomes a function taking params and returning `Aff (Variant response)`
209+- `@routes` - Your API route type (provided via VTA syntax)
210+- `baseUrl` - Base URL (e.g., `"https://api.example.com"`)
184211
185−### `deriveClientFn`
212+**Returns:** Record where each route becomes a function returning `Om context errors result`
186213
214+**Example:**
187215```purescript
188−deriveClientFn :: String -> Proxy (Route method segments request response) -> ClientFn params response
216+api = client @UserAPI "https://api.example.com"
189217```
190218
191−Derives a single client function from a route. Usually you'll use `deriveAPI` instead, but this is useful for individual routes.
192−
193−## Roadmap
219+## Features
194220
195−### Current Version (0.1.0)
196−- ✅ Core client derivation
221+- ✅ Core client derivation with VTA syntax
197222- ✅ All HTTP methods (GET, POST, PUT, PATCH, DELETE)
198223- ✅ Path, query, and body parameters
224+- ✅ Request/response headers
199225- ✅ JSON encoding/decoding
226+- ✅ FormData support
200227- ✅ Variant response handling
201−- ✅ CORS support
228+- ✅ Om monad integration
202229
203−### Future
204−- ⏳ Request/response headers support
205−- ⏳ Authentication (Bearer tokens, API keys)
206−- ⏳ Request/response interceptors
207−- ⏳ Retry logic with exponential backoff
208−- ⏳ Timeout configuration
209−- ⏳ FormData and file upload support
210−- ⏳ Progress events
211−- ⏳ Request cancellation
230+## Testing
212231
213−## Contributing
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.
214240
215−Contributions welcome! This library follows the patterns established by the yoga ecosystem:
216−- Type-level programming for automatic derivation
217−- Integration with yoga-json for serialization
218−- Consistent API design with yoga-fastify-om
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/`.
219244
220245## License
221246
@@ -226,4 +251,5 @@ MIT
226251- [purescript-yoga-http-api](https://github.com/rowtype-yoga/purescript-yoga-http-api) - HTTP API type definitions
227252- [purescript-yoga-fastify-om](https://github.com/rowtype-yoga/purescript-yoga-fastify-om) - Server-side counterpart
228253- [purescript-yoga-json](https://github.com/rowtype-yoga/purescript-yoga-json) - JSON serialization
229−- [purescript-yoga-om](https://github.com/rowtype-yoga/purescript-yoga-om) - Om monad for effects and DI
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+19−4
@@ -1,12 +1,14 @@
11{
22 "name": "purescript-yoga-fetch-om",
3− "version": "0.1.0",
3+ "version": "0.8.1",
44 "description": "Derive type-safe fetch clients from yoga HTTP API definitions",
55 "type": "module",
66 "scripts": {
77 "build": "spago build",
88 "test": "spago test",
9− "clean": "rm -rf output .spago",
9+ "coverage": "spago build && c8 spago test",
10+ "test:compile-fail": "bun compile-fail-tests/run.js",
11+ "clean": "rm -rf output .spago coverage",
1012 "watch": "spago build --watch",
1113 "repl": "spago repl",
1214 "docs": "spago docs",
@@ -24,10 +26,23 @@
2426 "author": "",
2527 "license": "MIT",
2628 "devDependencies": {
29+ "@fastify/cookie": "^11.1.2",
30+ "@fastify/cors": "^11.3.0",
31+ "@fastify/formbody": "^9.0.0",
32+ "@fastify/helmet": "^13.1.0",
33+ "@fastify/multipart": "^10.1.0",
34+ "@fastify/rate-limit": "^11.2.0",
35+ "@fastify/websocket": "^11.3.0",
36+ "argon2": "^0.45.1",
37+ "c8": "^12.0.0",
38+ "fastify": "^5.7.4",
39+ "jose": "^6.2.8",
2740 "jsdom": "^28.0.0",
2841 "purescript": "^0.15.15",
29− "spago": "^0.93.42",
42+ "spago": "^1.0.3",
3043 "vitest": "^2.1.8"
3144 },
32− "dependencies": {}
45+ "dependencies": {
46+ "pino": "^10.3.1"
47+ }
3348}
spago.locknew file+1792
@@ -0,0 +1,1792 @@
1+{
2+ "workspace": {
3+ "packages": {
4+ "yoga-fetch-om": {
5+ "path": "./",
6+ "core": {
7+ "dependencies": [
8+ {
9+ "aff": ">=8.0.0 <9.0.0"
10+ },
11+ {
12+ "arraybuffer-types": ">=3.0.2 <4.0.0"
13+ },
14+ {
15+ "arrays": ">=7.3.0 <8.0.0"
16+ },
17+ {
18+ "effect": ">=4.0.0 <5.0.0"
19+ },
20+ {
21+ "either": ">=6.1.0 <7.0.0"
22+ },
23+ {
24+ "exceptions": ">=6.1.0 <7.0.0"
25+ },
26+ {
27+ "http-methods": ">=6.1.0 <7.0.0"
28+ },
29+ {
30+ "js-fetch": ">=0.2.1 <0.3.0"
31+ },
32+ {
33+ "js-promise-aff": ">=1.0.0 <2.0.0"
34+ },
35+ {
36+ "maybe": ">=6.0.0 <7.0.0"
37+ },
38+ {
39+ "prelude": ">=6.0.2 <7.0.0"
40+ },
41+ {
42+ "record": ">=4.0.0 <5.0.0"
43+ },
44+ {
45+ "strings": ">=6.0.1 <7.0.0"
46+ },
47+ {
48+ "tailrec": ">=6.1.0 <7.0.0"
49+ },
50+ {
51+ "transformers": ">=6.1.0 <7.0.0"
52+ },
53+ {
54+ "tuples": ">=7.0.0 <8.0.0"
55+ },
56+ {
57+ "typelevel-prelude": ">=7.0.0 <8.0.0"
58+ },
59+ {
60+ "unsafe-coerce": ">=6.0.0 <7.0.0"
61+ },
62+ {
63+ "variant": ">=8.0.0 <9.0.0"
64+ },
65+ {
66+ "yoga-http-api": ">=0.3.4 <0.4.0"
67+ },
68+ {
69+ "yoga-json": ">=5.2.1 <6.0.0"
70+ },
71+ {
72+ "yoga-om": ">=1.5.2 <2.0.0"
73+ },
74+ {
75+ "yoga-om-strom": ">=0.4.3 <0.5.0"
76+ }
77+ ]
78+ },
79+ "test": {
80+ "dependencies": [
81+ {
82+ "console": ">=6.0.0 <7.0.0"
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+ },
100+ {
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"
115+ }
116+ ]
117+ }
118+ }
119+ },
120+ "extra_packages": {}
121+ },
122+ "packages": {
123+ "aff": {
124+ "type": "registry",
125+ "version": "8.0.0",
126+ "integrity": "sha256-9BRIrSdRoYybfiUmBqgKJ66cPqxYHb277bVWQMn/9sg=",
127+ "dependencies": [
128+ "control",
129+ "datetime",
130+ "effect",
131+ "either",
132+ "exceptions",
133+ "functions",
134+ "newtype",
135+ "parallel",
136+ "partial",
137+ "prelude",
138+ "st",
139+ "tailrec",
140+ "transformers",
141+ "unsafe-coerce"
142+ ]
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+ },
215+ "arraybuffer-types": {
216+ "type": "registry",
217+ "version": "3.0.2",
218+ "integrity": "sha256-p05cJnSkyeoB7VHzMyc2Eb1RwUaB7Nl0sQSqEGNEUD8=",
219+ "dependencies": []
220+ },
221+ "arrays": {
222+ "type": "registry",
223+ "version": "7.3.0",
224+ "integrity": "sha256-GD4z7LCi9wzi7FCQqbWhvs8paoUK9NO+HwgXZ+KP8Rk=",
225+ "dependencies": [
226+ "bifunctors",
227+ "control",
228+ "foldable-traversable",
229+ "functions",
230+ "maybe",
231+ "nonempty",
232+ "partial",
233+ "prelude",
234+ "safe-coerce",
235+ "st",
236+ "tailrec",
237+ "tuples",
238+ "unfoldable",
239+ "unsafe-coerce"
240+ ]
241+ },
242+ "avar": {
243+ "type": "registry",
244+ "version": "5.0.1",
245+ "integrity": "sha256-8V4SxF4TIWZ9Ik1P4lPzIRUAZeFBe8w5WA2VcCEKsIk=",
246+ "dependencies": [
247+ "aff",
248+ "effect",
249+ "either",
250+ "exceptions",
251+ "functions",
252+ "maybe",
253+ "prelude"
254+ ]
255+ },
256+ "bifunctors": {
257+ "type": "registry",
258+ "version": "6.1.0",
259+ "integrity": "sha256-CvZRb8uI75eKIqiYntR+k05Wk6tKlMS5w8sCA2AFna0=",
260+ "dependencies": [
261+ "const",
262+ "either",
263+ "newtype",
264+ "prelude",
265+ "tuples"
266+ ]
267+ },
268+ "catenable-lists": {
269+ "type": "registry",
270+ "version": "7.0.0",
271+ "integrity": "sha256-7+i/MHBLIRh604iCNT4ENWRWE/wuHIotER4kpdX9Ve0=",
272+ "dependencies": [
273+ "control",
274+ "foldable-traversable",
275+ "lists",
276+ "maybe",
277+ "prelude",
278+ "tuples",
279+ "unfoldable"
280+ ]
281+ },
282+ "console": {
283+ "type": "registry",
284+ "version": "6.1.0",
285+ "integrity": "sha256-3T5PPz1mATZmV0C4GzkCkpLlCa5xG7u8ZNgnLrrrI1Q=",
286+ "dependencies": [
287+ "effect",
288+ "prelude"
289+ ]
290+ },
291+ "const": {
292+ "type": "registry",
293+ "version": "6.0.0",
294+ "integrity": "sha256-S+m0lQsxnji0z1XRUmkn+3RFuWJ5CXWuIr1u3i8Bfoc=",
295+ "dependencies": [
296+ "invariant",
297+ "newtype",
298+ "prelude"
299+ ]
300+ },
301+ "contravariant": {
302+ "type": "registry",
303+ "version": "6.0.0",
304+ "integrity": "sha256-nuckEWqL80NaPmR/l9AUyrfycNh18XcvlZV9PAamwqc=",
305+ "dependencies": [
306+ "const",
307+ "either",
308+ "newtype",
309+ "prelude",
310+ "tuples"
311+ ]
312+ },
313+ "control": {
314+ "type": "registry",
315+ "version": "6.0.0",
316+ "integrity": "sha256-0vxyYh5FL+ilxgLo954w1LqCUC0RSIivNNlb9CaNUQg=",
317+ "dependencies": [
318+ "newtype",
319+ "prelude"
320+ ]
321+ },
322+ "datetime": {
323+ "type": "registry",
324+ "version": "6.1.0",
325+ "integrity": "sha256-tbv6eDXy6QOD7YSknxYSbsDF32OC2JrNbSBpzMs7Doc=",
326+ "dependencies": [
327+ "bifunctors",
328+ "control",
329+ "either",
330+ "enums",
331+ "foldable-traversable",
332+ "functions",
333+ "gen",
334+ "integers",
335+ "lists",
336+ "maybe",
337+ "newtype",
338+ "numbers",
339+ "ordered-collections",
340+ "partial",
341+ "prelude",
342+ "tuples"
343+ ]
344+ },
345+ "distributive": {
346+ "type": "registry",
347+ "version": "6.0.0",
348+ "integrity": "sha256-netyJK4B32PDYHD0tF2AenKgo3urzbI770ycM0pArAo=",
349+ "dependencies": [
350+ "identity",
351+ "newtype",
352+ "prelude",
353+ "tuples",
354+ "type-equality"
355+ ]
356+ },
357+ "effect": {
358+ "type": "registry",
359+ "version": "4.0.0",
360+ "integrity": "sha256-DdqU3bncRTjgMVPPRlAuGHVeajSPEcRaRhNuio7if6s=",
361+ "dependencies": [
362+ "prelude"
363+ ]
364+ },
365+ "either": {
366+ "type": "registry",
367+ "version": "6.1.0",
368+ "integrity": "sha256-tBHx3PgtH4GZOApZCDkcM7szRTYJinrUrVGWazQCUUg=",
369+ "dependencies": [
370+ "control",
371+ "invariant",
372+ "maybe",
373+ "prelude"
374+ ]
375+ },
376+ "enums": {
377+ "type": "registry",
378+ "version": "6.0.1",
379+ "integrity": "sha256-sdZOmLX5+5pASGpvVyT0vSD5BBYQjZiayjV5XiPutso=",
380+ "dependencies": [
381+ "control",
382+ "either",
383+ "gen",
384+ "maybe",
385+ "newtype",
386+ "nonempty",
387+ "partial",
388+ "prelude",
389+ "tuples",
390+ "unfoldable"
391+ ]
392+ },
393+ "exceptions": {
394+ "type": "registry",
395+ "version": "6.1.0",
396+ "integrity": "sha256-nRrr+N0wk0TUFOWLR7e1n1oxcXo/X345bQUtoY9lW6A=",
397+ "dependencies": [
398+ "effect",
399+ "either",
400+ "maybe",
401+ "prelude"
402+ ]
403+ },
404+ "exists": {
405+ "type": "registry",
406+ "version": "6.0.0",
407+ "integrity": "sha256-vtLbrWNaI+pzx/2fw2AQnCovoLtcosClIhjO26QKkh8=",
408+ "dependencies": [
409+ "unsafe-coerce"
410+ ]
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+ },
422+ "foldable-traversable": {
423+ "type": "registry",
424+ "version": "6.0.0",
425+ "integrity": "sha256-NGGWyCio/xjce6fPP7y3wwg7CheqllID6aJudDxbWhA=",
426+ "dependencies": [
427+ "bifunctors",
428+ "const",
429+ "control",
430+ "either",
431+ "functors",
432+ "identity",
433+ "maybe",
434+ "newtype",
435+ "orders",
436+ "prelude",
437+ "tuples"
438+ ]
439+ },
440+ "foreign": {
441+ "type": "registry",
442+ "version": "7.0.0",
443+ "integrity": "sha256-jiDRMVQfovGsbbA/FzbzYf6iWk9i2b5gNrIjz+gFfsI=",
444+ "dependencies": [
445+ "either",
446+ "functions",
447+ "integers",
448+ "lists",
449+ "maybe",
450+ "prelude",
451+ "strings",
452+ "transformers",
453+ "unsafe-coerce"
454+ ]
455+ },
456+ "foreign-object": {
457+ "type": "registry",
458+ "version": "4.1.0",
459+ "integrity": "sha256-x/Q7r80z/vmHRKvPwhYmHP/DnJciPtsyGfRTMzayKIU=",
460+ "dependencies": [
461+ "arrays",
462+ "foldable-traversable",
463+ "functions",
464+ "gen",
465+ "lists",
466+ "maybe",
467+ "prelude",
468+ "st",
469+ "tailrec",
470+ "tuples",
471+ "typelevel-prelude",
472+ "unfoldable",
473+ "unsafe-coerce"
474+ ]
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+ },
486+ "free": {
487+ "type": "registry",
488+ "version": "7.1.0",
489+ "integrity": "sha256-tUBInfUpRQEw4u94GWcYW7EYdmSdDHSRn88a+C0eltU=",
490+ "dependencies": [
491+ "catenable-lists",
492+ "control",
493+ "distributive",
494+ "either",
495+ "exists",
496+ "foldable-traversable",
497+ "invariant",
498+ "lazy",
499+ "maybe",
500+ "prelude",
501+ "tailrec",
502+ "transformers",
503+ "tuples",
504+ "unsafe-coerce"
505+ ]
506+ },
507+ "functions": {
508+ "type": "registry",
509+ "version": "6.0.0",
510+ "integrity": "sha256-0jgtOr+SFO4UScKc40oA/9Vdcf2rLn3h3vZlY0KfqVo=",
511+ "dependencies": [
512+ "prelude"
513+ ]
514+ },
515+ "functors": {
516+ "type": "registry",
517+ "version": "5.0.0",
518+ "integrity": "sha256-W1o/4wcp6S3chodYL8uixlXK2mfr1L+SzM1R+whqUco=",
519+ "dependencies": [
520+ "bifunctors",
521+ "const",
522+ "contravariant",
523+ "control",
524+ "distributive",
525+ "either",
526+ "invariant",
527+ "maybe",
528+ "newtype",
529+ "prelude",
530+ "profunctor",
531+ "tuples",
532+ "unsafe-coerce"
533+ ]
534+ },
535+ "gen": {
536+ "type": "registry",
537+ "version": "4.0.0",
538+ "integrity": "sha256-kyOateeOtQa07vfTMYeSuCKdelgKE1R65fwKkZ10wsY=",
539+ "dependencies": [
540+ "either",
541+ "foldable-traversable",
542+ "identity",
543+ "maybe",
544+ "newtype",
545+ "nonempty",
546+ "prelude",
547+ "tailrec",
548+ "tuples",
549+ "unfoldable"
550+ ]
551+ },
552+ "heterogeneous": {
553+ "type": "registry",
554+ "version": "0.7.0",
555+ "integrity": "sha256-pQwcFvhWL8noktxaoD7FlLs2F2lYf3QHoyO0VOWO3I0=",
556+ "dependencies": [
557+ "either",
558+ "foldable-traversable",
559+ "functors",
560+ "prelude",
561+ "record",
562+ "tuples",
563+ "variant"
564+ ]
565+ },
566+ "http-methods": {
567+ "type": "registry",
568+ "version": "6.1.0",
569+ "integrity": "sha256-WbjEmCv6rysY/aKwlrcspyzShi5bIQy+AFpDzPMnSP4=",
570+ "dependencies": [
571+ "either",
572+ "prelude",
573+ "strings"
574+ ]
575+ },
576+ "identity": {
577+ "type": "registry",
578+ "version": "6.0.0",
579+ "integrity": "sha256-RY/iXPpxpqvKHYfJeFVNI1J06kohB9rbtWYuha8t0LE=",
580+ "dependencies": [
581+ "control",
582+ "invariant",
583+ "newtype",
584+ "prelude"
585+ ]
586+ },
587+ "integers": {
588+ "type": "registry",
589+ "version": "6.0.0",
590+ "integrity": "sha256-Y8yozC1uHRLcruobEyv5/bMKzE1BoKJ/olHv6o+bgm8=",
591+ "dependencies": [
592+ "maybe",
593+ "numbers",
594+ "prelude"
595+ ]
596+ },
597+ "invariant": {
598+ "type": "registry",
599+ "version": "6.0.0",
600+ "integrity": "sha256-HyoT1I5qIHRq8RSZji6zZDvqnpVjLw748utrKwZtqsw=",
601+ "dependencies": [
602+ "control",
603+ "prelude"
604+ ]
605+ },
606+ "js-bigints": {
607+ "type": "registry",
608+ "version": "2.2.1",
609+ "integrity": "sha256-r1rE6Olv5b3JrP32lY2uKJ+eBRTsrVnC983K8VWwqe4=",
610+ "dependencies": [
611+ "integers",
612+ "maybe",
613+ "prelude"
614+ ]
615+ },
616+ "js-date": {
617+ "type": "registry",
618+ "version": "8.0.0",
619+ "integrity": "sha256-DNQrp4xYc8o80jtxo0aOkUeXGi6h0Xidkk2ZQ8Y4l5Y=",
620+ "dependencies": [
621+ "datetime",
622+ "effect",
623+ "enums",
624+ "foreign",
625+ "functions",
626+ "integers",
627+ "maybe",
628+ "prelude"
629+ ]
630+ },
631+ "js-fetch": {
632+ "type": "registry",
633+ "version": "0.2.1",
634+ "integrity": "sha256-jsMrMh8kIUW5bHbHl7hy8ev/iMru1MzfDa+hoAxo14Q=",
635+ "dependencies": [
636+ "arraybuffer-types",
637+ "arrays",
638+ "effect",
639+ "foldable-traversable",
640+ "foreign",
641+ "foreign-object",
642+ "functions",
643+ "http-methods",
644+ "js-promise",
645+ "maybe",
646+ "newtype",
647+ "prelude",
648+ "record",
649+ "tuples",
650+ "typelevel-prelude",
651+ "unfoldable",
652+ "web-file",
653+ "web-streams"
654+ ]
655+ },
656+ "js-promise": {
657+ "type": "registry",
658+ "version": "1.0.0",
659+ "integrity": "sha256-u+6+ofIwT5XhT2fbMu4dShwkyWGfPRt9642Cnzw/Wvk=",
660+ "dependencies": [
661+ "effect",
662+ "exceptions",
663+ "foldable-traversable",
664+ "functions",
665+ "maybe",
666+ "newtype",
667+ "prelude"
668+ ]
669+ },
670+ "js-promise-aff": {
671+ "type": "registry",
672+ "version": "1.0.0",
673+ "integrity": "sha256-gEKAdNnTWjXS+tTp/AhYl/ezloKfuoQFcBKrdOx2USo=",
674+ "dependencies": [
675+ "aff",
676+ "control",
677+ "effect",
678+ "either",
679+ "exceptions",
680+ "foreign",
681+ "js-promise",
682+ "maybe",
683+ "prelude",
684+ "transformers"
685+ ]
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+ },
708+ "lazy": {
709+ "type": "registry",
710+ "version": "6.0.0",
711+ "integrity": "sha256-MHRC+1Pc+AjortBp4b/e1e6KpEpsDwaJBNBB7TOL+1I=",
712+ "dependencies": [
713+ "control",
714+ "foldable-traversable",
715+ "invariant",
716+ "prelude"
717+ ]
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+ },
733+ "lists": {
734+ "type": "registry",
735+ "version": "7.0.0",
736+ "integrity": "sha256-/mGd7wLoU+wsTcqii7SSUByxsvwvjnu2PjwAD47yYb4=",
737+ "dependencies": [
738+ "bifunctors",
739+ "control",
740+ "foldable-traversable",
741+ "lazy",
742+ "maybe",
743+ "newtype",
744+ "nonempty",
745+ "partial",
746+ "prelude",
747+ "tailrec",
748+ "tuples",
749+ "unfoldable"
750+ ]
751+ },
752+ "literals": {
753+ "type": "registry",
754+ "version": "1.0.2",
755+ "integrity": "sha256-ZkGRFD6gb8etYvyMYpX1Z7lx1Tql7R1VGnD1Dzinup8=",
756+ "dependencies": [
757+ "integers",
758+ "maybe",
759+ "numbers",
760+ "partial",
761+ "prelude",
762+ "typelevel-prelude",
763+ "unsafe-coerce"
764+ ]
765+ },
766+ "maybe": {
767+ "type": "registry",
768+ "version": "6.0.0",
769+ "integrity": "sha256-8X6XEgtZ5lqfkEuc1hWOofnL6mwhk8gSJTxwvbR/SbY=",
770+ "dependencies": [
771+ "control",
772+ "invariant",
773+ "newtype",
774+ "prelude"
775+ ]
776+ },
777+ "media-types": {
778+ "type": "registry",
779+ "version": "6.0.0",
780+ "integrity": "sha256-3Wyq+vfqJFTmy1k6DGVnXStxtZxgK631wusDPzlFVxg=",
781+ "dependencies": [
782+ "newtype",
783+ "prelude"
784+ ]
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+ },
803+ "newtype": {
804+ "type": "registry",
805+ "version": "5.0.0",
806+ "integrity": "sha256-dt6w0cty4OS+Pjt9fsh3iU6ottqSU83mdAZkaEEvo6c=",
807+ "dependencies": [
808+ "prelude",
809+ "safe-coerce"
810+ ]
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+ },
913+ "nonempty": {
914+ "type": "registry",
915+ "version": "7.0.0",
916+ "integrity": "sha256-Ctkq8/+KiGqCE53Y/LvibA/coy7ev9HtRZU+GYS7yfQ=",
917+ "dependencies": [
918+ "control",
919+ "foldable-traversable",
920+ "maybe",
921+ "prelude",
922+ "tuples",
923+ "unfoldable"
924+ ]
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+ },
936+ "nullable": {
937+ "type": "registry",
938+ "version": "6.0.0",
939+ "integrity": "sha256-CDAZk+L9Sc0GCFTkE8n+qdp4Ys8avvnkU+m2hVALt7M=",
940+ "dependencies": [
941+ "functions",
942+ "maybe",
943+ "prelude"
944+ ]
945+ },
946+ "numbers": {
947+ "type": "registry",
948+ "version": "9.0.1",
949+ "integrity": "sha256-ZydYckgwRAnRaFC7pu6fADhzdwX+UqHZrfJyet64zls=",
950+ "dependencies": [
951+ "functions",
952+ "maybe",
953+ "prelude"
954+ ]
955+ },
956+ "open-memoize": {
957+ "type": "registry",
958+ "version": "6.2.0",
959+ "integrity": "sha256-yJLuVYX8FocHIJ+6q7efH/Pj4S+e1Y57DZ5Dn7qf4ww=",
960+ "dependencies": [
961+ "either",
962+ "integers",
963+ "lazy",
964+ "lists",
965+ "maybe",
966+ "partial",
967+ "prelude",
968+ "strings",
969+ "tuples"
970+ ]
971+ },
972+ "optparse": {
973+ "type": "registry",
974+ "version": "5.0.1",
975+ "integrity": "sha256-2btDjHPRR0uujUQJuZAN9AzvVoXe7yG6IwnY5evERhQ=",
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": [
1032+ "newtype",
1033+ "prelude"
1034+ ]
1035+ },
1036+ "parallel": {
1037+ "type": "registry",
1038+ "version": "7.0.0",
1039+ "integrity": "sha256-bAGpVhrFz2IyHkA4MR7+tE/19FZ7dzYBdYPgK5svkqw=",
1040+ "dependencies": [
1041+ "control",
1042+ "effect",
1043+ "either",
1044+ "foldable-traversable",
1045+ "functors",
1046+ "maybe",
1047+ "newtype",
1048+ "prelude",
1049+ "profunctor",
1050+ "refs",
1051+ "transformers"
1052+ ]
1053+ },
1054+ "partial": {
1055+ "type": "registry",
1056+ "version": "4.0.0",
1057+ "integrity": "sha256-mdrlJBAgFMh79hNRW39uv7c+BwnnaOrAMmdh7+VhEhs=",
1058+ "dependencies": []
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+ },
1091+ "prelude": {
1092+ "type": "registry",
1093+ "version": "6.0.2",
1094+ "integrity": "sha256-IqykrDRquGUD/LRSad7y75hZv3bUsGQ4knc8tdez8Qw=",
1095+ "dependencies": []
1096+ },
1097+ "profunctor": {
1098+ "type": "registry",
1099+ "version": "6.0.1",
1100+ "integrity": "sha256-Ow0mJC+PIIRq/a3mzCWY5vxuTgR/e+Ee68+mWeSRnWY=",
1101+ "dependencies": [
1102+ "control",
1103+ "distributive",
1104+ "either",
1105+ "exists",
1106+ "invariant",
1107+ "newtype",
1108+ "prelude",
1109+ "tuples"
1110+ ]
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+ },
1155+ "record": {
1156+ "type": "registry",
1157+ "version": "4.0.0",
1158+ "integrity": "sha256-WQ8fqp4X1wTA70J4qUcPW4Z7DCwx6e5KJnT/EuTdWSc=",
1159+ "dependencies": [
1160+ "functions",
1161+ "prelude",
1162+ "unsafe-coerce"
1163+ ]
1164+ },
1165+ "record-studio": {
1166+ "type": "registry",
1167+ "version": "1.0.4",
1168+ "integrity": "sha256-CRnFvSXpDDzio+buibUxFU9zPZDd7/lzrYC1Ya3FuIw=",
1169+ "dependencies": [
1170+ "heterogeneous",
1171+ "lists",
1172+ "prelude",
1173+ "record",
1174+ "typelevel-prelude",
1175+ "unsafe-coerce"
1176+ ]
1177+ },
1178+ "refs": {
1179+ "type": "registry",
1180+ "version": "6.0.0",
1181+ "integrity": "sha256-KKD5G9dR2SauuDGULUJsxjqDOEi1Jqm+2Sq5U2mY8YU=",
1182+ "dependencies": [
1183+ "effect",
1184+ "prelude"
1185+ ]
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+ },
1208+ "safe-coerce": {
1209+ "type": "registry",
1210+ "version": "2.0.0",
1211+ "integrity": "sha256-EJrxtKt5xC7TYjBZrSBO/J7Aw3DmtilYjt4olpAXWVc=",
1212+ "dependencies": [
1213+ "unsafe-coerce"
1214+ ]
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+ },
1311+ "st": {
1312+ "type": "registry",
1313+ "version": "6.2.0",
1314+ "integrity": "sha256-WI4MEkkwUd4pnwZQ3G8VKWgX5t1RSNJludm5e9boaRo=",
1315+ "dependencies": [
1316+ "effect",
1317+ "partial",
1318+ "prelude",
1319+ "tailrec",
1320+ "unsafe-coerce"
1321+ ]
1322+ },
1323+ "strings": {
1324+ "type": "registry",
1325+ "version": "6.0.1",
1326+ "integrity": "sha256-FNto2hoNW5Da6W6crIk77YXMagBvOGHZE1kO7eZ1vLM=",
1327+ "dependencies": [
1328+ "arrays",
1329+ "control",
1330+ "either",
1331+ "enums",
1332+ "foldable-traversable",
1333+ "gen",
1334+ "integers",
1335+ "maybe",
1336+ "newtype",
1337+ "nonempty",
1338+ "partial",
1339+ "prelude",
1340+ "tailrec",
1341+ "tuples",
1342+ "unfoldable",
1343+ "unsafe-coerce"
1344+ ]
1345+ },
1346+ "tailrec": {
1347+ "type": "registry",
1348+ "version": "6.1.0",
1349+ "integrity": "sha256-xrorCfP0xV1wubs27idQCHQqdvSUuqmmL/am7Ji1wVU=",
1350+ "dependencies": [
1351+ "bifunctors",
1352+ "effect",
1353+ "either",
1354+ "identity",
1355+ "maybe",
1356+ "partial",
1357+ "prelude",
1358+ "refs"
1359+ ]
1360+ },
1361+ "transformers": {
1362+ "type": "registry",
1363+ "version": "6.1.0",
1364+ "integrity": "sha256-QJmgNT/y7ljPHSsXaW4zxF/982BFiQ90KNL1g/NtEOQ=",
1365+ "dependencies": [
1366+ "control",
1367+ "distributive",
1368+ "effect",
1369+ "either",
1370+ "exceptions",
1371+ "foldable-traversable",
1372+ "identity",
1373+ "lazy",
1374+ "maybe",
1375+ "newtype",
1376+ "prelude",
1377+ "st",
1378+ "tailrec",
1379+ "tuples",
1380+ "unfoldable"
1381+ ]
1382+ },
1383+ "tuples": {
1384+ "type": "registry",
1385+ "version": "7.0.0",
1386+ "integrity": "sha256-BKy7EfK8S1KeTCwyWqyW1wwcpSAQuh3IsczSVgS6fzY=",
1387+ "dependencies": [
1388+ "control",
1389+ "invariant",
1390+ "prelude"
1391+ ]
1392+ },
1393+ "type-equality": {
1394+ "type": "registry",
1395+ "version": "4.0.1",
1396+ "integrity": "sha256-BBqYOSnAKmayRvR5mtZoCf4SlYs2ipatD/+5nQx73aw=",
1397+ "dependencies": []
1398+ },
1399+ "typelevel-prelude": {
1400+ "type": "registry",
1401+ "version": "7.0.0",
1402+ "integrity": "sha256-+pHi14/40mTj/+lmCWqL0T7iXIXD1+NSg/KItyyoB0A=",
1403+ "dependencies": [
1404+ "prelude",
1405+ "type-equality"
1406+ ]
1407+ },
1408+ "uncurried-transformers": {
1409+ "type": "registry",
1410+ "version": "1.1.0",
1411+ "integrity": "sha256-AqqBMzVLzozVoDorE3QcVVRDkLd7O3624mRHCDnPhaI=",
1412+ "dependencies": [
1413+ "control",
1414+ "effect",
1415+ "either",
1416+ "functions",
1417+ "identity",
1418+ "prelude",
1419+ "safe-coerce",
1420+ "tailrec",
1421+ "transformers",
1422+ "tuples"
1423+ ]
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+ },
1441+ "unfoldable": {
1442+ "type": "registry",
1443+ "version": "6.0.0",
1444+ "integrity": "sha256-BK/6By1sKp/ztk+9CF3q43SV0QDTLVdsGqFerKHSmbg=",
1445+ "dependencies": [
1446+ "foldable-traversable",
1447+ "maybe",
1448+ "partial",
1449+ "prelude",
1450+ "tuples"
1451+ ]
1452+ },
1453+ "unsafe-coerce": {
1454+ "type": "registry",
1455+ "version": "6.0.0",
1456+ "integrity": "sha256-0L1QsaY20OILjfU5TV72d3U/tSjhmL9hJ32WMp857Lk=",
1457+ "dependencies": []
1458+ },
1459+ "untagged-union": {
1460+ "type": "registry",
1461+ "version": "1.0.0",
1462+ "integrity": "sha256-MQvpb13TZEBoi9n/AoQ7ugniSQqbN6XQmAjii9pqJRk=",
1463+ "dependencies": [
1464+ "either",
1465+ "foreign",
1466+ "foreign-object",
1467+ "literals",
1468+ "maybe",
1469+ "newtype",
1470+ "prelude",
1471+ "tuples",
1472+ "unsafe-coerce"
1473+ ]
1474+ },
1475+ "variant": {
1476+ "type": "registry",
1477+ "version": "8.0.0",
1478+ "integrity": "sha256-Ydrgl8liWEwYQOWW76PdZtg3vxKpoKgsnmFyTLgZkoU=",
1479+ "dependencies": [
1480+ "control",
1481+ "enums",
1482+ "foldable-traversable",
1483+ "lists",
1484+ "maybe",
1485+ "partial",
1486+ "prelude",
1487+ "type-equality",
1488+ "unsafe-coerce"
1489+ ]
1490+ },
1491+ "web-events": {
1492+ "type": "registry",
1493+ "version": "4.0.0",
1494+ "integrity": "sha256-wZNeldG0OU+8qGycUJ9e6vxf1/GC4Ep4OlRMhvjs9LA=",
1495+ "dependencies": [
1496+ "datetime",
1497+ "effect",
1498+ "enums",
1499+ "foreign",
1500+ "functions",
1501+ "maybe",
1502+ "newtype",
1503+ "nullable",
1504+ "prelude",
1505+ "unsafe-coerce"
1506+ ]
1507+ },
1508+ "web-file": {
1509+ "type": "registry",
1510+ "version": "4.0.0",
1511+ "integrity": "sha256-txsdkeWBKU/+5v9m5c6kysgJQSDyV1r7MHaeT4STpjQ=",
1512+ "dependencies": [
1513+ "datetime",
1514+ "effect",
1515+ "enums",
1516+ "foreign",
1517+ "integers",
1518+ "maybe",
1519+ "media-types",
1520+ "nullable",
1521+ "numbers",
1522+ "partial",
1523+ "prelude",
1524+ "tuples",
1525+ "unfoldable",
1526+ "unsafe-coerce",
1527+ "web-events"
1528+ ]
1529+ },
1530+ "web-streams": {
1531+ "type": "registry",
1532+ "version": "4.0.0",
1533+ "integrity": "sha256-iKQbHh/3wNOWyGTZmWJZ1Yuri9J/wQIJKXcxrELaHVo=",
1534+ "dependencies": [
1535+ "arraybuffer-types",
1536+ "effect",
1537+ "exceptions",
1538+ "js-promise",
1539+ "maybe",
1540+ "nullable",
1541+ "prelude",
1542+ "tuples"
1543+ ]
1544+ },
1545+ "yoga-fastify": {
1546+ "type": "registry",
1547+ "version": "0.5.4",
1548+ "integrity": "sha256-YenjePRdiApMPpcvkXlzre7c4fIuvk5T19Z2uEAWhR4=",
1549+ "dependencies": [
1550+ "aff",
1551+ "arrays",
1552+ "bifunctors",
1553+ "effect",
1554+ "either",
1555+ "foldable-traversable",
1556+ "foreign",
1557+ "foreign-object",
1558+ "js-promise",
1559+ "js-promise-aff",
1560+ "maybe",
1561+ "newtype",
1562+ "nullable",
1563+ "prelude",
1564+ "record",
1565+ "strings",
1566+ "unsafe-coerce",
1567+ "variant",
1568+ "yoga-http-api",
1569+ "yoga-json"
1570+ ]
1571+ },
1572+ "yoga-fastify-om": {
1573+ "type": "registry",
1574+ "version": "0.4.7",
1575+ "integrity": "sha256-KGQ7lHI7pcSiEQoEq6j6K+3XilcPy8wQ/M9C1y+uaEk=",
1576+ "dependencies": [
1577+ "aff",
1578+ "console",
1579+ "effect",
1580+ "either",
1581+ "exceptions",
1582+ "foreign",
1583+ "foreign-object",
1584+ "integers",
1585+ "maybe",
1586+ "node-buffer",
1587+ "numbers",
1588+ "prelude",
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"
1603+ ]
1604+ },
1605+ "yoga-http-api": {
1606+ "type": "registry",
1607+ "version": "0.3.4",
1608+ "integrity": "sha256-DSIJ8YWr5vB5IMpyaBALM+fx57lz3AAAR0MGtfHeWxo=",
1609+ "dependencies": [
1610+ "aff",
1611+ "arraybuffer-types",
1612+ "arrays",
1613+ "effect",
1614+ "either",
1615+ "foldable-traversable",
1616+ "foreign",
1617+ "foreign-object",
1618+ "identity",
1619+ "integers",
1620+ "js-promise",
1621+ "js-promise-aff",
1622+ "maybe",
1623+ "newtype",
1624+ "nullable",
1625+ "numbers",
1626+ "prelude",
1627+ "record",
1628+ "strings",
1629+ "tuples",
1630+ "undefined-is-not-a-problem",
1631+ "unsafe-coerce",
1632+ "variant",
1633+ "yoga-json",
1634+ "yoga-options"
1635+ ]
1636+ },
1637+ "yoga-json": {
1638+ "type": "registry",
1639+ "version": "5.2.1",
1640+ "integrity": "sha256-12I7qIf27oKV/D/YRcHtGpN8n7GQ0Bm3uzvI4ZjOQrs=",
1641+ "dependencies": [
1642+ "arrays",
1643+ "bifunctors",
1644+ "control",
1645+ "datetime",
1646+ "effect",
1647+ "either",
1648+ "exceptions",
1649+ "foldable-traversable",
1650+ "foreign",
1651+ "foreign-object",
1652+ "free",
1653+ "identity",
1654+ "integers",
1655+ "js-bigints",
1656+ "js-date",
1657+ "lists",
1658+ "maybe",
1659+ "newtype",
1660+ "nullable",
1661+ "numbers",
1662+ "ordered-collections",
1663+ "partial",
1664+ "prelude",
1665+ "record",
1666+ "safe-coerce",
1667+ "strings",
1668+ "transformers",
1669+ "tuples",
1670+ "typelevel-prelude",
1671+ "unsafe-coerce",
1672+ "variant",
1673+ "yoga-tree"
1674+ ]
1675+ },
1676+ "yoga-om": {
1677+ "type": "registry",
1678+ "version": "1.5.2",
1679+ "integrity": "sha256-2ys14BF6NPgzFDFUIFk0EDxmqkA8+yghIA9TXHlOmE0=",
1680+ "dependencies": [
1681+ "aff",
1682+ "aff-retry",
1683+ "avar",
1684+ "console",
1685+ "control",
1686+ "datetime",
1687+ "effect",
1688+ "either",
1689+ "exceptions",
1690+ "functions",
1691+ "heterogeneous",
1692+ "maybe",
1693+ "newtype",
1694+ "parallel",
1695+ "prelude",
1696+ "record",
1697+ "record-studio",
1698+ "refs",
1699+ "tailrec",
1700+ "transformers",
1701+ "tuples",
1702+ "typelevel-prelude",
1703+ "uncurried-transformers",
1704+ "unsafe-coerce",
1705+ "variant"
1706+ ]
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+ },
1777+ "yoga-tree": {
1778+ "type": "registry",
1779+ "version": "1.0.0",
1780+ "integrity": "sha256-s8rIVEQkRi1VrMc92T9ceXcY0X6r5A4QqT6f7GgmDQI=",
1781+ "dependencies": [
1782+ "arrays",
1783+ "control",
1784+ "foldable-traversable",
1785+ "free",
1786+ "maybe",
1787+ "prelude",
1788+ "tailrec"
1789+ ]
1790+ }
1791+ }
1792+}
spago.yaml+25−8
@@ -1,9 +1,8 @@
11workspace: {}
2−
32package:
43 name: yoga-fetch-om
54 publish:
6− version: 0.1.0
5+ version: 0.8.1
76 license: MIT
87 location:
98 githubOwner: rowtype-yoga
@@ -11,31 +10,49 @@ package:
1110 include:
1211 - LICENSE
1312 - README.md
14− - CHANGELOG.md
1513 exclude:
1614 - .github
1715 dependencies:
1816 - aff: ">=8.0.0 <9.0.0"
17+ - arraybuffer-types: ">=3.0.2 <4.0.0"
1918 - arrays: ">=7.3.0 <8.0.0"
2019 - effect: ">=4.0.0 <5.0.0"
20+ - either: ">=6.1.0 <7.0.0"
2121 - 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"
2323 - js-fetch: ">=0.2.1 <0.3.0"
2424 - js-promise-aff: ">=1.0.0 <2.0.0"
2525 - maybe: ">=6.0.0 <7.0.0"
2626 - prelude: ">=6.0.2 <7.0.0"
2727 - record: ">=4.0.0 <5.0.0"
2828 - strings: ">=6.0.1 <7.0.0"
29+ - tailrec: ">=6.1.0 <7.0.0"
2930 - transformers: ">=6.1.0 <7.0.0"
3031 - tuples: ">=7.0.0 <8.0.0"
3132 - typelevel-prelude: ">=7.0.0 <8.0.0"
3233 - unsafe-coerce: ">=6.0.0 <7.0.0"
3334 - variant: ">=8.0.0 <9.0.0"
34− - yoga-http-api: ">=0.1.0 <0.2.0"
35− - yoga-json: ">=5.2.0 <6.0.0"
36− - yoga-om: ">=0.1.0 <0.2.0"
35+ - yoga-http-api: ">=0.3.4 <0.4.0"
36+ - yoga-json: ">=5.2.1 <6.0.0"
37+ - yoga-om: ">=1.5.2 <2.0.0"
38+ - yoga-om-strom: ">=0.4.3 <0.5.0"
3739 test:
3840 main: Test.Main
3941 dependencies:
40− - 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
4150 - 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+231−37
@@ -1,18 +1,34 @@
11module Yoga.Fetch.Om
2− ( class DeriveClient
2+ ( client
3+ , clientWith
4+ , ClientInputs
5+ , ClientWithConfig
36 , deriveClient
4− , class DeriveClientFn
5− , deriveClientFn
7+ , class DeriveClient
8+ , deriveClientImpl
69 , class DeriveClientRL
710 , deriveClientRL
11+ , class DeriveClientFn
12+ , deriveClientFn
13+ , class DeriveClientFnWith
14+ , deriveClientFnWith
15+ , class DeriveClientWith
16+ , deriveClientWithImpl
17+ , class DeriveClientWithRL
18+ , deriveClientWithRL
19+ , class RecordRow
20+ , class ToHeaders
21+ , toHeaders
822 , module Yoga.HTTP.API.Route
923 , module Yoga.HTTP.API.Path
24+ , module Yoga.Fetch.Om.StreamDecode
1025 , module Yoga.Fetch.Om.Simple
1126 ) where
1227
1328import Prelude
1429
15−import Data.Symbol (class IsSymbol)
30+import Data.Symbol (class IsSymbol, reflectSymbol)
31+import Data.Tuple.Nested ((/\))
1632import Data.Variant (Variant)
1733import Prim.Row as Row
1834import Prim.RowList (class RowToList, RowList)
@@ -20,87 +36,265 @@ import Prim.RowList as RL
2036import Record as Record
2137import Type.Proxy (Proxy(..))
2238import Unsafe.Coerce (unsafeCoerce)
39+import JS.Fetch.Headers (Headers)
40+import JS.Fetch.Headers as Headers
2341import Yoga.Fetch.Om.BuildUrl (class BuildUrl, buildUrl)
2442import Yoga.Fetch.Om.Simple (class DecodeResponse, decodeResponse, FetchError, FetchResponse, get, getWithHeaders, delete, deleteWithHeaders, delete_, post, postWithHeaders, post_, put, putWithHeaders, put_, patch, patchWithHeaders, patch_)
25−import Yoga.Fetch.Om.ClientFn (class BuildClientFn, class CheckBodyIsUnit, class CheckRowEmpty, buildClientFn)
26−import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractRequestHeaders)
27−import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class SerializeBody, makeRequest, serializeBody)
43+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
44+import Yoga.Fetch.Om.ClientFunction (class BuildClientFn, class CheckBodyIsUnit, buildClientFn)
45+import Yoga.Fetch.Om.ExtractParams (class ExtractRequestBody, class ExtractBodyEncoding, class ExtractRequestHeaders)
46+import Yoga.Fetch.Om.MakeRequest (class MakeRequest, class BodyEncoding, makeRequest, encodingContentType, encodeBody)
2847import Yoga.Fetch.Om.ParseResponse (class ParseResponse, parseResponse)
2948import Yoga.Fetch.Om.SplitResponses (class SplitResponses)
3049import Yoga.Fetch.Om.Variant (class VariantOrValue, variantOrValue)
3150import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParams, Required, type (/), type (:), type (:?), class PathPattern)
32−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)
3352import Yoga.HTTP.API.Route.Handler (class SegmentPathParams, class SegmentQueryParams)
34−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
66+
67+-- | Extract row type from Record type
68+class RecordRow :: Type -> Row Type -> Constraint
69+class RecordRow t r | t -> r
70+
71+instance RecordRow (Record r) r
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)
3593
36−class DeriveClientFn :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
94+class DeriveClientFn
95+ :: forall k1. Type -> k1 -> Type -> Row Type -> Row Type -> Row Type -> Row Type -> Type -> Type -> Constraint
3796class
38− DeriveClientFn method segments request response errorRow successRow result fn
39− | method segments request response -> errorRow successRow result fn where
97+ DeriveClientFn method segments request response ctx extraErr routeErrors result fn
98+ | method segments request response -> routeErrors result
99+ , method segments request response ctx extraErr -> fn where
40100 deriveClientFn :: String -> Proxy (Route method segments request response) -> fn
41101
42102instance
43103 ( SegmentPathParams segments pathParams
44104 , SegmentQueryParams segments queryParams
45105 , ExtractRequestBody request body
106+ , ExtractBodyEncoding request encoding
46107 , ExtractRequestHeaders request headers
47108 , PathPattern segments
48109 , BuildUrl segments pathParams queryParams
49110 , MakeRequest method
50− , SerializeBody body
51− , SplitResponses response successRow errorRow
52− , ParseResponse errorRow successRow
111+ , BodyEncoding encoding body
112+ , SplitResponses response successRow routeErrors
113+ , ParseResponse routeErrors successRow
53114 , RowToList successRow successRL
54115 , VariantOrValue successRL successRow result
55116 , Row.Union pathParams queryParams pathQuery
56117 , Row.Nub pathQuery pathQuery
57118 , RowToList pathQuery pathQueryRL
58− , CheckRowEmpty headers headersFlag
119+ , RowToList headers headersRL
120+ , ToHeaders headersRL headers
59121 , CheckBodyIsUnit body bodyFlag
60− , BuildClientFn pathQueryRL headersFlag bodyFlag body pathQuery headers errorRow result fn
122+ , Row.Union routeErrors extraErr errRow
123+ , BuildClientFn pathQueryRL headersRL bodyFlag body pathQuery headers (Record pathQuery) { | ctx } errRow result fn
61124 ) =>
62− DeriveClientFn method segments request response errorRow successRow result fn where
125+ DeriveClientFn method segments request response ctx extraErr routeErrors result fn where
63126 deriveClientFn baseUrl _ =
64− buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersFlag) (Proxy :: _ bodyFlag) impl
127+ buildClientFn (Proxy :: _ pathQueryRL) (Proxy :: _ headersRL) (Proxy :: _ bodyFlag) identity impl
65128 where
66− impl :: Record pathQuery -> Record headers -> body -> Om {} errorRow result
67− impl pathQueryRec _headers bodyVal = do
129+ impl :: Record pathQuery -> Record headers -> body -> Om { | ctx } errRow result
130+ impl pathQueryRec headersRec bodyVal = widenOm do
68131 let url = buildUrl baseUrl (Proxy :: _ segments) pathParamsRec queryParamsRec
69− fetchResp <- makeRequest (Proxy :: _ method) url (serializeBody bodyVal) # fromAff
70− variant <- parseResponse fetchResp :: Om {} errorRow (Variant successRow)
132+ let hdrs = toHeaders (Proxy :: _ headersRL) headersRec
133+ let ct = encodingContentType (Proxy :: _ encoding)
134+ fetchResp <- makeRequest (Proxy :: _ method) url hdrs ct (encodeBody @encoding bodyVal) # toOm
135+ variant <- parseResponse fetchResp :: Om (Record ()) routeErrors (Variant successRow)
71136 variantOrValue (Proxy :: _ successRL) variant # pure
72137 where
73138 pathParamsRec = unsafeCoerce pathQueryRec :: Record pathParams
74139 queryParamsRec = unsafeCoerce pathQueryRec :: Record queryParams
75140
76−class DeriveClient :: Row Type -> Row Type -> Constraint
77−class DeriveClient routesRow clientsRow | routesRow -> clientsRow where
78− deriveClient :: String -> Proxy (Record routesRow) -> Record clientsRow
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+
208+class DeriveClient :: Row Type -> Row Type -> Row Type -> Row Type -> Constraint
209+class DeriveClient ctx extraErr routesRow clientsRow | routesRow ctx extraErr -> clientsRow where
210+ deriveClientImpl :: String -> Proxy (Record routesRow) -> Record clientsRow
211+
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.
215+-- |
216+-- | ```purescript
217+-- | type UserAPI = { getUser :: Route ... }
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
222+-- | ```
223+client :: forall @routes routesRow ctx extraErr clientsRow. RecordRow routes routesRow => DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
224+client baseUrl = deriveClientImpl @ctx @extraErr baseUrl (Proxy :: _ { | routesRow })
225+
226+-- | Deprecated: Use `client` with VTA instead
227+-- |
228+-- | ```purescript
229+-- | api = deriveClient @UserAPI "https://api.example.com"
230+-- | ```
231+deriveClient :: forall @routesRow ctx extraErr clientsRow. DeriveClient ctx extraErr routesRow clientsRow => String -> Record clientsRow
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 })
79241
80242instance
81243 ( RowToList routesRow rl
82− , DeriveClientRL rl () clientsRow
244+ , DeriveClientRL ctx extraErr rl () clientsRow
83245 ) =>
84− DeriveClient routesRow clientsRow where
85− deriveClient baseUrl _ = deriveClientRL baseUrl (Proxy :: _ rl) {}
246+ DeriveClient ctx extraErr routesRow clientsRow where
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
86280
87−class DeriveClientRL :: RowList Type -> Row Type -> Row Type -> Constraint
88−class DeriveClientRL rl acc out | rl acc -> out where
281+class DeriveClientRL :: Row Type -> Row Type -> RowList Type -> Row Type -> Row Type -> Constraint
282+class DeriveClientRL ctx extraErr rl acc out | ctx extraErr rl acc -> out where
89283 deriveClientRL :: String -> Proxy rl -> Record acc -> Record out
90284
91−instance DeriveClientRL RL.Nil acc acc where
285+instance DeriveClientRL ctx extraErr RL.Nil acc acc where
92286 deriveClientRL _ _ acc = acc
93287
94288instance
95289 ( IsSymbol label
96− , DeriveClientFn method segments request response errorRow successRow result fn
97− , DeriveClientRL tail acc1 acc2
290+ , DeriveClientFn method segments request response ctx extraErr routeErrors result fn
291+ , DeriveClientRL ctx extraErr tail acc1 acc2
98292 , Row.Cons label fn acc2 out
99293 , Row.Lacks label acc2
100294 , Row.Cons label (Route method segments request response) routeTail routeRow
101295 ) =>
102− 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
103297 deriveClientRL baseUrl _ acc = Record.insert (Proxy :: _ label) clientFn rest
104298 where
105− clientFn = deriveClientFn baseUrl (Proxy :: _ (Route method segments request response))
106− rest = deriveClientRL baseUrl (Proxy :: _ tail) acc
299+ clientFn = deriveClientFn @_ @_ @_ @_ @ctx @extraErr baseUrl (Proxy :: _ (Route method segments request response))
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+60−17
@@ -3,8 +3,10 @@ module Yoga.Fetch.Om.BuildUrl
33 , buildUrl
44 , class SubstitutePathParams
55 , substitutePathParams
6+ , substitutePathParamsImpl
67 , class AppendQueryParams
78 , appendQueryParams
9+ , appendQueryParamsImpl
810 , class AppendQueryParamsRL
911 , appendQueryParamsRL
1012 , class SubstitutePathParamsRL
@@ -17,7 +19,6 @@ import Prelude
1719
1820import Data.Array as Array
1921import Data.Maybe (Maybe(..))
20−import Data.String (Pattern(..), Replacement(..))
2122import Data.String as String
2223import Data.Symbol (class IsSymbol, reflectSymbol)
2324import Prim.Row as Row
@@ -26,6 +27,13 @@ import Prim.RowList as RL
2627import Record as Record
2728import Type.Proxy (Proxy(..))
2829import 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
2937
3038class BuildUrl :: forall k. k -> Row Type -> Row Type -> Constraint
3139class BuildUrl segments pathParams queryParams | segments -> pathParams queryParams where
@@ -42,20 +50,23 @@ instance
4250 pattern = pathPattern segmentsProxy
4351 withBase = case baseUrl of
4452 "" -> pattern
45− base -> base <> pattern
46− withPathParams = substitutePathParams (Proxy :: _ pathParams) pathParamsRec withBase
47− withQueryParams = appendQueryParams (Proxy :: _ queryParams) queryParamsRec withPathParams
53+ base -> appendPath_ base pattern
54+ withPathParams = substitutePathParamsImpl (Proxy :: _ pathParams) pathParamsRec withBase
55+ withQueryParams = appendQueryParamsImpl (Proxy :: _ queryParams) queryParamsRec withPathParams
4856
4957class SubstitutePathParams :: Row Type -> Constraint
5058class SubstitutePathParams params where
51− substitutePathParams :: Proxy params -> Record params -> String -> String
59+ substitutePathParamsImpl :: Proxy params -> Record params -> String -> String
60+
61+substitutePathParams :: forall @params. SubstitutePathParams params => Record params -> String -> String
62+substitutePathParams = substitutePathParamsImpl (Proxy :: _ params)
5263
5364instance
5465 ( RowToList params rl
5566 , SubstitutePathParamsRL rl params
5667 ) =>
5768 SubstitutePathParams params where
58− substitutePathParams _ = substitutePathParamsRL (Proxy :: _ rl)
69+ substitutePathParamsImpl _ = substitutePathParamsRL (Proxy :: _ rl)
5970
6071class SubstitutePathParamsRL :: RowList Type -> Row Type -> Constraint
6172class SubstitutePathParamsRL rl r where
@@ -74,24 +85,26 @@ instance
7485 substitutePathParamsRL _ rec url = substitutePathParamsRL (Proxy :: _ tail) rec replaced
7586 where
7687 value = Record.get (Proxy :: _ name) rec
77− pattern = Pattern (":" <> reflectSymbol (Proxy :: _ name))
78− replaced = String.replaceAll pattern (Replacement (serializeParam value)) url
88+ name = reflectSymbol (Proxy :: _ name)
89+ replaced = substitutePathParam_ name (serializeParam value) url
7990
8091class AppendQueryParams :: Row Type -> Constraint
8192class AppendQueryParams params where
82− appendQueryParams :: Proxy params -> Record params -> String -> String
93+ appendQueryParamsImpl :: Proxy params -> Record params -> String -> String
94+
95+appendQueryParams :: forall @params. AppendQueryParams params => Record params -> String -> String
96+appendQueryParams = appendQueryParamsImpl (Proxy :: _ params)
8397
8498instance
8599 ( RowToList params rl
86100 , AppendQueryParamsRL rl params
87101 ) =>
88102 AppendQueryParams params where
89− appendQueryParams _ rec url = url <> queryString
103+ appendQueryParamsImpl _ rec url = case pairs of
104+ [] -> url
105+ _ -> appendQueryString_ url (String.joinWith "&" pairs)
90106 where
91107 pairs = appendQueryParamsRL (Proxy :: _ rl) rec []
92− queryString = case pairs of
93− [] -> ""
94− _ -> "?" <> String.joinWith "&" pairs
95108
96109class AppendQueryParamsRL :: RowList Type -> Row Type -> Constraint
97110class AppendQueryParamsRL rl r where
@@ -100,19 +113,49 @@ class AppendQueryParamsRL rl r where
100113instance AppendQueryParamsRL RL.Nil r where
101114 appendQueryParamsRL _ _ acc = acc
102115
116+-- Maybe (Array ty): optional repeated query param — missing/empty field → no pairs
103117instance
104118 ( IsSymbol name
105119 , SerializeParam ty
106120 , AppendQueryParamsRL tail r
107− , 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
108135 ) =>
109136 AppendQueryParamsRL (RL.Cons name (Maybe ty) tail) r where
110137 appendQueryParamsRL _ rec acc = appendQueryParamsRL (Proxy :: _ tail) rec acc'
111138 where
112− acc' = case Record.get (Proxy :: _ name) rec of
139+ acc' = case (readOptional @name rec :: Maybe ty) of
113140 Nothing -> acc
114− 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
115157
158+-- bare ty (Required query param): always present
116159else instance
117160 ( IsSymbol name
118161 , SerializeParam ty
@@ -123,7 +166,7 @@ else instance
123166 appendQueryParamsRL _ rec acc =
124167 appendQueryParamsRL (Proxy :: _ tail) rec (Array.snoc acc pair)
125168 where
126− pair = reflectSymbol (Proxy :: _ name) <> "=" <> serializeParam (Record.get (Proxy :: _ name) rec)
169+ pair = encodeURIComponent_ (reflectSymbol (Proxy :: _ name)) <> "=" <> encodeURIComponent_ (serializeParam (Record.get (Proxy :: _ name) rec))
127170
128171class SerializeParam :: Type -> Constraint
129172class 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 @@
11module Yoga.Fetch.Om.ExtractParams
22 ( class ExtractRequestHeaders
33 , class ExtractRequestBody
4+ , class ExtractBodyEncoding
45 , class FindBodyType
56 , class UnwrapEncoding
67 , class FindHeaders
@@ -9,7 +10,8 @@ module Yoga.Fetch.Om.ExtractParams
910import Data.Unit (Unit)
1011import Prim.RowList (class RowToList, RowList)
1112import 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)
1315
1416class ExtractRequestBody :: Type -> Type -> Constraint
1517class ExtractRequestBody request body | request -> body
@@ -20,6 +22,21 @@ instance
2022 , UnwrapEncoding encoding body
2123 ) =>
2224 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
2340
2441class ExtractRequestHeaders :: Type -> Row Type -> Constraint
2542class ExtractRequestHeaders request headers | request -> headers
@@ -29,6 +46,9 @@ instance
2946 , FindHeaders rl headers
3047 ) =>
3148 ExtractRequestHeaders (Record row) headers
49+else instance
50+ ExtractRequestHeaders (Record row) headers =>
51+ ExtractRequestHeaders (Request (Record row)) headers
3252
3353class FindBodyType :: RowList Type -> Type -> Constraint
3454class FindBodyType rl encoding | rl -> encoding
@@ -43,6 +63,7 @@ class UnwrapEncoding encoding body | encoding -> body
4363instance UnwrapEncoding (JSON ty) ty
4464instance UnwrapEncoding (FormData ty) ty
4565instance UnwrapEncoding NoBody Unit
66+instance UnwrapEncoding PlainText String
4667
4768class FindHeaders :: RowList Type -> Row Type -> Constraint
4869class 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
22 ( class MakeRequest
33 , httpMethod
44 , makeRequest
5− , class SerializeBody
6− , serializeBody
5+ , class BodyEncoding
6+ , encodingContentType
7+ , encodeBody
78 ) where
89
910import Prelude
1011
1112import Data.HTTP.Method (Method(..))
13+import Data.Array as Array
1214import Data.Maybe (Maybe(..))
15+import Data.Tuple (fst)
1316import Data.Tuple.Nested ((/\))
1417import Effect.Aff (Aff)
1518import Effect.Class (liftEffect)
1619import JS.Fetch as Fetch
20+import JS.Fetch.Headers (Headers)
1721import JS.Fetch.Headers as Headers
1822import JS.Fetch.Request as Request
1923import JS.Fetch.RequestBody as Body
@@ -27,6 +31,7 @@ import JS.Fetch.Duplex as Duplex
2731import JS.Fetch.Response (Response) as FetchResponse
2832import Promise.Aff as Promise
2933import Type.Proxy (Proxy)
34+import Yoga.HTTP.API.Route.Encoding (JSON, FormData, PlainText, NoBody)
3035import Yoga.HTTP.API.Route.Method as Method
3136import Yoga.JSON (class WriteForeign, writeJSON)
3237
@@ -49,42 +54,65 @@ instance MakeRequest Method.DELETE where
4954instance MakeRequest Method.PATCH where
5055 httpMethod _ = PATCH
5156
57+instance MakeRequest Method.QUERY where
58+ httpMethod _ = QUERY
59+
5260makeRequest
5361 :: forall method
5462 . MakeRequest method
5563 => Proxy method
5664 -> String
65+ -> Headers
66+ -> String
5767 -> Maybe String
5868 -> Aff FetchResponse.Response
59−makeRequest proxy url maybeBody = do
69+makeRequest proxy url customHeaders bodyContentType maybeBody = do
6070 request <- Request.new url options # liftEffect
6171 Promise.toAffE $ Fetch.fetch request
6272 where
6373 method = httpMethod proxy
64− headers = case maybeBody of
65− Nothing -> Headers.empty
66− Just _ -> Headers.fromFoldable [ "Content-Type" /\ "application/json" ]
74+ customArr = Headers.toArray customHeaders
75+ hasContentType = Array.any (\t -> fst t == "content-type") customArr
76+ contentTypeArr = case maybeBody of
77+ Nothing -> []
78+ Just _ | hasContentType -> []
79+ Just _ -> [ "Content-Type" /\ bodyContentType ]
80+ allHeaders = Headers.fromFoldable (contentTypeArr <> customArr)
6781 body = case maybeBody of
6882 Nothing -> Body.empty
6983 Just b -> Body.fromString b
7084 options =
7185 { method
72− , headers
86+ , headers: allHeaders
7387 , body
74− , credentials: Credentials.Include
88+ , credentials: Credentials.SameOrigin
7589 , mode: Mode.Cors
76− , referrer: Referrer.ReferrerClient
90+ , referrer: Referrer.ReferrerUrl ""
7791 , referrerPolicy: ReferrerPolicy.NoReferrer
7892 , integrity: Integrity ""
7993 , duplex: Duplex.Half
8094 , cache: Cache.Default
8195 }
8296
83−class SerializeBody :: Type -> Constraint
84−class SerializeBody body where
85− serializeBody :: body -> Maybe String
97+class BodyEncoding :: Type -> Type -> Constraint
98+class BodyEncoding encoding body | encoding -> body where
99+ encodingContentType :: Proxy encoding -> 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
86117
87−instance SerializeBody Unit where
88− serializeBody _ = Nothing
89−else instance WriteForeign body => SerializeBody body where
90− serializeBody b = Just (writeJSON b)
118+foreign import toUrlEncoded :: forall a. a -> String
src/Yoga/Fetch/Om/ParseResponse.purs+83−20
@@ -5,17 +5,20 @@ module Yoga.Fetch.Om.ParseResponse
55 , parseSuccessRL
66 , class ParseErrorRL
77 , parseErrorRL
8+ , class ParseResponseBody
9+ , parseResponseBody
810 ) where
911
1012import Prelude
1113
1214import Control.Monad.Error.Class (throwError)
13−import Data.Maybe (Maybe(..))
15+import Data.Either (Either(..))
1416import Data.Symbol (class IsSymbol)
1517import Data.Variant (Variant)
1618import Data.Variant as Variant
1719import Effect.Aff (Aff)
1820import Effect.Aff as Aff
21+import Effect.Class (liftEffect)
1922import Effect.Exception as Exception
2023import JS.Fetch.Response (Response) as FetchResponse
2124import JS.Fetch.Response as Fetch
@@ -24,10 +27,24 @@ import Prim.RowList (class RowToList, RowList)
2427import Prim.RowList as RL
2528import Promise.Aff as Promise
2629import Type.Proxy (Proxy(..))
30+import Unsafe.Coerce (unsafeCoerce)
31+import Yoga.Fetch.Om.StreamDecode (class StreamDecode, decodeStream)
2732import Yoga.HTTP.API.Route.StatusCode (class StatusCodeMap, statusCodeFor, StatusCode(..))
28−import Yoga.JSON (class ReadForeign, readJSON_)
29−import Yoga.Om (Om, fromAff)
33+import Yoga.JSON (class ReadForeign, readJSON)
34+import Yoga.JSON.Error (withStringErrors)
35+import Yoga.Om (Om, toOm)
3036import 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
3148
3249class ParseResponse (errorRow :: Row Type) (successRow :: Row Type) where
3350 parseResponse :: FetchResponse.Response -> Om {} errorRow (Variant successRow)
@@ -41,16 +58,14 @@ instance
4158 ParseResponse errorRow successRow where
4259 parseResponse fetchResp = do
4360 let status = Fetch.status fetchResp
44− jsonText <- Promise.toAffE (Fetch.text fetchResp) # fromAff
4561 if status >= 200 && status < 300 then
46− parseSuccessRL (Proxy :: _ successRL) status jsonText # fromAff
47− else if status >= 400 then
48− parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status jsonText
49− else
50− throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Unexpected HTTP status code: " <> show status)))
62+ parseSuccessRL (Proxy :: _ successRL) status fetchResp # toOm
63+ else do
64+ errorText <- Promise.toAffE (Fetch.text fetchResp) # toOm
65+ parseErrorRL (Proxy :: _ errorRL) (Proxy :: _ errorRow) status errorText
5166
5267class 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)
5469
5570instance ParseSuccessRL RL.Nil successRow where
5671 parseSuccessRL _ status _ =
@@ -59,19 +74,53 @@ instance ParseSuccessRL RL.Nil successRow where
5974else instance
6075 ( IsSymbol label
6176 , 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
63110 , Row.Cons label body tailRow successRow
64111 , Row.Lacks label tailRow
65112 , ParseSuccessRL tailRL successRow
66113 ) =>
67114 ParseSuccessRL (RL.Cons label body tailRL) successRow where
68− parseSuccessRL _ actualStatus jsonText = do
115+ parseSuccessRL _ actualStatus fetchResp = do
69116 let StatusCode expected = statusCodeFor (Proxy :: _ label)
70− if actualStatus == expected then case readJSON_ jsonText of
71− Nothing -> Aff.throwError (Exception.error "JSON parse error")
72− Just parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
117+ if actualStatus == expected then do
118+ jsonText <- liftEffect (Fetch.text fetchResp) >>= Promise.toAff
119+ case parseResponseBody jsonText of
120+ Left errs -> Aff.throwError (Exception.error ("Failed to parse response (status " <> show actualStatus <> "):\n" <> errs))
121+ Right parsed -> pure (Variant.inj (Proxy :: _ label) parsed)
73122 else
74− parseSuccessRL (Proxy :: _ tailRL) actualStatus jsonText
123+ parseSuccessRL (Proxy :: _ tailRL) actualStatus fetchResp
75124
76125class ParseErrorRL (rl :: RowList Type) (errorRow :: Row Type) | rl -> errorRow where
77126 parseErrorRL
@@ -89,7 +138,21 @@ instance ParseErrorRL RL.Nil errorRow where
89138else instance
90139 ( IsSymbol label
91140 , 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
93156 , Row.Cons label body tailRow errorRow
94157 , Row.Lacks label tailRow
95158 , Row.Cons label body tailExc (Exception errorRow)
@@ -98,8 +161,8 @@ else instance
98161 ParseErrorRL (RL.Cons label body tailRL) errorRow where
99162 parseErrorRL _ errorProxy actualStatus jsonText = do
100163 let StatusCode expected = statusCodeFor (Proxy :: _ label)
101− if actualStatus == expected then case readJSON_ jsonText of
102− Nothing -> throwError (Variant.inj (Proxy :: _ "exception") (Exception.error "JSON parse error"))
103− Just parsed -> throwError (Variant.inj (Proxy :: _ label) parsed)
164+ if actualStatus == expected then case parseResponseBody jsonText of
165+ Left errs -> throwError (Variant.inj (Proxy :: _ "exception") (Exception.error ("Failed to parse error response (status " <> show actualStatus <> "):\n" <> errs)))
166+ Right parsed -> throwError (Variant.inj (Proxy :: _ label) parsed)
104167 else
105168 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+63−72
@@ -22,27 +22,24 @@ module Yoga.Fetch.Om.Simple
2222import Prelude
2323
2424import Data.HTTP.Method (Method(..))
25+import Data.Array as Array
26+import Data.Either (Either(..))
2527import Data.Maybe (Maybe(..))
28+import Data.Tuple (fst)
2629import Data.Tuple.Nested ((/\))
2730import Effect.Class (liftEffect)
2831import JS.Fetch as Fetch
2932import JS.Fetch.Duplex as Duplex
3033import JS.Fetch.Headers (Headers)
3134import 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
3535import JS.Fetch.Request as Request
3636import 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
4037import JS.Fetch.Response as Resp
4138import Promise.Aff as Promise
4239import Type.Row.Homogeneous (class Homogeneous)
43−-- PlainText removed, using local phantom type
44−import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON_, writeJSON)
45−import Yoga.Om (Om, fromAff, throw)
40+import Yoga.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
41+import Yoga.JSON.Error (withStringErrors)
42+import Yoga.Om (Om, toOm, throw)
4643
4744type FetchError = { status :: Int, body :: String }
4845
@@ -50,15 +47,13 @@ type FetchResponse a = { headers :: Headers, body :: a }
5047
5148class DecodeResponse :: forall k. k -> Type -> Constraint
5249class DecodeResponse a result | a -> result where
53− decodeResponse :: String -> Maybe result
50+ decodeResponse :: String -> Either String result
5451
55−data PlainTextResponse
56−
57−instance DecodeResponse PlainTextResponse String where
58− decodeResponse = Just
52+instance DecodeResponse String String where
53+ decodeResponse = Right
5954
6055else instance ReadForeign a => DecodeResponse a a where
61− decodeResponse = readJSON_
56+ decodeResponse = withStringErrors <<< readJSON
6257
6358simpleFetch
6459 :: forall @a result headers ctx err
@@ -70,25 +65,27 @@ simpleFetch
7065 -> Maybe String
7166 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
7267simpleFetch method headers url maybeBody = do
73− resp <- doFetch # fromAff
74− respText <- Promise.toAffE (Resp.text resp) # fromAff
68+ resp <- doFetch # toOm
69+ respText <- Promise.toAffE (Resp.text resp) # toOm
7570 let status = Resp.status resp
7671 let respHeaders = Resp.headers resp
7772 if status >= 200 && status < 300 then
7873 case decodeResponse @a respText of
79− Nothing -> throw { fetchError: { status, body: respText } }
80− Just body -> pure { headers: respHeaders, body }
74+ Left errs -> throw { fetchError: { status, body: "Failed to parse response:\n" <> errs <> "\nBody: " <> respText } }
75+ Right body -> pure { headers: respHeaders, body }
8176 else
8277 throw { fetchError: { status, body: respText } }
8378 where
8479 doFetch = do
8580 request <- Request.new url options # liftEffect
8681 Promise.toAffE $ Fetch.fetch request
87− contentTypeHeaders = case maybeBody of
88− Nothing -> Headers.empty
89− Just _ -> Headers.fromFoldable [ "Content-Type" /\ "application/json" ]
90− allHeaders = Headers.fromFoldable
91− (Headers.toArray contentTypeHeaders <> Headers.toArray (Headers.fromRecord headers))
82+ customArr = Headers.toArray (Headers.fromRecord headers)
83+ hasContentType = Array.any (\t -> fst t == "content-type") customArr
84+ contentTypeArr = case maybeBody of
85+ Nothing -> []
86+ Just _ | hasContentType -> []
87+ Just _ -> [ "Content-Type" /\ "application/json" ]
88+ allHeaders = Headers.fromFoldable (contentTypeArr <> customArr)
9289 reqBody = case maybeBody of
9390 Nothing -> Body.empty
9491 Just b -> Body.fromString b
@@ -96,151 +93,145 @@ simpleFetch method headers url maybeBody = do
9693 { method
9794 , headers: allHeaders
9895 , body: reqBody
99− , credentials: Credentials.Include
100− , mode: Mode.Cors
101− , referrer: Referrer.ReferrerClient
102− , referrerPolicy: ReferrerPolicy.NoReferrer
103− , integrity: Integrity ""
10496 , duplex: Duplex.Half
105− , cache: Cache.Default
10697 }
10798
10899get
109100 :: forall @a result headers ctx err
110101 . DecodeResponse a result
111102 => Homogeneous headers String
112− => Record headers
113− -> String
103+ => String
104+ -> Record headers
114105 -> Om ctx (fetchError :: FetchError | err) result
115−get headers url = _.body <$> simpleFetch @a GET headers url Nothing
106+get url headers = _.body <$> simpleFetch @a GET headers url Nothing
116107
117108getWithHeaders
118109 :: forall @a result headers ctx err
119110 . DecodeResponse a result
120111 => Homogeneous headers String
121− => Record headers
122− -> String
112+ => String
113+ -> Record headers
123114 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
124−getWithHeaders headers url = simpleFetch @a GET headers url Nothing
115+getWithHeaders url headers = simpleFetch @a GET headers url Nothing
125116
126117delete
127118 :: forall @a result headers ctx err
128119 . DecodeResponse a result
129120 => Homogeneous headers String
130− => Record headers
131− -> String
121+ => String
122+ -> Record headers
132123 -> Om ctx (fetchError :: FetchError | err) result
133−delete headers url = _.body <$> simpleFetch @a DELETE headers url Nothing
124+delete url headers = _.body <$> simpleFetch @a DELETE headers url Nothing
134125
135126deleteWithHeaders
136127 :: forall @a result headers ctx err
137128 . DecodeResponse a result
138129 => Homogeneous headers String
139− => Record headers
140− -> String
130+ => String
131+ -> Record headers
141132 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
142−deleteWithHeaders headers url = simpleFetch @a DELETE headers url Nothing
133+deleteWithHeaders url headers = simpleFetch @a DELETE headers url Nothing
143134
144135delete_
145136 :: forall headers ctx err
146137 . Homogeneous headers String
147− => Record headers
148− -> String
138+ => String
139+ -> Record headers
149140 -> Om ctx (fetchError :: FetchError | err) Unit
150−delete_ headers url = void $ simpleFetch @PlainTextResponse DELETE headers url Nothing
141+delete_ url headers = void $ simpleFetch @String DELETE headers url Nothing
151142
152143post
153144 :: forall @a result headers body ctx err
154145 . DecodeResponse a result
155146 => Homogeneous headers String
156147 => WriteForeign body
157− => Record headers
158− -> String
148+ => String
149+ -> Record headers
159150 -> body
160151 -> Om ctx (fetchError :: FetchError | err) result
161−post headers url body = _.body <$> simpleFetch @a POST headers url (Just (writeJSON body))
152+post url headers body = _.body <$> simpleFetch @a POST headers url (Just (writeJSON body))
162153
163154postWithHeaders
164155 :: forall @a result headers body ctx err
165156 . DecodeResponse a result
166157 => Homogeneous headers String
167158 => WriteForeign body
168− => Record headers
169− -> String
159+ => String
160+ -> Record headers
170161 -> body
171162 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
172−postWithHeaders headers url body = simpleFetch @a POST headers url (Just (writeJSON body))
163+postWithHeaders url headers body = simpleFetch @a POST headers url (Just (writeJSON body))
173164
174165post_
175166 :: forall headers body ctx err
176167 . Homogeneous headers String
177168 => WriteForeign body
178− => Record headers
179− -> String
169+ => String
170+ -> Record headers
180171 -> body
181172 -> Om ctx (fetchError :: FetchError | err) Unit
182−post_ headers url body = void $ simpleFetch @PlainTextResponse POST headers url (Just (writeJSON body))
173+post_ url headers body = void $ simpleFetch @String POST headers url (Just (writeJSON body))
183174
184175put
185176 :: forall @a result headers body ctx err
186177 . DecodeResponse a result
187178 => Homogeneous headers String
188179 => WriteForeign body
189− => Record headers
190− -> String
180+ => String
181+ -> Record headers
191182 -> body
192183 -> Om ctx (fetchError :: FetchError | err) result
193−put headers url body = _.body <$> simpleFetch @a PUT headers url (Just (writeJSON body))
184+put url headers body = _.body <$> simpleFetch @a PUT headers url (Just (writeJSON body))
194185
195186putWithHeaders
196187 :: forall @a result headers body ctx err
197188 . DecodeResponse a result
198189 => Homogeneous headers String
199190 => WriteForeign body
200− => Record headers
201− -> String
191+ => String
192+ -> Record headers
202193 -> body
203194 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
204−putWithHeaders headers url body = simpleFetch @a PUT headers url (Just (writeJSON body))
195+putWithHeaders url headers body = simpleFetch @a PUT headers url (Just (writeJSON body))
205196
206197put_
207198 :: forall headers body ctx err
208199 . Homogeneous headers String
209200 => WriteForeign body
210− => Record headers
211− -> String
201+ => String
202+ -> Record headers
212203 -> body
213204 -> Om ctx (fetchError :: FetchError | err) Unit
214−put_ headers url body = void $ simpleFetch @PlainTextResponse PUT headers url (Just (writeJSON body))
205+put_ url headers body = void $ simpleFetch @String PUT headers url (Just (writeJSON body))
215206
216207patch
217208 :: forall @a result headers body ctx err
218209 . DecodeResponse a result
219210 => Homogeneous headers String
220211 => WriteForeign body
221− => Record headers
222− -> String
212+ => String
213+ -> Record headers
223214 -> body
224215 -> Om ctx (fetchError :: FetchError | err) result
225−patch headers url body = _.body <$> simpleFetch @a PATCH headers url (Just (writeJSON body))
216+patch url headers body = _.body <$> simpleFetch @a PATCH headers url (Just (writeJSON body))
226217
227218patchWithHeaders
228219 :: forall @a result headers body ctx err
229220 . DecodeResponse a result
230221 => Homogeneous headers String
231222 => WriteForeign body
232− => Record headers
233− -> String
223+ => String
224+ -> Record headers
234225 -> body
235226 -> Om ctx (fetchError :: FetchError | err) (FetchResponse result)
236−patchWithHeaders headers url body = simpleFetch @a PATCH headers url (Just (writeJSON body))
227+patchWithHeaders url headers body = simpleFetch @a PATCH headers url (Just (writeJSON body))
237228
238229patch_
239230 :: forall headers body ctx err
240231 . Homogeneous headers String
241232 => WriteForeign body
242− => Record headers
243− -> String
233+ => String
234+ -> Record headers
244235 -> body
245236 -> Om ctx (fetchError :: FetchError | err) Unit
246−patch_ headers url 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 @@
11module Yoga.Fetch.Om.SplitResponses
22 ( class IsSuccessStatus
3+ , class ExtractBodyRL
34 , class ExtractBody
5+ , class ClientResponseBody
46 , class SplitResponses
57 , class SplitResponsesRL
68 , class DispatchResponse
@@ -10,7 +12,10 @@ import Prim.Boolean (True, False)
1012import Prim.Row as Row
1113import Prim.RowList (RowList)
1214import Prim.RowList as RL
15+import Data.Unit (Unit)
1316import Yoga.HTTP.API.Route (Response)
17+import Yoga.HTTP.API.Route.Encoding (PlainText, Streaming)
18+import Yoga.Om.Strom (Strom)
1419
1520class IsSuccessStatus (label :: Symbol) (isSuccess :: Boolean) | label -> isSuccess
1621
@@ -26,12 +31,29 @@ else instance IsSuccessStatus "alreadyReported" True
2631else instance IsSuccessStatus "imUsed" True
2732else instance IsSuccessStatus label False
2833
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+
2949class ExtractBody (responseType :: Type) (bodyType :: Type) | responseType -> bodyType
3050
31−instance ExtractBody (Response headers body) body
51+instance ClientResponseBody encoded body => ExtractBody (Response headers encoded) body
3252
3353else instance
34− ( Row.Cons "body" body rest row
54+ ( RL.RowToList row rl
55+ , ExtractBodyRL rl encoded
56+ , ClientResponseBody encoded body
3557 ) =>
3658 ExtractBody (Record row) body
3759
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
33import Prelude
44
55import Data.Maybe (Maybe(..))
6−import Effect (Effect)
7−import Effect.Aff (Aff)
6+import Test.Spec (Spec, describe, it)
7+import Test.Spec.Assertions (shouldEqual)
88import Type.Proxy (Proxy(..))
9+import Unsafe.Coerce (unsafeCoerce)
910import Yoga.Fetch.Om.BuildUrl (buildUrl, substitutePathParams, appendQueryParams)
1011import Yoga.HTTP.API.Path (Path, type (/), type (:))
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)
1612
1713--------------------------------------------------------------------------------
1814-- SubstitutePathParams Tests
1915--------------------------------------------------------------------------------
2016
21−testSubstitutePathParams :: Effect ViTest
22−testSubstitutePathParams = describe "substitutePathParams" $ do
23− _ <- test "substitutes single path param" do
24− let
25− result = substitutePathParams (Proxy :: _ (id :: Int)) { id: 42 } "/users/:id"
26− expectToEqual "/users/42" result
27−
28− _ <- test "substitutes multiple path params" do
29− let
30− result = substitutePathParams (Proxy :: _ (userId :: Int, postId :: Int))
31− { userId: 1, postId: 99 }
32− "/users/:userId/posts/:postId"
33− expectToEqual "/users/1/posts/99" result
34−
35− _ <- test "handles String params" do
36− let
37− result = substitutePathParams (Proxy :: _ (slug :: String))
38− { slug: "hello-world" }
39− "/posts/:slug"
40− expectToEqual "/posts/hello-world" result
41−
42− test "handles empty params" do
43− let
44− result = substitutePathParams (Proxy :: _ ()) {} "/users"
45− expectToEqual "/users" result
46−
47−--------------------------------------------------------------------------------
48−-- AppendQueryParams Tests
49−--------------------------------------------------------------------------------
50−
51−testAppendQueryParams :: Effect ViTest
52−testAppendQueryParams = describe "appendQueryParams" $ do
53− _ <- test "appends single query param" do
54− let
55− result = appendQueryParams (Proxy :: _ (limit :: Int)) { limit: 10 } "/users"
56− expectToEqual "/users?limit=10" result
57−
58− _ <- test "appends multiple query params" do
59− let
60− result = appendQueryParams (Proxy :: _ (limit :: Int, offset :: Int))
61− { limit: 10, offset: 20 }
62− "/users"
63− expectToEqual "/users?limit=10&offset=20" result
64−
65− _ <- test "handles Maybe params - Just" do
66− let
67− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int))
68− { limit: Just 10 }
69− "/users"
70− expectToEqual "/users?limit=10" result
71−
72− _ <- test "handles Maybe params - Nothing" do
73− let
74− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int))
75− { limit: Nothing }
76− "/users"
77− expectToEqual "/users" result
78−
79− _ <- test "handles mixed Maybe and required params" do
80− let
81− result = appendQueryParams (Proxy :: _ (limit :: Maybe Int, offset :: Int))
82− { limit: Nothing, offset: 20 }
83− "/users"
84− expectToEqual "/users?offset=20" result
85−
86− test "handles empty query params" do
87− let
88− result = appendQueryParams (Proxy :: _ ()) {} "/users"
89− expectToEqual "/users" result
90−
91−--------------------------------------------------------------------------------
92−-- BuildUrl Tests
93−--------------------------------------------------------------------------------
94−
95−testBuildUrl :: Effect ViTest
96−testBuildUrl = describe "buildUrl" $ do
97− _ <- test "builds URL with path params only" do
98− let
99− result = buildUrl
100− "https://api.example.com"
101− (Proxy :: _ (Path ("users" / "id" : Int)))
102− { id: 42 }
103− {}
104− expectToEqual "https://api.example.com/users/42" result
105−
106− test "builds URL with query params only" do
107− let
108− result = buildUrl
109− "https://api.example.com"
110− (Proxy :: _ (Path "users"))
111− {}
112− { limit: 10, offset: 20 }
113− expectToEqual "https://api.example.com/users?limit=10&offset=20" result
17+spec :: Spec Unit
18+spec = do
19+ describe "substitutePathParams" do
20+ it "substitutes single path param" do
21+ let result = substitutePathParams @(id :: Int) { id: 42 } "/users/:id"
22+ result `shouldEqual` "/users/42"
23+
24+ it "substitutes multiple path params" do
25+ let
26+ result = substitutePathParams @(userId :: Int, postId :: Int)
27+ { userId: 1, postId: 99 }
28+ "/users/:userId/posts/:postId"
29+ result `shouldEqual` "/users/1/posts/99"
30+
31+ it "handles String params" do
32+ let
33+ result = substitutePathParams @(slug :: String)
34+ { slug: "hello-world" }
35+ "/posts/:slug"
36+ result `shouldEqual` "/posts/hello-world"
37+ it "URL-encodes reserved characters in path params" do
38+ let
39+ result = substitutePathParams @(slug :: String)
40+ { slug: "a/b c?d#e" }
41+ "/users/:slug"
42+ result `shouldEqual` "/users/a%2Fb%20c%3Fd%23e"
43+
44+ it "distinguishes path param names with shared prefixes" do
45+ let
46+ result = substitutePathParams @(id :: Int, id2 :: Int)
47+ { id: 1, id2: 2 }
48+ "/pairs/:id/:id2"
49+ result `shouldEqual` "/pairs/1/2"
50+
51+ it "handles empty params" do
52+ let result = substitutePathParams @() {} "/users"
53+ result `shouldEqual` "/users"
54+
55+ describe "appendQueryParams" do
56+ it "appends single query param" do
57+ let result = appendQueryParams @(limit :: Int) { limit: 10 } "/users"
58+ result `shouldEqual` "/users?limit=10"
59+
60+ it "appends multiple query params" do
61+ let
62+ result = appendQueryParams @(limit :: Int, offset :: Int)
63+ { limit: 10, offset: 20 }
64+ "/users"
65+ result `shouldEqual` "/users?limit=10&offset=20"
66+
67+ it "handles optional params - provided via partial record" do
68+ let
69+ result = appendQueryParams @(limit :: Maybe Int)
70+ (unsafeCoerce { limit: 10 } :: Record (limit :: Maybe Int))
71+ "/users"
72+ result `shouldEqual` "/users?limit=10"
73+
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
83+ let
84+ result = appendQueryParams @(limit :: Maybe Int)
85+ (unsafeCoerce {} :: Record (limit :: Maybe Int))
86+ "/users"
87+ result `shouldEqual` "/users"
88+
89+ it "handles mixed optional and required params" do
90+ let
91+ result = appendQueryParams @(limit :: Maybe Int, offset :: Int)
92+ (unsafeCoerce { offset: 20 } :: Record (limit :: Maybe Int, offset :: Int))
93+ "/users"
94+ result `shouldEqual` "/users?offset=20"
95+
96+ it "handles empty query params" do
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
104+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users?token=old"
105+ result `shouldEqual` "/users?token=old&search=new%20value"
106+
107+ it "inserts query params before a URL fragment" do
108+ let result = appendQueryParams @(search :: String) { search: "new value" } "/users#details"
109+ result `shouldEqual` "/users?search=new%20value#details"
110+
111+ it "repeats Array query params one pair per element" do
112+ let
113+ result = appendQueryParams @(categories :: Array Int)
114+ { categories: [ 3030, 103130 ] }
115+ "/users"
116+ result `shouldEqual` "/users?categories=3030&categories=103130"
117+
118+ it "omits empty Array query params" do
119+ let
120+ result = appendQueryParams @(categories :: Array Int)
121+ { categories: [] }
122+ "/users"
123+ result `shouldEqual` "/users"
124+
125+ it "mixes Array and bare query params" do
126+ let
127+ result = appendQueryParams @(query :: String, categories :: Array Int)
128+ { query: "foo", categories: [ 1, 2 ] }
129+ "/users"
130+ result `shouldEqual` "/users?categories=1&categories=2&query=foo"
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
152+ let
153+ result = buildUrl
154+ "https://api.example.com/base/?token=old#details"
155+ (Proxy :: _ (Path "users"))
156+ {}
157+ { search: "new value" }
158+ result `shouldEqual` "https://api.example.com/base/users?token=old&search=new%20value#details"
159+
160+ it "does not substitute path captures inside a base URL query string" do
161+ let
162+ result = buildUrl
163+ "https://api.example.com?redirect=/:id#details"
164+ (Proxy :: _ (Path ("users" / "id" : Int)))
165+ { id: 42 }
166+ {}
167+ result `shouldEqual` "https://api.example.com/users/42?redirect=/:id#details"
test/BuildUrlProperty.Spec.pursnew file+256
@@ -0,0 +1,256 @@
1+module BuildUrlProperty.Spec where
2+
3+import Prelude
4+
5+import Data.Array as Array
6+import Data.Array.NonEmpty as NEA
7+import Data.Enum (toEnum)
8+import Data.Foldable (all)
9+import Data.Maybe (Maybe(..), fromJust, isJust)
10+import Data.String as String
11+import Data.String.CodeUnits as CodeUnits
12+import Data.String.Gen (genAsciiString)
13+import Data.String.CodePoints (CodePoint, fromCodePointArray)
14+import Data.String.Pattern (Pattern(..))
15+import JSURI (decodeURIComponent)
16+import Test.QuickCheck ((<?>), (===))
17+import Test.QuickCheck.Arbitrary (arbitrary)
18+import Test.QuickCheck.Gen (Gen, arrayOf1, chooseInt)
19+import Test.Spec (Spec, describe, it)
20+import Test.Spec.Assertions (shouldEqual)
21+import Test.Spec.QuickCheck (quickCheck)
22+import Partial.Unsafe (unsafePartial)
23+import Unsafe.Coerce (unsafeCoerce)
24+import Yoga.Fetch.Om.BuildUrl (appendQueryParams, substitutePathParams)
25+
26+spec :: Spec Unit
27+spec = describe "URL properties" do
28+ it "never substitutes path captures in the query or fragment" $ quickCheck do
29+ value <- (genAsciiString :: Gen String)
30+ let
31+ suffix = "?next=/:slug#/:slug"
32+ result = substitutePathParams @(slug :: String)
33+ { slug: value }
34+ ("/items/:slug" <> suffix)
35+ property = case String.stripSuffix (Pattern suffix) result of
36+ Nothing -> false
37+ Just path -> not $ String.contains (Pattern ":slug") path
38+ pure $ property <?> "unexpected path substitution result: " <> show result
39+
40+ it "round-trips percent-encoded path and query values" $ quickCheck do
41+ value <- (genAsciiString :: Gen String)
42+ let
43+ pathResult = substitutePathParams @(value :: String)
44+ { value }
45+ "/items/:value"
46+ queryResult = appendQueryParams @(value :: String)
47+ { value }
48+ "/items"
49+ property = case
50+ String.stripPrefix (Pattern "/items/") pathResult,
51+ String.stripPrefix (Pattern "/items?value=") queryResult
52+ of
53+ Just encodedPath, Just encodedQuery ->
54+ decodeURIComponent encodedPath == Just value
55+ && decodeURIComponent encodedQuery == Just value
56+ _, _ -> false
57+ pure $ property <?> "failed to round-trip value " <> show value
58+
59+ it "preserves existing query values and fragments" $ quickCheck do
60+ existing <- (genAsciiString :: Gen String)
61+ added <- (genAsciiString :: Gen String)
62+ let
63+ base = appendQueryParams @(token :: String)
64+ { token: existing }
65+ "/items#details"
66+ result = appendQueryParams @(search :: String)
67+ { search: added }
68+ base
69+ property = do
70+ withoutFragment <- String.stripSuffix (Pattern "#details") result
71+ query <- String.stripPrefix (Pattern "/items?token=") withoutFragment
72+ case String.split (Pattern "&search=") query of
73+ [ encodedExisting, encodedAdded ] ->
74+ pure
75+ $ decodeURIComponent encodedExisting == Just existing
76+ && decodeURIComponent encodedAdded == Just added
77+ _ -> Nothing
78+ pure
79+ $ (property == Just true)
80+ <?> "query or fragment changed unexpectedly: " <> show result
81+
82+ it "preserves repeated query parameter order and multiplicity" $ quickCheck do
83+ values <- arbitrary
84+ let
85+ result = appendQueryParams @(tag :: Array Int) { tag: values } "/items"
86+ expected = case values of
87+ [] -> "/items"
88+ _ -> "/items?" <> String.joinWith "&" (map (\value -> "tag=" <> show value) values)
89+ pure $ result === expected
90+
91+ it "normalizes absent, Nothing, and Just optional fields" $ quickCheck do
92+ missing <- arbitrary
93+ value <- arbitrary
94+ let
95+ params :: Record (id :: Maybe Int)
96+ params = if missing then unsafeCoerce {} else { id: value }
97+ result = appendQueryParams @(id :: Maybe Int) params "/items"
98+ expected = case missing, value of
99+ true, _ -> "/items"
100+ false, Nothing -> "/items"
101+ false, Just id -> "/items?id=" <> show id
102+ pure $ result === expected
103+
104+ it "never introduces malformed query separators" $ quickCheck do
105+ value <- arbitrary
106+ let
107+ bases =
108+ [ "/items"
109+ , "/items?existing=1"
110+ , "/items?"
111+ , "/items?existing=1&"
112+ , "/items#details"
113+ , "/items?existing=1#details"
114+ , "/items?#details"
115+ , "/items?existing=1&#details"
116+ ]
117+ results = map (appendQueryParams @(id :: Int) { id: value }) bases
118+ pure
119+ $ all isWellFormed results
120+ <?> "malformed URL generated from " <> show results
121+
122+ it "preserves repeated existing query keys" $ quickCheck do
123+ first <- (arbitrary :: Gen Int)
124+ second <- (arbitrary :: Gen Int)
125+ page <- (arbitrary :: Gen Int)
126+ let
127+ base = "/items?id=" <> show first <> "&id=" <> show second <> "#details"
128+ result = appendQueryParams @(page :: Int) { page } base
129+ expected =
130+ "/items?id=" <> show first <> "&id=" <> show second
131+ <> "&page="
132+ <> show page
133+ <> "#details"
134+ pure $ result === expected
135+
136+ it "appends duplicate query values without coalescing existing ones" $ quickCheck do
137+ first <- (arbitrary :: Gen Int)
138+ second <- (arbitrary :: Gen Int)
139+ added <- (arbitrary :: Gen (Array Int))
140+ let
141+ base = "/items?id=" <> show first <> "&id=" <> show second
142+ result = appendQueryParams @(id :: Array Int) { id: added } base
143+ expected = case added of
144+ [] -> base
145+ _ -> base <> "&" <> String.joinWith "&" (map (\value -> "id=" <> show value) added)
146+ pure $ result === expected
147+
148+ it "ignores query-looking text inside fragments" $ quickCheck do
149+ value <- (arbitrary :: Gen Int)
150+ let
151+ rendered = show (value :: Int)
152+ cases =
153+ [ { base: "/items#section?fake=1", expected: "/items?id=" <> rendered <> "#section?fake=1" }
154+ , { base: "/items?real=1#section?fake=1", expected: "/items?real=1&id=" <> rendered <> "#section?fake=1" }
155+ , { base: "/items#?fake=1&other=2", expected: "/items?id=" <> rendered <> "#?fake=1&other=2" }
156+ , { base: "/items#first#second?fake=1", expected: "/items?id=" <> rendered <> "#first#second?fake=1" }
157+ ]
158+ property = all
159+ (\entry -> appendQueryParams @(id :: Int) { id: value } entry.base == entry.expected)
160+ cases
161+ pure $ property <?> "fragment handling failed for id " <> rendered
162+
163+ it "encodes every query delimiter inside values" $ quickCheck do
164+ prefix <- (genAsciiString :: Gen String)
165+ suffix <- (genAsciiString :: Gen String)
166+ let
167+ value = prefix <> "?#&=%+ /" <> suffix
168+ result = appendQueryParams @(value :: String) { value } "/items"
169+ property = case String.stripPrefix (Pattern "/items?value=") result of
170+ Nothing -> false
171+ Just encoded ->
172+ decodeURIComponent encoded == Just value
173+ && all
174+ (\delimiter -> not $ String.contains (Pattern delimiter) encoded)
175+ [ "?", "#", "&", "=", "+", "/" ]
176+ && String.contains (Pattern "%25") encoded
177+ pure $ property <?> "delimiter encoding failed: " <> show result
178+
179+ it "substitutes repeated captures without matching overlapping names" $ quickCheck do
180+ id <- (arbitrary :: Gen Int)
181+ id2 <- (arbitrary :: Gen Int)
182+ let
183+ result = substitutePathParams @(id :: Int, id2 :: Int)
184+ { id, id2 }
185+ "/:id/:id/:id2/:id20?next=/:id#/:id2"
186+ expected =
187+ "/" <> show id <> "/" <> show id <> "/" <> show id2
188+ <> "/:id20?next=/:id#/:id2"
189+ pure $ result === expected
190+
191+ it "normalizes lone UTF-16 surrogates before percent-encoding" do
192+ let
193+ value = codeUnit 0xD800 <> "x" <> codeUnit 0xDFFF
194+ result = appendQueryParams @(value :: String) { value } "/items"
195+ result `shouldEqual` "/items?value=%EF%BF%BDx%EF%BF%BD"
196+
197+ it "percent-encodes arbitrary UTF-16 strings without throwing" $ quickCheck do
198+ value <- (arbitrary :: Gen String)
199+ let
200+ pathResult = substitutePathParams @(value :: String) { value } "/items/:value"
201+ queryResult = appendQueryParams @(value :: String) { value } "/items"
202+ property =
203+ isJust (String.stripPrefix (Pattern "/items/") pathResult)
204+ && isJust (String.stripPrefix (Pattern "/items?value=") queryResult)
205+ pure $ property <?> "encoding failed for UTF-16 input " <> show value
206+
207+ it "round-trips valid non-ASCII scalar values" $ quickCheck do
208+ value <- unicodeScalarString
209+ let
210+ pathResult = substitutePathParams @(value :: String) { value } "/items/:value"
211+ queryResult = appendQueryParams @(value :: String) { value } "/items"
212+ property = case
213+ String.stripPrefix (Pattern "/items/") pathResult,
214+ String.stripPrefix (Pattern "/items?value=") queryResult
215+ of
216+ Just encodedPath, Just encodedQuery ->
217+ decodeURIComponent encodedPath == Just value
218+ && decodeURIComponent encodedQuery == Just value
219+ _, _ -> false
220+ pure $ property <?> "Unicode round-trip failed for " <> show value
221+
222+isWellFormed :: String -> Boolean
223+isWellFormed url =
224+ occurrences "?" url == 1
225+ && occurrences "#" url <= 1
226+ && not (String.contains (Pattern "??") url)
227+ && not (String.contains (Pattern "&&") url)
228+ && not (String.contains (Pattern "?#") url)
229+ && not (String.contains (Pattern "&#") url)
230+ && queryPrecedesFragment url
231+
232+queryPrecedesFragment :: String -> Boolean
233+queryPrecedesFragment url = case String.indexOf (Pattern "#") url of
234+ Nothing -> true
235+ Just fragmentIndex -> case String.indexOf (Pattern "?") url of
236+ Nothing -> false
237+ Just queryIndex -> queryIndex < fragmentIndex
238+
239+occurrences :: String -> String -> Int
240+occurrences needle = (_ - 1) <<< Array.length <<< String.split (Pattern needle)
241+
242+unicodeScalarString :: Gen String
243+unicodeScalarString =
244+ fromCodePointArray <<< NEA.toArray <$> arrayOf1 unicodeScalar
245+
246+unicodeScalar :: Gen CodePoint
247+unicodeScalar = do
248+ useBasicMultilingualPlane <- arbitrary
249+ value <- if useBasicMultilingualPlane then
250+ chooseInt 0x80 0xD7FF
251+ else
252+ chooseInt 0xE000 0x10FFFF
253+ pure $ unsafePartial $ fromJust $ toEnum value
254+
255+codeUnit :: Int -> String
256+codeUnit value = CodeUnits.singleton $ unsafePartial $ fromJust $ toEnum value
test/Complete.Example.purs+29−26
@@ -3,10 +3,10 @@ module Complete.Example where
33import Prelude
44
55import Data.Array (length)
6−import Data.Maybe (Maybe(..))
6+import Justifill (justifill)
77import Effect.Class.Console (log)
8−import Type.Proxy (Proxy(..))
9−import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, Path, type (/), type (:), type (:?), deriveClient)
8+import Yoga.Fetch.Om (GET, POST, PUT, DELETE, Route, JSON, type (/), type (:), type (:?), client)
9+import Yoga.HTTP.API.Route (BearerToken(..))
1010import Yoga.Om (Om, handleErrors)
1111
1212type User =
@@ -31,50 +31,44 @@ type ErrorMessage =
3131
3232type UserAPI =
3333 { getUser ::
34− Route GET
35− (Path ("users" / "id" : Int))
34+ Route GET ("users" / "id" : Int)
3635 {}
3736 ( ok :: { body :: User }
3837 , notFound :: { body :: ErrorMessage }
3938 )
4039 , listUsers ::
41− Route GET
42− (Path "users" :? { limit :: Int, offset :: Int })
40+ Route GET ("users" :? { limit :: Int, offset :: Int })
4341 {}
4442 ( ok :: { body :: Array User }
4543 )
4644 , createUser ::
47− Route POST
48− (Path "users")
45+ Route POST "users"
4946 { body :: JSON CreateUserRequest }
5047 ( created :: { body :: User }
5148 , badRequest :: { body :: ErrorMessage }
5249 )
5350 , updateUser ::
54− Route PUT
55− (Path ("users" / "id" : Int))
51+ Route PUT ("users" / "id" : Int)
5652 { body :: JSON UpdateUserRequest }
5753 ( ok :: { body :: User }
5854 , notFound :: { body :: ErrorMessage }
5955 , badRequest :: { body :: ErrorMessage }
6056 )
6157 , deleteUser ::
62− Route DELETE
63− (Path ("users" / "id" : Int))
58+ Route DELETE ("users" / "id" : Int)
6459 {}
65− ( noContent :: { body :: {} }
60+ ( noContent :: {}
6661 , notFound :: { body :: ErrorMessage }
6762 )
63+ , createUserAuth ::
64+ Route POST "users"
65+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
66+ ( created :: { body :: User }
67+ , badRequest :: { body :: ErrorMessage }
68+ )
6869 }
6970
70−api
71− :: { createUser :: CreateUserRequest -> Om {} (badRequest :: ErrorMessage) User
72− , deleteUser :: { id :: Int } -> Om {} (notFound :: ErrorMessage) {}
73− , getUser :: { id :: Int } -> Om {} (notFound :: ErrorMessage) User
74− , listUsers :: { limit :: Maybe Int, offset :: Maybe Int } -> Om {} () (Array User)
75− , updateUser :: { id :: Int } -> UpdateUserRequest -> Om {} (badRequest :: ErrorMessage, notFound :: ErrorMessage) User
76− }
77−api = deriveClient "https://api.example.com" (Proxy :: Proxy UserAPI)
71+api = client @UserAPI "https://api.example.com"
7872
7973exampleGetUser :: Om {} (notFound :: ErrorMessage) Unit
8074exampleGetUser = do
@@ -85,15 +79,15 @@ exampleGetUserHandled :: Om {} () Unit
8579exampleGetUserHandled = do
8680 user <- api.getUser { id: 42 }
8781 # handleErrors
88− { notFound: \err -> do
82+ { notFound: \err -> ado
8983 log $ "User not found: " <> err.error
90− pure { id: 0, name: "unknown", email: "" }
84+ in { id: 0, name: "unknown", email: "" }
9185 }
9286 log $ "Found user: " <> user.name
9387
9488exampleListUsers :: Om {} () Unit
9589exampleListUsers = do
96− users <- api.listUsers { limit: Just 10, offset: Just 0 }
90+ users <- api.listUsers (justifill { limit: 10, offset: 0 })
9791 log $ "Found " <> show (length users) <> " users"
9892
9993exampleCreateUser :: Om {} (badRequest :: ErrorMessage) Unit
@@ -106,7 +100,8 @@ exampleCreateUser = do
106100
107101exampleUpdateUser :: Om {} (notFound :: ErrorMessage, badRequest :: ErrorMessage) Unit
108102exampleUpdateUser = do
109− user <- api.updateUser { id: 42 }
103+ user <- api.updateUser
104+ { id: 42 }
110105 { name: "Alice Updated"
111106 , email: "alice.new@example.com"
112107 }
@@ -116,3 +111,11 @@ exampleDeleteUser :: Om {} (notFound :: ErrorMessage) Unit
116111exampleDeleteUser = do
117112 _ <- api.deleteUser { id: 42 }
118113 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 @@
11module Example where
22
3−import Yoga.HTTP.API.Route (GET, POST, DELETE, Route)
3+import Yoga.HTTP.API.Route (GET, POST, DELETE, Route, BearerToken)
44import Yoga.HTTP.API.Route.Encoding (JSON)
55import Yoga.HTTP.API.Path (Path, type (/), type (:), type (:?))
66
7−type User = { id :: Int, name :: String, email :: String }
8−type CreateUserRequest = { name :: String, email :: String }
7+type User = { id :: UserId, name :: UserName, email :: UserMail }
8+type CreateUserRequest = { name :: String, email :: UserMail }
99type ErrorMessage = { error :: String }
10+newtype UserId = UserId Int
11+newtype UserName = UserName String
12+newtype UserMail = UserMail String
13+newtype Limit = Limit Int
14+newtype Offset = Offset Int
1015
1116type UserAPI =
1217 { getUser ::
1318 Route GET
14− (Path ("users" / "id" : Int))
19+ ("users" / "id" : UserId)
1520 {}
1621 ( ok :: { body :: User }
1722 , notFound :: { body :: ErrorMessage }
1823 )
1924 , listUsers ::
2025 Route GET
21− (Path "users" :? { limit :: Int, offset :: Int })
26+ ("users" :? { limit :: Limit, offset :: Offset })
2227 {}
2328 ( ok :: { body :: Array User }
2429 )
2530 , createUser ::
2631 Route POST
27− (Path "users")
32+ "users"
2833 { body :: JSON CreateUserRequest }
2934 ( created :: { body :: User }
3035 , badRequest :: { body :: ErrorMessage }
3136 )
3237 , deleteUser ::
3338 Route DELETE
34− (Path ("users" / "id" : Int))
39+ ("users" / "id" : UserId)
3540 {}
36− ( noContent :: { body :: {} }
41+ ( noContent :: {}
3742 , notFound :: { body :: ErrorMessage }
3843 )
44+ , createUserAuth ::
45+ Route POST
46+ "users"
47+ { headers :: Record (authorization :: BearerToken), body :: JSON CreateUserRequest }
48+ ( created :: { body :: User }
49+ , badRequest :: { body :: ErrorMessage }
50+ )
3951 }
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/Simple.Spec.purs+16−16
@@ -10,59 +10,59 @@ type User = { id :: Int, name :: String, email :: String }
1010type CreateUser = { name :: String, email :: String }
1111
1212exampleGet :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
13−exampleGet = get @User {} "https://api.example.com/users/42"
13+exampleGet = get @User "https://api.example.com/users/42" {}
1414
1515exampleGetWithAuth :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
16−exampleGetWithAuth = get @User { authorization: "Bearer abc123" } "https://api.example.com/users/42"
16+exampleGetWithAuth = get @User "https://api.example.com/users/42" { authorization: "Bearer abc123" }
1717
1818exampleGetPlainText :: forall ctx err. Om ctx (fetchError :: FetchError | err) String
19−exampleGetPlainText = get @String {} "https://api.example.com/health"
19+exampleGetPlainText = get @String "https://api.example.com/health" {}
2020
2121exampleGetWithHeaders :: forall ctx err. Om ctx (fetchError :: FetchError | err) (FetchResponse User)
2222exampleGetWithHeaders = do
23− { headers, body: user } <- getWithHeaders @User {} "https://api.example.com/users/42"
23+ { headers, body: user } <- getWithHeaders @User "https://api.example.com/users/42" {}
2424 log $ "Got user: " <> user.name
2525 pure { headers, body: user }
2626
2727examplePost :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
28−examplePost = post @User {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
28+examplePost = post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
2929
3030examplePostFireAndForget :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
31−examplePostFireAndForget = post_ {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
31+examplePostFireAndForget = post_ "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
3232
3333examplePut :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
34−examplePut = put @User {} "https://api.example.com/users/42" { name: "Bob", email: "bob@example.com" }
34+examplePut = put @User "https://api.example.com/users/42" {} { name: "Bob", email: "bob@example.com" }
3535
3636examplePut_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
37−examplePut_ = put_ {} "https://api.example.com/users/42" { name: "Bob", email: "bob@example.com" }
37+examplePut_ = put_ "https://api.example.com/users/42" {} { name: "Bob", email: "bob@example.com" }
3838
3939examplePatch :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
40−examplePatch = patch @User {} "https://api.example.com/users/42" { name: "Bob" }
40+examplePatch = patch @User "https://api.example.com/users/42" {} { name: "Bob" }
4141
4242examplePatch_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
43−examplePatch_ = patch_ {} "https://api.example.com/users/42" { name: "Bob" }
43+examplePatch_ = patch_ "https://api.example.com/users/42" {} { name: "Bob" }
4444
4545exampleDelete :: forall ctx err. Om ctx (fetchError :: FetchError | err) User
46−exampleDelete = delete @User {} "https://api.example.com/users/42"
46+exampleDelete = delete @User "https://api.example.com/users/42" {}
4747
4848exampleDelete_ :: forall ctx err. Om ctx (fetchError :: FetchError | err) Unit
49−exampleDelete_ = delete_ {} "https://api.example.com/users/42"
49+exampleDelete_ = delete_ "https://api.example.com/users/42" {}
5050
5151exampleHandleError :: forall ctx. Om ctx () Unit
5252exampleHandleError = do
53− user <- get @User {} "https://api.example.com/users/42"
53+ user <- get @User "https://api.example.com/users/42" {}
5454 # handleErrors { fetchError: \_ -> pure { id: 0, name: "unknown", email: "" } }
5555 log $ "Got user: " <> user.name
5656
5757exampleComposition :: forall ctx. Om ctx () Unit
5858exampleComposition = do
59− user <- post @User {} "https://api.example.com/users" { name: "Alice", email: "alice@example.com" }
59+ user <- post @User "https://api.example.com/users" {} { name: "Alice", email: "alice@example.com" }
6060 # handleErrors
6161 { fetchError: \e -> do
6262 log $ "Failed with status " <> show e.status <> ": " <> e.body
6363 pure { id: 0, name: "", email: "" }
6464 }
65− _ <- put @User {} ("https://api.example.com/users/" <> show user.id) { name: "Alice Updated", email: user.email }
65+ _ <- put @User ("https://api.example.com/users/" <> show user.id) {} { name: "Alice Updated", email: user.email }
6666 # handleErrors { fetchError: \_ -> pure user }
67− delete_ {} ("https://api.example.com/users/" <> show user.id)
67+ delete_ ("https://api.example.com/users/" <> show user.id) {}
6868 # handleErrors { fetchError: \_ -> pure unit }
test/SplitParams.Spec.purs+22−31
@@ -2,22 +2,13 @@ module SplitParams.Spec where
22
33import Prelude
44
5−import Effect (Effect)
6−import Effect.Aff (Aff)
5+import Test.Spec (Spec, describe, it)
6+import Test.Spec.Assertions (shouldEqual)
77import Yoga.Fetch.Om.SplitParams (splitParams)
8−import ViTest (ViTest, describe, test)
9−import ViTest.Expect (expectToBe)
108
11−expectToEqual :: forall a. Eq a => a -> a -> Aff 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
20− _ <- test "splits path and query params" do
9+spec :: Spec Unit
10+spec = describe "splitParams" do
11+ it "splits path and query params" do
2112 let
2213 allParams = { id: 42, limit: 10, offset: 20 }
2314 result =
@@ -26,12 +17,12 @@ testSplitParams = describe "splitParams" $ do
2617 , query :: { limit :: Int, offset :: Int }
2718 , body :: {}
2819 }
29− expectToEqual 42 result.path.id
30− expectToEqual 10 result.query.limit
31− expectToEqual 20 result.query.offset
32− expectToEqual {} result.body
20+ result.path.id `shouldEqual` 42
21+ result.query.limit `shouldEqual` 10
22+ result.query.offset `shouldEqual` 20
23+ result.body `shouldEqual` {}
3324
34− _ <- test "splits path and body params" do
25+ it "splits path and body params" do
3526 let
3627 allParams = { id: 42, name: "Alice", email: "alice@example.com" }
3728 result =
@@ -40,12 +31,12 @@ testSplitParams = describe "splitParams" $ do
4031 , query :: {}
4132 , body :: { name :: String, email :: String }
4233 }
43− expectToEqual 42 result.path.id
44− expectToEqual {} result.query
45− expectToEqual "Alice" result.body.name
46− expectToEqual "alice@example.com" result.body.email
34+ result.path.id `shouldEqual` 42
35+ result.query `shouldEqual` {}
36+ result.body.name `shouldEqual` "Alice"
37+ result.body.email `shouldEqual` "alice@example.com"
4738
48− _ <- test "splits all three param types" do
39+ it "splits all three param types" do
4940 let
5041 allParams =
5142 { id: 42
@@ -58,11 +49,11 @@ testSplitParams = describe "splitParams" $ do
5849 , query :: { limit :: Int }
5950 , body :: { name :: String }
6051 }
61− expectToEqual 42 result.path.id
62− expectToEqual 10 result.query.limit
63− expectToEqual "Bob" result.body.name
52+ result.path.id `shouldEqual` 42
53+ result.query.limit `shouldEqual` 10
54+ result.body.name `shouldEqual` "Bob"
6455
65− test "handles empty splits" do
56+ it "handles empty splits" do
6657 let
6758 allParams = { id: 42 }
6859 result =
@@ -71,6 +62,6 @@ testSplitParams = describe "splitParams" $ do
7162 , query :: {}
7263 , body :: {}
7364 }
74− expectToEqual 42 result.path.id
75− expectToEqual {} result.query
76− expectToEqual {} result.body
65+ result.path.id `shouldEqual` 42
66+ result.query `shouldEqual` {}
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
44
55import Data.Variant (Variant)
66import Data.Variant as Variant
7−import Effect (Effect)
8−import Effect.Aff (Aff)
7+import Test.Spec (Spec, describe, it)
8+import Test.Spec.Assertions (shouldEqual)
99import Type.Proxy (Proxy(..))
10−import ViTest (ViTest, describe, test)
11−import ViTest.Expect (expectToBe)
1210import Yoga.Fetch.Om.Variant (only)
1311
14−expectToEqual :: forall a. Eq a => a -> a -> Aff Unit
15−expectToEqual expected actual = expectToBe true (expected == actual)
16−
17−testOnly :: Effect ViTest
18−testOnly = describe "only" do
19− test "extracts single variant value - record" do
12+spec :: Spec Unit
13+spec = describe "only" do
14+ it "extracts single variant value - record" do
2015 let
2116 variant :: Variant (ok :: { id :: Int, name :: String })
2217 variant = Variant.inj (Proxy :: _ "ok") { id: 42, name: "Alice" }
2318 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−});