- commit
- a9ff967
- parent
- 2fd2abe
- author
- Eric Bower
- date
- 2023-05-20 21:48:09 +0000 UTC
feat(query): Data synchronization using a middleware system for front-end apps (#1)
55 files changed,
+6579,
-324
+36,
-0
1@@ -0,0 +1,36 @@
2+name: release to npm
3+
4+on:
5+ release:
6+ types: [published]
7+
8+permissions:
9+ contents: read
10+
11+jobs:
12+ release:
13+ runs-on: ubuntu-latest
14+ steps:
15+ - name: checkout
16+ uses: actions/checkout@v3
17+ - name: setup deno
18+ uses: denoland/setup-deno@v1
19+ with:
20+ deno-version: v1.x
21+ - name: get version
22+ id: vars
23+ run: echo ::set-output name=version::$(echo ${{github.ref_name}} | sed 's/^v//')
24+ - name: setup node
25+ uses: actions/setup-node@v2
26+ with:
27+ node-version: 18.x
28+ registry-url: https://registry.npmjs.com
29+ - name: build
30+ run: deno task npm $NPM_VERSION
31+ env:
32+ NPM_VERSION: ${{steps.vars.outputs.version}}
33+ - name: publish
34+ run: npm publish --access=public
35+ working-directory: ./build/npm
36+ env:
37+ NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
+1,
-1
1@@ -28,4 +28,4 @@ jobs:
2 run: deno lint
3
4 - name: test
5- run: deno test
6+ run: deno task test
+20,
-0
1@@ -0,0 +1,20 @@
2+# The MIT License (MIT)
3+
4+Copyright © `2023` `Eric Bower`
5+
6+Permission is hereby granted, free of charge, to any person obtaining a copy of
7+this software and associated documentation files (the “Software”), to deal in
8+the Software without restriction, including without limitation the rights to
9+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10+the Software, and to permit persons to whom the Software is furnished to do so,
11+subject to the following conditions:
12+
13+The above copyright notice and this permission notice shall be included in all
14+copies or substantial portions of the Software.
15+
16+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
D
Makefile
+0,
-3
1@@ -1,3 +0,0 @@
2-npm:
3- NPM_VERSION=0.0.1 deno run --allow-env --allow-read --allow-write --allow-run ./npm.ts
4-.PHONY: npm
+256,
-0
1@@ -0,0 +1,256 @@
2+function createSagaQueryApi() {
3+ const methods = [
4+ "get",
5+ "post",
6+ "put",
7+ "patch",
8+ "delete",
9+ "options",
10+ "head",
11+ "connect",
12+ "trace",
13+ ];
14+
15+ const uriTmpl = (method: string) =>
16+ `/**
17+ * Options only
18+ */
19+${method}(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
20+${method}<P>(
21+ req: { supervisor?: Supervisor }
22+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
23+${method}<P extends never, ApiSuccess, ApiError = unknown>(
24+ req: { supervisor?: Supervisor }
25+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
26+${method}<P, ApiSuccess, ApiError = unknown>(req: {
27+ supervisor?: Supervisor;
28+}): CreateActionWithPayload<
29+ Omit<Ctx, 'payload' | 'json'> &
30+ Payload<P> &
31+ FetchJson<ApiSuccess, ApiError>,
32+ P
33+>;
34+
35+/**
36+* Middleware only
37+*/
38+${method}(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
39+${method}<Gtx extends Ctx = Ctx>(
40+ fn: MiddlewareApiCo<Gtx>,
41+): CreateAction<Gtx>;
42+${method}<P>(
43+ fn: MiddlewareApiCo<Omit<Ctx, 'payload'> & Payload<P>>,
44+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
45+${method}<P, Gtx extends Ctx = Ctx>(
46+ fn: MiddlewareApiCo<Gtx>,
47+): CreateActionWithPayload<Gtx, P>;
48+${method}<P extends never, ApiSuccess, ApiError = unknown>(
49+ fn: MiddlewareApiCo<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>,
50+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
51+${method}<P, ApiSuccess, ApiError = unknown>(
52+ fn: MiddlewareApiCo<Ctx>,
53+): CreateActionWithPayload<
54+ Omit<Ctx, 'payload' | 'json'> &
55+ Payload<P> &
56+ FetchJson<ApiSuccess, ApiError>,
57+ P
58+>;
59+
60+/**
61+* Options and Middleware
62+*/
63+${method}(req: { supervisor?: Supervisor }, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
64+${method}<Gtx extends Ctx = Ctx>(
65+ req: { supervisor?: Supervisor },
66+ fn: MiddlewareApiCo<Gtx>,
67+): CreateAction<Gtx>;
68+${method}<P>(
69+ req: { supervisor?: Supervisor },
70+ fn: MiddlewareApiCo<Omit<Ctx, 'payload'> & Payload<P>>,
71+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
72+${method}<P, Gtx extends Ctx = Ctx>(
73+ req: { supervisor?: Supervisor },
74+ fn: MiddlewareApiCo<Gtx>,
75+): CreateActionWithPayload<Gtx, P>;
76+${method}<P extends never, ApiSuccess, ApiError = unknown>(
77+ req: { supervisor?: Supervisor },
78+ fn: MiddlewareApiCo<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>,
79+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
80+${method}<P, ApiSuccess, ApiError = unknown>(
81+ req: { supervisor?: Supervisor },
82+ fn: MiddlewareApiCo<Ctx>,
83+): CreateActionWithPayload<
84+ Omit<Ctx, 'payload' | 'json'> &
85+ Payload<P> &
86+ FetchJson<ApiSuccess, ApiError>,
87+ P
88+>;`;
89+ const uriMethods = methods.map((m) => uriTmpl(m)).join("\n\n");
90+
91+ const methodTmpl = (method: string) =>
92+ `/**
93+ * Only name
94+ */
95+${method}(name: ApiName): CreateAction<Ctx>;
96+${method}<P>(
97+ name: ApiName,
98+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
99+${method}<P extends never, ApiSuccess, ApiError = unknown>(
100+ name: ApiName,
101+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
102+${method}<P, ApiSuccess, ApiError = unknown>(
103+ name: ApiName,
104+): CreateActionWithPayload<
105+ Omit<Ctx, 'payload' | 'json'> &
106+ Payload<P> &
107+ FetchJson<ApiSuccess, ApiError>,
108+ P
109+>;
110+
111+/**
112+ * Name and options
113+ */
114+${method}(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
115+${method}<P>(
116+ name: ApiName,
117+ req: { supervisor?: Supervisor }
118+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
119+${method}<P, Gtx extends Ctx = Ctx>(
120+ name: ApiName,
121+ req: { supervisor?: Supervisor }
122+): CreateActionWithPayload<Gtx, P>;
123+${method}<P extends never, ApiSuccess, ApiError = unknown>(
124+ name: ApiName,
125+ req: { supervisor?: Supervisor }
126+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
127+${method}<P, ApiSuccess, ApiError = unknown>(
128+ name: ApiName,
129+ req: { supervisor?: Supervisor },
130+): CreateActionWithPayload<
131+ Omit<Ctx, 'payload' | 'json'> &
132+ Payload<P> &
133+ FetchJson<ApiSuccess, ApiError>,
134+ P
135+>;
136+
137+/**
138+ * Name and middleware
139+ */
140+${method}(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
141+${method}<Gtx extends Ctx = Ctx>(
142+ name: ApiName,
143+ fn: MiddlewareApiCo<Gtx>,
144+): CreateAction<Gtx>;
145+${method}<P>(
146+ name: ApiName,
147+ fn: MiddlewareApiCo<Omit<Ctx, 'payload'> & Payload<P>>,
148+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
149+${method}<P, Gtx extends Ctx = Ctx>(
150+ name: ApiName,
151+ fn: MiddlewareApiCo<Gtx>,
152+): CreateActionWithPayload<Gtx, P>;
153+${method}<P extends never, ApiSuccess, ApiError = unknown>(
154+ name: ApiName,
155+ fn: MiddlewareApiCo<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>,
156+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
157+${method}<P, ApiSuccess, ApiError = unknown>(
158+ name: ApiName,
159+ fn: MiddlewareApiCo<
160+ Omit<Ctx, 'payload' | 'json'> &
161+ Payload<P> &
162+ FetchJson<ApiSuccess, ApiError>
163+ >,
164+): CreateActionWithPayload<
165+ Omit<Ctx, 'payload' | 'json'> &
166+ Payload<P> &
167+ FetchJson<ApiSuccess, ApiError>,
168+ P
169+>;
170+
171+/**
172+ * Name, options, and middleware
173+ */
174+${method}(
175+ name: ApiName,
176+ req: { supervisor?: Supervisor },
177+ fn: MiddlewareApiCo<Ctx>,
178+): CreateAction<Ctx>;
179+${method}<Gtx extends Ctx = Ctx>(
180+ name: ApiName,
181+ req: { supervisor?: Supervisor },
182+ fn: MiddlewareApiCo<Gtx>,
183+): CreateAction<Gtx>;
184+${method}<P>(
185+ name: ApiName,
186+ req: { supervisor?: Supervisor },
187+ fn: MiddlewareApiCo<Omit<Ctx, 'payload'> & Payload<P>>,
188+): CreateActionWithPayload<Omit<Ctx, 'payload'> & Payload<P>, P>;
189+${method}<P, Gtx extends Ctx = Ctx>(
190+ name: ApiName,
191+ req: { supervisor?: Supervisor },
192+ fn: MiddlewareApiCo<Gtx>,
193+): CreateActionWithPayload<Gtx, P>;
194+${method}<P extends never, ApiSuccess, ApiError = unknown>(
195+ name: ApiName,
196+ req: { supervisor?: Supervisor },
197+ fn: MiddlewareApiCo<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>,
198+): CreateAction<Omit<Ctx, 'json'> & FetchJson<ApiSuccess, ApiError>>;
199+${method}<P, ApiSuccess, ApiError = unknown>(
200+ name: ApiName,
201+ req: { supervisor?: Supervisor },
202+ fn: MiddlewareApiCo<
203+ Omit<Ctx, 'payload' | 'json'> &
204+ Payload<P> &
205+ FetchJson<ApiSuccess, ApiError>
206+ >,
207+): CreateActionWithPayload<
208+ Omit<Ctx, 'payload' | 'json'> &
209+ Payload<P> &
210+ FetchJson<ApiSuccess, ApiError>,
211+ P
212+>;`;
213+ const regMethods = methods.map((m) => methodTmpl(m)).join("\n\n");
214+
215+ const tmpl = `/**
216+* This is an auto-generated file, do not edit directly!
217+* Run "yarn template" to generate this file.
218+*/
219+import type { SagaApi } from "./pipe.ts";
220+import type {
221+ ApiCtx,
222+ CreateAction,
223+ CreateActionWithPayload,
224+ Next,
225+ FetchJson,
226+ MiddlewareApiCo,
227+ Payload,
228+ Supervisor,
229+} from "./types.ts";
230+
231+export type ApiName = string | string[];
232+
233+export interface SagaQueryApi<Ctx extends ApiCtx = ApiCtx> extends SagaApi<Ctx> {
234+ request: (
235+ r: Partial<RequestInit>,
236+ ) => (ctx: Ctx, next: Next) => Iterator<unknown>;
237+ cache: () => (ctx: Ctx, next: Next) => Iterator<unknown>;
238+
239+ uri: (uri: string) => {
240+ ${uriMethods}
241+ }
242+
243+${regMethods}
244+}`;
245+
246+ return tmpl;
247+}
248+
249+async function createTemplateFile(tmpl: string) {
250+ try {
251+ await Deno.writeTextFile("./query/api-types.ts", tmpl);
252+ } catch (err) {
253+ console.error(err);
254+ }
255+}
256+
257+createTemplateFile(createSagaQueryApi()).then(console.log).catch(console.error);
R test/compose.test.ts =>
compose.test.ts
+3,
-3
1@@ -1,7 +1,7 @@
2-import { describe, expect, it } from "../test.ts";
3+import { describe, expect, it } from "./test.ts";
4
5-import { run, sleep } from "../deps.ts";
6-import { compose } from "../compose.ts";
7+import { run, sleep } from "./deps.ts";
8+import { compose } from "./compose.ts";
9
10 const tests = describe("compose()");
11
+1,
-1
1@@ -1,4 +1,4 @@
2-import { call } from "./fx/mod.ts";
3+import { call } from "./fx/index.ts";
4 import type { Instruction, Result } from "./deps.ts";
5 import { Err } from "./deps.ts";
6
+1,
-7
1@@ -1,10 +1,4 @@
2-import { createChannel, createContext, getframe } from "./deps.ts";
3-import type { Channel } from "./deps.ts";
4-
5-export const ErrContext = createContext<Channel<Error, void>>(
6- "fx:err",
7- createChannel<Error, void>(),
8-);
9+import { getframe } from "./deps.ts";
10
11 export function* contextualize(context: string, value: unknown) {
12 const frame = yield* getframe();
+7,
-6
1@@ -1,16 +1,17 @@
2 {
3+ "tasks": {
4+ "types": "deno run --allow-write ./api-type-template.ts",
5+ "npm": "deno run --allow-write --allow-read --allow-env --allow-run --allow-net --no-check ./npm.ts",
6+ "test": "deno test --allow-env --allow-read"
7+ },
8 "lint": {
9- "files": {
10- "exclude": ["npm/", "examples/"]
11- },
12+ "exclude": ["npm/", "examples/"],
13 "rules": {
14 "tags": ["recommended"],
15 "exclude": ["no-explicit-any", "require-yield"]
16 }
17 },
18 "fmt": {
19- "files": {
20- "exclude": ["npm/", "examples/"]
21- }
22+ "exclude": ["npm/", "examples/"]
23 }
24 }
+290,
-190
1@@ -1,60 +1,28 @@
2 {
3 "version": "2",
4 "remote": {
5- "https://deno.land/std@0.106.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
6- "https://deno.land/std@0.106.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
7- "https://deno.land/std@0.106.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
8- "https://deno.land/std@0.106.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7",
9- "https://deno.land/std@0.106.0/fs/expand_glob.ts": "73e7b13f01097b04ed782b3d63863379b718417417758ba622e282b1e5300b91",
10- "https://deno.land/std@0.106.0/fs/walk.ts": "b91c655c60d048035f9cae0e6177991ab3245e786e3ab7d20a5b60012edf2126",
11- "https://deno.land/std@0.106.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
12- "https://deno.land/std@0.106.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
13- "https://deno.land/std@0.106.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
14- "https://deno.land/std@0.106.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a",
15- "https://deno.land/std@0.106.0/path/glob.ts": "3b84af55c53febacf6afe214c095624b22a56b6f57d7312157479cc783a0de65",
16- "https://deno.land/std@0.106.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
17- "https://deno.land/std@0.106.0/path/posix.ts": "b81974c768d298f8dcd2c720229639b3803ca4a241fa9a355c762fa2bc5ef0c1",
18- "https://deno.land/std@0.106.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
19- "https://deno.land/std@0.106.0/path/win32.ts": "f4a3d4a3f2c9fe894da046d5eac48b5e789a0ebec5152b2c0985efe96a9f7ae1",
20- "https://deno.land/std@0.111.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
21- "https://deno.land/std@0.111.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
22- "https://deno.land/std@0.111.0/bytes/bytes_list.ts": "3bff6a09c72b2e0b1e92e29bd3b135053894196cca07a2bba842901073efe5cb",
23- "https://deno.land/std@0.111.0/bytes/equals.ts": "69f55fdbd45c71f920c1a621e6c0865dc780cd8ae34e0f5e55a9497b70c31c1b",
24- "https://deno.land/std@0.111.0/bytes/mod.ts": "fedb80b8da2e7ad8dd251148e65f92a04c73d6c5a430b7d197dc39588c8dda6f",
25- "https://deno.land/std@0.111.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621",
26- "https://deno.land/std@0.111.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b",
27- "https://deno.land/std@0.111.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7",
28- "https://deno.land/std@0.111.0/hash/sha256.ts": "bd85257c68d1fdd9da8457284c4fbb04efa9f4f2229b5f41a638d5b71a3a8d5c",
29- "https://deno.land/std@0.111.0/io/buffer.ts": "fdf93ba9e5d20ff3369e2c42443efd89131f8a73066f7f59c033cc588a0e2cfe",
30- "https://deno.land/std@0.111.0/io/types.d.ts": "89a27569399d380246ca7cdd9e14d5e68459f11fb6110790cc5ecbd4ee7f3215",
31- "https://deno.land/std@0.111.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
32- "https://deno.land/std@0.111.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
33- "https://deno.land/std@0.111.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
34- "https://deno.land/std@0.111.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4",
35- "https://deno.land/std@0.111.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820",
36- "https://deno.land/std@0.111.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
37- "https://deno.land/std@0.111.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2",
38- "https://deno.land/std@0.111.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
39- "https://deno.land/std@0.111.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e",
40- "https://deno.land/std@0.111.0/streams/conversion.ts": "fe0059ed9d3c53eda4ba44eb71a6a9acb98c5fdb5ba1b6c6ab28004724c7641b",
41- "https://deno.land/std@0.119.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58",
42- "https://deno.land/std@0.119.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac",
43- "https://deno.land/std@0.119.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621",
44- "https://deno.land/std@0.119.0/fs/empty_dir.ts": "5f08b263dd064dc7917c4bbeb13de0f5505a664b9cdfe312fa86e7518cfaeb84",
45- "https://deno.land/std@0.119.0/fs/expand_glob.ts": "f6f64ef54addcc84c9012d6dfcf66d39ecc2565e40c4d2b7644d585fe4d12a04",
46- "https://deno.land/std@0.119.0/fs/walk.ts": "31464d75099aa3fc7764212576a8772dfabb2692783e6eabb910f874a26eac54",
47- "https://deno.land/std@0.119.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853",
48- "https://deno.land/std@0.119.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4",
49- "https://deno.land/std@0.119.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b",
50- "https://deno.land/std@0.119.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4",
51- "https://deno.land/std@0.119.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820",
52- "https://deno.land/std@0.119.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12",
53- "https://deno.land/std@0.119.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2",
54- "https://deno.land/std@0.119.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
55- "https://deno.land/std@0.119.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e",
56- "https://deno.land/std@0.129.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37",
57- "https://deno.land/std@0.129.0/testing/_diff.ts": "9d849cd6877694152e01775b2d93f9d6b7aef7e24bfe3bfafc4d7a1ac8e9f392",
58- "https://deno.land/std@0.129.0/testing/asserts.ts": "0a95d9e8076dd3e7f0eeb605a67c148078b4b11f4abcd5eef115b0361b0736a2",
59+ "https://crux.land/api/get/2KNRVU.ts": "6a77d55844aba78d01520c5ff0b2f0af7f24cc1716a0de8b3bb6bd918c47b5ba",
60+ "https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
61+ "https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
62+ "https://deno.land/std@0.140.0/bytes/bytes_list.ts": "67eb118e0b7891d2f389dad4add35856f4ad5faab46318ff99653456c23b025d",
63+ "https://deno.land/std@0.140.0/bytes/equals.ts": "fc16dff2090cced02497f16483de123dfa91e591029f985029193dfaa9d894c9",
64+ "https://deno.land/std@0.140.0/bytes/mod.ts": "763f97d33051cc3f28af1a688dfe2830841192a9fea0cbaa55f927b49d49d0bf",
65+ "https://deno.land/std@0.140.0/fmt/colors.ts": "30455035d6d728394781c10755351742dd731e3db6771b1843f9b9e490104d37",
66+ "https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f",
67+ "https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d",
68+ "https://deno.land/std@0.140.0/hash/sha256.ts": "803846c7a5a8a5a97f31defeb37d72f519086c880837129934f5d6f72102a8e8",
69+ "https://deno.land/std@0.140.0/io/buffer.ts": "bd0c4bf53db4b4be916ca5963e454bddfd3fcd45039041ea161dbf826817822b",
70+ "https://deno.land/std@0.140.0/io/types.d.ts": "01f60ae7ec02675b5dbed150d258fc184a78dfe5c209ef53ba4422b46b58822c",
71+ "https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
72+ "https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
73+ "https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
74+ "https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
75+ "https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
76+ "https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
77+ "https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
78+ "https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
79+ "https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
80+ "https://deno.land/std@0.140.0/streams/conversion.ts": "712585bfa0172a97fb68dd46e784ae8ad59d11b88079d6a4ab098ff42e697d21",
81 "https://deno.land/std@0.158.0/fmt/colors.ts": "ff7dc9c9f33a72bd48bc24b21bbc1b4545d8494a431f17894dbc5fe92a938fc4",
82 "https://deno.land/std@0.158.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c",
83 "https://deno.land/std@0.158.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
84@@ -187,46 +155,82 @@
85 "https://deno.land/std@0.177.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
86 "https://deno.land/std@0.177.0/streams/write_all.ts": "3b2e1ce44913f966348ce353d02fa5369e94115181037cd8b602510853ec3033",
87 "https://deno.land/std@0.177.0/types.d.ts": "220ed56662a0bd393ba5d124aa6ae2ad36a00d2fcbc0e8666a65f4606aaa9784",
88+ "https://deno.land/std@0.181.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
89+ "https://deno.land/std@0.181.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
90+ "https://deno.land/std@0.181.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32",
91+ "https://deno.land/std@0.181.0/fs/ensure_dir.ts": "dc64c4c75c64721d4e3fb681f1382f803ff3d2868f08563ff923fdd20d071c40",
92+ "https://deno.land/std@0.181.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a",
93+ "https://deno.land/std@0.181.0/fs/walk.ts": "ea95ffa6500c1eda6b365be488c056edc7c883a1db41ef46ec3bf057b1c0fe32",
94+ "https://deno.land/std@0.181.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
95+ "https://deno.land/std@0.181.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
96+ "https://deno.land/std@0.181.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
97+ "https://deno.land/std@0.181.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
98+ "https://deno.land/std@0.181.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
99+ "https://deno.land/std@0.181.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c",
100+ "https://deno.land/std@0.181.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
101+ "https://deno.land/std@0.181.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
102+ "https://deno.land/std@0.181.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
103+ "https://deno.land/std@0.182.0/_util/asserts.ts": "178dfc49a464aee693a7e285567b3d0b555dc805ff490505a8aae34f9cfb1462",
104+ "https://deno.land/std@0.182.0/_util/os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3",
105+ "https://deno.land/std@0.182.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
106+ "https://deno.land/std@0.182.0/fs/_util.ts": "65381f341af1ff7f40198cee15c20f59951ac26e51ddc651c5293e24f9ce6f32",
107+ "https://deno.land/std@0.182.0/fs/empty_dir.ts": "c3d2da4c7352fab1cf144a1ecfef58090769e8af633678e0f3fabaef98594688",
108+ "https://deno.land/std@0.182.0/fs/expand_glob.ts": "e4f56259a0a70fe23f05215b00de3ac5e6ba46646ab2a06ebbe9b010f81c972a",
109+ "https://deno.land/std@0.182.0/fs/walk.ts": "920be35a7376db6c0b5b1caf1486fb962925e38c9825f90367f8f26b5e5d0897",
110+ "https://deno.land/std@0.182.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0",
111+ "https://deno.land/std@0.182.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b",
112+ "https://deno.land/std@0.182.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0",
113+ "https://deno.land/std@0.182.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000",
114+ "https://deno.land/std@0.182.0/path/glob.ts": "d479e0a695621c94d3fd7fe7abd4f9499caf32a8de13f25073451c6ef420a4e1",
115+ "https://deno.land/std@0.182.0/path/mod.ts": "bf718f19a4fdd545aee1b06409ca0805bd1b68ecf876605ce632e932fe54510c",
116+ "https://deno.land/std@0.182.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d",
117+ "https://deno.land/std@0.182.0/path/separator.ts": "0fb679739d0d1d7bf45b68dacfb4ec7563597a902edbaf3c59b50d5bcadd93b1",
118+ "https://deno.land/std@0.182.0/path/win32.ts": "d186344e5583bcbf8b18af416d13d82b35a317116e6460a5a3953508c3de5bba",
119+ "https://deno.land/std@0.185.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
120+ "https://deno.land/std@0.185.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea",
121+ "https://deno.land/std@0.185.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
122+ "https://deno.land/std@0.185.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f",
123+ "https://deno.land/std@0.187.0/fmt/colors.ts": "d67e3cd9f472535241a8e410d33423980bec45047e343577554d3356e1f0ef4e",
124+ "https://deno.land/std@0.187.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea",
125+ "https://deno.land/std@0.187.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7",
126+ "https://deno.land/std@0.187.0/testing/asserts.ts": "e16d98b4d73ffc4ed498d717307a12500ae4f2cbe668f1a215632d19fcffc22f",
127 "https://deno.land/std@0.97.0/fmt/colors.ts": "db22b314a2ae9430ae7460ce005e0a7130e23ae1c999157e3bb77cf55800f7e4",
128 "https://deno.land/std@0.97.0/testing/_diff.ts": "961eaf6d9f5b0a8556c9d835bbc6fa74f5addd7d3b02728ba7936ff93364f7a3",
129 "https://deno.land/std@0.97.0/testing/asserts.ts": "341292d12eebc44be4c3c2ca101ba8b6b5859cef2fa69d50c217f9d0bfbcfd1f",
130- "https://deno.land/x/code_block_writer@11.0.0/comment_char.ts": "22b66890bbdf7a2d59777ffd8231710c1fda1c11fadada67632a596937a1a314",
131- "https://deno.land/x/code_block_writer@11.0.0/mod.ts": "dc43d56c3487bae02886a09754fb09c607da4ea866817e80f3e60632f3391d70",
132- "https://deno.land/x/code_block_writer@11.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
133+ "https://deno.land/x/code_block_writer@12.0.0/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5",
134+ "https://deno.land/x/code_block_writer@12.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff",
135 "https://deno.land/x/continuation@0.1.5/mod.ts": "690def2735046367b3e1b4bc6e51b5912f2ed09c41c7df7a55c060f23720ad33",
136- "https://deno.land/x/deno_cache@0.2.1/auth_tokens.ts": "01b94d25abd974153a3111653998b9a43c66d84a0e4b362fc5f4bbbf40a6e0f7",
137- "https://deno.land/x/deno_cache@0.2.1/cache.ts": "67e301c20161546fea45405316314f4c3d85cc7a367b2fb72042903f308f55b7",
138- "https://deno.land/x/deno_cache@0.2.1/deno_dir.ts": "e4dc68da5641aa337bcc06fb1df28fcb086b366dcbea7d8aaed7ac7c853fedb1",
139- "https://deno.land/x/deno_cache@0.2.1/deps.ts": "2ebaba0ad86fff8b9027c6afd4c3909a17cd8bf8c9e263151c980c15c56a18ee",
140- "https://deno.land/x/deno_cache@0.2.1/dirs.ts": "e07003fabed7112375d4a50040297aae768f9d06bb6c2655ca46880653b576b4",
141- "https://deno.land/x/deno_cache@0.2.1/disk_cache.ts": "d7a361f0683a032bcca28513a7bbedc28c77cfcc6719e6f6cea156c0ff1108df",
142- "https://deno.land/x/deno_cache@0.2.1/file_fetcher.ts": "352702994c190c45215f3b8086621e117e88bc2174b020faefb5eca653d71d6a",
143- "https://deno.land/x/deno_cache@0.2.1/http_cache.ts": "af1500149496e2d0acadec24569e2a9c86a3f600cceef045dcf6f5ce8de72b3a",
144- "https://deno.land/x/deno_cache@0.2.1/mod.ts": "709ab9d1068be5fd77b020b33e7a9394f1e9b453553b1e2336b72c90283cf3c0",
145- "https://deno.land/x/deno_cache@0.2.1/util.ts": "652479928551259731686686ff2df6f26bc04e8e4d311137b2bf3bc10f779f48",
146- "https://deno.land/x/deno_graph@0.6.0/lib/deno_graph.generated.js": "3e1cccd6376d4ad0ea789d66aa0f6b19f737fa8da37b5e6185ef5c269c974f54",
147- "https://deno.land/x/deno_graph@0.6.0/lib/loader.ts": "13a11c1dea0d85e0ad211be77217b8c06138bbb916afef6f50a04cca415084a9",
148- "https://deno.land/x/deno_graph@0.6.0/lib/media_type.ts": "36be751aa63d6ae36475b90dca5fae8fd7c3a77cf13684c48cf23a85ee607b31",
149- "https://deno.land/x/deno_graph@0.6.0/lib/snippets/deno_graph-1c138d6136337537/src/deno_apis.js": "f13f2678d875372cf8489ceb7124623a39fa5bf8de8ee1ec722dbb2ec5ec7845",
150- "https://deno.land/x/deno_graph@0.6.0/lib/types.d.ts": "68cb232e02a984658b40ffaf6cafb979a06fbfdce7f5bd4c7a83ed1a32a07687",
151- "https://deno.land/x/deno_graph@0.6.0/mod.ts": "8fe3d39bdcb273adfb41a0bafbbaabec4c6fe6c611b47fed8f46f218edb37e8e",
152- "https://deno.land/x/dnt@0.17.0/lib/compiler.ts": "b741c149adaa3ed87877324ce4365fbb66e8ecbfb5bc417495dcb1ac2936f9ff",
153- "https://deno.land/x/dnt@0.17.0/lib/compiler_transforms.ts": "b14a398776f08d57a3a011c8c4b5a6a557032c4b125264f86f7877d3f0eef657",
154- "https://deno.land/x/dnt@0.17.0/lib/mod.deps.ts": "d6c6eb09013c471584bc271261ed896d9f0f6552620ac7d1d65cb46e57841236",
155- "https://deno.land/x/dnt@0.17.0/lib/npm_ignore.ts": "36fe32008cd71e995bc08569d2b43e8fba816cbada82fa37d1fe52358d5a2e17",
156- "https://deno.land/x/dnt@0.17.0/lib/package_json.ts": "c8ea56c948fa2379de2a1cf76d335d1b1c70a8c122e1d11932a84f4b70905275",
157- "https://deno.land/x/dnt@0.17.0/lib/pkg/dnt_wasm.js": "fc7ae54f777566ef7769825491cb868da9f92ec4453326c075ce1b3b2bdf06f3",
158- "https://deno.land/x/dnt@0.17.0/lib/pkg/dnt_wasm_bg.ts": "51742660365970445ca1e010d340085c7b1188166cc7cdb0d2ba72b3328f276d",
159- "https://deno.land/x/dnt@0.17.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "2f623f83602d4fbb30caa63444b10e35b45e9c2b267e49585ec9bb790a4888d8",
160- "https://deno.land/x/dnt@0.17.0/lib/shims.ts": "f9f7defa1cadaec4f04f47ac62757ffa47f83b4a5825fe68cbbfcf14086fcce7",
161- "https://deno.land/x/dnt@0.17.0/lib/test_runner/get_test_runner_code.ts": "5fe5543c8479b5f17c58db4d994de3f3d573e3ca7e4c32c7cf8e338e8e900ba7",
162- "https://deno.land/x/dnt@0.17.0/lib/test_runner/test_runner.ts": "fc93277907a49bdc098237619da7ff6898547f6f25b71c026375a81efbc8907b",
163- "https://deno.land/x/dnt@0.17.0/lib/transform.deps.ts": "0b86d5ad77af5b53ad72308fbf5b694abea1f0593652a662be7529ae60c540cb",
164- "https://deno.land/x/dnt@0.17.0/lib/types.ts": "8506b5ced3921a6ac2a1d5a2bb381bfdbf818c68207f14a1a1fffbf48ee95886",
165- "https://deno.land/x/dnt@0.17.0/lib/utils.ts": "d2681d634dfa6bd4ad2a32ad15bd419f6f1f895e06c0bf479455fbf1c5f49cd9",
166- "https://deno.land/x/dnt@0.17.0/mod.ts": "1cd16a64fa1861d624d6e9e02eaa0dfa733f8e2101686fb90e05bbb96ce5221d",
167- "https://deno.land/x/dnt@0.17.0/transform.ts": "0b1dc1fdf860b594fb50a3aee54bc8c22220f4601f1d227f4113b9b4c0c5166f",
168- "https://deno.land/x/effection@3.0.0-alpha.6/mod.ts": "b443a1e17d89f59ad847b1d1efc90465f81891d8e55c04c063d0c1c6c80dd7ed",
169+ "https://deno.land/x/deno_cache@0.4.1/auth_tokens.ts": "5fee7e9155e78cedf3f6ff3efacffdb76ac1a76c86978658d9066d4fb0f7326e",
170+ "https://deno.land/x/deno_cache@0.4.1/cache.ts": "51f72f4299411193d780faac8c09d4e8cbee951f541121ef75fcc0e94e64c195",
171+ "https://deno.land/x/deno_cache@0.4.1/deno_dir.ts": "f2a9044ce8c7fe1109004cda6be96bf98b08f478ce77e7a07f866eff1bdd933f",
172+ "https://deno.land/x/deno_cache@0.4.1/deps.ts": "8974097d6c17e65d9a82d39377ae8af7d94d74c25c0cbb5855d2920e063f2343",
173+ "https://deno.land/x/deno_cache@0.4.1/dirs.ts": "d2fa473ef490a74f2dcb5abb4b9ab92a48d2b5b6320875df2dee64851fa64aa9",
174+ "https://deno.land/x/deno_cache@0.4.1/disk_cache.ts": "1f3f5232cba4c56412d93bdb324c624e95d5dd179d0578d2121e3ccdf55539f9",
175+ "https://deno.land/x/deno_cache@0.4.1/file_fetcher.ts": "07a6c5f8fd94bf50a116278cc6012b4921c70d2251d98ce1c9f3c352135c39f7",
176+ "https://deno.land/x/deno_cache@0.4.1/http_cache.ts": "f632e0d6ec4a5d61ae3987737a72caf5fcdb93670d21032ddb78df41131360cd",
177+ "https://deno.land/x/deno_cache@0.4.1/mod.ts": "ef1cda9235a93b89cb175fe648372fc0f785add2a43aa29126567a05e3e36195",
178+ "https://deno.land/x/deno_cache@0.4.1/util.ts": "8cb686526f4be5205b92c819ca2ce82220aa0a8dd3613ef0913f6dc269dbbcfe",
179+ "https://deno.land/x/deno_graph@0.26.0/lib/deno_graph.generated.js": "2f7ca85b2ceb80ec4b3d1b7f3a504956083258610c7b9a1246238c5b7c68f62d",
180+ "https://deno.land/x/deno_graph@0.26.0/lib/loader.ts": "380e37e71d0649eb50176a9786795988fc3c47063a520a54b616d7727b0f8629",
181+ "https://deno.land/x/deno_graph@0.26.0/lib/media_type.ts": "222626d524fa2f9ebcc0ec7c7a7d5dfc74cc401cc46790f7c5e0eab0b0787707",
182+ "https://deno.land/x/deno_graph@0.26.0/lib/snippets/deno_graph-de651bc9c240ed8d/src/deno_apis.js": "41192baaa550a5c6a146280fae358cede917ae16ec4e4315be51bef6631ca892",
183+ "https://deno.land/x/deno_graph@0.26.0/lib/types.d.ts": "2bbdbf895321d1df8db511fab00160a0211c09c2e7cac56c522dd6e9ed6d2ef7",
184+ "https://deno.land/x/deno_graph@0.26.0/mod.ts": "11131ae166580a1c7fa8506ff553751465a81c263d94443f18f353d0c320bc14",
185+ "https://deno.land/x/dnt@0.35.0/lib/compiler.ts": "3259975196f44391525e4e3e96c1f95c3a90dd25ec31a1948ccd7c6b71323d76",
186+ "https://deno.land/x/dnt@0.35.0/lib/compiler_transforms.ts": "cbb1fd5948f5ced1aa5c5aed9e45134e2357ce1e7220924c1d7bded30dcd0dd0",
187+ "https://deno.land/x/dnt@0.35.0/lib/mod.deps.ts": "30367fc68bcd2acf3b7020cf5cdd26f817f7ac9ac35c4bfb6c4551475f91bc3e",
188+ "https://deno.land/x/dnt@0.35.0/lib/npm_ignore.ts": "b430caa1905b65ae89b119d84857b3ccc3cb783a53fc083d1970e442f791721d",
189+ "https://deno.land/x/dnt@0.35.0/lib/package_json.ts": "2d629dbaef8004971e38ce3661f04b915a35342b905c3d98ff4a25343c2a8293",
190+ "https://deno.land/x/dnt@0.35.0/lib/pkg/dnt_wasm.generated.js": "ad5c205f018b2bc6258d00d6a0539c2ffa94275f16f106f0f072bcf77f3c786b",
191+ "https://deno.land/x/dnt@0.35.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "a6b95adc943a68d513fe8ed9ec7d260ac466b7a4bced4e942f733e494bb9f1be",
192+ "https://deno.land/x/dnt@0.35.0/lib/shims.ts": "d12e2c3c70eab1b0b95c46f35771f9798d7be7b3b6d0af6b82cad068d26d746f",
193+ "https://deno.land/x/dnt@0.35.0/lib/test_runner/get_test_runner_code.ts": "2a4e26aa33120f3cc9e03b8538211a5047a4bad4c64e895944b87f2dcd55d904",
194+ "https://deno.land/x/dnt@0.35.0/lib/test_runner/test_runner.ts": "b91d77d9d4b82984cb2ba7431ba6935756ba72f62e7dd4db22cd47a680ebd952",
195+ "https://deno.land/x/dnt@0.35.0/lib/transform.deps.ts": "e42f2bdef46d098453bdba19261a67cf90b583f5d868f7fe83113c1380d9b85c",
196+ "https://deno.land/x/dnt@0.35.0/lib/types.ts": "5735f10d9f1ff741dd3a5518b46365a38bc6e037fc0996d5bcaf2d6e8c2f805b",
197+ "https://deno.land/x/dnt@0.35.0/lib/utils.ts": "878b7ac7003a10c16e6061aa49dbef9b42bd43174853ebffc9b67ea47eeb11d8",
198+ "https://deno.land/x/dnt@0.35.0/mod.ts": "3ee53f4d4d41df72e57ecbca9f3c2b7cf86166ef57fa04452865780f83c555a9",
199+ "https://deno.land/x/dnt@0.35.0/transform.ts": "1b127c5f22699c8ab2545b98aeca38c4e5c21405b0f5342ea17e9c46280ed277",
200 "https://deno.land/x/effection@3.0.0-alpha.7/lib/abort-signal.ts": "31224b5f61f1ef58a2acf72bfc1cb9c9bf589f15c263b6555eb06f1b49f77b54",
201 "https://deno.land/x/effection@3.0.0-alpha.7/lib/async.ts": "f12c5d8f4b21d9b6281dec505cbd40455144017f1087d637cecddb6b41d978c1",
202 "https://deno.land/x/effection@3.0.0-alpha.7/lib/call.ts": "69c465573031e6315e375c17e01e820239e01a93107866dd9f5ef584b79d13dd",
203@@ -253,106 +257,202 @@
204 "https://deno.land/x/expect@v0.3.0/matchers.ts": "a37ef4577739247af77a852cdcd69484f999a41ad86ec16bb63a88a7a47a2372",
205 "https://deno.land/x/expect@v0.3.0/mock.ts": "562d4b1d735d15b0b8e935f342679096b64fe452f86e96714fe8616c0c884914",
206 "https://deno.land/x/expect@v0.3.0/mod.ts": "0304d2430e1e96ba669a8495e24ba606dcc3d152e1f81aaa8da898cea24e36c2",
207- "https://deno.land/x/ts_morph@13.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9",
208- "https://deno.land/x/ts_morph@13.0.0/bootstrap/ts_morph_bootstrap.d.ts": "1dd46307a28a8e689ce214cb0fe260a280e7ecbaceb6fbae3cbcaa8a25a9fd3f",
209- "https://deno.land/x/ts_morph@13.0.0/bootstrap/ts_morph_bootstrap.js": "5b0c39c5a35d1445bfccfda2a31bf6235e269aacc304b2c6f21ec6fb3c346b26",
210- "https://deno.land/x/ts_morph@13.0.0/common/DenoRuntime.ts": "b9ac7200ac980c1aea503cf8302e6581c8ddcdc3dbca850d0dd5a43969d7d16e",
211- "https://deno.land/x/ts_morph@13.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed",
212- "https://deno.land/x/ts_morph@13.0.0/common/ts_morph_common.d.ts": "32345569d0356af0870227ca7cbc61a25d7f5e9e6b47ec2b3dca267f8f711840",
213- "https://deno.land/x/ts_morph@13.0.0/common/ts_morph_common.js": "7a63dede12ac30195099f42a07f8ae139799738351ed66e34d872e842d2ca687",
214- "https://deno.land/x/ts_morph@13.0.0/common/typescript.d.ts": "bbfbfa714a8f9fdf10e9483b4abd82ccb546fc0fc069ad80d0a254fa40930e98",
215- "https://deno.land/x/ts_morph@13.0.0/common/typescript.js": "dca27ae23ee7eaa46a03e7e7216755186a4c5aff76273e0a66d30358be0e121b",
216- "https://esm.sh/@reduxjs/toolkit@1.9.5": "ff3c8c03a33f48803490eb006f7ffdb725a40cd1957e6d3b61ed846ffcc32b9d",
217- "https://esm.sh/react@18.2.0": "742d8246041966ba1137ec8c60888c35882a9d2478bce63583875f86c1e3687c",
218+ "https://deno.land/x/mock_fetch@0.3.0/mod.ts": "7e7806c65ab17b2b684c334c4e565812bdaf504a3e9c938d2bb52bb67428bc89",
219+ "https://deno.land/x/ts_morph@18.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9",
220+ "https://deno.land/x/ts_morph@18.0.0/bootstrap/ts_morph_bootstrap.d.ts": "607e651c5ae5aa57c2ac4090759a6379e809c0cdc42114742ac67353b1a75038",
221+ "https://deno.land/x/ts_morph@18.0.0/bootstrap/ts_morph_bootstrap.js": "6645ac03c5e6687dfa8c78109dc5df0250b811ecb3aea2d97c504c35e8401c06",
222+ "https://deno.land/x/ts_morph@18.0.0/common/DenoRuntime.ts": "6a7180f0c6e90dcf23ccffc86aa8271c20b1c4f34c570588d08a45880b7e172d",
223+ "https://deno.land/x/ts_morph@18.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed",
224+ "https://deno.land/x/ts_morph@18.0.0/common/ts_morph_common.d.ts": "42a92b8263878ef48b60042dbb55adda3face9abdb8d503be4b4f0fe242f25f4",
225+ "https://deno.land/x/ts_morph@18.0.0/common/ts_morph_common.js": "845671ca951073400ce142f8acefa2d39ea9a51e29ca80928642f3f8cf2b7700",
226+ "https://deno.land/x/ts_morph@18.0.0/common/typescript.d.ts": "21c0786dddf52537611499340166278507eb9784628d321c2cb6acc696cba0f6",
227+ "https://deno.land/x/ts_morph@18.0.0/common/typescript.js": "d5c598b6a2db2202d0428fca5fd79fc9a301a71880831a805d778797d2413c59",
228+ "https://esm.sh/@reduxjs/toolkit@1.9.5?pin=v122": "416b310f0a0a16d47e456062344b65039acfd879b3e71bc2350208b8dfa6c5f5",
229+ "https://esm.sh/@testing-library/react@14.0.0?pin=v122": "d3ef63d4106b8399a0fe975d206cd5bb61bd5739525128fa7f67014461b5cf46",
230+ "https://esm.sh/react-redux@8.0.5?pin=v122": "f653f94604572dbd2cbd69d882e97172421b6a2e8c89444aff64ac696f50bac0",
231+ "https://esm.sh/react@18.2.0?pin=v122": "742d8246041966ba1137ec8c60888c35882a9d2478bce63583875f86c1e3687c",
232+ "https://esm.sh/redux-batched-actions@0.5.0?pin=v122": "beb7e1f38fd689d68208138d857b4c39aafe7ab66c423ce0c3d5398e77545afc",
233+ "https://esm.sh/robodux@15.0.1?pin=v122": "eeb98b8e64edf0cf5f73fbd462565383f94cba3a0b4891265c8c1feb64af370e",
234 "https://esm.sh/stable/react@18.2.0/deno/react.mjs": "a5a73ee24acca4744ee22c51d9357f31968d1f684ce253bde222b4e26d09f49f",
235- "https://esm.sh/v113/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea",
236- "https://esm.sh/v113/@types/react@18.0.28/global.d.ts": "bbdf156fea2fabed31a569445835aeedcc33643d404fcbaa54541f06c109df3f",
237- "https://esm.sh/v113/@types/react@18.0.28/index.d.ts": "bb79735dbe388c3c7446e3436a22e8161ff87b4eed1932a97dc08e5edc1ab798",
238- "https://esm.sh/v113/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",
239- "https://esm.sh/v113/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383",
240- "https://esm.sh/v115/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea",
241- "https://esm.sh/v115/@types/react@18.0.34/global.d.ts": "49a253ec027e56c55c7450a0c331cfe96212b3d1cc215b1710ba94a083404cf3",
242- "https://esm.sh/v115/@types/react@18.0.34/index.d.ts": "d5208107882e0e3409a5ebc1f1cd1b7cd8b22865688ac9184aab8abc1ce6231d",
243- "https://esm.sh/v115/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",
244- "https://esm.sh/v115/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288",
245- "https://esm.sh/v116/@babel/runtime@7.21.0/deno/helpers/esm/objectSpread2.js": "d6df47b28fa0ea915b43fd191239c2a303ea2d9c27319247e6330cbb3f3300d5",
246- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/deno/toolkit.mjs": "9a9e4ff26a321ac112f19166c2fdef4f7c5764f2f22dcdf44edc07c0c7afd5fc",
247- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/autoBatchEnhancer.d.ts": "1588dc63251f2ff156856e7d372c8fcf91ce2aba4214b7554af0741d48f55dba",
248- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/configureStore.d.ts": "529b36eb303f42a6fe5ac2762101089c13b317266ea2aee807db58b085c1bf29",
249- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/createAction.d.ts": "da69802abe75b0d872254de829659d2d0abde88087bd6615e71570d9df393537",
250- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/createAsyncThunk.d.ts": "52f167b5fe797d104a06fd547c52524ed1e47ed0e133f908bb858fa8d3200c39",
251- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/createDraftSafeSelector.d.ts": "7c30461494df47caf22d491c61b4c163bfa41a8cd0dd3187a5fd3ecdfe80d064",
252- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/createReducer.d.ts": "bfb74d106fcdbe7d3e8980ec4dad56beac15bdbc789834e406ac156b7aedce98",
253- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/createSlice.d.ts": "be53d34fe419fdf7328c61fac39a6b4dc67dbe72d685df4c6e87d3bb67fb7ace",
254- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/devtoolsExtension.d.ts": "42b582be2f188c44ef9c3ea7f1e4a331f96aa295ad7a8458d5510c2ac0cf5ba4",
255- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/entities/create_adapter.d.ts": "86047e291068bd62b05fd24479a1b8dc3b25f0e5a7b045c8e7836b937b410a10",
256- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/entities/models.d.ts": "573a8601ded5b0d4cb13710deaba6f587730963b5eef0616fa2833eaf95a2c12",
257- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/getDefaultMiddleware.d.ts": "72a93432821fa87e8fe3b4caa49a1300099c62b9532bb8906f226fd15cc144d7",
258- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/immutableStateInvariantMiddleware.d.ts": "b4826e2f70b1f969f31ca101e846ac2b975fe52ae415d069e0270eb71eecc4ef",
259- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/index.d.ts": "94d426259dcdaa9fd1a4dd6cf6fc68e43b849be4430bcd5cc4f718b4eb1933bf",
260- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/isPlainObject.d.ts": "9eb137d865a0d950d6ca3464986491ff4630bebfcad9bb8a51693e141414d5b5",
261- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/exceptions.d.ts": "ea4d6bde3b49be5fffdf07ea5378141b254fd9ae32082ff1c1b407e645bf1466",
262- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/index.d.ts": "aa3769da6c50472cde1f6b23e52e4d2c6c5534dd6f77ce5f2a71557a2c011154",
263- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/types.d.ts": "34933f88d0e39efbf49fc4590f6552b06a133d782060281d12ad718ab3e24bc3",
264- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/mapBuilders.d.ts": "4764ddd4bc8fa66d605bbdc3917ab982da9bbaa82853e64625cf0ef14a677560",
265- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/matchers.d.ts": "4ef992436eae16230a9188019008a43b3264768c126a007e5476f4cb337967f9",
266- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/nanoid.d.ts": "cba4372698930577bb0f3e9f8a959bb9fc9c0d0de9b118d4225584132fea3134",
267- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/serializableStateInvariantMiddleware.d.ts": "0b8f76b39f169a03258d1c90456d1662d016f58129423bb232d4df2945826fc9",
268- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/tsHelpers.d.ts": "a36ad3bb45c9ce0b2c8f0a9a45a6f66450f99dd178f2b2c50bf8eafa351f2147",
269- "https://esm.sh/v116/@reduxjs/toolkit@1.9.5/dist/utils.d.ts": "d1f2b2ed3cad61db965c649bb3de54eba971a076d52dcc1cd3db3adde2fe3af6",
270- "https://esm.sh/v116/immer@9.0.21/deno/immer.mjs": "72a04644488470cf7cdd867cc83736a7eb1b591ac522aad760a76f335ad33244",
271- "https://esm.sh/v116/immer@9.0.21/dist/core/current.d.ts": "99cf6d12499eac02b32c6fa20a8d361a43575ff2fbb774091921c1fa948d046a",
272- "https://esm.sh/v116/immer@9.0.21/dist/core/finalize.d.ts": "4e39c4f3c1a84861990fdf88c2ae5fd629e2445f6a8f24bff38fcfd95992b3b6",
273- "https://esm.sh/v116/immer@9.0.21/dist/core/immerClass.d.ts": "16648246bb3e97f7cb5fcc27ed75b158745e05fe838249195d65b37df3c9f4af",
274- "https://esm.sh/v116/immer@9.0.21/dist/core/proxy.d.ts": "152b6b88eb7d551ddee1aac42693b234c06b7a0914de2c36e947db2080ef8b97",
275- "https://esm.sh/v116/immer@9.0.21/dist/core/scope.d.ts": "54bbcd36a79afff3b496e8dcba7793bd90756466c41823804f404cec25d6401e",
276- "https://esm.sh/v116/immer@9.0.21/dist/immer.d.ts": "bc17494a8b7026424ff0c544ea0e3f0502d8f1597daba3c51b7eadee545b321c",
277- "https://esm.sh/v116/immer@9.0.21/dist/internal.d.ts": "db4029f5b7d82fe41bedb97f6486bc2c64b844e0d0356858679d1aab41c03688",
278- "https://esm.sh/v116/immer@9.0.21/dist/plugins/all.d.ts": "53361b025e519ff36cbaf171d3d119cb02964a9bf8e5e3a460010067e4048b14",
279- "https://esm.sh/v116/immer@9.0.21/dist/plugins/es5.d.ts": "20f03fd18464739bf7a688a622c684018c532ed7fdff414da90969d3a4f752f9",
280- "https://esm.sh/v116/immer@9.0.21/dist/plugins/mapset.d.ts": "72b014db5edcf7c04a88c8eefe7c02664c0513db13aab5810956063ce6716128",
281- "https://esm.sh/v116/immer@9.0.21/dist/plugins/patches.d.ts": "b7801c3343f3673b0caa4801952d0620b91914d447252aacba88fc1dfb5c7f6a",
282- "https://esm.sh/v116/immer@9.0.21/dist/types/types-external.d.ts": "9cfa1a46692b51ce18dafbce5259f9c0971832ab1f5201a89a70e23b0b7cf9e2",
283- "https://esm.sh/v116/immer@9.0.21/dist/types/types-internal.d.ts": "fca1f054dc1076d04d0d98db29996c954e8d02033c9c403ac757e9c10bbe52f3",
284- "https://esm.sh/v116/immer@9.0.21/dist/utils/common.d.ts": "c4438b8655cc0415fee50e035ce9e238c1c017f2098931cbf2ec425f38a6fce5",
285- "https://esm.sh/v116/immer@9.0.21/dist/utils/env.d.ts": "f5a35aab17a4fb73b9bca92b3db33cc10649f2af281862a01231ce8d0d500d40",
286- "https://esm.sh/v116/immer@9.0.21/dist/utils/errors.d.ts": "7a43554d2b957482977f76623246b78fab4bdfee55daae1531300f37602d4a88",
287- "https://esm.sh/v116/immer@9.0.21/dist/utils/plugins.d.ts": "282ca31db57b351ef685ea90d97dea434755390329b1297f3d67ee06dae144d0",
288- "https://esm.sh/v116/redux-thunk@2.4.2/deno/redux-thunk.mjs": "687502e01f5778ac5b2405b2d9b9821fc1d78f4e31f468a43ec2f29abb0bf16b",
289- "https://esm.sh/v116/redux-thunk@2.4.2/es/index.d.ts": "09ea3a885943344abff8c2abd11dd2644b923128cf8496f7153bfd27acb3a9d8",
290- "https://esm.sh/v116/redux-thunk@2.4.2/es/types.d.ts": "16d082070829e6986cc7e73958a398c39fc01359fb00ca14a4b29fdf5e4c1283",
291- "https://esm.sh/v116/redux@4.2.1/deno/redux.mjs": "47f30326af233ec342f863df2d327f074fa2b7b9c71ce648079fe0f8c46a1dc7",
292- "https://esm.sh/v116/redux@4.2.1/index.d.ts": "fd624f7d7b264922476685870f08c5e1c6d6a0f05dee2429a9747b41f6b699d4",
293- "https://esm.sh/v116/reselect@4.1.8/deno/reselect.mjs": "2de96350507153619ce7cdbd1a760c814b3974395f4b5bc8bd887d3b04b09028",
294- "https://esm.sh/v116/reselect@4.1.8/es/defaultMemoize.d.ts": "2d8a1d85b6155ca124eb6ce4d1043471e77b7b8aedda8f2eedb631aac1057416",
295- "https://esm.sh/v116/reselect@4.1.8/es/index.d.ts": "55a54814090dda59f242a9c891a6a57a985350e5dc0156646a42e88f400c8dd4",
296- "https://esm.sh/v116/reselect@4.1.8/es/types.d.ts": "0cf2ff23a287d45bf80b88e0c3aefa60881546a0184cae70af91fbc308d016dd",
297- "https://esm.sh/v116/reselect@4.1.8/es/versionedTypes/index.d.ts": "ed1c984ea8ee3824ab9e262580b845d407f8ca998d7e324a468981e2b461bad3",
298- "https://esm.sh/v116/reselect@4.1.8/es/versionedTypes/ts47-mergeParameters.d.ts": "1b3a9c3b73e5b61b48c6fc131806b5197a8243e2fa863956717173fbef934420",
299- "https://esm.sh/v117/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea",
300- "https://esm.sh/v117/@types/react@18.0.37/global.d.ts": "49a253ec027e56c55c7450a0c331cfe96212b3d1cc215b1710ba94a083404cf3",
301- "https://esm.sh/v117/@types/react@18.0.37/index.d.ts": "f901ba0415ae35c8f062758b2a849fe3aef2ce219323d2495114232c3afe9227",
302- "https://esm.sh/v117/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",
303- "https://esm.sh/v117/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288",
304- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/abort-signal.ts": "31224b5f61f1ef58a2acf72bfc1cb9c9bf589f15c263b6555eb06f1b49f77b54",
305- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/async.ts": "c3eae4b79fdd0338ca77915bea79e7df142293a55903c0ef774f5cf633b20171",
306- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/callback.ts": "db306b0b8860cb40dc8e47f18467b24161cfa1bbc7f99fc15fbdd4665fa02a88",
307- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/channel.ts": "ee820fb6bb716ec1c964ac8dea80ffc065a52a940a99cd72b8ae9f7aeca3a665",
308- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/context.ts": "2b7bcdb756adc12f6f431a250435492d8010e2eec9f75e0dacfdaeb68cd8559a",
309- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/deps.ts": "91062b4b97089a8cf36550d4f9605d325a0fd19bebc72d15524481a3b56ea669",
310- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/first.ts": "480621c722e8578c392d690647841785f333397f4eea32df2ecae47de5885279",
311- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/future.ts": "b8c222e9c6952be2dd66cad9c5737a5080f0ca37c9df9e986abd80fdea43ee97",
312- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/instructions.ts": "5ff5c044e2184552972536ddc5de0b8967610c9886e7ee4dd6077b52bff709a5",
313- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/lazy.ts": "92ea526c5ad7d88290f2a87168e038d482f97421379508d85cf2e049ee60639b",
314- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/log.ts": "312b6fb4226be5554b945bc9eb7b05ed7b2dd53dd139ca86971bef256eb78997",
315- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/mod.ts": "d10ab86eaa29695fc3ba4714d2788d85939c78e6b00184e659433ef8ae52384f",
316- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run.ts": "5c1eb89e19246ee4d47ec55c1b7b88842366151c1caabd5f3144b3d59e9d30db",
317- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run/block.ts": "50d8b1e85690f957abb4b1c4345e98c78c111c71edc8f5ff96a5a4d39f62e8a2",
318- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run/create.ts": "be9139af2fbe15908256d2d159dec8dca079f94cf02d488074c94fa26fc651fa",
319- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run/event-stream.ts": "8c6d40bf315652535a85aa02a61256678dc4e537a4fb436fbe5fecdad794c076",
320- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run/frame.ts": "01296da446da0c23527fc6242eb0a007dc2d3d1cd590d589f647cb571f96a364",
321- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/run/scope.ts": "3b24605305f033778307b327f14f8f1e9b7c5badf7791bd37eb3b25826f023e5",
322- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/sleep.ts": "d7000c05ace8143efd80f1e35243f5521316bc334e4051afe692e1a0c7268304",
323- "https://raw.githubusercontent.com/cowboyd/instructional-effection/v0.0.3/types.ts": "f6ac6ddd71a6464c78a0fb64c189903a148d97973e673868b9be98d4d8dd42d9"
324+ "https://esm.sh/v118/@types/prop-types@15.7.5/index.d.ts": "6a386ff939f180ae8ef064699d8b7b6e62bc2731a62d7fbf5e02589383838dea",
325+ "https://esm.sh/v118/@types/react@18.2.6/global.d.ts": "549df62b64a71004aee17685b445a8289013daf96246ce4d9b087d13d7a27a61",
326+ "https://esm.sh/v118/@types/react@18.2.6/index.d.ts": "2ef11908f0e5f1cae0a42694e5e38852c976ca99a5d7eecfb71ed6a1187736c7",
327+ "https://esm.sh/v118/@types/scheduler@0.16.3/tracing.d.ts": "f5a8b384f182b3851cec3596ccc96cb7464f8d3469f48c74bf2befb782a19de5",
328+ "https://esm.sh/v118/csstype@3.1.2/index.d.ts": "4c68749a564a6facdf675416d75789ee5a557afda8960e0803cf6711fa569288",
329+ "https://esm.sh/v122/@babel/runtime@7.21.5/deno/helpers/esm/extends.js": "084ce29d7ef3c5b201cb94c2226b48b24d752ce0f513fa2e6d943a10fee65744",
330+ "https://esm.sh/v122/@babel/runtime@7.21.5/deno/helpers/esm/objectSpread2.js": "1d4171276a85bdbb08076396490d12e880715d119f109cd1052b15b6dccd1425",
331+ "https://esm.sh/v122/@babel/runtime@7.21.5/deno/helpers/esm/objectWithoutPropertiesLoose.js": "68e69ae7b19401eb69fed31a606a16a571a656ce9969d1decc94a01777d96157",
332+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/deno/toolkit.mjs": "5323beb1a41a4e43c583a0d8dae67e88faa57a0f9742a4ca3272239dd3e776ab",
333+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/autoBatchEnhancer.d.ts": "163311515f4f9c1affba0f76627c04cac57c5c6ab4631cddd3e8fe63e15f6c38",
334+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/configureStore.d.ts": "b4621f57bea1e090c2bf884b79ba024c1f08ec7f55aaafa73ce6437294d170a7",
335+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/createAction.d.ts": "40cd61ce7d19c36d58c2b2723d0ce98a117da61446109c5606f5855ba70af2e3",
336+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/createAsyncThunk.d.ts": "96373f0e963f9660e847f33e339ea6fe5d925cf968786bcaf06530a1a5fe5e52",
337+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/createDraftSafeSelector.d.ts": "76ce4e1118a699d33257c4744e9104ce60259e2aa7c587ee6492e8b6c70eed9d",
338+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/createReducer.d.ts": "640131a2d58a76a0527855c6d2e7a39b9efbb26315c58d6b13885d5e6c584b2e",
339+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/createSlice.d.ts": "e4eeaed797c053bcb4d701cbf6ed0ca494d68df41a56cda1440f035743b83df6",
340+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/devtoolsExtension.d.ts": "c698439b3014a87eeed236e3b0d73af3c65d64daa1c9ee776d179f29fc914b21",
341+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/entities/create_adapter.d.ts": "86047e291068bd62b05fd24479a1b8dc3b25f0e5a7b045c8e7836b937b410a10",
342+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/entities/models.d.ts": "573a8601ded5b0d4cb13710deaba6f587730963b5eef0616fa2833eaf95a2c12",
343+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/getDefaultMiddleware.d.ts": "cd206f3e3df4e2664effd6f20b736497794d93977f2b29a6928a30bc3cb259ae",
344+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/immutableStateInvariantMiddleware.d.ts": "09b07b1bd91a39b16f971bbfe3cec6b85c39fb114127a9ac5264b44673c54e34",
345+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/index.d.ts": "2dd91a9c89118d471af0fae8ab84086b2c3005287176f6d257e728bd3b34513b",
346+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/isPlainObject.d.ts": "9eb137d865a0d950d6ca3464986491ff4630bebfcad9bb8a51693e141414d5b5",
347+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/exceptions.d.ts": "b240581208fa099b57df45ec7b2aed98de1fdec5835122c69c701818dbdb9d99",
348+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/index.d.ts": "1e9413df5d26e38d9b2570595a1d839d49aa7d3daebee8221b3bcb43158a9bf2",
349+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/listenerMiddleware/types.d.ts": "bd18316ccf53436aa56ac095ecb9100ed23a2afe11fdbc77461a1dda9eede8c1",
350+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/mapBuilders.d.ts": "2ce69dce79f37d194e6dc0e610ec62ca2a1a5906e9186748a9fce46299923c9e",
351+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/matchers.d.ts": "4ef992436eae16230a9188019008a43b3264768c126a007e5476f4cb337967f9",
352+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/nanoid.d.ts": "cba4372698930577bb0f3e9f8a959bb9fc9c0d0de9b118d4225584132fea3134",
353+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/serializableStateInvariantMiddleware.d.ts": "f9ff1b947da0074054d30826aaa38e876d1b7027b92324a346ef27c8e0865267",
354+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/tsHelpers.d.ts": "1d7bf5f58c4a24f4a654a2af61207662b910fa429fafa1d32518c4c6cc925c99",
355+ "https://esm.sh/v122/@reduxjs/toolkit@1.9.5/dist/utils.d.ts": "9f14472b238598bdf200a0de9f05dd6568e1171fd753fbd72c01a46bf336145d",
356+ "https://esm.sh/v122/@testing-library/dom@9.2.0/deno/dom.mjs": "f86d604327cdb2cfc8b36665af13a055f18fdc024f8b7624fff38f74f41f5fbc",
357+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/config.d.ts": "2a90177ebaef25de89351de964c2c601ab54d6e3a157cba60d9cd3eaf5a5ee1a",
358+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/events.d.ts": "1d2699a343a347a830be26eb17ab340d7875c6f549c8d7477efb1773060cc7e5",
359+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/get-node-text.d.ts": "a0a6f0095f25f08a7129bc4d7cb8438039ec422dc341218d274e1e5131115988",
360+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/get-queries-for-element.d.ts": "61b0bd9a20e0738fd87e67017a69df89106f12e516fdd15ce0a889f7c60d479f",
361+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/index.d.ts": "561aeabb2e1fa95bc9d1f9153ccb5e8cd8fb7ffd5a412616c3cfb24dfa613d79",
362+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/matches.d.ts": "0f4c3516347edf0800b6cbb38c84addc96de419019ea3e440d8866cc7b87733d",
363+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/pretty-dom.d.ts": "2d0c0bee7f35288ccb19c673ad8ffdf06b5f6052be8ddc4df0782b1412b5f22a",
364+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/queries.d.ts": "a2a6a00d5bd1e4a1c4782a291309c0269112492641c7167904af156a0bc7ece7",
365+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/query-helpers.d.ts": "56f39bce2cd0e3f3cdcb4bea7175eddba13ee18b91aa3ecc5d42dadc5fb64ccc",
366+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/role-helpers.d.ts": "a3ce619711ff1bcdaaf4b5187d1e3f84e76064909a7c7ecb2e2f404f145b7b5c",
367+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/screen.d.ts": "678c9811a30c4d5d94e185c4e2526bb0194b852eef79b10722920048a10b8edb",
368+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/suggestions.d.ts": "82200e963d3c767976a5a9f41ecf8c65eca14a6b33dcbe00214fcbe959698c46",
369+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/wait-for-element-to-be-removed.d.ts": "278ba90329ef4874f485dbd9f4e2ede0a71f0b10dbca0a6b0562d013f343d247",
370+ "https://esm.sh/v122/@testing-library/dom@9.2.0/types/wait-for.d.ts": "8387ec1601cf6b8948672537cf8d430431ba0d87b1f9537b4597c1ab8d3ade5b",
371+ "https://esm.sh/v122/@testing-library/react@14.0.0/deno/react.mjs": "630c48de164598d9bc2f26e7a87ae30cb89287d223ea47c034421a7c30b50f76",
372+ "https://esm.sh/v122/@testing-library/react@14.0.0/types/index.d.ts": "73edecc15b451ea14df5b698d53ce96549ae0ac1911cdfcd40e61581ac695f73",
373+ "https://esm.sh/v122/@types/aria-query@5.0.1/index.d.ts": "21522c0f405e58c8dd89cd97eb3d1aa9865ba017fde102d01f86ab50b44e5610",
374+ "https://esm.sh/v122/@types/hoist-non-react-statics@3.3.1/index.d.ts": "a84059e8ce2394008282b5a395b28820e4c2fd2da0cd4a15d0d50631b9993854",
375+ "https://esm.sh/v122/@types/react-dom@18.2.4/index.d.ts": "7f1ca21337a49885088a365081b652451342e45d8c29dfbf97243c66a3f60196",
376+ "https://esm.sh/v122/@types/react-dom@18.2.4/test-utils/index.d.ts": "00f67209791dc2295fce29aa9c7632621dd7605b83a94346275c232b33fd2927",
377+ "https://esm.sh/v122/@types/use-sync-external-store@0.0.3/index.d.ts": "61f41da9aaa809e5142b1d849d4e70f3e09913a5cb32c629bf6e61ef27967ff7",
378+ "https://esm.sh/v122/@types/use-sync-external-store@0.0.3/with-selector.d.ts": "da0195f35a277ff34bb5577062514ce75b7a1b12f476d6be3d4489e26fcf00d8",
379+ "https://esm.sh/v122/ansi-regex@5.0.1/deno/ansi-regex.mjs": "e9046a2f3bf92744288c81913b2eb394b3d92b324e27eed580c0e6402b2718ff",
380+ "https://esm.sh/v122/ansi-styles@5.2.0/deno/ansi-styles.mjs": "239ee56aafe37c20d095ae8f1690f79789b94130308e5507d7940f00d3b7553e",
381+ "https://esm.sh/v122/aria-query@5.1.3/deno/aria-query.mjs": "252fbc2c2b5798aac9cd4d4f00b8be8b4ae5a0366a7ee189a0f8768923e0201c",
382+ "https://esm.sh/v122/array-buffer-byte-length@1.0.0/deno/array-buffer-byte-length.mjs": "3a0090b61ca65d0fbee70c0ce5c0bc302e56119dd7626bcbeacd95c2f91582e8",
383+ "https://esm.sh/v122/available-typed-arrays@1.0.5/deno/available-typed-arrays.mjs": "af54a91db025307077322fbd7a753d45c16da66979d5b9f0bad3070a47c346f5",
384+ "https://esm.sh/v122/call-bind@1.0.2/deno/call-bind.mjs": "380a550c592e2c393378afd7da2b1f3cb25190ed90df015fe29cb90ce1bfcb29",
385+ "https://esm.sh/v122/call-bind@1.0.2/deno/callBound.js": "d8320a1cd92e56689c01bd6d5fa5e18b90359ee8face0987fa99902b7df80974",
386+ "https://esm.sh/v122/deep-equal@2.2.1/deno/deep-equal.mjs": "eff0dcdde8940f4fe247ba2408462b66830e4537689e2fb915cdd65225188b30",
387+ "https://esm.sh/v122/define-properties@1.2.0/deno/define-properties.mjs": "a70149a22ca1ec3924660c72bae7d5f7bb593e52bbe1257bd23947d6f5230dc8",
388+ "https://esm.sh/v122/dom-accessibility-api@0.5.16/deno/dom-accessibility-api.mjs": "e1cef803e4622d4f5c4707c572b004407d476165656c7dbfbafedf75d4bac98e",
389+ "https://esm.sh/v122/es-get-iterator@1.1.3/deno/es-get-iterator.mjs": "52ba4c2af9acd03d01931925036e1f850ed8fb4099963a2e5e0f62745e4dc7a5",
390+ "https://esm.sh/v122/for-each@0.3.3/deno/for-each.mjs": "61efde1a03cf53cf4bb08fd840451b2a168f93485be881c780bb2b9cdf094b48",
391+ "https://esm.sh/v122/function-bind@1.1.1/deno/function-bind.mjs": "85ea0ce74df7d28231e143fc7c177e6933ab9a86ea61b5675d4eea6c62de01df",
392+ "https://esm.sh/v122/functions-have-names@1.2.3/deno/functions-have-names.mjs": "de051469d191cd9819a0d88e3584d42336c041f4bc19e6c2aff3ea2c543d9bea",
393+ "https://esm.sh/v122/get-intrinsic@1.2.1/deno/get-intrinsic.mjs": "487e8637302d49c1559f967a4baf823e3e1c320dd9c094dc6cd8a089d0926ac7",
394+ "https://esm.sh/v122/gopd@1.0.1/deno/gopd.mjs": "29ce793cf3e91b96e7f8a3622982cfac19cfb55e16832f5c28ea918442c80220",
395+ "https://esm.sh/v122/has-bigints@1.0.2/deno/has-bigints.mjs": "b0acdf47c90cd03b2e54d59eea16103748bd50a78f9a1de84b1d7b0929c3a673",
396+ "https://esm.sh/v122/has-property-descriptors@1.0.0/deno/has-property-descriptors.mjs": "fd11355cd2b9aac567550e1471be95c9f82b6e53b14d845d7b8f6f67c25c42a8",
397+ "https://esm.sh/v122/has-proto@1.0.1/deno/has-proto.mjs": "1f7413341b9935520f658d9fb241eb671a13ed73378863ee118eb52c74867eed",
398+ "https://esm.sh/v122/has-symbols@1.0.3/deno/shams.js": "25687c8bedcbefa7ad0d261ace3024b8ff5e7cb058f1871aa9b600b9743d0584",
399+ "https://esm.sh/v122/has-tostringtag@1.0.0/deno/shams.js": "829db6f56bc746da2a23201f9e7753e70958fa81d3671a7fce9e63b12964fdf5",
400+ "https://esm.sh/v122/hoist-non-react-statics@3.3.2/deno/hoist-non-react-statics.mjs": "7dbd22e052d4b2e27307cd90b2080e998682532c89ef41a1d96f88efc6c7b4e1",
401+ "https://esm.sh/v122/immer@9.0.21/deno/immer.mjs": "72a04644488470cf7cdd867cc83736a7eb1b591ac522aad760a76f335ad33244",
402+ "https://esm.sh/v122/immer@9.0.21/dist/core/current.d.ts": "99cf6d12499eac02b32c6fa20a8d361a43575ff2fbb774091921c1fa948d046a",
403+ "https://esm.sh/v122/immer@9.0.21/dist/core/finalize.d.ts": "4e39c4f3c1a84861990fdf88c2ae5fd629e2445f6a8f24bff38fcfd95992b3b6",
404+ "https://esm.sh/v122/immer@9.0.21/dist/core/immerClass.d.ts": "16648246bb3e97f7cb5fcc27ed75b158745e05fe838249195d65b37df3c9f4af",
405+ "https://esm.sh/v122/immer@9.0.21/dist/core/proxy.d.ts": "152b6b88eb7d551ddee1aac42693b234c06b7a0914de2c36e947db2080ef8b97",
406+ "https://esm.sh/v122/immer@9.0.21/dist/core/scope.d.ts": "54bbcd36a79afff3b496e8dcba7793bd90756466c41823804f404cec25d6401e",
407+ "https://esm.sh/v122/immer@9.0.21/dist/immer.d.ts": "bc17494a8b7026424ff0c544ea0e3f0502d8f1597daba3c51b7eadee545b321c",
408+ "https://esm.sh/v122/immer@9.0.21/dist/internal.d.ts": "db4029f5b7d82fe41bedb97f6486bc2c64b844e0d0356858679d1aab41c03688",
409+ "https://esm.sh/v122/immer@9.0.21/dist/plugins/all.d.ts": "53361b025e519ff36cbaf171d3d119cb02964a9bf8e5e3a460010067e4048b14",
410+ "https://esm.sh/v122/immer@9.0.21/dist/plugins/es5.d.ts": "20f03fd18464739bf7a688a622c684018c532ed7fdff414da90969d3a4f752f9",
411+ "https://esm.sh/v122/immer@9.0.21/dist/plugins/mapset.d.ts": "72b014db5edcf7c04a88c8eefe7c02664c0513db13aab5810956063ce6716128",
412+ "https://esm.sh/v122/immer@9.0.21/dist/plugins/patches.d.ts": "b7801c3343f3673b0caa4801952d0620b91914d447252aacba88fc1dfb5c7f6a",
413+ "https://esm.sh/v122/immer@9.0.21/dist/types/types-external.d.ts": "9cfa1a46692b51ce18dafbce5259f9c0971832ab1f5201a89a70e23b0b7cf9e2",
414+ "https://esm.sh/v122/immer@9.0.21/dist/types/types-internal.d.ts": "fca1f054dc1076d04d0d98db29996c954e8d02033c9c403ac757e9c10bbe52f3",
415+ "https://esm.sh/v122/immer@9.0.21/dist/utils/common.d.ts": "c4438b8655cc0415fee50e035ce9e238c1c017f2098931cbf2ec425f38a6fce5",
416+ "https://esm.sh/v122/immer@9.0.21/dist/utils/env.d.ts": "f5a35aab17a4fb73b9bca92b3db33cc10649f2af281862a01231ce8d0d500d40",
417+ "https://esm.sh/v122/immer@9.0.21/dist/utils/errors.d.ts": "7a43554d2b957482977f76623246b78fab4bdfee55daae1531300f37602d4a88",
418+ "https://esm.sh/v122/immer@9.0.21/dist/utils/plugins.d.ts": "282ca31db57b351ef685ea90d97dea434755390329b1297f3d67ee06dae144d0",
419+ "https://esm.sh/v122/internal-slot@1.0.5/deno/internal-slot.mjs": "12db06def16dd59958a915f833ded3eeb7f958143dcd3941617a71ef6bfa7b05",
420+ "https://esm.sh/v122/is-arguments@1.1.1/deno/is-arguments.mjs": "5da115ea0d2747a4bebeb3fccf077a5ed9def5a85fe5f0de60989afde26b1dbc",
421+ "https://esm.sh/v122/is-array-buffer@3.0.2/deno/is-array-buffer.mjs": "56f8916d5300d88bb56c604c721a2b33217629757d186d8a9255c949d054a296",
422+ "https://esm.sh/v122/is-bigint@1.0.4/deno/is-bigint.mjs": "d384b790df301872790cdddcc9f8650354ec48748efd154653a54a0a4575f625",
423+ "https://esm.sh/v122/is-boolean-object@1.1.2/deno/is-boolean-object.mjs": "16fe4a5077e89fa9baf6a6b820b1aa0df89d4e18ff40ccb778ba62466957c11e",
424+ "https://esm.sh/v122/is-callable@1.2.7/deno/is-callable.mjs": "c36722455e75f6492e0006897afed4c0288a2045045acdff5801aeb27ee1ebad",
425+ "https://esm.sh/v122/is-date-object@1.0.5/deno/is-date-object.mjs": "8ffb0dbd0434dabfd74fdb036c6381cb53a3048b2fd7f8c16357c66f7b231809",
426+ "https://esm.sh/v122/is-map@2.0.2/deno/is-map.mjs": "e8cadfdf3d613dfea00940656584ad3ad0897534da815b7840fed2815fc93aec",
427+ "https://esm.sh/v122/is-number-object@1.0.7/deno/is-number-object.mjs": "b5550d43dfa128620d92ef4f0cbe1a5d361d39175ceb6c02e4865d357cab4e88",
428+ "https://esm.sh/v122/is-regex@1.1.4/deno/is-regex.mjs": "2a180b3fc43c77a84c73caaef62a8445cdc57c74a05b199e6792edb787ae2d25",
429+ "https://esm.sh/v122/is-set@2.0.2/deno/is-set.mjs": "88f15482f021bf438158a6b945788b6c35dea27507a14bdafd33b4668815b387",
430+ "https://esm.sh/v122/is-shared-array-buffer@1.0.2/deno/is-shared-array-buffer.mjs": "bfefdb478f4c85b22acac15e11a81f84adebc712e927287b5a7be29d2a17bc14",
431+ "https://esm.sh/v122/is-string@1.0.7/deno/is-string.mjs": "6b4683ec301751b102d3565ab2d9b74a68cc5760c9c8bc3a1abdb8bc4f957c27",
432+ "https://esm.sh/v122/is-symbol@1.0.4/deno/is-symbol.mjs": "452b3ba89c04b95ddd51653f70bc91657c5901c5cbd2f7fdea93b1d524c79dfc",
433+ "https://esm.sh/v122/is-typed-array@1.1.10/deno/is-typed-array.mjs": "0d11c17cc14088554153f1b850cbf0d3f1fb3264e5c12b0d9227cfced4f150db",
434+ "https://esm.sh/v122/is-weakmap@2.0.1/deno/is-weakmap.mjs": "20fae3683116b3b8ff78a87fdd5c65ca3c5386ff30b9652abf2af24c1eb28f95",
435+ "https://esm.sh/v122/is-weakset@2.0.2/deno/is-weakset.mjs": "b8c808f905f598cf9bab89febb15e2e63a22f951eafaf302c4fb6159137fe80c",
436+ "https://esm.sh/v122/isarray@2.0.5/deno/isarray.mjs": "1cc4c24399952c70babf37f531295df4355e3bd64a7cd14406facf8404f51666",
437+ "https://esm.sh/v122/lz-string@1.5.0/deno/lz-string.mjs": "4b47a5999d6135dcddb4de33bb49e9010bfd8c3284dc26079f434900088d294e",
438+ "https://esm.sh/v122/object-inspect@1.12.3/deno/object-inspect.mjs": "8eec27956657c7c22ce22b84cba68df4dd7513570024c87f62c8e7df1731b86d",
439+ "https://esm.sh/v122/object-is@1.1.5/deno/object-is.mjs": "e1276d267efbf13cb140dae101c8389858333f86c7b1cf5c73cfaecae1dd4223",
440+ "https://esm.sh/v122/object-keys@1.1.1/deno/object-keys.mjs": "102b4ac2862d00d8e34e6b5e9f82a1d3ed82aec7ac726e15d1cb40828c8ced7f",
441+ "https://esm.sh/v122/object.assign@4.1.4/deno/object.assign.mjs": "657c265b2123f0462796bc126dcbf613bc7394cc09160aa569652b32cd702c4c",
442+ "https://esm.sh/v122/pretty-format@27.5.1/build/index.d.ts": "56a50e283257c7fc16958f9e31d8260262505551782a6afc31030970ff48775a",
443+ "https://esm.sh/v122/pretty-format@27.5.1/build/types.d.ts": "462bccdf75fcafc1ae8c30400c9425e1a4681db5d605d1a0edb4f990a54d8094",
444+ "https://esm.sh/v122/pretty-format@27.5.1/deno/pretty-format.mjs": "0e9dbbd2dc90301221d47700ee633bad42cbca2bb34d45bd1f881e2dc1cf2310",
445+ "https://esm.sh/v122/react-dom@18.2.0/deno/client.js": "b1ece336dd563def20ab5a55dc80babe034770cdd1df22a6d6f84e22853613d7",
446+ "https://esm.sh/v122/react-dom@18.2.0/deno/react-dom.mjs": "e1a40770411a2fe42b594ededa8238d31f3cac1334b0cb3fcd9ab7a34d4eafee",
447+ "https://esm.sh/v122/react-dom@18.2.0/deno/test-utils.js": "b7fa07cc73bced0dec05ed30e7cb3aaca14f97e983b7a87fbc86420bea73f7eb",
448+ "https://esm.sh/v122/react-is@16.13.1/deno/react-is.mjs": "5b3810c2d2c7421f78f37df755a1440996b54c04e5cfa7b9ca10efefbe5f329f",
449+ "https://esm.sh/v122/react-is@17.0.2/deno/react-is.mjs": "4b2a2ef2487512ba9113abf7cae5d60eb8ee6f4fde18049639204edf84ab0833",
450+ "https://esm.sh/v122/react-is@18.2.0/deno/react-is.mjs": "efe49ec2efb7a2d0d8f360e7ddb8c865c1194bf7f42125027205d68b4e7c2095",
451+ "https://esm.sh/v122/react-redux@8.0.5/deno/react-redux.mjs": "d02bb23a68461f8a6818a1b564ccb520c0fd0ca5092ecd5aaecc355bd9a7c2b7",
452+ "https://esm.sh/v122/react-redux@8.0.5/es/components/Context.d.ts": "7c66a0e01cd2d48baf1572a2f697565765140cc5cdff8e5a04b06a4295591845",
453+ "https://esm.sh/v122/react-redux@8.0.5/es/components/Provider.d.ts": "311529a26ec91ca60f064079e4133b54e2be281c707326fd86f1f6787969d403",
454+ "https://esm.sh/v122/react-redux@8.0.5/es/components/connect.d.ts": "299ff0a06a3833c39a5766c07d6635eddab9b3ed3c297dd84c122a57480e0f2f",
455+ "https://esm.sh/v122/react-redux@8.0.5/es/connect/selectorFactory.d.ts": "cba4b94121af5365d4ce689e6d711c913748634ef12bf657fd3d28f20d10c0bd",
456+ "https://esm.sh/v122/react-redux@8.0.5/es/exports.d.ts": "bf60c690ff0e05557fa8c1a7439870274741fe8a049ad532339e02f2e0b5d63b",
457+ "https://esm.sh/v122/react-redux@8.0.5/es/hooks/useDispatch.d.ts": "9c6d98ae248560494f7a8e0b1ef2b7cd1eef8a1f5231a8c62d4324d902ff5dd3",
458+ "https://esm.sh/v122/react-redux@8.0.5/es/hooks/useSelector.d.ts": "665c9bbb2ac098a5236f7582fb62e26d1ab9e0a9c8213850494cf3611cc2a0a4",
459+ "https://esm.sh/v122/react-redux@8.0.5/es/hooks/useStore.d.ts": "1d31c2e06d653151138715cc8201527ca7ea2a20e8776ee1c07049233884de72",
460+ "https://esm.sh/v122/react-redux@8.0.5/es/index.d.ts": "a4c3e41066fc4cd9c74c2f98f9e8ee0acbbe6d8f5e59ae403c7f75b0c8fe428e",
461+ "https://esm.sh/v122/react-redux@8.0.5/es/types.d.ts": "bcb9e4bebdccb122cadfab1a87adf15760694d306f9fd9ec24f5238b7f72ccf6",
462+ "https://esm.sh/v122/react-redux@8.0.5/es/utils/Subscription.d.ts": "05be35caefacdcbbc14ff6ec605c9a1458c92491448034c174a401ecb463b4f3",
463+ "https://esm.sh/v122/react-redux@8.0.5/es/utils/reactBatchedUpdates.d.ts": "3a61d355ee0e26e20e6dc05898cfc5b1f57f10996e532b22240bb3d2c5e9730f",
464+ "https://esm.sh/v122/react-redux@8.0.5/es/utils/shallowEqual.d.ts": "42d3863cb9aa6d1ffc8bfe897a1f2203a603e826848c8c41b0c8a786a2f7ecef",
465+ "https://esm.sh/v122/react-redux@8.0.5/es/utils/useSyncExternalStore.d.ts": "6d3d2341958a5fc3beab09a73d9d9280a16ce5412e0dc4b5b7a237af430c5f30",
466+ "https://esm.sh/v122/redux-batched-actions@0.5.0/deno/redux-batched-actions.mjs": "e786d6cb927e25e09cd0a08d562c7788e2943a3c45c4edf96e0a3d45f4e1d6cd",
467+ "https://esm.sh/v122/redux-batched-actions@0.5.0/lib/index.d.ts": "38139476cb2f3e6be4947e6e97c293f61c6858b5604f59b1a9908a39421e8bef",
468+ "https://esm.sh/v122/redux-thunk@2.4.2/deno/redux-thunk.mjs": "687502e01f5778ac5b2405b2d9b9821fc1d78f4e31f468a43ec2f29abb0bf16b",
469+ "https://esm.sh/v122/redux-thunk@2.4.2/es/index.d.ts": "c2319b7067346ca83427b59d3e2f63501f71d92bf7559a4ba91be5e00ec12324",
470+ "https://esm.sh/v122/redux-thunk@2.4.2/es/types.d.ts": "ea3b2c86e942ca9808195d90bda3aae12dfb8fddd43b67668e238aa6753ce3a8",
471+ "https://esm.sh/v122/redux@4.2.1/deno/redux.mjs": "5c2067c80d9b36b4ff7e19fa1ebb5bcdf4875fcdf0840035746a4654450d1a09",
472+ "https://esm.sh/v122/redux@4.2.1/index.d.ts": "fd624f7d7b264922476685870f08c5e1c6d6a0f05dee2429a9747b41f6b699d4",
473+ "https://esm.sh/v122/regexp.prototype.flags@1.5.0/deno/regexp.prototype.flags.mjs": "48819674cd2886ccd5b7cb8c882606d06b76e0382dc8f35a499b29cf07b2efa4",
474+ "https://esm.sh/v122/reselect@4.1.8/deno/reselect.mjs": "2de96350507153619ce7cdbd1a760c814b3974395f4b5bc8bd887d3b04b09028",
475+ "https://esm.sh/v122/reselect@4.1.8/es/defaultMemoize.d.ts": "2d8a1d85b6155ca124eb6ce4d1043471e77b7b8aedda8f2eedb631aac1057416",
476+ "https://esm.sh/v122/reselect@4.1.8/es/index.d.ts": "55a54814090dda59f242a9c891a6a57a985350e5dc0156646a42e88f400c8dd4",
477+ "https://esm.sh/v122/reselect@4.1.8/es/types.d.ts": "0cf2ff23a287d45bf80b88e0c3aefa60881546a0184cae70af91fbc308d016dd",
478+ "https://esm.sh/v122/reselect@4.1.8/es/versionedTypes/index.d.ts": "ed1c984ea8ee3824ab9e262580b845d407f8ca998d7e324a468981e2b461bad3",
479+ "https://esm.sh/v122/reselect@4.1.8/es/versionedTypes/ts47-mergeParameters.d.ts": "1b3a9c3b73e5b61b48c6fc131806b5197a8243e2fa863956717173fbef934420",
480+ "https://esm.sh/v122/robodux@15.0.1/deno/robodux.mjs": "670792c90500620b7d2edcf1890f3e589f2765c15f4b6bca3babd69361a29289",
481+ "https://esm.sh/v122/robodux@15.0.1/dist/combine.d.ts": "f450e144d24536a0793cf1775aae2356705245c5e724047d4bedc2f8001ad788",
482+ "https://esm.sh/v122/robodux@15.0.1/dist/create-action.d.ts": "61f663dd8b8fd207a4a2e274e86601f92400c2979b9691b71a982f353c19d2f0",
483+ "https://esm.sh/v122/robodux@15.0.1/dist/create-assign.d.ts": "bf1f5a48d6b40310799d392178973eba60ae39273e85a8e9ffcbff8cded20200",
484+ "https://esm.sh/v122/robodux@15.0.1/dist/create-list-table.d.ts": "300926e447d1ddb582a1a086483719a9f07ee3af50c51727bf534d029936f1ca",
485+ "https://esm.sh/v122/robodux@15.0.1/dist/create-list.d.ts": "131c84be65e76bff5be662ef3d7e08d4a665e2bd1a3b7c3f6dcc82004b4ccd51",
486+ "https://esm.sh/v122/robodux@15.0.1/dist/create-loader-table.d.ts": "0f6f08d4db08212f2dd3dadb2cd84190fac29d376428e0df7e9155ecdeed4efb",
487+ "https://esm.sh/v122/robodux@15.0.1/dist/create-loader.d.ts": "f2325a8588a434f725dd621c3d0d16d6c7f023c5158cd87dca8f70c4f2bd98bb",
488+ "https://esm.sh/v122/robodux@15.0.1/dist/create-map.d.ts": "2851c7ad3861c25035b8bd4e61d894af7164cb04b921e8258432ef601be9eeb8",
489+ "https://esm.sh/v122/robodux@15.0.1/dist/create-reducer.d.ts": "a0f32cafe42727eef6c67eb211dc77f19ad5a2d53d6dceaf1c87d0468df5cd13",
490+ "https://esm.sh/v122/robodux@15.0.1/dist/create-slice.d.ts": "56a3d4d6469100edfb88ba672ee2035bba2e1f807b2cbca9b88b1047952b16d6",
491+ "https://esm.sh/v122/robodux@15.0.1/dist/create-table.d.ts": "b72f2a6924ff17af20ff9198366261322c3d8793741188ee387bad675306b253",
492+ "https://esm.sh/v122/robodux@15.0.1/dist/index.d.ts": "b87f84ed5171496f497914f3cc7d65121f59bd0e8982b6d392f63f19006b0645",
493+ "https://esm.sh/v122/robodux@15.0.1/dist/types.d.ts": "f3a61fa962ed1917c97804a3127b2e6ccea283bdbe7b990dc4d3c2f0434eeca6",
494+ "https://esm.sh/v122/scheduler@0.23.0/deno/scheduler.mjs": "585bd99e523275a2447f9d1c29f36b43be0a20123e7c7915f6f4d5405ecc2884",
495+ "https://esm.sh/v122/side-channel@1.0.4/deno/side-channel.mjs": "09064d7f47f5cbfd3a8fcce2f03191ab65654e63ec670545d0519e7a3bafa347",
496+ "https://esm.sh/v122/stop-iteration-iterator@1.0.0/deno/stop-iteration-iterator.mjs": "74f2c3bd4ec754f3665cc303a5e9c512286d12cc3455da2985ad08fca697685e",
497+ "https://esm.sh/v122/use-sync-external-store@1.2.0/deno/cjs/use-sync-external-store-shim.production.min.js": "ef11625e3f8af74256cdbfb9586a54928b24429786dd9e1e10bb98808b8e1646",
498+ "https://esm.sh/v122/use-sync-external-store@1.2.0/deno/cjs/use-sync-external-store-shim/with-selector.production.min.js": "58c96b48e3170c737357ecd7b6637f9dcf1d3d7cb2407e626a1bfc9fd7eff994",
499+ "https://esm.sh/v122/use-sync-external-store@1.2.0/deno/shim.js": "00b99d32c77998883615ca14d43947d65a7102fe13f9dd1464addd74a72a3cec",
500+ "https://esm.sh/v122/use-sync-external-store@1.2.0/deno/shim/with-selector.js": "b510c0190c8491339d243df109585b6f53d49d23c7c08a92bbc66392b4b804d2",
501+ "https://esm.sh/v122/which-boxed-primitive@1.0.2/deno/which-boxed-primitive.mjs": "ea1bdc1ef3c6df1a7171bd96431111ec117967454fe2784a597db475020e27d9",
502+ "https://esm.sh/v122/which-collection@1.0.1/deno/which-collection.mjs": "f08c0f7a244d3c9caf2a369844ba61c3eabb002f74054a7630474901ac431393",
503+ "https://esm.sh/v122/which-typed-array@1.1.9/deno/which-typed-array.mjs": "1ff946187cdb2822f2d85509ef7752c6120ed209b9a726cdfd25e629bc907666"
504 }
505 }
M
deps.ts
+42,
-3
1@@ -1,12 +1,10 @@
2-import React from "https://esm.sh/react@18.2.0";
3-export { React };
4-export { configureStore } from "https://esm.sh/@reduxjs/toolkit@1.9.5";
5 export type {
6 Channel,
7 Instruction,
8 Operation,
9 Result,
10 Scope,
11+ Stream,
12 Task,
13 } from "https://deno.land/x/effection@3.0.0-alpha.7/mod.ts";
14 export {
15@@ -24,3 +22,44 @@ export {
16 spawn,
17 useAbortSignal,
18 } from "https://deno.land/x/effection@3.0.0-alpha.7/mod.ts";
19+
20+import React from "https://esm.sh/react@18.2.0?pin=v122";
21+export { React };
22+export {
23+ Provider,
24+ useDispatch,
25+ useSelector,
26+} from "https://esm.sh/react-redux@8.0.5?pin=v122";
27+
28+export type {
29+ Action,
30+ Middleware,
31+ Reducer,
32+} from "https://esm.sh/@reduxjs/toolkit@1.9.5?pin=v122";
33+export {
34+ combineReducers,
35+ configureStore,
36+ createImmutableStateInvariantMiddleware,
37+ createSerializableStateInvariantMiddleware,
38+ getDefaultMiddleware,
39+} from "https://esm.sh/@reduxjs/toolkit@1.9.5?pin=v122";
40+export {
41+ BATCH,
42+ batchActions,
43+ enableBatching,
44+} from "https://esm.sh/redux-batched-actions@0.5.0?pin=v122";
45+export type {
46+ LoadingItemState,
47+ LoadingMapPayload,
48+ LoadingState,
49+ MapEntity,
50+} from "https://esm.sh/robodux@15.0.1?pin=v122";
51+export {
52+ createAction,
53+ createAssign,
54+ createLoaderTable,
55+ createReducerMap,
56+ createTable,
57+ defaultLoader,
58+ defaultLoadingItem,
59+} from "https://esm.sh/robodux@15.0.1?pin=v122";
+2,
-0
1@@ -61,6 +61,7 @@ function main() {
2 function* users() {
3 while (true) {
4 const action = yield* take("fetch-user");
5+ console.log(action);
6 yield* go(function* () {
7 console.log(action);
8 });
9@@ -69,6 +70,7 @@ function main() {
10 function* mailboxes() {
11 while (true) {
12 const action = yield* take("fetch-mailboxes");
13+ console.log(action);
14 yield* go(function* () {
15 console.log(action);
16 yield* sleep(1000);
R test/call.test.ts =>
fx/call.test.ts
+1,
-1
1@@ -1,7 +1,7 @@
2 import { describe, expect, it } from "../test.ts";
3
4 import { run } from "../deps.ts";
5-import { call } from "../mod.ts";
6+import { call } from "./call.ts";
7
8 const tests = describe("call()");
9
+0,
-7
1@@ -1,7 +1,6 @@
2 import type { OpFn } from "../types.ts";
3 import type { Operation, Result, Task } from "../deps.ts";
4 import { action, Err, expect, Ok, spawn } from "../deps.ts";
5-import { ErrContext } from "../context.ts";
6
7 export const isFunc = (f: unknown) => typeof f === "function";
8 export const isPromise = (p: unknown) =>
9@@ -36,8 +35,6 @@ export function* call<T>(opFn: OpFn<T>): Operation<Result<T>> {
10 const value = yield* unsafeCall(opFn);
11 return Ok(value);
12 } catch (error) {
13- const { input } = yield* ErrContext;
14- yield* input.send(error);
15 return Err(error);
16 }
17 }
18@@ -45,10 +42,6 @@ export function* call<T>(opFn: OpFn<T>): Operation<Result<T>> {
19 export function* go<T>(op: OpFn<T>): Operation<Task<Result<T>>> {
20 return yield* spawn(function* () {
21 const result = yield* call(op);
22- if (!result.ok) {
23- const { input } = yield* ErrContext;
24- yield* input.send(result.error);
25- }
26 return result;
27 });
28 }
+12,
-0
1@@ -0,0 +1,12 @@
2+import { resource } from "../deps.ts";
3+import type { Operation } from "../deps.ts";
4+
5+export function defer(op: () => Operation<unknown>): Operation<void> {
6+ return resource(function* (provide) {
7+ try {
8+ yield* provide();
9+ } finally {
10+ yield* op();
11+ }
12+ });
13+}
R fx/mod.ts =>
fx/index.ts
+1,
-0
1@@ -6,3 +6,4 @@ export * from "./race.ts";
2 export * from "./emit.ts";
3 export * from "./request.ts";
4 export * from "./watch.ts";
5+export * from "./defer.ts";
R test/parallel.test.ts =>
fx/parallel.test.ts
+6,
-4
1@@ -1,8 +1,9 @@
2 import { describe, expect, it } from "../test.ts";
3 import type { Operation, Result } from "../deps.ts";
4 import { Err, Ok, run, sleep, spawn } from "../deps.ts";
5-import { parallel } from "../fx/mod.ts";
6-import { cforEach } from "../iter.ts";
7+import { forEach } from "../iter.ts";
8+
9+import { parallel } from "./parallel.ts";
10
11 const test = describe("parallel()");
12
13@@ -40,7 +41,7 @@ it(
14 ]);
15
16 const res: Result<string>[] = [];
17- yield* cforEach(results.immediate, function* (val) {
18+ yield* forEach(results.immediate.output, function* (val) {
19 res.push(val);
20 });
21
22@@ -69,7 +70,8 @@ it(
23 ]);
24
25 const res: Result<string>[] = [];
26- yield* cforEach(results.sequence, function* (val) {
27+ const { output } = results.sequence;
28+ yield* forEach(output, function* (val) {
29 res.push(val);
30 });
31
+2,
-1
1@@ -1,9 +1,10 @@
2 import type { Channel, Operation, Result } from "../deps.ts";
3 import type { Computation, OpFn } from "../types.ts";
4 import { createChannel, resource, spawn } from "../deps.ts";
5+
6 import { call } from "./call.ts";
7
8-interface ParallelRet<T> extends Computation<Result<T>[]> {
9+export interface ParallelRet<T> extends Computation<Result<T>[]> {
10 sequence: Channel<Result<T>, void>;
11 immediate: Channel<Result<T>, void>;
12 }
+26,
-14
1@@ -1,31 +1,43 @@
2-import type { Operation } from "../deps.ts";
3+import type { Operation, Task } from "../deps.ts";
4 import { action, resource, spawn } from "../deps.ts";
5
6 import type { OpFn } from "../types.ts";
7-import { map } from "../iter.ts";
8 import { toOperation } from "./call.ts";
9
10-export function race<T>(operations: OpFn<T>[]): Operation<T> {
11+interface OpMap<T = unknown> {
12+ [key: string]: OpFn<T>;
13+}
14+
15+export function race(
16+ opMap: OpMap,
17+): Operation<{ [K in keyof OpMap<unknown>]: ReturnType<OpMap[K]> }> {
18 return resource(function* Race(provide) {
19- const tasks = yield* map(
20- operations.map((o) => () => toOperation(o)),
21- spawn,
22- );
23+ const keys = Object.keys(opMap);
24+ const taskMap: { [key: string]: Task<unknown> } = {};
25+ const resultMap: { [key: keyof OpMap]: OpMap[keyof OpMap] } = {};
26
27- const winner = yield* action<T>(function* (resolve) {
28- for (const task of tasks) {
29+ const winner = yield* action<Task<unknown>>(function* (resolve) {
30+ for (let i = 0; i < keys.length; i += 1) {
31+ const key = keys[i];
32 yield* spawn(function* () {
33- resolve(yield* task);
34+ const task = yield* spawn(() => toOperation(opMap[key]));
35+ taskMap[key] = task;
36+ (resultMap as any)[key] = yield* task;
37+ resolve(task);
38 });
39 }
40 });
41
42- for (const task of tasks) {
43- if (task !== winner) {
44- yield* spawn(() => task.halt());
45+ for (let i = 0; i < keys.length; i += 1) {
46+ const key = keys[i];
47+ const task = taskMap[key];
48+ if (task === winner) {
49+ continue;
50 }
51+
52+ yield* spawn(() => task.halt());
53 }
54
55- yield* provide(winner);
56+ yield* provide(resultMap);
57 });
58 }
+0,
-3
1@@ -1,4 +1,3 @@
2-import { ErrContext } from "../context.ts";
3 import { Err, expect, Ok, useAbortSignal } from "../deps.ts";
4
5 export function* request(url: string | URL | Request, opts?: RequestInit) {
6@@ -12,8 +11,6 @@ export function* json(response: Response) {
7 const result = yield* expect(response.json());
8 return Ok(result);
9 } catch (error) {
10- const { input } = yield* ErrContext;
11- yield* input.send(error);
12 return Err(error);
13 }
14 }
+2,
-1
1@@ -12,5 +12,6 @@ export function supervise<T>(op: OpFn<T>) {
2 }
3
4 export function* keepAlive(ops: OpFn[]) {
5- return yield* parallel(ops.map(supervise));
6+ const results = yield* parallel(ops.map(supervise));
7+ yield* results;
8 }
A
index.ts
+26,
-0
1@@ -0,0 +1,26 @@
2+export * from "./fx/index.ts";
3+export * from "./types.ts";
4+export * from "./iter.ts";
5+export * from "./context.ts";
6+export * from "./compose.ts";
7+export {
8+ action,
9+ createChannel,
10+ createContext,
11+ createScope,
12+ Err,
13+ getframe,
14+ Ok,
15+ resource,
16+ run,
17+ sleep,
18+ spawn,
19+} from "./deps.ts";
20+export type {
21+ Channel,
22+ Instruction,
23+ Operation,
24+ Result,
25+ Scope,
26+ Task,
27+} from "./deps.ts";
M
iter.ts
+3,
-20
1@@ -1,4 +1,4 @@
2-import type { Channel, Operation } from "./deps.ts";
3+import type { Channel, Operation, Stream } from "./deps.ts";
4 import type { Action } from "./types.ts";
5 import { ActionPattern, matcher } from "./matcher.ts";
6
7@@ -21,28 +21,11 @@ export function* once({
8 }
9 }
10
11-export function* cforEach<T>(
12- chan: Channel<T, void>,
13- each?: (val: T) => Operation<void>,
14-) {
15- const { output } = chan;
16- const msgList = yield* output;
17- while (true) {
18- const next = yield* msgList;
19- if (next.done) {
20- return next.value;
21- } else if (each) {
22- yield* each(next.value);
23- }
24- }
25-}
26-
27 export function* forEach<T>(
28- chan: Operation<Channel<T, void>>,
29+ stream: Stream<T, void>,
30 each?: (val: T) => Operation<void>,
31 ) {
32- const { output } = yield* chan;
33- const msgList = yield* output;
34+ const msgList = yield* stream;
35 while (true) {
36 const next = yield* msgList;
37 if (next.done) {
+4,
-4
1@@ -1,17 +1,17 @@
2 import type { Action, ActionType } from "./types.ts";
3
4 type GuardPredicate<G extends T, T = unknown> = (arg: T) => arg is G;
5-type Predicate<T> = (arg: T) => boolean;
6+type Predicate = (action: Action) => boolean;
7 type StringableActionCreator<A extends Action = Action> = {
8 (...args: unknown[]): A;
9 toString(): string;
10 };
11-type SubPattern<T> = Predicate<T> | StringableActionCreator | ActionType;
12-export type Pattern<T> = SubPattern<T> | SubPattern<T>[];
13+type SubPattern = Predicate | StringableActionCreator | ActionType;
14+export type Pattern = SubPattern | SubPattern[];
15 type ActionSubPattern<Guard extends Action = Action> =
16 | GuardPredicate<Guard, Action>
17 | StringableActionCreator<Guard>
18- | Predicate<Action>
19+ | Predicate
20 | ActionType;
21 export type ActionPattern<Guard extends Action = Action> =
22 | ActionSubPattern<Guard>
D
mod.ts
+0,
-5
1@@ -1,5 +0,0 @@
2-export * from "./fx/mod.ts";
3-export * from "./types.ts";
4-export * from "./iter.ts";
5-export * from "./context.ts";
6-export * from "./compose.ts";
M
npm.ts
+16,
-6
1@@ -1,15 +1,21 @@
2-import { build, emptyDir } from "https://deno.land/x/dnt@0.17.0/mod.ts";
3-import { assert } from "https://deno.land/std@0.129.0/testing/asserts.ts";
4+import { assert, build, emptyDir } from "./test.ts";
5+
6 await emptyDir("./npm");
7
8 const version = Deno.env.get("NPM_VERSION");
9 assert(version, "NPM_VERSION is required to build npm package");
10
11 await build({
12- entryPoints: ["./mod.ts", "./react.ts", "./redux.ts"],
13+ entryPoints: [
14+ "./index.ts",
15+ "./react.ts",
16+ "./redux/index.ts",
17+ "./query/index.ts",
18+ ],
19 outDir: "./npm",
20 shims: {
21 deno: false,
22+ undici: true,
23 },
24 test: false,
25 typeCheck: false,
26@@ -18,10 +24,9 @@ await build({
27 sourceMap: true,
28 },
29 package: {
30- // package.json properties
31 name: "starfx",
32 version,
33- description: "Declarative side-effects for your apps",
34+ description: "",
35 license: "MIT",
36 repository: {
37 author: "me@erock.io",
38@@ -32,8 +37,13 @@ await build({
39 url: "https://github.com/neurosnap/starfx/issues",
40 },
41 engines: {
42- node: ">= 14",
43+ node: ">= 18",
44 },
45+ sideEffects: false,
46+ },
47+ postBuild() {
48+ Deno.copyFileSync("LICENSE.md", "npm/LICENSE.md");
49+ Deno.copyFileSync("README.md", "npm/README.md");
50 },
51 });
52
+1818,
-0
1@@ -0,0 +1,1818 @@
2+/**
3+ * This is an auto-generated file, do not edit directly!
4+ * Run "yarn template" to generate this file.
5+ */
6+import type { SagaApi } from "./pipe.ts";
7+import type {
8+ ApiCtx,
9+ CreateAction,
10+ CreateActionWithPayload,
11+ FetchJson,
12+ MiddlewareApiCo,
13+ Next,
14+ Payload,
15+ Supervisor,
16+} from "./types.ts";
17+
18+export type ApiName = string | string[];
19+
20+export interface SagaQueryApi<Ctx extends ApiCtx = ApiCtx>
21+ extends SagaApi<Ctx> {
22+ request: (
23+ r: Partial<RequestInit>,
24+ ) => (ctx: Ctx, next: Next) => Iterator<unknown>;
25+ cache: () => (ctx: Ctx, next: Next) => Iterator<unknown>;
26+
27+ uri: (uri: string) => {
28+ /**
29+ * Options only
30+ */
31+ get(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
32+ get<P>(
33+ req: { supervisor?: Supervisor },
34+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
35+ get<P extends never, ApiSuccess, ApiError = unknown>(
36+ req: { supervisor?: Supervisor },
37+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
38+ get<P, ApiSuccess, ApiError = unknown>(req: {
39+ supervisor?: Supervisor;
40+ }): CreateActionWithPayload<
41+ & Omit<Ctx, "payload" | "json">
42+ & Payload<P>
43+ & FetchJson<ApiSuccess, ApiError>,
44+ P
45+ >;
46+
47+ /**
48+ * Middleware only
49+ */
50+ get(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
51+ get<Gtx extends Ctx = Ctx>(
52+ fn: MiddlewareApiCo<Gtx>,
53+ ): CreateAction<Gtx>;
54+ get<P>(
55+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
56+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
57+ get<P, Gtx extends Ctx = Ctx>(
58+ fn: MiddlewareApiCo<Gtx>,
59+ ): CreateActionWithPayload<Gtx, P>;
60+ get<P extends never, ApiSuccess, ApiError = unknown>(
61+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
62+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
63+ get<P, ApiSuccess, ApiError = unknown>(
64+ fn: MiddlewareApiCo<Ctx>,
65+ ): CreateActionWithPayload<
66+ & Omit<Ctx, "payload" | "json">
67+ & Payload<P>
68+ & FetchJson<ApiSuccess, ApiError>,
69+ P
70+ >;
71+
72+ /**
73+ * Options and Middleware
74+ */
75+ get(
76+ req: { supervisor?: Supervisor },
77+ fn: MiddlewareApiCo<Ctx>,
78+ ): CreateAction<Ctx>;
79+ get<Gtx extends Ctx = Ctx>(
80+ req: { supervisor?: Supervisor },
81+ fn: MiddlewareApiCo<Gtx>,
82+ ): CreateAction<Gtx>;
83+ get<P>(
84+ req: { supervisor?: Supervisor },
85+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
86+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
87+ get<P, Gtx extends Ctx = Ctx>(
88+ req: { supervisor?: Supervisor },
89+ fn: MiddlewareApiCo<Gtx>,
90+ ): CreateActionWithPayload<Gtx, P>;
91+ get<P extends never, ApiSuccess, ApiError = unknown>(
92+ req: { supervisor?: Supervisor },
93+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
94+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
95+ get<P, ApiSuccess, ApiError = unknown>(
96+ req: { supervisor?: Supervisor },
97+ fn: MiddlewareApiCo<Ctx>,
98+ ): CreateActionWithPayload<
99+ & Omit<Ctx, "payload" | "json">
100+ & Payload<P>
101+ & FetchJson<ApiSuccess, ApiError>,
102+ P
103+ >;
104+
105+ /**
106+ * Options only
107+ */
108+ post(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
109+ post<P>(
110+ req: { supervisor?: Supervisor },
111+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
112+ post<P extends never, ApiSuccess, ApiError = unknown>(
113+ req: { supervisor?: Supervisor },
114+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
115+ post<P, ApiSuccess, ApiError = unknown>(req: {
116+ supervisor?: Supervisor;
117+ }): CreateActionWithPayload<
118+ & Omit<Ctx, "payload" | "json">
119+ & Payload<P>
120+ & FetchJson<ApiSuccess, ApiError>,
121+ P
122+ >;
123+
124+ /**
125+ * Middleware only
126+ */
127+ post(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
128+ post<Gtx extends Ctx = Ctx>(
129+ fn: MiddlewareApiCo<Gtx>,
130+ ): CreateAction<Gtx>;
131+ post<P>(
132+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
133+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
134+ post<P, Gtx extends Ctx = Ctx>(
135+ fn: MiddlewareApiCo<Gtx>,
136+ ): CreateActionWithPayload<Gtx, P>;
137+ post<P extends never, ApiSuccess, ApiError = unknown>(
138+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
139+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
140+ post<P, ApiSuccess, ApiError = unknown>(
141+ fn: MiddlewareApiCo<Ctx>,
142+ ): CreateActionWithPayload<
143+ & Omit<Ctx, "payload" | "json">
144+ & Payload<P>
145+ & FetchJson<ApiSuccess, ApiError>,
146+ P
147+ >;
148+
149+ /**
150+ * Options and Middleware
151+ */
152+ post(
153+ req: { supervisor?: Supervisor },
154+ fn: MiddlewareApiCo<Ctx>,
155+ ): CreateAction<Ctx>;
156+ post<Gtx extends Ctx = Ctx>(
157+ req: { supervisor?: Supervisor },
158+ fn: MiddlewareApiCo<Gtx>,
159+ ): CreateAction<Gtx>;
160+ post<P>(
161+ req: { supervisor?: Supervisor },
162+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
163+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
164+ post<P, Gtx extends Ctx = Ctx>(
165+ req: { supervisor?: Supervisor },
166+ fn: MiddlewareApiCo<Gtx>,
167+ ): CreateActionWithPayload<Gtx, P>;
168+ post<P extends never, ApiSuccess, ApiError = unknown>(
169+ req: { supervisor?: Supervisor },
170+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
171+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
172+ post<P, ApiSuccess, ApiError = unknown>(
173+ req: { supervisor?: Supervisor },
174+ fn: MiddlewareApiCo<Ctx>,
175+ ): CreateActionWithPayload<
176+ & Omit<Ctx, "payload" | "json">
177+ & Payload<P>
178+ & FetchJson<ApiSuccess, ApiError>,
179+ P
180+ >;
181+
182+ /**
183+ * Options only
184+ */
185+ put(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
186+ put<P>(
187+ req: { supervisor?: Supervisor },
188+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
189+ put<P extends never, ApiSuccess, ApiError = unknown>(
190+ req: { supervisor?: Supervisor },
191+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
192+ put<P, ApiSuccess, ApiError = unknown>(req: {
193+ supervisor?: Supervisor;
194+ }): CreateActionWithPayload<
195+ & Omit<Ctx, "payload" | "json">
196+ & Payload<P>
197+ & FetchJson<ApiSuccess, ApiError>,
198+ P
199+ >;
200+
201+ /**
202+ * Middleware only
203+ */
204+ put(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
205+ put<Gtx extends Ctx = Ctx>(
206+ fn: MiddlewareApiCo<Gtx>,
207+ ): CreateAction<Gtx>;
208+ put<P>(
209+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
210+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
211+ put<P, Gtx extends Ctx = Ctx>(
212+ fn: MiddlewareApiCo<Gtx>,
213+ ): CreateActionWithPayload<Gtx, P>;
214+ put<P extends never, ApiSuccess, ApiError = unknown>(
215+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
216+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
217+ put<P, ApiSuccess, ApiError = unknown>(
218+ fn: MiddlewareApiCo<Ctx>,
219+ ): CreateActionWithPayload<
220+ & Omit<Ctx, "payload" | "json">
221+ & Payload<P>
222+ & FetchJson<ApiSuccess, ApiError>,
223+ P
224+ >;
225+
226+ /**
227+ * Options and Middleware
228+ */
229+ put(
230+ req: { supervisor?: Supervisor },
231+ fn: MiddlewareApiCo<Ctx>,
232+ ): CreateAction<Ctx>;
233+ put<Gtx extends Ctx = Ctx>(
234+ req: { supervisor?: Supervisor },
235+ fn: MiddlewareApiCo<Gtx>,
236+ ): CreateAction<Gtx>;
237+ put<P>(
238+ req: { supervisor?: Supervisor },
239+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
240+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
241+ put<P, Gtx extends Ctx = Ctx>(
242+ req: { supervisor?: Supervisor },
243+ fn: MiddlewareApiCo<Gtx>,
244+ ): CreateActionWithPayload<Gtx, P>;
245+ put<P extends never, ApiSuccess, ApiError = unknown>(
246+ req: { supervisor?: Supervisor },
247+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
248+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
249+ put<P, ApiSuccess, ApiError = unknown>(
250+ req: { supervisor?: Supervisor },
251+ fn: MiddlewareApiCo<Ctx>,
252+ ): CreateActionWithPayload<
253+ & Omit<Ctx, "payload" | "json">
254+ & Payload<P>
255+ & FetchJson<ApiSuccess, ApiError>,
256+ P
257+ >;
258+
259+ /**
260+ * Options only
261+ */
262+ patch(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
263+ patch<P>(
264+ req: { supervisor?: Supervisor },
265+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
266+ patch<P extends never, ApiSuccess, ApiError = unknown>(
267+ req: { supervisor?: Supervisor },
268+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
269+ patch<P, ApiSuccess, ApiError = unknown>(req: {
270+ supervisor?: Supervisor;
271+ }): CreateActionWithPayload<
272+ & Omit<Ctx, "payload" | "json">
273+ & Payload<P>
274+ & FetchJson<ApiSuccess, ApiError>,
275+ P
276+ >;
277+
278+ /**
279+ * Middleware only
280+ */
281+ patch(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
282+ patch<Gtx extends Ctx = Ctx>(
283+ fn: MiddlewareApiCo<Gtx>,
284+ ): CreateAction<Gtx>;
285+ patch<P>(
286+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
287+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
288+ patch<P, Gtx extends Ctx = Ctx>(
289+ fn: MiddlewareApiCo<Gtx>,
290+ ): CreateActionWithPayload<Gtx, P>;
291+ patch<P extends never, ApiSuccess, ApiError = unknown>(
292+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
293+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
294+ patch<P, ApiSuccess, ApiError = unknown>(
295+ fn: MiddlewareApiCo<Ctx>,
296+ ): CreateActionWithPayload<
297+ & Omit<Ctx, "payload" | "json">
298+ & Payload<P>
299+ & FetchJson<ApiSuccess, ApiError>,
300+ P
301+ >;
302+
303+ /**
304+ * Options and Middleware
305+ */
306+ patch(
307+ req: { supervisor?: Supervisor },
308+ fn: MiddlewareApiCo<Ctx>,
309+ ): CreateAction<Ctx>;
310+ patch<Gtx extends Ctx = Ctx>(
311+ req: { supervisor?: Supervisor },
312+ fn: MiddlewareApiCo<Gtx>,
313+ ): CreateAction<Gtx>;
314+ patch<P>(
315+ req: { supervisor?: Supervisor },
316+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
317+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
318+ patch<P, Gtx extends Ctx = Ctx>(
319+ req: { supervisor?: Supervisor },
320+ fn: MiddlewareApiCo<Gtx>,
321+ ): CreateActionWithPayload<Gtx, P>;
322+ patch<P extends never, ApiSuccess, ApiError = unknown>(
323+ req: { supervisor?: Supervisor },
324+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
325+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
326+ patch<P, ApiSuccess, ApiError = unknown>(
327+ req: { supervisor?: Supervisor },
328+ fn: MiddlewareApiCo<Ctx>,
329+ ): CreateActionWithPayload<
330+ & Omit<Ctx, "payload" | "json">
331+ & Payload<P>
332+ & FetchJson<ApiSuccess, ApiError>,
333+ P
334+ >;
335+
336+ /**
337+ * Options only
338+ */
339+ delete(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
340+ delete<P>(
341+ req: { supervisor?: Supervisor },
342+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
343+ delete<P extends never, ApiSuccess, ApiError = unknown>(
344+ req: { supervisor?: Supervisor },
345+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
346+ delete<P, ApiSuccess, ApiError = unknown>(req: {
347+ supervisor?: Supervisor;
348+ }): CreateActionWithPayload<
349+ & Omit<Ctx, "payload" | "json">
350+ & Payload<P>
351+ & FetchJson<ApiSuccess, ApiError>,
352+ P
353+ >;
354+
355+ /**
356+ * Middleware only
357+ */
358+ delete(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
359+ delete<Gtx extends Ctx = Ctx>(
360+ fn: MiddlewareApiCo<Gtx>,
361+ ): CreateAction<Gtx>;
362+ delete<P>(
363+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
364+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
365+ delete<P, Gtx extends Ctx = Ctx>(
366+ fn: MiddlewareApiCo<Gtx>,
367+ ): CreateActionWithPayload<Gtx, P>;
368+ delete<P extends never, ApiSuccess, ApiError = unknown>(
369+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
370+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
371+ delete<P, ApiSuccess, ApiError = unknown>(
372+ fn: MiddlewareApiCo<Ctx>,
373+ ): CreateActionWithPayload<
374+ & Omit<Ctx, "payload" | "json">
375+ & Payload<P>
376+ & FetchJson<ApiSuccess, ApiError>,
377+ P
378+ >;
379+
380+ /**
381+ * Options and Middleware
382+ */
383+ delete(
384+ req: { supervisor?: Supervisor },
385+ fn: MiddlewareApiCo<Ctx>,
386+ ): CreateAction<Ctx>;
387+ delete<Gtx extends Ctx = Ctx>(
388+ req: { supervisor?: Supervisor },
389+ fn: MiddlewareApiCo<Gtx>,
390+ ): CreateAction<Gtx>;
391+ delete<P>(
392+ req: { supervisor?: Supervisor },
393+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
394+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
395+ delete<P, Gtx extends Ctx = Ctx>(
396+ req: { supervisor?: Supervisor },
397+ fn: MiddlewareApiCo<Gtx>,
398+ ): CreateActionWithPayload<Gtx, P>;
399+ delete<P extends never, ApiSuccess, ApiError = unknown>(
400+ req: { supervisor?: Supervisor },
401+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
402+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
403+ delete<P, ApiSuccess, ApiError = unknown>(
404+ req: { supervisor?: Supervisor },
405+ fn: MiddlewareApiCo<Ctx>,
406+ ): CreateActionWithPayload<
407+ & Omit<Ctx, "payload" | "json">
408+ & Payload<P>
409+ & FetchJson<ApiSuccess, ApiError>,
410+ P
411+ >;
412+
413+ /**
414+ * Options only
415+ */
416+ options(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
417+ options<P>(
418+ req: { supervisor?: Supervisor },
419+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
420+ options<P extends never, ApiSuccess, ApiError = unknown>(
421+ req: { supervisor?: Supervisor },
422+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
423+ options<P, ApiSuccess, ApiError = unknown>(req: {
424+ supervisor?: Supervisor;
425+ }): CreateActionWithPayload<
426+ & Omit<Ctx, "payload" | "json">
427+ & Payload<P>
428+ & FetchJson<ApiSuccess, ApiError>,
429+ P
430+ >;
431+
432+ /**
433+ * Middleware only
434+ */
435+ options(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
436+ options<Gtx extends Ctx = Ctx>(
437+ fn: MiddlewareApiCo<Gtx>,
438+ ): CreateAction<Gtx>;
439+ options<P>(
440+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
441+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
442+ options<P, Gtx extends Ctx = Ctx>(
443+ fn: MiddlewareApiCo<Gtx>,
444+ ): CreateActionWithPayload<Gtx, P>;
445+ options<P extends never, ApiSuccess, ApiError = unknown>(
446+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
447+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
448+ options<P, ApiSuccess, ApiError = unknown>(
449+ fn: MiddlewareApiCo<Ctx>,
450+ ): CreateActionWithPayload<
451+ & Omit<Ctx, "payload" | "json">
452+ & Payload<P>
453+ & FetchJson<ApiSuccess, ApiError>,
454+ P
455+ >;
456+
457+ /**
458+ * Options and Middleware
459+ */
460+ options(
461+ req: { supervisor?: Supervisor },
462+ fn: MiddlewareApiCo<Ctx>,
463+ ): CreateAction<Ctx>;
464+ options<Gtx extends Ctx = Ctx>(
465+ req: { supervisor?: Supervisor },
466+ fn: MiddlewareApiCo<Gtx>,
467+ ): CreateAction<Gtx>;
468+ options<P>(
469+ req: { supervisor?: Supervisor },
470+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
471+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
472+ options<P, Gtx extends Ctx = Ctx>(
473+ req: { supervisor?: Supervisor },
474+ fn: MiddlewareApiCo<Gtx>,
475+ ): CreateActionWithPayload<Gtx, P>;
476+ options<P extends never, ApiSuccess, ApiError = unknown>(
477+ req: { supervisor?: Supervisor },
478+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
479+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
480+ options<P, ApiSuccess, ApiError = unknown>(
481+ req: { supervisor?: Supervisor },
482+ fn: MiddlewareApiCo<Ctx>,
483+ ): CreateActionWithPayload<
484+ & Omit<Ctx, "payload" | "json">
485+ & Payload<P>
486+ & FetchJson<ApiSuccess, ApiError>,
487+ P
488+ >;
489+
490+ /**
491+ * Options only
492+ */
493+ head(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
494+ head<P>(
495+ req: { supervisor?: Supervisor },
496+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
497+ head<P extends never, ApiSuccess, ApiError = unknown>(
498+ req: { supervisor?: Supervisor },
499+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
500+ head<P, ApiSuccess, ApiError = unknown>(req: {
501+ supervisor?: Supervisor;
502+ }): CreateActionWithPayload<
503+ & Omit<Ctx, "payload" | "json">
504+ & Payload<P>
505+ & FetchJson<ApiSuccess, ApiError>,
506+ P
507+ >;
508+
509+ /**
510+ * Middleware only
511+ */
512+ head(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
513+ head<Gtx extends Ctx = Ctx>(
514+ fn: MiddlewareApiCo<Gtx>,
515+ ): CreateAction<Gtx>;
516+ head<P>(
517+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
518+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
519+ head<P, Gtx extends Ctx = Ctx>(
520+ fn: MiddlewareApiCo<Gtx>,
521+ ): CreateActionWithPayload<Gtx, P>;
522+ head<P extends never, ApiSuccess, ApiError = unknown>(
523+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
524+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
525+ head<P, ApiSuccess, ApiError = unknown>(
526+ fn: MiddlewareApiCo<Ctx>,
527+ ): CreateActionWithPayload<
528+ & Omit<Ctx, "payload" | "json">
529+ & Payload<P>
530+ & FetchJson<ApiSuccess, ApiError>,
531+ P
532+ >;
533+
534+ /**
535+ * Options and Middleware
536+ */
537+ head(
538+ req: { supervisor?: Supervisor },
539+ fn: MiddlewareApiCo<Ctx>,
540+ ): CreateAction<Ctx>;
541+ head<Gtx extends Ctx = Ctx>(
542+ req: { supervisor?: Supervisor },
543+ fn: MiddlewareApiCo<Gtx>,
544+ ): CreateAction<Gtx>;
545+ head<P>(
546+ req: { supervisor?: Supervisor },
547+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
548+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
549+ head<P, Gtx extends Ctx = Ctx>(
550+ req: { supervisor?: Supervisor },
551+ fn: MiddlewareApiCo<Gtx>,
552+ ): CreateActionWithPayload<Gtx, P>;
553+ head<P extends never, ApiSuccess, ApiError = unknown>(
554+ req: { supervisor?: Supervisor },
555+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
556+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
557+ head<P, ApiSuccess, ApiError = unknown>(
558+ req: { supervisor?: Supervisor },
559+ fn: MiddlewareApiCo<Ctx>,
560+ ): CreateActionWithPayload<
561+ & Omit<Ctx, "payload" | "json">
562+ & Payload<P>
563+ & FetchJson<ApiSuccess, ApiError>,
564+ P
565+ >;
566+
567+ /**
568+ * Options only
569+ */
570+ connect(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
571+ connect<P>(
572+ req: { supervisor?: Supervisor },
573+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
574+ connect<P extends never, ApiSuccess, ApiError = unknown>(
575+ req: { supervisor?: Supervisor },
576+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
577+ connect<P, ApiSuccess, ApiError = unknown>(req: {
578+ supervisor?: Supervisor;
579+ }): CreateActionWithPayload<
580+ & Omit<Ctx, "payload" | "json">
581+ & Payload<P>
582+ & FetchJson<ApiSuccess, ApiError>,
583+ P
584+ >;
585+
586+ /**
587+ * Middleware only
588+ */
589+ connect(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
590+ connect<Gtx extends Ctx = Ctx>(
591+ fn: MiddlewareApiCo<Gtx>,
592+ ): CreateAction<Gtx>;
593+ connect<P>(
594+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
595+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
596+ connect<P, Gtx extends Ctx = Ctx>(
597+ fn: MiddlewareApiCo<Gtx>,
598+ ): CreateActionWithPayload<Gtx, P>;
599+ connect<P extends never, ApiSuccess, ApiError = unknown>(
600+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
601+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
602+ connect<P, ApiSuccess, ApiError = unknown>(
603+ fn: MiddlewareApiCo<Ctx>,
604+ ): CreateActionWithPayload<
605+ & Omit<Ctx, "payload" | "json">
606+ & Payload<P>
607+ & FetchJson<ApiSuccess, ApiError>,
608+ P
609+ >;
610+
611+ /**
612+ * Options and Middleware
613+ */
614+ connect(
615+ req: { supervisor?: Supervisor },
616+ fn: MiddlewareApiCo<Ctx>,
617+ ): CreateAction<Ctx>;
618+ connect<Gtx extends Ctx = Ctx>(
619+ req: { supervisor?: Supervisor },
620+ fn: MiddlewareApiCo<Gtx>,
621+ ): CreateAction<Gtx>;
622+ connect<P>(
623+ req: { supervisor?: Supervisor },
624+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
625+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
626+ connect<P, Gtx extends Ctx = Ctx>(
627+ req: { supervisor?: Supervisor },
628+ fn: MiddlewareApiCo<Gtx>,
629+ ): CreateActionWithPayload<Gtx, P>;
630+ connect<P extends never, ApiSuccess, ApiError = unknown>(
631+ req: { supervisor?: Supervisor },
632+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
633+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
634+ connect<P, ApiSuccess, ApiError = unknown>(
635+ req: { supervisor?: Supervisor },
636+ fn: MiddlewareApiCo<Ctx>,
637+ ): CreateActionWithPayload<
638+ & Omit<Ctx, "payload" | "json">
639+ & Payload<P>
640+ & FetchJson<ApiSuccess, ApiError>,
641+ P
642+ >;
643+
644+ /**
645+ * Options only
646+ */
647+ trace(req: { supervisor?: Supervisor }): CreateAction<Ctx>;
648+ trace<P>(
649+ req: { supervisor?: Supervisor },
650+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
651+ trace<P extends never, ApiSuccess, ApiError = unknown>(
652+ req: { supervisor?: Supervisor },
653+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
654+ trace<P, ApiSuccess, ApiError = unknown>(req: {
655+ supervisor?: Supervisor;
656+ }): CreateActionWithPayload<
657+ & Omit<Ctx, "payload" | "json">
658+ & Payload<P>
659+ & FetchJson<ApiSuccess, ApiError>,
660+ P
661+ >;
662+
663+ /**
664+ * Middleware only
665+ */
666+ trace(fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
667+ trace<Gtx extends Ctx = Ctx>(
668+ fn: MiddlewareApiCo<Gtx>,
669+ ): CreateAction<Gtx>;
670+ trace<P>(
671+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
672+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
673+ trace<P, Gtx extends Ctx = Ctx>(
674+ fn: MiddlewareApiCo<Gtx>,
675+ ): CreateActionWithPayload<Gtx, P>;
676+ trace<P extends never, ApiSuccess, ApiError = unknown>(
677+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
678+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
679+ trace<P, ApiSuccess, ApiError = unknown>(
680+ fn: MiddlewareApiCo<Ctx>,
681+ ): CreateActionWithPayload<
682+ & Omit<Ctx, "payload" | "json">
683+ & Payload<P>
684+ & FetchJson<ApiSuccess, ApiError>,
685+ P
686+ >;
687+
688+ /**
689+ * Options and Middleware
690+ */
691+ trace(
692+ req: { supervisor?: Supervisor },
693+ fn: MiddlewareApiCo<Ctx>,
694+ ): CreateAction<Ctx>;
695+ trace<Gtx extends Ctx = Ctx>(
696+ req: { supervisor?: Supervisor },
697+ fn: MiddlewareApiCo<Gtx>,
698+ ): CreateAction<Gtx>;
699+ trace<P>(
700+ req: { supervisor?: Supervisor },
701+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
702+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
703+ trace<P, Gtx extends Ctx = Ctx>(
704+ req: { supervisor?: Supervisor },
705+ fn: MiddlewareApiCo<Gtx>,
706+ ): CreateActionWithPayload<Gtx, P>;
707+ trace<P extends never, ApiSuccess, ApiError = unknown>(
708+ req: { supervisor?: Supervisor },
709+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
710+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
711+ trace<P, ApiSuccess, ApiError = unknown>(
712+ req: { supervisor?: Supervisor },
713+ fn: MiddlewareApiCo<Ctx>,
714+ ): CreateActionWithPayload<
715+ & Omit<Ctx, "payload" | "json">
716+ & Payload<P>
717+ & FetchJson<ApiSuccess, ApiError>,
718+ P
719+ >;
720+ };
721+
722+ /**
723+ * Only name
724+ */
725+ get(name: ApiName): CreateAction<Ctx>;
726+ get<P>(
727+ name: ApiName,
728+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
729+ get<P extends never, ApiSuccess, ApiError = unknown>(
730+ name: ApiName,
731+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
732+ get<P, ApiSuccess, ApiError = unknown>(
733+ name: ApiName,
734+ ): CreateActionWithPayload<
735+ & Omit<Ctx, "payload" | "json">
736+ & Payload<P>
737+ & FetchJson<ApiSuccess, ApiError>,
738+ P
739+ >;
740+
741+ /**
742+ * Name and options
743+ */
744+ get(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
745+ get<P>(
746+ name: ApiName,
747+ req: { supervisor?: Supervisor },
748+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
749+ get<P, Gtx extends Ctx = Ctx>(
750+ name: ApiName,
751+ req: { supervisor?: Supervisor },
752+ ): CreateActionWithPayload<Gtx, P>;
753+ get<P extends never, ApiSuccess, ApiError = unknown>(
754+ name: ApiName,
755+ req: { supervisor?: Supervisor },
756+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
757+ get<P, ApiSuccess, ApiError = unknown>(
758+ name: ApiName,
759+ req: { supervisor?: Supervisor },
760+ ): CreateActionWithPayload<
761+ & Omit<Ctx, "payload" | "json">
762+ & Payload<P>
763+ & FetchJson<ApiSuccess, ApiError>,
764+ P
765+ >;
766+
767+ /**
768+ * Name and middleware
769+ */
770+ get(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
771+ get<Gtx extends Ctx = Ctx>(
772+ name: ApiName,
773+ fn: MiddlewareApiCo<Gtx>,
774+ ): CreateAction<Gtx>;
775+ get<P>(
776+ name: ApiName,
777+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
778+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
779+ get<P, Gtx extends Ctx = Ctx>(
780+ name: ApiName,
781+ fn: MiddlewareApiCo<Gtx>,
782+ ): CreateActionWithPayload<Gtx, P>;
783+ get<P extends never, ApiSuccess, ApiError = unknown>(
784+ name: ApiName,
785+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
786+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
787+ get<P, ApiSuccess, ApiError = unknown>(
788+ name: ApiName,
789+ fn: MiddlewareApiCo<
790+ & Omit<Ctx, "payload" | "json">
791+ & Payload<P>
792+ & FetchJson<ApiSuccess, ApiError>
793+ >,
794+ ): CreateActionWithPayload<
795+ & Omit<Ctx, "payload" | "json">
796+ & Payload<P>
797+ & FetchJson<ApiSuccess, ApiError>,
798+ P
799+ >;
800+
801+ /**
802+ * Name, options, and middleware
803+ */
804+ get(
805+ name: ApiName,
806+ req: { supervisor?: Supervisor },
807+ fn: MiddlewareApiCo<Ctx>,
808+ ): CreateAction<Ctx>;
809+ get<Gtx extends Ctx = Ctx>(
810+ name: ApiName,
811+ req: { supervisor?: Supervisor },
812+ fn: MiddlewareApiCo<Gtx>,
813+ ): CreateAction<Gtx>;
814+ get<P>(
815+ name: ApiName,
816+ req: { supervisor?: Supervisor },
817+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
818+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
819+ get<P, Gtx extends Ctx = Ctx>(
820+ name: ApiName,
821+ req: { supervisor?: Supervisor },
822+ fn: MiddlewareApiCo<Gtx>,
823+ ): CreateActionWithPayload<Gtx, P>;
824+ get<P extends never, ApiSuccess, ApiError = unknown>(
825+ name: ApiName,
826+ req: { supervisor?: Supervisor },
827+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
828+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
829+ get<P, ApiSuccess, ApiError = unknown>(
830+ name: ApiName,
831+ req: { supervisor?: Supervisor },
832+ fn: MiddlewareApiCo<
833+ & Omit<Ctx, "payload" | "json">
834+ & Payload<P>
835+ & FetchJson<ApiSuccess, ApiError>
836+ >,
837+ ): CreateActionWithPayload<
838+ & Omit<Ctx, "payload" | "json">
839+ & Payload<P>
840+ & FetchJson<ApiSuccess, ApiError>,
841+ P
842+ >;
843+
844+ /**
845+ * Only name
846+ */
847+ post(name: ApiName): CreateAction<Ctx>;
848+ post<P>(
849+ name: ApiName,
850+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
851+ post<P extends never, ApiSuccess, ApiError = unknown>(
852+ name: ApiName,
853+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
854+ post<P, ApiSuccess, ApiError = unknown>(
855+ name: ApiName,
856+ ): CreateActionWithPayload<
857+ & Omit<Ctx, "payload" | "json">
858+ & Payload<P>
859+ & FetchJson<ApiSuccess, ApiError>,
860+ P
861+ >;
862+
863+ /**
864+ * Name and options
865+ */
866+ post(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
867+ post<P>(
868+ name: ApiName,
869+ req: { supervisor?: Supervisor },
870+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
871+ post<P, Gtx extends Ctx = Ctx>(
872+ name: ApiName,
873+ req: { supervisor?: Supervisor },
874+ ): CreateActionWithPayload<Gtx, P>;
875+ post<P extends never, ApiSuccess, ApiError = unknown>(
876+ name: ApiName,
877+ req: { supervisor?: Supervisor },
878+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
879+ post<P, ApiSuccess, ApiError = unknown>(
880+ name: ApiName,
881+ req: { supervisor?: Supervisor },
882+ ): CreateActionWithPayload<
883+ & Omit<Ctx, "payload" | "json">
884+ & Payload<P>
885+ & FetchJson<ApiSuccess, ApiError>,
886+ P
887+ >;
888+
889+ /**
890+ * Name and middleware
891+ */
892+ post(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
893+ post<Gtx extends Ctx = Ctx>(
894+ name: ApiName,
895+ fn: MiddlewareApiCo<Gtx>,
896+ ): CreateAction<Gtx>;
897+ post<P>(
898+ name: ApiName,
899+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
900+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
901+ post<P, Gtx extends Ctx = Ctx>(
902+ name: ApiName,
903+ fn: MiddlewareApiCo<Gtx>,
904+ ): CreateActionWithPayload<Gtx, P>;
905+ post<P extends never, ApiSuccess, ApiError = unknown>(
906+ name: ApiName,
907+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
908+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
909+ post<P, ApiSuccess, ApiError = unknown>(
910+ name: ApiName,
911+ fn: MiddlewareApiCo<
912+ & Omit<Ctx, "payload" | "json">
913+ & Payload<P>
914+ & FetchJson<ApiSuccess, ApiError>
915+ >,
916+ ): CreateActionWithPayload<
917+ & Omit<Ctx, "payload" | "json">
918+ & Payload<P>
919+ & FetchJson<ApiSuccess, ApiError>,
920+ P
921+ >;
922+
923+ /**
924+ * Name, options, and middleware
925+ */
926+ post(
927+ name: ApiName,
928+ req: { supervisor?: Supervisor },
929+ fn: MiddlewareApiCo<Ctx>,
930+ ): CreateAction<Ctx>;
931+ post<Gtx extends Ctx = Ctx>(
932+ name: ApiName,
933+ req: { supervisor?: Supervisor },
934+ fn: MiddlewareApiCo<Gtx>,
935+ ): CreateAction<Gtx>;
936+ post<P>(
937+ name: ApiName,
938+ req: { supervisor?: Supervisor },
939+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
940+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
941+ post<P, Gtx extends Ctx = Ctx>(
942+ name: ApiName,
943+ req: { supervisor?: Supervisor },
944+ fn: MiddlewareApiCo<Gtx>,
945+ ): CreateActionWithPayload<Gtx, P>;
946+ post<P extends never, ApiSuccess, ApiError = unknown>(
947+ name: ApiName,
948+ req: { supervisor?: Supervisor },
949+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
950+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
951+ post<P, ApiSuccess, ApiError = unknown>(
952+ name: ApiName,
953+ req: { supervisor?: Supervisor },
954+ fn: MiddlewareApiCo<
955+ & Omit<Ctx, "payload" | "json">
956+ & Payload<P>
957+ & FetchJson<ApiSuccess, ApiError>
958+ >,
959+ ): CreateActionWithPayload<
960+ & Omit<Ctx, "payload" | "json">
961+ & Payload<P>
962+ & FetchJson<ApiSuccess, ApiError>,
963+ P
964+ >;
965+
966+ /**
967+ * Only name
968+ */
969+ put(name: ApiName): CreateAction<Ctx>;
970+ put<P>(
971+ name: ApiName,
972+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
973+ put<P extends never, ApiSuccess, ApiError = unknown>(
974+ name: ApiName,
975+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
976+ put<P, ApiSuccess, ApiError = unknown>(
977+ name: ApiName,
978+ ): CreateActionWithPayload<
979+ & Omit<Ctx, "payload" | "json">
980+ & Payload<P>
981+ & FetchJson<ApiSuccess, ApiError>,
982+ P
983+ >;
984+
985+ /**
986+ * Name and options
987+ */
988+ put(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
989+ put<P>(
990+ name: ApiName,
991+ req: { supervisor?: Supervisor },
992+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
993+ put<P, Gtx extends Ctx = Ctx>(
994+ name: ApiName,
995+ req: { supervisor?: Supervisor },
996+ ): CreateActionWithPayload<Gtx, P>;
997+ put<P extends never, ApiSuccess, ApiError = unknown>(
998+ name: ApiName,
999+ req: { supervisor?: Supervisor },
1000+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1001+ put<P, ApiSuccess, ApiError = unknown>(
1002+ name: ApiName,
1003+ req: { supervisor?: Supervisor },
1004+ ): CreateActionWithPayload<
1005+ & Omit<Ctx, "payload" | "json">
1006+ & Payload<P>
1007+ & FetchJson<ApiSuccess, ApiError>,
1008+ P
1009+ >;
1010+
1011+ /**
1012+ * Name and middleware
1013+ */
1014+ put(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1015+ put<Gtx extends Ctx = Ctx>(
1016+ name: ApiName,
1017+ fn: MiddlewareApiCo<Gtx>,
1018+ ): CreateAction<Gtx>;
1019+ put<P>(
1020+ name: ApiName,
1021+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1022+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1023+ put<P, Gtx extends Ctx = Ctx>(
1024+ name: ApiName,
1025+ fn: MiddlewareApiCo<Gtx>,
1026+ ): CreateActionWithPayload<Gtx, P>;
1027+ put<P extends never, ApiSuccess, ApiError = unknown>(
1028+ name: ApiName,
1029+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1030+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1031+ put<P, ApiSuccess, ApiError = unknown>(
1032+ name: ApiName,
1033+ fn: MiddlewareApiCo<
1034+ & Omit<Ctx, "payload" | "json">
1035+ & Payload<P>
1036+ & FetchJson<ApiSuccess, ApiError>
1037+ >,
1038+ ): CreateActionWithPayload<
1039+ & Omit<Ctx, "payload" | "json">
1040+ & Payload<P>
1041+ & FetchJson<ApiSuccess, ApiError>,
1042+ P
1043+ >;
1044+
1045+ /**
1046+ * Name, options, and middleware
1047+ */
1048+ put(
1049+ name: ApiName,
1050+ req: { supervisor?: Supervisor },
1051+ fn: MiddlewareApiCo<Ctx>,
1052+ ): CreateAction<Ctx>;
1053+ put<Gtx extends Ctx = Ctx>(
1054+ name: ApiName,
1055+ req: { supervisor?: Supervisor },
1056+ fn: MiddlewareApiCo<Gtx>,
1057+ ): CreateAction<Gtx>;
1058+ put<P>(
1059+ name: ApiName,
1060+ req: { supervisor?: Supervisor },
1061+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1062+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1063+ put<P, Gtx extends Ctx = Ctx>(
1064+ name: ApiName,
1065+ req: { supervisor?: Supervisor },
1066+ fn: MiddlewareApiCo<Gtx>,
1067+ ): CreateActionWithPayload<Gtx, P>;
1068+ put<P extends never, ApiSuccess, ApiError = unknown>(
1069+ name: ApiName,
1070+ req: { supervisor?: Supervisor },
1071+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1072+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1073+ put<P, ApiSuccess, ApiError = unknown>(
1074+ name: ApiName,
1075+ req: { supervisor?: Supervisor },
1076+ fn: MiddlewareApiCo<
1077+ & Omit<Ctx, "payload" | "json">
1078+ & Payload<P>
1079+ & FetchJson<ApiSuccess, ApiError>
1080+ >,
1081+ ): CreateActionWithPayload<
1082+ & Omit<Ctx, "payload" | "json">
1083+ & Payload<P>
1084+ & FetchJson<ApiSuccess, ApiError>,
1085+ P
1086+ >;
1087+
1088+ /**
1089+ * Only name
1090+ */
1091+ patch(name: ApiName): CreateAction<Ctx>;
1092+ patch<P>(
1093+ name: ApiName,
1094+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1095+ patch<P extends never, ApiSuccess, ApiError = unknown>(
1096+ name: ApiName,
1097+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1098+ patch<P, ApiSuccess, ApiError = unknown>(
1099+ name: ApiName,
1100+ ): CreateActionWithPayload<
1101+ & Omit<Ctx, "payload" | "json">
1102+ & Payload<P>
1103+ & FetchJson<ApiSuccess, ApiError>,
1104+ P
1105+ >;
1106+
1107+ /**
1108+ * Name and options
1109+ */
1110+ patch(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1111+ patch<P>(
1112+ name: ApiName,
1113+ req: { supervisor?: Supervisor },
1114+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1115+ patch<P, Gtx extends Ctx = Ctx>(
1116+ name: ApiName,
1117+ req: { supervisor?: Supervisor },
1118+ ): CreateActionWithPayload<Gtx, P>;
1119+ patch<P extends never, ApiSuccess, ApiError = unknown>(
1120+ name: ApiName,
1121+ req: { supervisor?: Supervisor },
1122+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1123+ patch<P, ApiSuccess, ApiError = unknown>(
1124+ name: ApiName,
1125+ req: { supervisor?: Supervisor },
1126+ ): CreateActionWithPayload<
1127+ & Omit<Ctx, "payload" | "json">
1128+ & Payload<P>
1129+ & FetchJson<ApiSuccess, ApiError>,
1130+ P
1131+ >;
1132+
1133+ /**
1134+ * Name and middleware
1135+ */
1136+ patch(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1137+ patch<Gtx extends Ctx = Ctx>(
1138+ name: ApiName,
1139+ fn: MiddlewareApiCo<Gtx>,
1140+ ): CreateAction<Gtx>;
1141+ patch<P>(
1142+ name: ApiName,
1143+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1144+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1145+ patch<P, Gtx extends Ctx = Ctx>(
1146+ name: ApiName,
1147+ fn: MiddlewareApiCo<Gtx>,
1148+ ): CreateActionWithPayload<Gtx, P>;
1149+ patch<P extends never, ApiSuccess, ApiError = unknown>(
1150+ name: ApiName,
1151+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1152+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1153+ patch<P, ApiSuccess, ApiError = unknown>(
1154+ name: ApiName,
1155+ fn: MiddlewareApiCo<
1156+ & Omit<Ctx, "payload" | "json">
1157+ & Payload<P>
1158+ & FetchJson<ApiSuccess, ApiError>
1159+ >,
1160+ ): CreateActionWithPayload<
1161+ & Omit<Ctx, "payload" | "json">
1162+ & Payload<P>
1163+ & FetchJson<ApiSuccess, ApiError>,
1164+ P
1165+ >;
1166+
1167+ /**
1168+ * Name, options, and middleware
1169+ */
1170+ patch(
1171+ name: ApiName,
1172+ req: { supervisor?: Supervisor },
1173+ fn: MiddlewareApiCo<Ctx>,
1174+ ): CreateAction<Ctx>;
1175+ patch<Gtx extends Ctx = Ctx>(
1176+ name: ApiName,
1177+ req: { supervisor?: Supervisor },
1178+ fn: MiddlewareApiCo<Gtx>,
1179+ ): CreateAction<Gtx>;
1180+ patch<P>(
1181+ name: ApiName,
1182+ req: { supervisor?: Supervisor },
1183+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1184+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1185+ patch<P, Gtx extends Ctx = Ctx>(
1186+ name: ApiName,
1187+ req: { supervisor?: Supervisor },
1188+ fn: MiddlewareApiCo<Gtx>,
1189+ ): CreateActionWithPayload<Gtx, P>;
1190+ patch<P extends never, ApiSuccess, ApiError = unknown>(
1191+ name: ApiName,
1192+ req: { supervisor?: Supervisor },
1193+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1194+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1195+ patch<P, ApiSuccess, ApiError = unknown>(
1196+ name: ApiName,
1197+ req: { supervisor?: Supervisor },
1198+ fn: MiddlewareApiCo<
1199+ & Omit<Ctx, "payload" | "json">
1200+ & Payload<P>
1201+ & FetchJson<ApiSuccess, ApiError>
1202+ >,
1203+ ): CreateActionWithPayload<
1204+ & Omit<Ctx, "payload" | "json">
1205+ & Payload<P>
1206+ & FetchJson<ApiSuccess, ApiError>,
1207+ P
1208+ >;
1209+
1210+ /**
1211+ * Only name
1212+ */
1213+ delete(name: ApiName): CreateAction<Ctx>;
1214+ delete<P>(
1215+ name: ApiName,
1216+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1217+ delete<P extends never, ApiSuccess, ApiError = unknown>(
1218+ name: ApiName,
1219+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1220+ delete<P, ApiSuccess, ApiError = unknown>(
1221+ name: ApiName,
1222+ ): CreateActionWithPayload<
1223+ & Omit<Ctx, "payload" | "json">
1224+ & Payload<P>
1225+ & FetchJson<ApiSuccess, ApiError>,
1226+ P
1227+ >;
1228+
1229+ /**
1230+ * Name and options
1231+ */
1232+ delete(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1233+ delete<P>(
1234+ name: ApiName,
1235+ req: { supervisor?: Supervisor },
1236+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1237+ delete<P, Gtx extends Ctx = Ctx>(
1238+ name: ApiName,
1239+ req: { supervisor?: Supervisor },
1240+ ): CreateActionWithPayload<Gtx, P>;
1241+ delete<P extends never, ApiSuccess, ApiError = unknown>(
1242+ name: ApiName,
1243+ req: { supervisor?: Supervisor },
1244+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1245+ delete<P, ApiSuccess, ApiError = unknown>(
1246+ name: ApiName,
1247+ req: { supervisor?: Supervisor },
1248+ ): CreateActionWithPayload<
1249+ & Omit<Ctx, "payload" | "json">
1250+ & Payload<P>
1251+ & FetchJson<ApiSuccess, ApiError>,
1252+ P
1253+ >;
1254+
1255+ /**
1256+ * Name and middleware
1257+ */
1258+ delete(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1259+ delete<Gtx extends Ctx = Ctx>(
1260+ name: ApiName,
1261+ fn: MiddlewareApiCo<Gtx>,
1262+ ): CreateAction<Gtx>;
1263+ delete<P>(
1264+ name: ApiName,
1265+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1266+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1267+ delete<P, Gtx extends Ctx = Ctx>(
1268+ name: ApiName,
1269+ fn: MiddlewareApiCo<Gtx>,
1270+ ): CreateActionWithPayload<Gtx, P>;
1271+ delete<P extends never, ApiSuccess, ApiError = unknown>(
1272+ name: ApiName,
1273+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1274+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1275+ delete<P, ApiSuccess, ApiError = unknown>(
1276+ name: ApiName,
1277+ fn: MiddlewareApiCo<
1278+ & Omit<Ctx, "payload" | "json">
1279+ & Payload<P>
1280+ & FetchJson<ApiSuccess, ApiError>
1281+ >,
1282+ ): CreateActionWithPayload<
1283+ & Omit<Ctx, "payload" | "json">
1284+ & Payload<P>
1285+ & FetchJson<ApiSuccess, ApiError>,
1286+ P
1287+ >;
1288+
1289+ /**
1290+ * Name, options, and middleware
1291+ */
1292+ delete(
1293+ name: ApiName,
1294+ req: { supervisor?: Supervisor },
1295+ fn: MiddlewareApiCo<Ctx>,
1296+ ): CreateAction<Ctx>;
1297+ delete<Gtx extends Ctx = Ctx>(
1298+ name: ApiName,
1299+ req: { supervisor?: Supervisor },
1300+ fn: MiddlewareApiCo<Gtx>,
1301+ ): CreateAction<Gtx>;
1302+ delete<P>(
1303+ name: ApiName,
1304+ req: { supervisor?: Supervisor },
1305+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1306+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1307+ delete<P, Gtx extends Ctx = Ctx>(
1308+ name: ApiName,
1309+ req: { supervisor?: Supervisor },
1310+ fn: MiddlewareApiCo<Gtx>,
1311+ ): CreateActionWithPayload<Gtx, P>;
1312+ delete<P extends never, ApiSuccess, ApiError = unknown>(
1313+ name: ApiName,
1314+ req: { supervisor?: Supervisor },
1315+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1316+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1317+ delete<P, ApiSuccess, ApiError = unknown>(
1318+ name: ApiName,
1319+ req: { supervisor?: Supervisor },
1320+ fn: MiddlewareApiCo<
1321+ & Omit<Ctx, "payload" | "json">
1322+ & Payload<P>
1323+ & FetchJson<ApiSuccess, ApiError>
1324+ >,
1325+ ): CreateActionWithPayload<
1326+ & Omit<Ctx, "payload" | "json">
1327+ & Payload<P>
1328+ & FetchJson<ApiSuccess, ApiError>,
1329+ P
1330+ >;
1331+
1332+ /**
1333+ * Only name
1334+ */
1335+ options(name: ApiName): CreateAction<Ctx>;
1336+ options<P>(
1337+ name: ApiName,
1338+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1339+ options<P extends never, ApiSuccess, ApiError = unknown>(
1340+ name: ApiName,
1341+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1342+ options<P, ApiSuccess, ApiError = unknown>(
1343+ name: ApiName,
1344+ ): CreateActionWithPayload<
1345+ & Omit<Ctx, "payload" | "json">
1346+ & Payload<P>
1347+ & FetchJson<ApiSuccess, ApiError>,
1348+ P
1349+ >;
1350+
1351+ /**
1352+ * Name and options
1353+ */
1354+ options(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1355+ options<P>(
1356+ name: ApiName,
1357+ req: { supervisor?: Supervisor },
1358+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1359+ options<P, Gtx extends Ctx = Ctx>(
1360+ name: ApiName,
1361+ req: { supervisor?: Supervisor },
1362+ ): CreateActionWithPayload<Gtx, P>;
1363+ options<P extends never, ApiSuccess, ApiError = unknown>(
1364+ name: ApiName,
1365+ req: { supervisor?: Supervisor },
1366+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1367+ options<P, ApiSuccess, ApiError = unknown>(
1368+ name: ApiName,
1369+ req: { supervisor?: Supervisor },
1370+ ): CreateActionWithPayload<
1371+ & Omit<Ctx, "payload" | "json">
1372+ & Payload<P>
1373+ & FetchJson<ApiSuccess, ApiError>,
1374+ P
1375+ >;
1376+
1377+ /**
1378+ * Name and middleware
1379+ */
1380+ options(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1381+ options<Gtx extends Ctx = Ctx>(
1382+ name: ApiName,
1383+ fn: MiddlewareApiCo<Gtx>,
1384+ ): CreateAction<Gtx>;
1385+ options<P>(
1386+ name: ApiName,
1387+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1388+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1389+ options<P, Gtx extends Ctx = Ctx>(
1390+ name: ApiName,
1391+ fn: MiddlewareApiCo<Gtx>,
1392+ ): CreateActionWithPayload<Gtx, P>;
1393+ options<P extends never, ApiSuccess, ApiError = unknown>(
1394+ name: ApiName,
1395+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1396+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1397+ options<P, ApiSuccess, ApiError = unknown>(
1398+ name: ApiName,
1399+ fn: MiddlewareApiCo<
1400+ & Omit<Ctx, "payload" | "json">
1401+ & Payload<P>
1402+ & FetchJson<ApiSuccess, ApiError>
1403+ >,
1404+ ): CreateActionWithPayload<
1405+ & Omit<Ctx, "payload" | "json">
1406+ & Payload<P>
1407+ & FetchJson<ApiSuccess, ApiError>,
1408+ P
1409+ >;
1410+
1411+ /**
1412+ * Name, options, and middleware
1413+ */
1414+ options(
1415+ name: ApiName,
1416+ req: { supervisor?: Supervisor },
1417+ fn: MiddlewareApiCo<Ctx>,
1418+ ): CreateAction<Ctx>;
1419+ options<Gtx extends Ctx = Ctx>(
1420+ name: ApiName,
1421+ req: { supervisor?: Supervisor },
1422+ fn: MiddlewareApiCo<Gtx>,
1423+ ): CreateAction<Gtx>;
1424+ options<P>(
1425+ name: ApiName,
1426+ req: { supervisor?: Supervisor },
1427+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1428+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1429+ options<P, Gtx extends Ctx = Ctx>(
1430+ name: ApiName,
1431+ req: { supervisor?: Supervisor },
1432+ fn: MiddlewareApiCo<Gtx>,
1433+ ): CreateActionWithPayload<Gtx, P>;
1434+ options<P extends never, ApiSuccess, ApiError = unknown>(
1435+ name: ApiName,
1436+ req: { supervisor?: Supervisor },
1437+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1438+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1439+ options<P, ApiSuccess, ApiError = unknown>(
1440+ name: ApiName,
1441+ req: { supervisor?: Supervisor },
1442+ fn: MiddlewareApiCo<
1443+ & Omit<Ctx, "payload" | "json">
1444+ & Payload<P>
1445+ & FetchJson<ApiSuccess, ApiError>
1446+ >,
1447+ ): CreateActionWithPayload<
1448+ & Omit<Ctx, "payload" | "json">
1449+ & Payload<P>
1450+ & FetchJson<ApiSuccess, ApiError>,
1451+ P
1452+ >;
1453+
1454+ /**
1455+ * Only name
1456+ */
1457+ head(name: ApiName): CreateAction<Ctx>;
1458+ head<P>(
1459+ name: ApiName,
1460+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1461+ head<P extends never, ApiSuccess, ApiError = unknown>(
1462+ name: ApiName,
1463+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1464+ head<P, ApiSuccess, ApiError = unknown>(
1465+ name: ApiName,
1466+ ): CreateActionWithPayload<
1467+ & Omit<Ctx, "payload" | "json">
1468+ & Payload<P>
1469+ & FetchJson<ApiSuccess, ApiError>,
1470+ P
1471+ >;
1472+
1473+ /**
1474+ * Name and options
1475+ */
1476+ head(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1477+ head<P>(
1478+ name: ApiName,
1479+ req: { supervisor?: Supervisor },
1480+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1481+ head<P, Gtx extends Ctx = Ctx>(
1482+ name: ApiName,
1483+ req: { supervisor?: Supervisor },
1484+ ): CreateActionWithPayload<Gtx, P>;
1485+ head<P extends never, ApiSuccess, ApiError = unknown>(
1486+ name: ApiName,
1487+ req: { supervisor?: Supervisor },
1488+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1489+ head<P, ApiSuccess, ApiError = unknown>(
1490+ name: ApiName,
1491+ req: { supervisor?: Supervisor },
1492+ ): CreateActionWithPayload<
1493+ & Omit<Ctx, "payload" | "json">
1494+ & Payload<P>
1495+ & FetchJson<ApiSuccess, ApiError>,
1496+ P
1497+ >;
1498+
1499+ /**
1500+ * Name and middleware
1501+ */
1502+ head(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1503+ head<Gtx extends Ctx = Ctx>(
1504+ name: ApiName,
1505+ fn: MiddlewareApiCo<Gtx>,
1506+ ): CreateAction<Gtx>;
1507+ head<P>(
1508+ name: ApiName,
1509+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1510+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1511+ head<P, Gtx extends Ctx = Ctx>(
1512+ name: ApiName,
1513+ fn: MiddlewareApiCo<Gtx>,
1514+ ): CreateActionWithPayload<Gtx, P>;
1515+ head<P extends never, ApiSuccess, ApiError = unknown>(
1516+ name: ApiName,
1517+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1518+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1519+ head<P, ApiSuccess, ApiError = unknown>(
1520+ name: ApiName,
1521+ fn: MiddlewareApiCo<
1522+ & Omit<Ctx, "payload" | "json">
1523+ & Payload<P>
1524+ & FetchJson<ApiSuccess, ApiError>
1525+ >,
1526+ ): CreateActionWithPayload<
1527+ & Omit<Ctx, "payload" | "json">
1528+ & Payload<P>
1529+ & FetchJson<ApiSuccess, ApiError>,
1530+ P
1531+ >;
1532+
1533+ /**
1534+ * Name, options, and middleware
1535+ */
1536+ head(
1537+ name: ApiName,
1538+ req: { supervisor?: Supervisor },
1539+ fn: MiddlewareApiCo<Ctx>,
1540+ ): CreateAction<Ctx>;
1541+ head<Gtx extends Ctx = Ctx>(
1542+ name: ApiName,
1543+ req: { supervisor?: Supervisor },
1544+ fn: MiddlewareApiCo<Gtx>,
1545+ ): CreateAction<Gtx>;
1546+ head<P>(
1547+ name: ApiName,
1548+ req: { supervisor?: Supervisor },
1549+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1550+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1551+ head<P, Gtx extends Ctx = Ctx>(
1552+ name: ApiName,
1553+ req: { supervisor?: Supervisor },
1554+ fn: MiddlewareApiCo<Gtx>,
1555+ ): CreateActionWithPayload<Gtx, P>;
1556+ head<P extends never, ApiSuccess, ApiError = unknown>(
1557+ name: ApiName,
1558+ req: { supervisor?: Supervisor },
1559+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1560+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1561+ head<P, ApiSuccess, ApiError = unknown>(
1562+ name: ApiName,
1563+ req: { supervisor?: Supervisor },
1564+ fn: MiddlewareApiCo<
1565+ & Omit<Ctx, "payload" | "json">
1566+ & Payload<P>
1567+ & FetchJson<ApiSuccess, ApiError>
1568+ >,
1569+ ): CreateActionWithPayload<
1570+ & Omit<Ctx, "payload" | "json">
1571+ & Payload<P>
1572+ & FetchJson<ApiSuccess, ApiError>,
1573+ P
1574+ >;
1575+
1576+ /**
1577+ * Only name
1578+ */
1579+ connect(name: ApiName): CreateAction<Ctx>;
1580+ connect<P>(
1581+ name: ApiName,
1582+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1583+ connect<P extends never, ApiSuccess, ApiError = unknown>(
1584+ name: ApiName,
1585+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1586+ connect<P, ApiSuccess, ApiError = unknown>(
1587+ name: ApiName,
1588+ ): CreateActionWithPayload<
1589+ & Omit<Ctx, "payload" | "json">
1590+ & Payload<P>
1591+ & FetchJson<ApiSuccess, ApiError>,
1592+ P
1593+ >;
1594+
1595+ /**
1596+ * Name and options
1597+ */
1598+ connect(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1599+ connect<P>(
1600+ name: ApiName,
1601+ req: { supervisor?: Supervisor },
1602+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1603+ connect<P, Gtx extends Ctx = Ctx>(
1604+ name: ApiName,
1605+ req: { supervisor?: Supervisor },
1606+ ): CreateActionWithPayload<Gtx, P>;
1607+ connect<P extends never, ApiSuccess, ApiError = unknown>(
1608+ name: ApiName,
1609+ req: { supervisor?: Supervisor },
1610+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1611+ connect<P, ApiSuccess, ApiError = unknown>(
1612+ name: ApiName,
1613+ req: { supervisor?: Supervisor },
1614+ ): CreateActionWithPayload<
1615+ & Omit<Ctx, "payload" | "json">
1616+ & Payload<P>
1617+ & FetchJson<ApiSuccess, ApiError>,
1618+ P
1619+ >;
1620+
1621+ /**
1622+ * Name and middleware
1623+ */
1624+ connect(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1625+ connect<Gtx extends Ctx = Ctx>(
1626+ name: ApiName,
1627+ fn: MiddlewareApiCo<Gtx>,
1628+ ): CreateAction<Gtx>;
1629+ connect<P>(
1630+ name: ApiName,
1631+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1632+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1633+ connect<P, Gtx extends Ctx = Ctx>(
1634+ name: ApiName,
1635+ fn: MiddlewareApiCo<Gtx>,
1636+ ): CreateActionWithPayload<Gtx, P>;
1637+ connect<P extends never, ApiSuccess, ApiError = unknown>(
1638+ name: ApiName,
1639+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1640+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1641+ connect<P, ApiSuccess, ApiError = unknown>(
1642+ name: ApiName,
1643+ fn: MiddlewareApiCo<
1644+ & Omit<Ctx, "payload" | "json">
1645+ & Payload<P>
1646+ & FetchJson<ApiSuccess, ApiError>
1647+ >,
1648+ ): CreateActionWithPayload<
1649+ & Omit<Ctx, "payload" | "json">
1650+ & Payload<P>
1651+ & FetchJson<ApiSuccess, ApiError>,
1652+ P
1653+ >;
1654+
1655+ /**
1656+ * Name, options, and middleware
1657+ */
1658+ connect(
1659+ name: ApiName,
1660+ req: { supervisor?: Supervisor },
1661+ fn: MiddlewareApiCo<Ctx>,
1662+ ): CreateAction<Ctx>;
1663+ connect<Gtx extends Ctx = Ctx>(
1664+ name: ApiName,
1665+ req: { supervisor?: Supervisor },
1666+ fn: MiddlewareApiCo<Gtx>,
1667+ ): CreateAction<Gtx>;
1668+ connect<P>(
1669+ name: ApiName,
1670+ req: { supervisor?: Supervisor },
1671+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1672+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1673+ connect<P, Gtx extends Ctx = Ctx>(
1674+ name: ApiName,
1675+ req: { supervisor?: Supervisor },
1676+ fn: MiddlewareApiCo<Gtx>,
1677+ ): CreateActionWithPayload<Gtx, P>;
1678+ connect<P extends never, ApiSuccess, ApiError = unknown>(
1679+ name: ApiName,
1680+ req: { supervisor?: Supervisor },
1681+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1682+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1683+ connect<P, ApiSuccess, ApiError = unknown>(
1684+ name: ApiName,
1685+ req: { supervisor?: Supervisor },
1686+ fn: MiddlewareApiCo<
1687+ & Omit<Ctx, "payload" | "json">
1688+ & Payload<P>
1689+ & FetchJson<ApiSuccess, ApiError>
1690+ >,
1691+ ): CreateActionWithPayload<
1692+ & Omit<Ctx, "payload" | "json">
1693+ & Payload<P>
1694+ & FetchJson<ApiSuccess, ApiError>,
1695+ P
1696+ >;
1697+
1698+ /**
1699+ * Only name
1700+ */
1701+ trace(name: ApiName): CreateAction<Ctx>;
1702+ trace<P>(
1703+ name: ApiName,
1704+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1705+ trace<P extends never, ApiSuccess, ApiError = unknown>(
1706+ name: ApiName,
1707+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1708+ trace<P, ApiSuccess, ApiError = unknown>(
1709+ name: ApiName,
1710+ ): CreateActionWithPayload<
1711+ & Omit<Ctx, "payload" | "json">
1712+ & Payload<P>
1713+ & FetchJson<ApiSuccess, ApiError>,
1714+ P
1715+ >;
1716+
1717+ /**
1718+ * Name and options
1719+ */
1720+ trace(name: ApiName, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
1721+ trace<P>(
1722+ name: ApiName,
1723+ req: { supervisor?: Supervisor },
1724+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1725+ trace<P, Gtx extends Ctx = Ctx>(
1726+ name: ApiName,
1727+ req: { supervisor?: Supervisor },
1728+ ): CreateActionWithPayload<Gtx, P>;
1729+ trace<P extends never, ApiSuccess, ApiError = unknown>(
1730+ name: ApiName,
1731+ req: { supervisor?: Supervisor },
1732+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1733+ trace<P, ApiSuccess, ApiError = unknown>(
1734+ name: ApiName,
1735+ req: { supervisor?: Supervisor },
1736+ ): CreateActionWithPayload<
1737+ & Omit<Ctx, "payload" | "json">
1738+ & Payload<P>
1739+ & FetchJson<ApiSuccess, ApiError>,
1740+ P
1741+ >;
1742+
1743+ /**
1744+ * Name and middleware
1745+ */
1746+ trace(name: ApiName, fn: MiddlewareApiCo<Ctx>): CreateAction<Ctx>;
1747+ trace<Gtx extends Ctx = Ctx>(
1748+ name: ApiName,
1749+ fn: MiddlewareApiCo<Gtx>,
1750+ ): CreateAction<Gtx>;
1751+ trace<P>(
1752+ name: ApiName,
1753+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1754+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1755+ trace<P, Gtx extends Ctx = Ctx>(
1756+ name: ApiName,
1757+ fn: MiddlewareApiCo<Gtx>,
1758+ ): CreateActionWithPayload<Gtx, P>;
1759+ trace<P extends never, ApiSuccess, ApiError = unknown>(
1760+ name: ApiName,
1761+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1762+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1763+ trace<P, ApiSuccess, ApiError = unknown>(
1764+ name: ApiName,
1765+ fn: MiddlewareApiCo<
1766+ & Omit<Ctx, "payload" | "json">
1767+ & Payload<P>
1768+ & FetchJson<ApiSuccess, ApiError>
1769+ >,
1770+ ): CreateActionWithPayload<
1771+ & Omit<Ctx, "payload" | "json">
1772+ & Payload<P>
1773+ & FetchJson<ApiSuccess, ApiError>,
1774+ P
1775+ >;
1776+
1777+ /**
1778+ * Name, options, and middleware
1779+ */
1780+ trace(
1781+ name: ApiName,
1782+ req: { supervisor?: Supervisor },
1783+ fn: MiddlewareApiCo<Ctx>,
1784+ ): CreateAction<Ctx>;
1785+ trace<Gtx extends Ctx = Ctx>(
1786+ name: ApiName,
1787+ req: { supervisor?: Supervisor },
1788+ fn: MiddlewareApiCo<Gtx>,
1789+ ): CreateAction<Gtx>;
1790+ trace<P>(
1791+ name: ApiName,
1792+ req: { supervisor?: Supervisor },
1793+ fn: MiddlewareApiCo<Omit<Ctx, "payload"> & Payload<P>>,
1794+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
1795+ trace<P, Gtx extends Ctx = Ctx>(
1796+ name: ApiName,
1797+ req: { supervisor?: Supervisor },
1798+ fn: MiddlewareApiCo<Gtx>,
1799+ ): CreateActionWithPayload<Gtx, P>;
1800+ trace<P extends never, ApiSuccess, ApiError = unknown>(
1801+ name: ApiName,
1802+ req: { supervisor?: Supervisor },
1803+ fn: MiddlewareApiCo<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>,
1804+ ): CreateAction<Omit<Ctx, "json"> & FetchJson<ApiSuccess, ApiError>>;
1805+ trace<P, ApiSuccess, ApiError = unknown>(
1806+ name: ApiName,
1807+ req: { supervisor?: Supervisor },
1808+ fn: MiddlewareApiCo<
1809+ & Omit<Ctx, "payload" | "json">
1810+ & Payload<P>
1811+ & FetchJson<ApiSuccess, ApiError>
1812+ >,
1813+ ): CreateActionWithPayload<
1814+ & Omit<Ctx, "payload" | "json">
1815+ & Payload<P>
1816+ & FetchJson<ApiSuccess, ApiError>,
1817+ P
1818+ >;
1819+}
+423,
-0
1@@ -0,0 +1,423 @@
2+import { describe, expect, it } from "../test.ts";
3+
4+import { call } from "../fx/index.ts";
5+import { put, takeEvery } from "../redux/index.ts";
6+import { createAction, createReducerMap, createTable } from "../deps.ts";
7+import type { MapEntity } from "../deps.ts";
8+
9+import { queryCtx, requestMonitor, urlParser } from "./middleware.ts";
10+import { createApi } from "./api.ts";
11+import { setupStore, sleep } from "./util.ts";
12+import { createKey } from "./create-key.ts";
13+import type { ApiCtx } from "./types.ts";
14+import { poll } from "./saga.ts";
15+
16+interface User {
17+ id: string;
18+ name: string;
19+ email: string;
20+}
21+
22+const mockUser: User = { id: "1", name: "test", email: "test@test.com" };
23+
24+const jsonBlob = (data: unknown) => {
25+ return JSON.stringify(data);
26+};
27+
28+const tests = describe("createApi()");
29+
30+it(tests, "createApi - POST", async () => {
31+ const name = "users";
32+ const cache = createTable<User>({ name });
33+ const query = createApi();
34+
35+ query.use(queryCtx);
36+ query.use(urlParser);
37+ query.use(query.routes());
38+ query.use(function* fetchApi(ctx, next): Iterator<unknown> {
39+ expect(ctx.req()).toEqual({
40+ url: "/users",
41+ headers: {},
42+ method: "POST",
43+ body: JSON.stringify({ email: mockUser.email }),
44+ });
45+ const data = {
46+ users: [mockUser],
47+ };
48+
49+ ctx.response = new Response(jsonBlob(data), { status: 200 });
50+
51+ yield* next();
52+ });
53+
54+ const createUser = query.post<{ email: string }, { users: User[] }>(
55+ `/users`,
56+ function* processUsers(ctx, next) {
57+ ctx.request = ctx.req({
58+ method: "POST",
59+ body: JSON.stringify({ email: ctx.payload.email }),
60+ });
61+ yield* next();
62+
63+ const buff = yield* call(() => {
64+ if (!ctx.response) throw new Error("no response");
65+ const res = ctx.response.arrayBuffer();
66+ return res;
67+ });
68+
69+ if (!buff.ok) {
70+ throw buff.error;
71+ }
72+
73+ const result = new TextDecoder("utf-8").decode(buff.value);
74+ const { users } = JSON.parse(result);
75+ if (!users) return;
76+ const curUsers = (users as User[]).reduce<MapEntity<User>>((acc, u) => {
77+ acc[u.id] = u;
78+ return acc;
79+ }, {});
80+ yield* put(cache.actions.add(curUsers));
81+ },
82+ );
83+
84+ const reducers = createReducerMap(cache);
85+ const { store, run } = setupStore(reducers, { fx: query.bootup });
86+ run();
87+
88+ store.dispatch(createUser({ email: mockUser.email }));
89+ await sleep(150);
90+ expect(store.getState().users).toEqual({
91+ "1": { id: "1", name: "test", email: "test@test.com" },
92+ });
93+});
94+
95+it(tests, "POST with uri", () => {
96+ const name = "users";
97+ const cache = createTable<User>({ name });
98+ const query = createApi();
99+
100+ query.use(queryCtx);
101+ query.use(urlParser);
102+ query.use(query.routes());
103+ query.use(function* fetchApi(ctx, next) {
104+ expect(ctx.req()).toEqual({
105+ url: "/users",
106+ headers: {},
107+ method: "POST",
108+ body: JSON.stringify({ email: mockUser.email }),
109+ });
110+
111+ const data = {
112+ users: [mockUser],
113+ };
114+ ctx.response = new Response(jsonBlob(data), { status: 200 });
115+ yield* next();
116+ });
117+
118+ const userApi = query.uri("/users");
119+ const createUser = userApi.post<{ email: string }>(function* processUsers(
120+ ctx: ApiCtx<{ email: string }, { users: User[] }>,
121+ next,
122+ ) {
123+ ctx.request = ctx.req({
124+ body: JSON.stringify({ email: ctx.payload.email }),
125+ });
126+
127+ yield* next();
128+ if (!ctx.json.ok) return;
129+ const { users } = ctx.json.data;
130+ const curUsers = users.reduce<MapEntity<User>>((acc, u) => {
131+ acc[u.id] = u;
132+ return acc;
133+ }, {});
134+ yield* put(cache.actions.add(curUsers));
135+ });
136+
137+ const reducers = createReducerMap(cache);
138+ const { store, run } = setupStore(reducers, { fx: query.bootup });
139+ run();
140+
141+ store.dispatch(createUser({ email: mockUser.email }));
142+});
143+
144+it(tests, "middleware - with request fn", () => {
145+ const query = createApi();
146+ query.use(queryCtx);
147+ query.use(urlParser);
148+ query.use(query.routes());
149+ query.use(function* (ctx, next) {
150+ expect(ctx.req().method).toEqual("POST");
151+ expect(ctx.req().url).toEqual("/users");
152+ yield* next();
153+ });
154+ const createUser = query.create("/users", query.request({ method: "POST" }));
155+ const { store, run } = setupStore({ def: (s) => s || null }, {
156+ fx: query.bootup,
157+ });
158+ run();
159+
160+ store.dispatch(createUser());
161+});
162+
163+it(tests, "run() on endpoint action - should run the effect", () => {
164+ const api = createApi<TestCtx>();
165+ api.use(api.routes());
166+ let acc = "";
167+ const action1 = api.get<{ id: string }, { result: boolean }>(
168+ "/users/:id",
169+ function* (_, next) {
170+ yield* next();
171+ acc += "a";
172+ },
173+ );
174+ const action2 = api.get("/users2", function* (_, next) {
175+ yield* next();
176+ yield* call(() => action1.run(action1({ id: "1" })));
177+ acc += "b";
178+ expect(acc).toEqual("ab");
179+ });
180+
181+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
182+ run();
183+
184+ store.dispatch(action2());
185+});
186+
187+it(tests, "run() from a normal saga", () => {
188+ const api = createApi();
189+ api.use(api.routes());
190+ let acc = "";
191+ const action1 = api.get<{ id: string }>("/users/:id", function* (_, next) {
192+ yield* next();
193+ acc += "a";
194+ });
195+ const action2 = createAction("ACTION");
196+ function* onAction() {
197+ const ctx = yield* call(() => action1.run(action1({ id: "1" })));
198+ if (!ctx.ok) {
199+ throw new Error("no ctx");
200+ }
201+ const payload = { name: "/users/:id [GET]", options: { id: "1" } };
202+ expect(ctx.value.action.type).toEqual(`@@saga-query${action1}`);
203+ expect(ctx.value.action.payload).toEqual(payload);
204+ expect(ctx.value.name).toEqual("/users/:id [GET]");
205+ expect(ctx.value.payload).toEqual({ id: "1" });
206+ acc += "b";
207+ expect(acc).toEqual("ab");
208+ }
209+
210+ function* watchAction() {
211+ const task = yield* takeEvery(`${action2}`, onAction);
212+ yield* task;
213+ }
214+
215+ const { store, run } = setupStore({ def: () => null }, {
216+ api: api.bootup,
217+ watchAction,
218+ });
219+ run();
220+
221+ store.dispatch(action2());
222+});
223+
224+it(tests, "createApi with hash key on a large post", async () => {
225+ const query = createApi();
226+ query.use(requestMonitor());
227+ query.use(query.routes());
228+ query.use(function* fetchApi(ctx, next) {
229+ const data = {
230+ users: [{ ...mockUser, ...ctx.action.payload.options }],
231+ };
232+ ctx.response = new Response(jsonBlob(data), { status: 200 });
233+ yield* next();
234+ });
235+ const createUserDefaultKey = query.post<{ email: string; largetext: string }>(
236+ `/users`,
237+ function* processUsers(ctx, next) {
238+ ctx.cache = true;
239+ yield* next();
240+ const buff = yield* call(() => {
241+ if (!ctx.response) {
242+ throw new Error("no response");
243+ }
244+ return ctx.response.arrayBuffer();
245+ });
246+
247+ if (!buff.ok) {
248+ throw buff.error;
249+ }
250+ const result = new TextDecoder("utf-8").decode(buff.value);
251+ const { users } = JSON.parse(result);
252+ if (!users) return;
253+ const curUsers = (users as User[]).reduce<MapEntity<User>>((acc, u) => {
254+ acc[u.id] = u;
255+ return acc;
256+ }, {});
257+ ctx.response = new Response();
258+ ctx.json = {
259+ ok: true,
260+ data: curUsers,
261+ };
262+ },
263+ );
264+
265+ const email = mockUser.email + "9";
266+ const largetext = "abc-def-ghi-jkl-mno-pqr".repeat(100);
267+ const reducers = createReducerMap();
268+ const { store, run } = setupStore(reducers, { fx: query.bootup });
269+ run();
270+
271+ store.dispatch(createUserDefaultKey({ email, largetext }));
272+ await sleep(150);
273+ const s = store.getState();
274+ const expectedKey = createKey(`${createUserDefaultKey}`, {
275+ email,
276+ largetext,
277+ });
278+
279+ expect([8, 9].includes(expectedKey.split("|")[1].length)).toBeTruthy();
280+
281+ expect(s["@@saga-query/data"][expectedKey]).toEqual({
282+ "1": { id: "1", name: "test", email: email, largetext: largetext },
283+ });
284+});
285+
286+it(tests, "createApi - two identical endpoints", async () => {
287+ const actual: string[] = [];
288+ const api = createApi();
289+ api.use(requestMonitor());
290+ api.use(api.routes());
291+
292+ const first = api.get("/health", function* (ctx, next) {
293+ actual.push(ctx.req().url);
294+ yield* next();
295+ });
296+
297+ const second = api.get(
298+ ["/health", "poll"],
299+ { supervisor: poll(1 * 1000) },
300+ function* (ctx, next) {
301+ actual.push(ctx.req().url);
302+ yield* next();
303+ },
304+ );
305+
306+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
307+ run();
308+ store.dispatch(first());
309+ store.dispatch(second());
310+
311+ await sleep(150);
312+
313+ // stop poll
314+ store.dispatch(second());
315+
316+ expect(actual).toEqual(["/health", "/health"]);
317+});
318+
319+interface TestCtx<P = any, S = any, E = any> extends ApiCtx<P, S, E> {
320+ something: boolean;
321+}
322+
323+// this is strictly for testing types
324+it(tests, "ensure types for get() endpoint", () => {
325+ const api = createApi<TestCtx>();
326+ api.use(api.routes());
327+ api.use(function* (ctx, next) {
328+ yield* next();
329+ ctx.json = { ok: true, data: { result: "wow" } };
330+ });
331+
332+ const acc: string[] = [];
333+ const action1 = api.get<{ id: string }, { result: string }>(
334+ "/users/:id",
335+ function* (ctx, next) {
336+ ctx.something = false;
337+ acc.push(ctx.payload.id);
338+
339+ yield* next();
340+
341+ if (ctx.json.ok) {
342+ acc.push(ctx.json.data.result);
343+ }
344+ },
345+ );
346+
347+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
348+ run();
349+
350+ store.dispatch(action1({ id: "1" }));
351+ expect(acc).toEqual(["1", "wow"]);
352+});
353+
354+interface FetchUserProps {
355+ id: string;
356+}
357+type FetchUserCtx = TestCtx<FetchUserProps>;
358+
359+// this is strictly for testing types
360+it(tests, "ensure ability to cast `ctx` in function definition", () => {
361+ const api = createApi<TestCtx>();
362+ api.use(api.routes());
363+ api.use(function* (ctx, next) {
364+ yield* next();
365+ ctx.json = { ok: true, data: { result: "wow" } };
366+ });
367+
368+ const acc: string[] = [];
369+ const action1 = api.get<FetchUserProps>(
370+ "/users/:id",
371+ function* (ctx: FetchUserCtx, next) {
372+ ctx.something = false;
373+ acc.push(ctx.payload.id);
374+
375+ yield* next();
376+
377+ if (ctx.json.ok) {
378+ acc.push(ctx.json.data.result);
379+ }
380+ },
381+ );
382+
383+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
384+ run();
385+
386+ store.dispatch(action1({ id: "1" }));
387+ expect(acc).toEqual(["1", "wow"]);
388+});
389+
390+type FetchUserSecondCtx = TestCtx<any, { result: string }>;
391+
392+// this is strictly for testing types
393+it(
394+ tests,
395+ "ensure ability to cast `ctx` in function definition with no props",
396+ () => {
397+ const api = createApi<TestCtx>();
398+ api.use(api.routes());
399+ api.use(function* (ctx, next) {
400+ yield* next();
401+ ctx.json = { ok: true, data: { result: "wow" } };
402+ });
403+
404+ const acc: string[] = [];
405+ const action1 = api.get<never, { result: string }>(
406+ "/users",
407+ function* (ctx: FetchUserSecondCtx, next) {
408+ ctx.something = false;
409+
410+ yield* next();
411+
412+ if (ctx.json.ok) {
413+ acc.push(ctx.json.data.result);
414+ }
415+ },
416+ );
417+
418+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
419+ run();
420+
421+ store.dispatch(action1());
422+ expect(acc).toEqual(["wow"]);
423+ },
424+);
+93,
-0
1@@ -0,0 +1,93 @@
2+// deno-lint-ignore-file no-explicit-any
3+import type { ApiCtx, ApiRequest, Next } from "./types.ts";
4+import { createPipe } from "./pipe.ts";
5+import type { SagaApi } from "./pipe.ts";
6+import type { ApiName, SagaQueryApi } from "./api-types.ts";
7+
8+/**
9+ * Creates a middleware pipeline for HTTP requests.
10+ *
11+ * @remarks
12+ * It uses {@link createPipe} under the hood.
13+ *
14+ * @example
15+ * ```ts
16+ * import { createApi, requestMonitor, fetcher } from 'saga-query';
17+ *
18+ * const api = createApi();
19+ * api.use(requestMonitor());
20+ * api.use(api.routes());
21+ * api.use(fetcher({ baseUrl: 'https://api.com' }));
22+ *
23+ * const fetchUsers = api.get('/users', function*(ctx, next) {
24+ * yield next();
25+ * });
26+ *
27+ * store.dispatch(fetchUsers());
28+ * ```
29+ */
30+export function createApi<Ctx extends ApiCtx = ApiCtx>(
31+ basePipe?: SagaApi<Ctx>,
32+): SagaQueryApi<Ctx> {
33+ const pipe = basePipe || createPipe<Ctx>();
34+ const uri = (prename: ApiName) => {
35+ const create = pipe.create as any;
36+
37+ let name = prename;
38+ let remainder = "";
39+ if (Array.isArray(name)) {
40+ if (name.length === 0) {
41+ throw new Error(
42+ "createApi requires a non-empty array for the name of the endpoint",
43+ );
44+ }
45+ name = prename[0];
46+ if (name.length > 1) {
47+ const [_, ...other] = prename;
48+ remainder = ` ${other.join("|")}`;
49+ }
50+ }
51+ const tmpl = (method: string) => `${name} [${method}]${remainder}`;
52+
53+ return {
54+ get: (...args: any[]) => create(tmpl("GET"), ...args),
55+ post: (...args: any[]) => create(tmpl("POST"), ...args),
56+ put: (...args: any[]) => create(tmpl("PUT"), ...args),
57+ patch: (...args: any[]) => create(tmpl("PATCH"), ...args),
58+ delete: (...args: any[]) => create(tmpl("DELETE"), ...args),
59+ options: (...args: any[]) => create(tmpl("OPTIONS"), ...args),
60+ head: (...args: any[]) => create(tmpl("HEAD"), ...args),
61+ connect: (...args: any[]) => create(tmpl("CONNECT"), ...args),
62+ trace: (...args: any[]) => create(tmpl("TRACE"), ...args),
63+ };
64+ };
65+
66+ return {
67+ use: pipe.use,
68+ bootup: pipe.bootup,
69+ create: pipe.create,
70+ routes: pipe.routes,
71+ cache: () => {
72+ return function* onCache(ctx: Ctx, next: Next) {
73+ ctx.cache = true;
74+ yield next();
75+ };
76+ },
77+ request: (req: ApiRequest) => {
78+ return function* onRequest(ctx: Ctx, next: Next) {
79+ ctx.request = ctx.req(req);
80+ yield next();
81+ };
82+ },
83+ uri,
84+ get: (name: ApiName, ...args: any[]) => uri(name).get(...args),
85+ post: (name: ApiName, ...args: any[]) => uri(name).post(...args),
86+ put: (name: ApiName, ...args: any[]) => uri(name).put(...args),
87+ patch: (name: ApiName, ...args: any[]) => uri(name).patch(...args),
88+ delete: (name: ApiName, ...args: any[]) => uri(name).delete(...args),
89+ options: (name: ApiName, ...args: any[]) => uri(name).options(...args),
90+ head: (name: ApiName, ...args: any[]) => uri(name).head(...args),
91+ connect: (name: ApiName, ...args: any[]) => uri(name).connect(...args),
92+ trace: (name: ApiName, ...args: any[]) => uri(name).trace(...args),
93+ };
94+}
+1,
-0
1@@ -0,0 +1 @@
2+export const API_ACTION_PREFIX = "@@starfx";
+357,
-0
1@@ -0,0 +1,357 @@
2+import { describe, expect, it } from "../test.ts";
3+import type { ActionWithPayload } from "./types.ts";
4+import { createApi } from "./api.ts";
5+import { poll } from "./saga.ts";
6+
7+const getKeyOf = (action: ActionWithPayload<{ key: string }>): string =>
8+ action.payload.key;
9+
10+const tests = describe("create-key");
11+
12+it(
13+ tests,
14+ "options object keys order for action key identity - 0: empty options",
15+ () => {
16+ const api = createApi();
17+ api.use(api.routes());
18+ // no param
19+ const action0 = api.get(
20+ "/users",
21+ { supervisor: poll(5 * 1000) }, // with poll middleware
22+ function* (ctx, next) {
23+ ctx.request = {
24+ method: "GET",
25+ };
26+ yield* next();
27+ },
28+ );
29+ const sendNop0 = action0();
30+ const sendNop1 = action0();
31+ expect(getKeyOf(sendNop0)).toEqual(getKeyOf(sendNop1));
32+ },
33+);
34+
35+it(
36+ tests,
37+ "options object keys order for action key identity - 1: simple object",
38+ () => {
39+ const api = createApi();
40+ api.use(api.routes());
41+ // no param
42+ const action0 = api.get<{
43+ [key: string]: string | boolean | number | null | undefined;
44+ }>(
45+ "/users",
46+ { supervisor: poll(5 * 1000) }, // with poll middleware
47+ function* (ctx, next) {
48+ ctx.request = {
49+ method: "GET",
50+ };
51+ yield* next();
52+ },
53+ );
54+ const sendPojo0 = action0({
55+ a: "a",
56+ b: "b",
57+ c: 1,
58+ d: 2,
59+ e: true,
60+ f: false,
61+ "100": 100,
62+ 101: "101",
63+ });
64+ const sendPojo1 = action0({
65+ a: "a",
66+ b: "b",
67+ c: 1,
68+ d: 2,
69+ e: true,
70+ f: false,
71+ 100: 100,
72+ 101: "101",
73+ });
74+ const sendPojo2 = action0({
75+ e: true,
76+ f: false,
77+ "100": 100,
78+ "101": "101",
79+ a: "a",
80+ b: "b",
81+ c: 1,
82+ d: 2,
83+ });
84+ const sendPojo3 = action0({
85+ e: true,
86+ f: false,
87+ "100": 100,
88+ "101": "101",
89+ a: "a",
90+ b: "b",
91+ c: 1,
92+ d: 2000000,
93+ });
94+ const sendPojo4 = action0({
95+ e: null,
96+ f: false,
97+ "100": undefined,
98+ "101": "101",
99+ a: "a",
100+ b: "b",
101+ c: 1,
102+ d: `Thomas O'Malley`,
103+ });
104+ const sendPojo5 = action0({
105+ d: `Thomas O'Malley`,
106+ e: null,
107+ f: false,
108+ "100": undefined,
109+ "101": "101",
110+ a: "a",
111+ b: "b",
112+ c: 1,
113+ });
114+ expect(getKeyOf(sendPojo0)).toEqual(getKeyOf(sendPojo1));
115+ expect(getKeyOf(sendPojo0)).toEqual(getKeyOf(sendPojo2));
116+ expect(getKeyOf(sendPojo0)).not.toEqual(getKeyOf(sendPojo3));
117+ expect(getKeyOf(sendPojo4)).toEqual(getKeyOf(sendPojo5));
118+ },
119+);
120+
121+it(
122+ tests,
123+ "options object keys order for action key identity - 2: object (with array values)",
124+ () => {
125+ interface Ip0 {
126+ param1: string;
127+ param2: string[];
128+ }
129+ const api = createApi();
130+ api.use(api.routes());
131+ const action = api.get<Ip0>(
132+ "/users/:param1/:param2",
133+ function* (ctx, next) {
134+ ctx.request = {
135+ method: "GET",
136+ };
137+ yield* next();
138+ },
139+ );
140+ const sendFirst = action({ param1: "1", param2: ["2", "e", "f"] });
141+ const sendSecond = action({ param2: ["2", "f", "e"], param1: "1" });
142+ const sendThird = action({ param2: ["2", "e", "f"], param1: "1" });
143+ expect(getKeyOf(sendFirst)).not.toEqual(getKeyOf(sendSecond));
144+ expect(getKeyOf(sendFirst)).toEqual(getKeyOf(sendThird));
145+ },
146+);
147+
148+it(
149+ tests,
150+ "options object keys order for action key identity - 3: nested object",
151+ () => {
152+ interface Ip0 {
153+ param1: string;
154+ param2: string[];
155+ }
156+ interface Ip1 {
157+ param1: string;
158+ param2: string;
159+ param3: number;
160+ param4: Ip0;
161+ param5: boolean;
162+ }
163+ const o1: Ip1 = {
164+ param1: "1",
165+ param2: "2",
166+ param3: 3,
167+ param4: {
168+ param1: "4",
169+ param2: ["5", "6"],
170+ },
171+ param5: true,
172+ };
173+ const o2: Ip1 = {
174+ param4: {
175+ param1: "4",
176+ param2: ["5", "6"],
177+ },
178+ param5: true,
179+ param2: "2",
180+ param1: "1",
181+ param3: 3,
182+ };
183+ const api = createApi();
184+ api.use(api.routes());
185+ //nested with array
186+ const action2 = api.get<Ip1>(
187+ "/users/:param1/:param2/:param3/:param4/:param5",
188+ function* (ctx, next) {
189+ ctx.request = {
190+ method: "GET",
191+ };
192+ yield* next();
193+ },
194+ );
195+ const sendO1 = action2(o1);
196+ const sendO2 = action2(o2);
197+ const sendO3 = action2({
198+ ...o1,
199+ param4: { ...o1.param4, param2: ["5", "6", "7"] },
200+ });
201+ expect(getKeyOf(sendO1)).toEqual(getKeyOf(sendO2));
202+ expect(getKeyOf(sendO1)).not.toEqual(getKeyOf(sendO3));
203+ },
204+);
205+
206+it(
207+ tests,
208+ "options object keys order for action key identity - 4: deepNested object",
209+ () => {
210+ interface Ip0 {
211+ param1: string;
212+ param2: string[];
213+ }
214+ interface Ip1 {
215+ param1: string;
216+ param2: string;
217+ param3: number;
218+ param4: Ip0;
219+ param5: boolean;
220+ }
221+ interface Ip3 {
222+ param1: string;
223+ param2: {
224+ param3: Ip1;
225+ param4: Ip0;
226+ };
227+ }
228+ const o1: Ip1 = {
229+ param1: "1",
230+ param2: "2",
231+ param3: 3,
232+ param4: {
233+ param1: "4",
234+ param2: ["5", "6"],
235+ },
236+ param5: true,
237+ };
238+ const oo1: Ip3 = {
239+ param1: "1",
240+ param2: {
241+ param3: o1,
242+ param4: {
243+ param1: "4",
244+ param2: ["5", "6"],
245+ },
246+ },
247+ };
248+ const oo2: Ip3 = {
249+ param1: "1",
250+ param2: {
251+ param4: {
252+ param1: "4",
253+ param2: ["5", "6"],
254+ },
255+ param3: o1,
256+ },
257+ };
258+ const api = createApi();
259+ api.use(api.routes());
260+ // deepNested
261+ const action4 = api.get<Ip3>(
262+ "/users/:param1/:param2/:param3/:param4/:param5",
263+ function* (ctx, next) {
264+ ctx.request = {
265+ method: "GET",
266+ };
267+ yield* next();
268+ },
269+ );
270+ const send_oo1 = action4(oo1);
271+ const send_oo1_shuff = action4({ param2: oo1.param2, param1: oo1.param1 });
272+ const send_oo1_value_changed = action4({ ...oo1, param1: "x" });
273+ const send_oo2 = action4(oo2);
274+ expect(send_oo1.payload.options).toEqual(send_oo2.payload.options);
275+ expect(getKeyOf(send_oo1)).toEqual(getKeyOf(send_oo1_shuff));
276+ expect(getKeyOf(send_oo1)).not.toEqual(getKeyOf(send_oo1_value_changed));
277+ expect(getKeyOf(send_oo1)).toEqual(getKeyOf(send_oo2));
278+ },
279+);
280+
281+it(
282+ tests,
283+ "options object keys order for action key identity - 5: other",
284+ () => {
285+ const api = createApi();
286+ api.use(api.routes());
287+ //array options
288+ const action5 = api.post<
289+ | number
290+ | boolean
291+ | string
292+ | undefined
293+ | null
294+ | { param1: string; param2: (string | number)[] }[]
295+ | string[]
296+ >("/users/:allRecords", function* (ctx, next) {
297+ ctx.request = {
298+ method: "POST",
299+ body: JSON.stringify(ctx.action.payload),
300+ };
301+ yield* next();
302+ });
303+ const falsy0 = action5(0);
304+ const falsy1 = action5(false);
305+ const falsy2 = action5("");
306+ const falsy3 = action5(undefined);
307+ const falsy4 = action5(null);
308+ const primNo0 = action5(NaN);
309+ const primNo1 = action5(1);
310+ const primNo1bis = action5(1);
311+ const primNo2 = action5(2);
312+ const str1 = action5("1234");
313+ const str1bis = action5("1234");
314+ const str2 = action5("2345");
315+ const aStrings1 = action5(["1", "2", "3"]);
316+ const aStrings2 = action5(["1", "2", "3"]);
317+ const aStrings3 = action5(["1", "2", "1"]);
318+ const aObjects1 = action5([
319+ { param1: "1", param2: ["2", "3"] },
320+ { param1: "2", param2: ["2", "3"] },
321+ ]);
322+ const aObjects2 = action5([
323+ { param1: "1", param2: ["2", "3"] },
324+ { param1: "2", param2: ["2", "3"] },
325+ ]);
326+ // the objects are not identical.
327+ const aObjects3 = action5([
328+ { param1: "1", param2: ["2", "3"] },
329+ { param1: "2", param2: ["2", 3] },
330+ ]);
331+ //object inside the array is shuffled
332+ const aObjects4 = action5([
333+ { param2: ["2", "3"], param1: "1" },
334+ { param2: ["2", "3"], param1: "2" },
335+ ]);
336+ // cont the order of array elements is changed will get different keys.
337+ const aObjects5 = action5([
338+ { param1: "2", param2: ["2", "3"] },
339+ { param1: "1", param2: ["2", "3"] },
340+ ]);
341+ expect(getKeyOf(falsy0)).not.toEqual(getKeyOf(falsy1));
342+ expect(getKeyOf(falsy1)).not.toEqual(getKeyOf(falsy2));
343+ expect(getKeyOf(falsy1)).not.toEqual(getKeyOf(falsy3));
344+ expect(getKeyOf(falsy3)).not.toEqual(getKeyOf(falsy4));
345+ expect(getKeyOf(primNo0)).not.toEqual(getKeyOf(falsy0));
346+ expect(getKeyOf(primNo0)).not.toEqual(getKeyOf(primNo1));
347+ expect(getKeyOf(primNo1)).not.toEqual(getKeyOf(primNo2));
348+ expect(getKeyOf(primNo1)).toEqual(getKeyOf(primNo1bis));
349+ expect(getKeyOf(str1)).not.toEqual(getKeyOf(str2));
350+ expect(getKeyOf(str1)).toEqual(getKeyOf(str1bis));
351+ expect(getKeyOf(aStrings1)).toEqual(getKeyOf(aStrings2));
352+ expect(getKeyOf(aStrings1)).not.toEqual(getKeyOf(aStrings3));
353+ expect(getKeyOf(aObjects1)).toEqual(getKeyOf(aObjects2));
354+ expect(getKeyOf(aObjects1)).not.toEqual(getKeyOf(aObjects3));
355+ expect(getKeyOf(aObjects1)).toEqual(getKeyOf(aObjects4));
356+ expect(getKeyOf(aObjects1)).not.toEqual(getKeyOf(aObjects5));
357+ },
358+);
+44,
-0
1@@ -0,0 +1,44 @@
2+import { isObject } from "./util.ts";
3+
4+// deno-lint-ignore no-explicit-any
5+const deepSortObject = (opts?: any) => {
6+ if (!isObject(opts)) return opts;
7+ return Object.keys(opts)
8+ .sort()
9+ .reduce<Record<string, unknown>>((res, key) => {
10+ res[`${key}`] = opts[key];
11+ if (opts[key] && isObject(opts[key])) {
12+ res[`${key}`] = deepSortObject(opts[key]);
13+ }
14+ return res;
15+ }, {});
16+};
17+
18+function padStart(hash: string, len: number) {
19+ while (hash.length < len) {
20+ hash = "0" + hash;
21+ }
22+ return hash;
23+}
24+//credit to Ivan Perelivskiy: https://gist.github.com/iperelivskiy/4110988
25+const tinySimpleHash = (s: string) => {
26+ let h = 9;
27+ for (let i = 0; i < s.length;) {
28+ h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
29+ }
30+ return h ^ (h >>> 9);
31+};
32+
33+/**
34+ * This function used to set `ctx.key`
35+ */
36+// deno-lint-ignore no-explicit-any
37+export const createKey = (name: string, payload?: any) => {
38+ const normJsonString = typeof payload !== "undefined"
39+ ? JSON.stringify(deepSortObject(payload))
40+ : "";
41+ const hash = normJsonString
42+ ? padStart(tinySimpleHash(normJsonString).toString(16), 8)
43+ : "";
44+ return hash ? `${name}|${hash}` : name;
45+};
+403,
-0
1@@ -0,0 +1,403 @@
2+import { describe, expect, install, it, mock } from "../test.ts";
3+
4+import { fetcher, fetchRetry } from "./fetch.ts";
5+import { createApi } from "./api.ts";
6+import { setupStore } from "./util.ts";
7+import { requestMonitor } from "./middleware.ts";
8+
9+install();
10+
11+const baseUrl = "https://saga-query.com";
12+const mockUser = { id: "1", email: "test@saga-query.com" };
13+
14+const delay = (n = 200) =>
15+ new Promise((resolve) => {
16+ setTimeout(resolve, n);
17+ });
18+
19+const tests = describe("fetcher()");
20+
21+it(
22+ tests,
23+ "should be able to fetch a resource and save automatically",
24+ async () => {
25+ mock(`GET@/users`, () => {
26+ return new Response(JSON.stringify(mockUser));
27+ });
28+
29+ const api = createApi();
30+ api.use(requestMonitor());
31+ api.use(api.routes());
32+ api.use(fetcher({ baseUrl }));
33+
34+ const fetchUsers = api.get("/users", function* (ctx, next) {
35+ ctx.cache = true;
36+ yield* next();
37+
38+ expect(ctx.request).toEqual({
39+ url: `${baseUrl}/users`,
40+ method: "GET",
41+ headers: {
42+ "Content-Type": "application/json",
43+ },
44+ });
45+
46+ expect(ctx.json).toEqual({ ok: true, data: mockUser });
47+ });
48+
49+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
50+ run();
51+
52+ const action = fetchUsers();
53+ store.dispatch(action);
54+
55+ await delay();
56+
57+ const state = store.getState();
58+ expect(state["@@saga-query/data"][action.payload.key]).toEqual(mockUser);
59+ },
60+);
61+
62+it(
63+ tests,
64+ "fetch - should be able to fetch a resource and parse as text instead of json",
65+ async () => {
66+ mock(`GET@/users`, () => {
67+ return new Response("this is some text");
68+ });
69+
70+ const api = createApi();
71+ api.use(requestMonitor());
72+ api.use(api.routes());
73+ api.use(fetcher({ baseUrl }));
74+
75+ const fetchUsers = api.get("/users", function* (ctx, next) {
76+ ctx.cache = true;
77+ ctx.bodyType = "text";
78+ yield next();
79+ expect(ctx.json).toEqual({ ok: true, data: "this is some text" });
80+ });
81+
82+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
83+ run();
84+
85+ const action = fetchUsers();
86+ store.dispatch(action);
87+
88+ await delay();
89+ },
90+);
91+
92+it(tests, "fetch - error handling", async () => {
93+ const errMsg = { message: "something happened" };
94+ mock(`GET@/users`, () => {
95+ return new Response(JSON.stringify(errMsg), { status: 500 });
96+ });
97+
98+ const api = createApi();
99+ api.use(requestMonitor());
100+ api.use(api.routes());
101+ api.use(function* (ctx, next) {
102+ const url = ctx.req().url;
103+ ctx.request = ctx.req({ url: `${baseUrl}${url}` });
104+ yield* next();
105+ });
106+ api.use(fetcher());
107+
108+ const fetchUsers = api.get("/users", function* (ctx, next) {
109+ ctx.cache = true;
110+ yield* next();
111+
112+ expect(ctx.json).toEqual({ ok: false, data: errMsg });
113+ });
114+
115+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
116+ run();
117+ const action = fetchUsers();
118+ store.dispatch(action);
119+
120+ await delay();
121+
122+ const state = store.getState();
123+ expect(state["@@saga-query/data"][action.payload.key]).toEqual(errMsg);
124+});
125+
126+it(tests, "fetch - status 204", async () => {
127+ mock(`GET@/users`, () => {
128+ return new Response("", { status: 204 });
129+ });
130+
131+ const api = createApi();
132+ api.use(requestMonitor());
133+ api.use(api.routes());
134+ api.use(function* (ctx, next) {
135+ const url = ctx.req().url;
136+ ctx.request = ctx.req({ url: `${baseUrl}${url}` });
137+ yield* next();
138+ });
139+ api.use(fetcher());
140+
141+ const fetchUsers = api.get("/users", function* (ctx, next) {
142+ ctx.cache = true;
143+ yield* next();
144+
145+ expect(ctx.json).toEqual({ ok: true, data: {} });
146+ });
147+
148+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
149+ run();
150+ const action = fetchUsers();
151+ store.dispatch(action);
152+
153+ await delay();
154+
155+ const state = store.getState();
156+ expect(state["@@saga-query/data"][action.payload.key]).toEqual({});
157+});
158+
159+it(tests, "fetch - malformed json", async () => {
160+ mock(`GET@/users`, () => {
161+ return new Response("not json", { status: 200 });
162+ });
163+
164+ const api = createApi();
165+ api.use(requestMonitor());
166+ api.use(api.routes());
167+ api.use(function* (ctx, next) {
168+ const url = ctx.req().url;
169+ ctx.request = ctx.req({ url: `${baseUrl}${url}` });
170+ yield* next();
171+ });
172+ api.use(fetcher());
173+
174+ const fetchUsers = api.get("/users", function* (ctx, next) {
175+ ctx.cache = true;
176+ yield* next();
177+
178+ expect(ctx.json).toEqual({
179+ ok: false,
180+ data: {
181+ message:
182+ "invalid json response body at https://saga-query.com/users reason: Unexpected token o in JSON at position 1",
183+ },
184+ });
185+ });
186+
187+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
188+ run();
189+
190+ const action = fetchUsers();
191+ store.dispatch(action);
192+
193+ await delay();
194+});
195+
196+it(tests, "fetch - POST", async () => {
197+ mock(`POST@/users`, () => {
198+ return new Response(JSON.stringify(mockUser));
199+ });
200+
201+ const api = createApi();
202+ api.use(requestMonitor());
203+ api.use(api.routes());
204+ api.use(fetcher({ baseUrl }));
205+
206+ const fetchUsers = api.post("/users", function* (ctx, next) {
207+ ctx.cache = true;
208+ ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
209+ yield* next();
210+
211+ expect(ctx.req()).toEqual({
212+ url: `${baseUrl}/users`,
213+ headers: {
214+ "Content-Type": "application/json",
215+ },
216+ method: "POST",
217+ body: JSON.stringify(mockUser),
218+ });
219+
220+ expect(ctx.json).toEqual({ ok: true, data: mockUser });
221+ });
222+
223+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
224+ run();
225+ const action = fetchUsers();
226+ store.dispatch(action);
227+
228+ await delay();
229+});
230+
231+it(tests, "fetch - POST multiple endpoints with same uri", async () => {
232+ mock(`POST@/users/1/something`, () => {
233+ return new Response(JSON.stringify(mockUser));
234+ });
235+
236+ const api = createApi();
237+ api.use(requestMonitor());
238+ api.use(api.routes());
239+ api.use(fetcher({ baseUrl }));
240+
241+ const fetchUsers = api.post<{ id: string }>(
242+ "/users/:id/something",
243+ function* (ctx, next) {
244+ ctx.cache = true;
245+ ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
246+ yield* next();
247+
248+ expect(ctx.req()).toEqual({
249+ url: `${baseUrl}/users/1/something`,
250+ headers: {
251+ "Content-Type": "application/json",
252+ },
253+ method: "POST",
254+ body: JSON.stringify(mockUser),
255+ });
256+
257+ expect(ctx.json).toEqual({ ok: true, data: mockUser });
258+ },
259+ );
260+
261+ const fetchUsersSecond = api.post<{ id: string }>(
262+ ["/users/:id/something", "next"],
263+ function* (ctx, next) {
264+ ctx.cache = true;
265+ ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
266+ yield* next();
267+
268+ expect(ctx.req()).toEqual({
269+ url: `${baseUrl}/users/1/something`,
270+ headers: {
271+ "Content-Type": "application/json",
272+ },
273+ method: "POST",
274+ body: JSON.stringify(mockUser),
275+ });
276+
277+ expect(ctx.json).toEqual({ ok: true, data: mockUser });
278+ },
279+ );
280+
281+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
282+ run();
283+
284+ store.dispatch(fetchUsers({ id: "1" }));
285+ store.dispatch(fetchUsersSecond({ id: "1" }));
286+
287+ await delay();
288+});
289+
290+it(
291+ tests,
292+ "fetch - slug in url but payload has empty string for slug value",
293+ async () => {
294+ const api = createApi();
295+ api.use(requestMonitor());
296+ api.use(api.routes());
297+ api.use(fetcher({ baseUrl }));
298+
299+ const fetchUsers = api.post<{ id: string }>(
300+ "/users/:id",
301+ function* (ctx, next) {
302+ ctx.cache = true;
303+ ctx.request = ctx.req({ body: JSON.stringify(mockUser) });
304+
305+ yield* next();
306+
307+ expect(ctx.json).toEqual({
308+ ok: false,
309+ data:
310+ "found :id in endpoint name (/users/:id [POST]) but payload has falsy value ()",
311+ });
312+ },
313+ );
314+
315+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
316+ run();
317+
318+ const action = fetchUsers({ id: "" });
319+ store.dispatch(action);
320+
321+ await delay();
322+ },
323+);
324+
325+it(
326+ tests,
327+ "fetch retry - with success - should keep retrying fetch request",
328+ async () => {
329+ let counter = 0;
330+ mock(`GET@/users`, () => {
331+ counter += 1;
332+ if (counter > 4) {
333+ return new Response(JSON.stringify(mockUser));
334+ }
335+ return new Response(JSON.stringify({ message: "error" }), {
336+ status: 400,
337+ });
338+ });
339+
340+ const api = createApi();
341+ api.use(requestMonitor());
342+ api.use(api.routes());
343+ api.use(fetcher({ baseUrl }));
344+
345+ const fetchUsers = api.get("/users", [
346+ function* (ctx, next) {
347+ ctx.cache = true;
348+ yield* next();
349+
350+ if (!ctx.json.ok) {
351+ expect(true).toBe(false);
352+ }
353+
354+ expect(ctx.json).toEqual({ ok: true, data: mockUser });
355+ },
356+ fetchRetry((n) => (n > 4 ? -1 : 10)),
357+ ]);
358+
359+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
360+ run();
361+
362+ const action = fetchUsers();
363+ store.dispatch(action);
364+
365+ await delay();
366+
367+ const state = store.getState();
368+ expect(state["@@saga-query/data"][action.payload.key]).toEqual(mockUser);
369+ },
370+);
371+
372+it.ignore(
373+ tests,
374+ "fetch retry - with failure - should keep retrying and then quit",
375+ async () => {
376+ mock(`GET@/users`, () => {
377+ return new Response(JSON.stringify({ message: "error" }), {
378+ status: 400,
379+ });
380+ });
381+
382+ const api = createApi();
383+ api.use(requestMonitor());
384+ api.use(api.routes());
385+ api.use(fetcher({ baseUrl }));
386+
387+ const fetchUsers = api.get("/users", [
388+ function* (ctx, next) {
389+ ctx.cache = true;
390+ yield* next();
391+ expect(ctx.json).toEqual({ ok: false, data: { message: "error" } });
392+ },
393+ fetchRetry((n) => (n > 2 ? -1 : 10)),
394+ ]);
395+
396+ const { store, run } = setupStore({ def: () => null }, { fx: api.bootup });
397+ run();
398+
399+ const action = fetchUsers();
400+ store.dispatch(action);
401+
402+ await delay();
403+ },
404+);
+245,
-0
1@@ -0,0 +1,245 @@
2+import { call } from "../fx/index.ts";
3+import { sleep } from "../deps.ts";
4+import { compose } from "../compose.ts";
5+import { noop } from "./util.ts";
6+import type { FetchCtx, FetchJsonCtx, Next } from "./types.ts";
7+
8+/**
9+ * Automatically sets `content-type` to `application/json` when
10+ * that header is not already present.
11+ */
12+export function* headersMdw<CurCtx extends FetchCtx = FetchCtx>(
13+ ctx: CurCtx,
14+ next: Next,
15+) {
16+ if (!ctx.request) {
17+ yield* next();
18+ return;
19+ }
20+
21+ const cur = ctx.req();
22+ if (!Object.hasOwn(cur.headers, "Content-Type")) {
23+ ctx.request = ctx.req({
24+ headers: { "Content-Type": "application/json" },
25+ });
26+ }
27+
28+ yield* next();
29+}
30+
31+/**
32+ * This middleware takes the `ctx.response` and sets `ctx.json` to the body representation
33+ * requested. It uses the `ctx.bodyType` property to determine how to represent the body.
34+ * The default is set to `json` which calls `Response.json()`.
35+ *
36+ * @example
37+ * ```ts
38+ * const fetchUsers = api.get('/users', function*(ctx, next) {
39+ * ctx.bodyType = 'text'; // calls Response.text();
40+ * yield next();
41+ * })
42+ * ```
43+ */
44+export function* jsonMdw<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
45+ ctx: CurCtx,
46+ next: Next,
47+) {
48+ if (!ctx.response) {
49+ yield* next();
50+ return;
51+ }
52+
53+ if (ctx.response.status === 204) {
54+ ctx.json = {
55+ ok: ctx.response.ok,
56+ data: {},
57+ };
58+ yield* next();
59+ return;
60+ }
61+
62+ const data = yield* call(() => {
63+ const resp = ctx.response;
64+ if (!resp) throw new Error("response is falsy");
65+ return resp[ctx.bodyType]();
66+ });
67+
68+ if (data.ok) {
69+ ctx.json = {
70+ ok: ctx.response.ok,
71+ data: data.value,
72+ };
73+ } else {
74+ ctx.json = {
75+ ok: false,
76+ data: { message: data.error.message },
77+ };
78+ }
79+
80+ yield* next();
81+}
82+
83+/*
84+ * This middleware takes the `baseUrl` provided to `fetcher()` and combines it
85+ * with the url from `ctx.request.url`.
86+ */
87+export function apiUrlMdw<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
88+ baseUrl = "",
89+) {
90+ return function* (ctx: CurCtx, next: Next) {
91+ const req = ctx.req();
92+ ctx.request = ctx.req({ url: `${baseUrl}${req.url}` });
93+ yield* next();
94+ };
95+}
96+
97+/**
98+ * If there's a slug inside the ctx.name (which is the URL segement in this case)
99+ * and there is *not* a corresponding truthy value in the payload, then that means
100+ * the user has an empty value (e.g. empty string) which means we want to abort the
101+ * fetch request.
102+ *
103+ * e.g. `ctx.name = "/apps/:id"` with `payload = { id: '' }`
104+ *
105+ * Ideally the action wouldn't have been dispatched at all but that is *not* a
106+ * gaurantee we can make here.
107+ */
108+export function* payloadMdw<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
109+ ctx: CurCtx,
110+ next: Next,
111+) {
112+ const payload = ctx.payload;
113+ if (!payload) {
114+ yield* next();
115+ return;
116+ }
117+
118+ const keys = Object.keys(payload);
119+ for (let i = 0; i < keys.length; i += 1) {
120+ const key = keys[i];
121+ if (!ctx.name.includes(`:${key}`)) {
122+ continue;
123+ }
124+
125+ const val = payload[key];
126+ if (!val) {
127+ ctx.json = {
128+ ok: false,
129+ data:
130+ `found :${key} in endpoint name (${ctx.name}) but payload has falsy value (${val})`,
131+ };
132+ return;
133+ }
134+ }
135+
136+ yield* next();
137+}
138+
139+/*
140+ * This middleware makes the `fetch` http request using `ctx.request` and
141+ * assigns the response to `ctx.response`.
142+ */
143+export function* fetchMdw<CurCtx extends FetchCtx = FetchCtx>(
144+ ctx: CurCtx,
145+ next: Next,
146+) {
147+ const { url, ...req } = ctx.req();
148+ const request = new Request(url, req);
149+ const response = yield* call(() => fetch(request));
150+ if (response.ok) {
151+ ctx.response = response.value;
152+ } else {
153+ throw response.error;
154+ }
155+ yield* next();
156+}
157+
158+function backoffExp(attempt: number): number {
159+ if (attempt > 5) return -1;
160+ // 1s, 1s, 1s, 2s, 4s
161+ return Math.max(2 ** attempt * 125, 1000);
162+}
163+
164+/**
165+ * This middleware will retry failed `Fetch` request if `response.ok` is `false`.
166+ * It accepts a backoff function to determine how long to continue retrying.
167+ * The default is an exponential backoff {@link backoffExp} where the minimum is
168+ * 1sec between attempts and it'll reach 4s between attempts at the end with a
169+ * max of 5 attempts.
170+ *
171+ * An example backoff:
172+ * @example
173+ * ```ts
174+ * // Any value less than 0 will stop the retry middleware.
175+ * // Each attempt will wait 1s
176+ * const backoff = (attempt: number) => {
177+ * if (attempt > 5) return -1;
178+ * return 1000;
179+ * }
180+ *
181+ * const api = createApi();
182+ * api.use(requestMonitor());
183+ * api.use(api.routes());
184+ * api.use(fetcher());
185+ *
186+ * const fetchUsers = api.get('/users', [
187+ * function*(ctx, next) {
188+ * // ...
189+ * yield next();
190+ * },
191+ * // fetchRetry should be after your endpoint function because
192+ * // the retry middleware will update `ctx.json` before it reaches your middleware
193+ * fetchRetry(backoff),
194+ * ])
195+ * ```
196+ */
197+export function fetchRetry<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
198+ backoff: (attempt: number) => number = backoffExp,
199+) {
200+ return function* (ctx: CurCtx, next: Next) {
201+ yield* next();
202+
203+ if (!ctx.response) {
204+ return;
205+ }
206+
207+ if (ctx.response.ok) {
208+ return;
209+ }
210+
211+ let attempt = 1;
212+ let waitFor = backoff(attempt);
213+ while (waitFor >= 1) {
214+ yield* sleep(waitFor);
215+ yield* call(() => fetchMdw(ctx, noop));
216+ yield* call(() => jsonMdw(ctx, noop));
217+
218+ if (ctx.response.ok) {
219+ return;
220+ }
221+
222+ attempt += 1;
223+ waitFor = backoff(attempt);
224+ }
225+ };
226+}
227+
228+/**
229+ * This middleware is a composition of other middleware required to use `window.fetch`
230+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} with {@link createApi}
231+ */
232+export function fetcher<CurCtx extends FetchJsonCtx = FetchJsonCtx>(
233+ {
234+ baseUrl = "",
235+ }: {
236+ baseUrl?: string;
237+ } = { baseUrl: "" },
238+) {
239+ return compose<CurCtx>([
240+ headersMdw,
241+ apiUrlMdw(baseUrl),
242+ payloadMdw,
243+ fetchMdw,
244+ jsonMdw,
245+ ]);
246+}
+12,
-0
1@@ -0,0 +1,12 @@
2+export { BATCH, batchActions } from "../deps.ts";
3+
4+export * from "./pipe.ts";
5+export * from "./api.ts";
6+export * from "./types.ts";
7+export * from "./fetch.ts";
8+export * from "./middleware.ts";
9+export * from "./constant.ts";
10+export * from "./store.ts";
11+export * from "./slice.ts";
12+export * from "./create-key.ts";
13+export * from "./saga.ts";
+533,
-0
1@@ -0,0 +1,533 @@
2+import { assertLike, asserts, describe, expect, it } from "../test.ts";
3+
4+import { put, takeLatest } from "../redux/index.ts";
5+import {
6+ createReducerMap,
7+ createTable,
8+ defaultLoadingItem,
9+ sleep as delay,
10+} from "../deps.ts";
11+import type { MapEntity } from "../deps.ts";
12+
13+import { createApi } from "./api.ts";
14+import {
15+ customKey,
16+ queryCtx,
17+ requestMonitor,
18+ undo,
19+ undoer,
20+ urlParser,
21+} from "./middleware.ts";
22+import type { UndoCtx } from "./middleware.ts";
23+import type { ApiCtx } from "./types.ts";
24+import { setupStore, sleep } from "./util.ts";
25+import { createKey } from "./create-key.ts";
26+import {
27+ createQueryState,
28+ DATA_NAME,
29+ LOADERS_NAME,
30+ selectDataById,
31+} from "./slice.ts";
32+import { call } from "../fx/index.ts";
33+
34+interface User {
35+ id: string;
36+ name: string;
37+ email: string;
38+}
39+
40+const mockUser: User = { id: "1", name: "test", email: "test@test.com" };
41+const mockUser2: User = { id: "2", name: "two", email: "two@test.com" };
42+
43+// deno-lint-ignore no-explicit-any
44+const jsonBlob = (data: any) => {
45+ return JSON.stringify(data);
46+};
47+
48+const tests = describe("middleware");
49+
50+it(tests, "basic", () => {
51+ const name = "users";
52+ const cache = createTable<User>({ name });
53+ const query = createApi<ApiCtx>();
54+
55+ query.use(queryCtx);
56+ query.use(urlParser);
57+ query.use(query.routes());
58+ query.use(function* fetchApi(ctx, next) {
59+ if (`${ctx.req().url}`.startsWith("/users/")) {
60+ ctx.json = { ok: true, data: mockUser2 };
61+ yield* next();
62+ return;
63+ }
64+ const data = {
65+ users: [mockUser],
66+ };
67+ ctx.json = { ok: true, data };
68+ yield* next();
69+ });
70+
71+ const fetchUsers = query.create(
72+ `/users`,
73+ function* processUsers(ctx: ApiCtx<unknown, { users: User[] }>, next) {
74+ yield* next();
75+ if (!ctx.json.ok) return;
76+ const { users } = ctx.json.data;
77+ const curUsers = users.reduce<MapEntity<User>>((acc, u) => {
78+ acc[u.id] = u;
79+ return acc;
80+ }, {});
81+ yield* put(cache.actions.add(curUsers));
82+ },
83+ );
84+
85+ const fetchUser = query.create<{ id: string }>(
86+ `/users/:id`,
87+ {
88+ supervisor: takeLatest,
89+ },
90+ function* processUser(ctx, next) {
91+ ctx.request = ctx.req({ method: "POST" });
92+ yield* next();
93+ if (!ctx.json.ok) return;
94+ const curUser = ctx.json.data;
95+ const curUsers = { [curUser.id]: curUser };
96+ yield* put(cache.actions.add(curUsers));
97+ },
98+ );
99+
100+ const reducers = createReducerMap(cache);
101+ const { store, run } = setupStore(reducers, { fx: query.bootup });
102+ run();
103+
104+ store.dispatch(fetchUsers());
105+ expect(store.getState()).toEqual({
106+ ...createQueryState(),
107+ users: { [mockUser.id]: mockUser },
108+ });
109+ store.dispatch(fetchUser({ id: "2" }));
110+ expect(store.getState()).toEqual({
111+ ...createQueryState(),
112+ users: { [mockUser.id]: mockUser, [mockUser2.id]: mockUser2 },
113+ });
114+});
115+
116+it(tests, "with loader", () => {
117+ const users = createTable<User>({ name: "users" });
118+
119+ const api = createApi<ApiCtx>();
120+ api.use(requestMonitor());
121+ api.use(api.routes());
122+ api.use(function* fetchApi(ctx, next) {
123+ ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
124+ ctx.json = { ok: true, data: { users: [mockUser] } };
125+ yield* next();
126+ });
127+
128+ const fetchUsers = api.create(
129+ `/users`,
130+ function* processUsers(ctx: ApiCtx<unknown, { users: User[] }>, next) {
131+ yield* next();
132+ if (!ctx.json.ok) return;
133+
134+ const { data } = ctx.json;
135+ const curUsers = data.users.reduce<MapEntity<User>>((acc, u) => {
136+ acc[u.id] = u;
137+ return acc;
138+ }, {});
139+
140+ ctx.actions.push(users.actions.add(curUsers));
141+ },
142+ );
143+
144+ const reducers = createReducerMap(users);
145+ const { store, run } = setupStore(reducers, { fx: api.bootup });
146+ run();
147+
148+ store.dispatch(fetchUsers());
149+ assertLike(store.getState(), {
150+ [users.name]: { [mockUser.id]: mockUser },
151+ [LOADERS_NAME]: {
152+ "/users": {
153+ status: "success",
154+ },
155+ },
156+ });
157+});
158+
159+it(tests, "with item loader", () => {
160+ const users = createTable<User>({ name: "users" });
161+
162+ const api = createApi<ApiCtx>();
163+ api.use(requestMonitor());
164+ api.use(api.routes());
165+ api.use(function* fetchApi(ctx, next) {
166+ ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
167+ ctx.json = { ok: true, data: { users: [mockUser] } };
168+ yield* next();
169+ });
170+
171+ const fetchUser = api.create<{ id: string }>(
172+ `/users/:id`,
173+ function* processUsers(ctx: ApiCtx<unknown, { users: User[] }>, next) {
174+ yield* next();
175+ if (!ctx.json.ok) return;
176+
177+ const { data } = ctx.json;
178+ const curUsers = data.users.reduce<MapEntity<User>>((acc, u) => {
179+ acc[u.id] = u;
180+ return acc;
181+ }, {});
182+
183+ ctx.actions.push(users.actions.add(curUsers));
184+ },
185+ );
186+
187+ const reducers = createReducerMap(users);
188+ const { store, run } = setupStore(reducers, { fx: api.bootup });
189+ run();
190+
191+ const action = fetchUser({ id: mockUser.id });
192+ store.dispatch(action);
193+ assertLike(store.getState(), {
194+ [users.name]: { [mockUser.id]: mockUser },
195+ [LOADERS_NAME]: {
196+ "/users/:id": {
197+ status: "success",
198+ },
199+ [action.payload.key]: {
200+ status: "success",
201+ },
202+ },
203+ });
204+});
205+
206+it(tests, "with POST", () => {
207+ const name = "users";
208+ const cache = createTable<User>({ name });
209+ const query = createApi();
210+
211+ query.use(queryCtx);
212+ query.use(urlParser);
213+ query.use(query.routes());
214+ query.use(function* fetchApi(ctx, next) {
215+ const request = ctx.req();
216+ asserts.assertEquals(request, {
217+ url: "/users",
218+ headers: {},
219+ method: "POST",
220+ body: JSON.stringify({ email: "test@test.com" }),
221+ });
222+
223+ const data = {
224+ users: [mockUser],
225+ };
226+ ctx.response = new Response(jsonBlob(data), { status: 200 });
227+ yield* next();
228+ });
229+
230+ const createUser = query.create<{ email: string }>(
231+ `/users [POST]`,
232+ function* processUsers(
233+ ctx: ApiCtx<{ email: string }, { users: User[] }>,
234+ next,
235+ ) {
236+ ctx.request = ctx.req({
237+ method: "POST",
238+ body: JSON.stringify({ email: ctx.payload.email }),
239+ });
240+
241+ yield* next();
242+
243+ if (!ctx.json.ok) return;
244+
245+ const { users } = ctx.json.data;
246+ const curUsers = users.reduce<MapEntity<User>>((acc, u) => {
247+ acc[u.id] = u;
248+ return acc;
249+ }, {});
250+ yield* put(cache.actions.add(curUsers));
251+ },
252+ );
253+
254+ const reducers = createReducerMap(cache);
255+ const { store, run } = setupStore(reducers, { fx: query.bootup });
256+ run();
257+ store.dispatch(createUser({ email: mockUser.email }));
258+});
259+
260+it(tests, "simpleCache", () => {
261+ const api = createApi<ApiCtx>();
262+ api.use(requestMonitor());
263+ api.use(api.routes());
264+ api.use(function* fetchApi(ctx, next) {
265+ const data = { users: [mockUser] };
266+ ctx.response = new Response(jsonBlob(data));
267+ ctx.json = { ok: true, data };
268+ yield* next();
269+ });
270+
271+ const fetchUsers = api.get("/users", api.cache());
272+ const { store, run } = setupStore({ def: (s) => s || null }, {
273+ fx: api.bootup,
274+ });
275+ run();
276+
277+ const action = fetchUsers();
278+ store.dispatch(action);
279+ assertLike(store.getState(), {
280+ [DATA_NAME]: {
281+ [action.payload.key]: { users: [mockUser] },
282+ },
283+ [LOADERS_NAME]: {
284+ [`${fetchUsers}`]: {
285+ status: "success",
286+ },
287+ },
288+ });
289+});
290+
291+it(tests, "overriding default loader behavior", () => {
292+ const users = createTable<User>({ name: "users" });
293+
294+ const api = createApi<ApiCtx>();
295+ api.use(requestMonitor());
296+ api.use(api.routes());
297+ api.use(function* fetchApi(ctx, next) {
298+ const data = { users: [mockUser] };
299+ ctx.response = new Response(jsonBlob(data));
300+ ctx.json = { ok: true, data };
301+ yield* next();
302+ });
303+
304+ const fetchUsers = api.create(
305+ `/users`,
306+ function* (ctx: ApiCtx<unknown, { users: User[] }>, next) {
307+ const id = ctx.name;
308+ yield* next();
309+
310+ if (!ctx.json.ok) {
311+ return;
312+ }
313+ const { data } = ctx.json;
314+ const curUsers = data.users.reduce<MapEntity<User>>((acc, u) => {
315+ acc[u.id] = u;
316+ return acc;
317+ }, {});
318+
319+ ctx.loader = { id, message: "yes", meta: { wow: true } };
320+ ctx.actions.push(users.actions.add(curUsers));
321+ },
322+ );
323+
324+ const reducers = createReducerMap(users);
325+ const { store, run } = setupStore(reducers, { fx: api.bootup });
326+ run();
327+
328+ store.dispatch(fetchUsers());
329+ assertLike(store.getState(), {
330+ [users.name]: { [mockUser.id]: mockUser },
331+ [LOADERS_NAME]: {
332+ [`${fetchUsers}`]: {
333+ status: "success",
334+ message: "yes",
335+ meta: { wow: true },
336+ },
337+ },
338+ });
339+});
340+
341+it(tests, "undo", () => {
342+ const api = createApi<UndoCtx>();
343+ api.use(requestMonitor());
344+ api.use(api.routes());
345+ api.use(undoer());
346+
347+ api.use(function* fetchApi(ctx, next) {
348+ yield* delay(500);
349+ ctx.response = new Response(jsonBlob({ users: [mockUser] }), {
350+ status: 200,
351+ });
352+ yield* next();
353+ });
354+
355+ const createUser = api.post("/users", function* (ctx, next) {
356+ ctx.undoable = true;
357+ yield* next();
358+ });
359+
360+ const { store, run } = setupStore({ def: (s) => s || null }, {
361+ fx: api.bootup,
362+ });
363+ run();
364+
365+ const action = createUser();
366+ store.dispatch(action);
367+ store.dispatch(undo());
368+ assertLike(store.getState(), {
369+ ...createQueryState({
370+ [LOADERS_NAME]: {
371+ [`${createUser}`]: defaultLoadingItem(),
372+ [action.payload.name]: defaultLoadingItem(),
373+ },
374+ }),
375+ });
376+});
377+
378+it(tests, "requestMonitor - error handler", () => {
379+ let err = false;
380+ console.error = (msg: string) => {
381+ if (err) return;
382+ asserts.assertEquals(
383+ msg,
384+ "Error: something happened. Check the endpoint [/users]",
385+ );
386+ err = true;
387+ };
388+ const name = "users";
389+ const cache = createTable<User>({ name });
390+ const query = createApi<ApiCtx>();
391+
392+ query.use(requestMonitor());
393+ query.use(query.routes());
394+ query.use(function* () {
395+ throw new Error("something happened");
396+ });
397+
398+ const fetchUsers = query.create(`/users`);
399+
400+ const reducers = createReducerMap(cache);
401+ const { store, run } = setupStore(reducers, { fx: query.bootup });
402+ run();
403+ store.dispatch(fetchUsers());
404+});
405+
406+it(tests, "createApi with own key", async () => {
407+ const query = createApi();
408+ query.use(requestMonitor());
409+ query.use(query.routes());
410+ query.use(customKey);
411+ query.use(function* fetchApi(ctx, next) {
412+ const data = {
413+ users: [{ ...mockUser, ...ctx.action.payload.options }],
414+ };
415+ ctx.response = new Response(jsonBlob(data), { status: 200 });
416+ yield* next();
417+ });
418+
419+ const theTestKey = `some-custom-key-${Math.ceil(Math.random() * 1000)}`;
420+
421+ const createUserCustomKey = query.post<{ email: string }>(
422+ `/users`,
423+ function* processUsers(ctx: ApiCtx, next) {
424+ ctx.cache = true;
425+ ctx.key = theTestKey; // or some calculated key //
426+ yield* next();
427+ const buff = yield* call(() => {
428+ if (!ctx.response) throw new Error("no response");
429+ return ctx.response.arrayBuffer();
430+ });
431+ if (!buff.ok) {
432+ throw buff.error;
433+ }
434+
435+ const result = new TextDecoder("utf-8").decode(buff.value);
436+ const { users } = JSON.parse(result);
437+ if (!users) return;
438+ const curUsers = (users as User[]).reduce<MapEntity<User>>((acc, u) => {
439+ acc[u.id] = u;
440+ return acc;
441+ }, {});
442+ ctx.response = new Response();
443+ ctx.json = {
444+ ok: true,
445+ data: curUsers,
446+ };
447+ },
448+ );
449+ const newUEmail = mockUser.email + ".org";
450+ const reducers = createReducerMap();
451+ const { store, run } = setupStore(reducers, { fx: query.bootup });
452+ run();
453+
454+ store.dispatch(createUserCustomKey({ email: newUEmail }));
455+ await sleep(150);
456+ const expectedKey = theTestKey
457+ ? `/users [POST]|${theTestKey}`
458+ : createKey("/users [POST]", { email: newUEmail });
459+
460+ const s = store.getState();
461+ asserts.assertEquals(selectDataById(s, { id: expectedKey }), {
462+ "1": { id: "1", name: "test", email: newUEmail },
463+ });
464+
465+ asserts.assert(
466+ expectedKey.split("|")[1] === theTestKey,
467+ "the keypart should match the input",
468+ );
469+});
470+
471+it(tests, "createApi with custom key but no payload", async () => {
472+ const query = createApi();
473+ query.use(requestMonitor());
474+ query.use(query.routes());
475+ query.use(customKey);
476+ query.use(function* fetchApi(ctx, next) {
477+ const data = {
478+ users: [mockUser],
479+ };
480+ ctx.response = new Response(jsonBlob(data), { status: 200 });
481+ yield* next();
482+ });
483+
484+ const theTestKey = `some-custom-key-${Math.ceil(Math.random() * 1000)}`;
485+
486+ const getUsers = query.get(
487+ `/users`,
488+ function* processUsers(ctx: ApiCtx, next) {
489+ ctx.cache = true;
490+ ctx.key = theTestKey; // or some calculated key //
491+ yield* next();
492+ const buff = yield* call(() => {
493+ if (!ctx.response) throw new Error("no response");
494+ return ctx.response?.arrayBuffer();
495+ });
496+ if (!buff.ok) {
497+ throw buff.error;
498+ }
499+
500+ const result = new TextDecoder("utf-8").decode(buff.value);
501+ const { users } = JSON.parse(result);
502+ if (!users) return;
503+ const curUsers = (users as User[]).reduce<MapEntity<User>>((acc, u) => {
504+ acc[u.id] = u;
505+ return acc;
506+ }, {});
507+ ctx.response = new Response();
508+ ctx.json = {
509+ ok: true,
510+ data: curUsers,
511+ };
512+ },
513+ );
514+
515+ const reducers = createReducerMap();
516+ const { store, run } = setupStore(reducers, { fx: query.bootup });
517+ run();
518+
519+ store.dispatch(getUsers());
520+ await sleep(150);
521+ const expectedKey = theTestKey
522+ ? `/users [GET]|${theTestKey}`
523+ : createKey("/users [GET]", null);
524+
525+ const s = store.getState();
526+ asserts.assertEquals(selectDataById(s, { id: expectedKey }), {
527+ "1": mockUser,
528+ });
529+
530+ asserts.assert(
531+ expectedKey.split("|")[1] === theTestKey,
532+ "the keypart should match the input",
533+ );
534+});
+348,
-0
1@@ -0,0 +1,348 @@
2+import { call, race } from "../fx/index.ts";
3+import { put, select, take } from "../redux/index.ts";
4+import { batchActions, sleep } from "../deps.ts";
5+import { compose } from "../compose.ts";
6+import type { OpFn } from "../types.ts";
7+
8+import type {
9+ Action,
10+ ApiCtx,
11+ ApiRequest,
12+ LoaderCtx,
13+ Next,
14+ PipeCtx,
15+ RequiredApiRequest,
16+} from "./types.ts";
17+import { createAction, isObject, mergeRequest } from "./util.ts";
18+import {
19+ addData,
20+ resetLoaderById,
21+ selectDataById,
22+ setLoaderError,
23+ setLoaderStart,
24+ setLoaderSuccess,
25+} from "./slice.ts";
26+
27+/**
28+ * This middleware will catch any errors in the pipeline
29+ * and return the context object.
30+ */
31+export function* errorHandler<Ctx extends PipeCtx = PipeCtx>(
32+ ctx: Ctx,
33+ next: Next,
34+) {
35+ try {
36+ yield* next();
37+ } catch (err) {
38+ console.error(
39+ `Error: ${err.message}. Check the endpoint [${ctx.name}]`,
40+ ctx,
41+ );
42+ throw err;
43+ }
44+}
45+
46+/**
47+ * This middleware sets up the context object with some values that are
48+ * necessary for {@link createApi} to work properly.
49+ */
50+export function* queryCtx<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx, next: Next) {
51+ if (!ctx.req) {
52+ ctx.req = (r?: ApiRequest): RequiredApiRequest =>
53+ mergeRequest(ctx.request, r);
54+ }
55+ if (!ctx.request) ctx.request = ctx.req();
56+ if (!ctx.response) ctx.response = null;
57+ if (!ctx.json) ctx.json = { ok: false, data: {} };
58+ if (!ctx.actions) ctx.actions = [];
59+ if (!ctx.bodyType) ctx.bodyType = "json";
60+ yield* next();
61+}
62+
63+/**
64+ * This middleware converts the name provided to {@link createApi} into data for the fetch request.
65+ */
66+export function* urlParser<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx, next: Next) {
67+ const httpMethods = [
68+ "get",
69+ "head",
70+ "post",
71+ "put",
72+ "delete",
73+ "connect",
74+ "options",
75+ "trace",
76+ "patch",
77+ ];
78+
79+ const options = ctx.payload || {};
80+ if (!isObject(options)) {
81+ yield* next();
82+ return;
83+ }
84+
85+ let url = Object.keys(options).reduce((acc, key) => {
86+ return acc.replace(`:${key}`, options[key]);
87+ }, ctx.name);
88+
89+ let method = "";
90+ httpMethods.forEach((curMethod) => {
91+ const pattern = new RegExp(`\\s*\\[` + curMethod + `\\]\\s*\\w*`, "i");
92+ const tmpUrl = url.replace(pattern, "");
93+ if (tmpUrl.length !== url.length) {
94+ method = curMethod.toLocaleUpperCase();
95+ }
96+ url = tmpUrl;
97+ }, url);
98+
99+ ctx.request = ctx.req({ url });
100+ if (method) {
101+ ctx.request = ctx.req({ method });
102+ }
103+
104+ yield* next();
105+}
106+
107+/**
108+ * This middleware will take the result of `ctx.actions` and dispatch them
109+ * as a single batch.
110+ *
111+ * @remarks This is useful because sometimes there are a lot of actions that need dispatched
112+ * within the pipeline of the middleware and instead of dispatching them serially this
113+ * improves performance by only hitting the reducers once.
114+ */
115+export function* dispatchActions(ctx: { actions: Action[] }, next: Next) {
116+ if (!ctx.actions) ctx.actions = [];
117+ yield* next();
118+ if (ctx.actions.length === 0) return;
119+ yield* put(batchActions(ctx.actions));
120+}
121+
122+/**
123+ * This middleware creates a loader for a generator function which allows us to track
124+ * the status of a pipeline function.
125+ */
126+export function* loadingMonitorSimple<Ctx extends LoaderCtx = LoaderCtx>(
127+ ctx: Ctx,
128+ next: Next,
129+) {
130+ yield* put(
131+ batchActions([
132+ setLoaderStart({ id: ctx.name }),
133+ setLoaderStart({ id: ctx.key }),
134+ ]),
135+ );
136+ if (!ctx.loader) ctx.loader = {} as any;
137+
138+ yield* next();
139+
140+ const payload = ctx.loader || {};
141+ yield* put(
142+ batchActions([
143+ setLoaderSuccess({ id: ctx.name, ...payload }),
144+ setLoaderSuccess({ id: ctx.key, ...payload }),
145+ ]),
146+ );
147+}
148+
149+/**
150+ * This middleware will track the status of a fetch request.
151+ */
152+export function loadingMonitor<Ctx extends ApiCtx = ApiCtx>(
153+ errorFn: (ctx: Ctx) => string = (ctx) => ctx.json?.data?.message || "",
154+) {
155+ return function* trackLoading(ctx: Ctx, next: Next) {
156+ yield* put(
157+ batchActions([
158+ setLoaderStart({ id: ctx.name }),
159+ setLoaderStart({ id: ctx.key }),
160+ ]),
161+ );
162+ if (!ctx.loader) ctx.loader = {} as any;
163+
164+ yield* next();
165+
166+ if (!ctx.response) {
167+ ctx.actions.push(resetLoaderById(ctx.name), resetLoaderById(ctx.key));
168+ return;
169+ }
170+
171+ const payload = ctx.loader || {};
172+ if (!ctx.response.ok) {
173+ ctx.actions.push(
174+ setLoaderError({ id: ctx.name, message: errorFn(ctx), ...payload }),
175+ setLoaderError({ id: ctx.key, message: errorFn(ctx), ...payload }),
176+ );
177+ return;
178+ }
179+
180+ ctx.actions.push(
181+ setLoaderSuccess({ id: ctx.name, ...payload }),
182+ setLoaderSuccess({ id: ctx.key, ...payload }),
183+ );
184+ };
185+}
186+
187+export interface UndoCtx<P = any, S = any, E = any> extends ApiCtx<P, S, E> {
188+ undoable: boolean;
189+}
190+
191+export const doIt = createAction("DO_IT");
192+export const undo = createAction("UNDO");
193+/**
194+ * This middleware will allow pipeline functions to be undoable which means before they are activated
195+ * we have a timeout that allows the function to be cancelled.
196+ */
197+export function undoer<Ctx extends UndoCtx = UndoCtx>(
198+ doItType = `${doIt}`,
199+ undoType = `${undo}`,
200+ timeout = 30 * 1000,
201+) {
202+ return function* onUndo(ctx: Ctx, next: Next) {
203+ if (!ctx.undoable) {
204+ yield* next();
205+ return;
206+ }
207+
208+ const winner = yield* race({
209+ doIt: () => take(`${doItType}`),
210+ undo: () => take(`${undoType}`),
211+ timeout: () => sleep(timeout),
212+ });
213+
214+ if (winner.undo || winner.timeout) {
215+ return;
216+ }
217+
218+ yield* next();
219+ };
220+}
221+
222+export interface OptimisticCtx<
223+ A extends Action = Action,
224+ R extends Action = Action,
225+> extends ApiCtx {
226+ optimistic: {
227+ apply: A;
228+ revert: R;
229+ };
230+}
231+
232+/**
233+ * This middleware performs an optimistic update for a middleware pipeline.
234+ * It accepts an `apply` and `revert` action.
235+ *
236+ * @remarks This means that we will first `apply` and then if the request is successful we
237+ * keep the change or we `revert` if there's an error.
238+ */
239+export function* optimistic<
240+ Ctx extends OptimisticCtx = OptimisticCtx,
241+>(ctx: Ctx, next: Next) {
242+ if (!ctx.optimistic) {
243+ yield* next();
244+ return;
245+ }
246+
247+ const { apply, revert } = ctx.optimistic;
248+ // optimistically update user
249+ yield* put(apply);
250+
251+ yield* next();
252+
253+ if (!ctx.response || !ctx.response.ok) {
254+ yield* put(revert);
255+ }
256+}
257+
258+/**
259+ * This middleware will automatically cache any data found inside `ctx.json`
260+ * which is where we store JSON data from the `fetcher` middleware.
261+ */
262+export function* simpleCache<Ctx extends ApiCtx = ApiCtx>(
263+ ctx: Ctx,
264+ next: Next,
265+) {
266+ ctx.cacheData = yield* select((state) =>
267+ selectDataById(state, { id: ctx.key })
268+ );
269+ yield* next();
270+ if (!ctx.cache) return;
271+ const { data } = ctx.json;
272+ ctx.actions.push(addData({ [ctx.key]: data }));
273+ ctx.cacheData = data;
274+}
275+
276+/**
277+ * This middleware allows the user to override the default key provided to every pipeline function
278+ * and instead use whatever they want.
279+ *
280+ * @example
281+ * ```ts
282+ * import { createPipe } from 'saga-query';
283+ *
284+ * const thunk = createPipe();
285+ * thunk.use(customKey);
286+ *
287+ * const doit = thunk.create('some-action', function*(ctx, next) {
288+ * ctx.key = 'something-i-want';
289+ * })
290+ * ```
291+ */
292+export function* customKey<Ctx extends ApiCtx = ApiCtx>(ctx: Ctx, next: Next) {
293+ if (
294+ ctx?.key &&
295+ ctx?.action?.payload?.key &&
296+ ctx.key !== ctx.action.payload.key
297+ ) {
298+ const newKey = ctx.name.split("|")[0] + "|" + ctx.key;
299+ ctx.key = newKey;
300+ ctx.action.payload.key = newKey;
301+ }
302+ yield* next();
303+}
304+
305+/**
306+ * This middleware is a composition of many middleware used to faciliate the {@link createApi}
307+ */
308+export function requestMonitor<Ctx extends ApiCtx = ApiCtx>(
309+ errorFn?: (ctx: Ctx) => string,
310+) {
311+ return compose<Ctx>([
312+ errorHandler,
313+ queryCtx,
314+ urlParser,
315+ dispatchActions,
316+ loadingMonitor(errorFn),
317+ simpleCache,
318+ customKey,
319+ ]);
320+}
321+
322+export interface PerfCtx<P = unknown> extends PipeCtx<P> {
323+ performance: number;
324+}
325+
326+/**
327+ * This middleware will add `performance.now()` before and after your middleware pipeline.
328+ */
329+export function* performanceMonitor<Ctx extends PerfCtx = PerfCtx>(
330+ ctx: Ctx,
331+ next: Next,
332+) {
333+ const t0 = performance.now();
334+ yield* next();
335+ const t1 = performance.now();
336+ ctx.performance = t1 - t0;
337+}
338+
339+/**
340+ * This middleware will call the `saga` provided with the action sent to the middleware pipeline.
341+ */
342+export function wrap<Ctx extends PipeCtx = PipeCtx>(
343+ op: (a: Action) => OpFn,
344+) {
345+ return function* (ctx: Ctx, next: Next) {
346+ yield* call(() => op(ctx.action));
347+ yield* next();
348+ };
349+}
+410,
-0
1@@ -0,0 +1,410 @@
2+import { assertLike, asserts, describe, it } from "../test.ts";
3+import { call } from "../fx/index.ts";
4+import { put } from "../redux/index.ts";
5+import { createReducerMap, createTable, sleep as delay } from "../deps.ts";
6+import type { Action, MapEntity } from "../deps.ts";
7+
8+import { createPipe } from "./pipe.ts";
9+import type { Next, PipeCtx } from "./types.ts";
10+import { setupStore as prepStore, sleep } from "./util.ts";
11+import { createQueryState } from "./slice.ts";
12+import { OpFn } from "../types.ts";
13+
14+// deno-lint-ignore no-explicit-any
15+interface RoboCtx<D = Record<string, unknown>, P = any> extends PipeCtx<P> {
16+ url: string;
17+ request: { method: string; body?: Record<string, unknown> };
18+ response: D;
19+ actions: Action[];
20+}
21+
22+interface User {
23+ id: string;
24+ name: string;
25+ email: string;
26+}
27+
28+interface UserResponse {
29+ id: string;
30+ name: string;
31+ email_address: string;
32+}
33+
34+const deserializeUser = (u: UserResponse): User => {
35+ return {
36+ id: u.id,
37+ name: u.name,
38+ email: u.email_address,
39+ };
40+};
41+
42+interface Ticket {
43+ id: string;
44+ name: string;
45+}
46+
47+interface TicketResponse {
48+ id: string;
49+ name: string;
50+}
51+
52+const deserializeTicket = (u: TicketResponse): Ticket => {
53+ return {
54+ id: u.id,
55+ name: u.name,
56+ };
57+};
58+
59+const users = createTable<User>({ name: "USER" });
60+const tickets = createTable<Ticket>({ name: "TICKET" });
61+const reducers = createReducerMap(users, tickets);
62+
63+const mockUser = { id: "1", name: "test", email_address: "test@test.com" };
64+const mockTicket = { id: "2", name: "test-ticket" };
65+
66+function* convertNameToUrl(ctx: RoboCtx, next: Next) {
67+ if (!ctx.url) {
68+ ctx.url = ctx.name;
69+ }
70+ yield* next();
71+}
72+
73+function* onFetchApi(ctx: RoboCtx, next: Next) {
74+ const url = ctx.url;
75+ let json = {};
76+ if (url === "/users") {
77+ json = {
78+ users: [mockUser],
79+ };
80+ }
81+
82+ if (url === "/tickets") {
83+ json = {
84+ tickets: [mockTicket],
85+ };
86+ }
87+
88+ ctx.response = json;
89+ yield* next();
90+}
91+
92+function* setupActionState(ctx: RoboCtx, next: Next) {
93+ ctx.actions = [];
94+ yield* next();
95+}
96+
97+function* processUsers(ctx: RoboCtx<{ users?: UserResponse[] }>, next: Next) {
98+ if (!ctx.response.users) {
99+ yield* next();
100+ return;
101+ }
102+ const curUsers = ctx.response.users.reduce<MapEntity<User>>((acc, u) => {
103+ acc[u.id] = deserializeUser(u);
104+ return acc;
105+ }, {});
106+ ctx.actions.push(users.actions.add(curUsers));
107+ yield* next();
108+}
109+
110+function* processTickets(
111+ ctx: RoboCtx<{ tickets?: UserResponse[] }>,
112+ next: Next,
113+) {
114+ if (!ctx.response.tickets) {
115+ yield* next();
116+ return;
117+ }
118+ const curTickets = ctx.response.tickets.reduce<MapEntity<Ticket>>(
119+ (acc, u) => {
120+ acc[u.id] = deserializeTicket(u);
121+ return acc;
122+ },
123+ {},
124+ );
125+ ctx.actions.push(tickets.actions.add(curTickets));
126+ yield* next();
127+}
128+
129+function* saveToRedux(ctx: RoboCtx, next: Next) {
130+ for (let i = 0; i < ctx.actions.length; i += 1) {
131+ const action = ctx.actions[i];
132+ yield* put(action);
133+ }
134+ yield* next();
135+}
136+
137+function setupStore(fx: OpFn) {
138+ const store = prepStore(reducers, { fx });
139+ return store;
140+}
141+
142+const tests = describe("createPipe()");
143+
144+it(
145+ tests,
146+ "when create a query fetch pipeline - execute all middleware and save to redux",
147+ () => {
148+ const api = createPipe<RoboCtx>();
149+ api.use(api.routes());
150+ api.use(convertNameToUrl);
151+ api.use(onFetchApi);
152+ api.use(setupActionState);
153+ api.use(processUsers);
154+ api.use(processTickets);
155+ api.use(saveToRedux);
156+ const fetchUsers = api.create(`/users`);
157+
158+ const { store, run } = setupStore(api.bootup);
159+ run();
160+
161+ store.dispatch(fetchUsers());
162+
163+ asserts.assertEquals(store.getState(), {
164+ ...createQueryState(),
165+ [users.name]: { [mockUser.id]: deserializeUser(mockUser) },
166+ [tickets.name]: {},
167+ });
168+ },
169+);
170+
171+it(
172+ tests,
173+ "when providing a generator the to api.create function - should call that generator before all other middleware",
174+ () => {
175+ const api = createPipe<RoboCtx>();
176+ api.use(api.routes());
177+ api.use(convertNameToUrl);
178+ api.use(onFetchApi);
179+ api.use(setupActionState);
180+ api.use(processUsers);
181+ api.use(processTickets);
182+ api.use(saveToRedux);
183+ const fetchUsers = api.create(`/users`);
184+ const fetchTickets = api.create(`/ticket-wrong-url`, function* (ctx, next) {
185+ // before middleware has been triggered
186+ ctx.url = "/tickets";
187+
188+ // triggers all middleware
189+ yield* next();
190+
191+ // after middleware has been triggered
192+ asserts.assertEquals(ctx.actions, [
193+ tickets.actions.add({
194+ [mockTicket.id]: deserializeTicket(mockTicket),
195+ }),
196+ ]);
197+ yield* put(fetchUsers());
198+ });
199+
200+ const { store, run } = setupStore(api.bootup);
201+ run();
202+
203+ store.dispatch(fetchTickets());
204+ asserts.assertEquals(store.getState(), {
205+ ...createQueryState(),
206+ [users.name]: { [mockUser.id]: deserializeUser(mockUser) },
207+ [tickets.name]: { [mockTicket.id]: deserializeTicket(mockTicket) },
208+ });
209+ },
210+);
211+
212+it(tests, "error handling", () => {
213+ const api = createPipe<RoboCtx>();
214+ api.use(api.routes());
215+ api.use(function* upstream(_, next) {
216+ try {
217+ yield* next();
218+ } catch (_) {
219+ asserts.assert(true);
220+ }
221+ });
222+ api.use(function* fail() {
223+ throw new Error("some error");
224+ });
225+
226+ const action = api.create(`/error`);
227+ const { store, run } = setupStore(api.bootup);
228+ run();
229+ store.dispatch(action());
230+});
231+
232+it(tests, "error handling inside create", () => {
233+ const api = createPipe<RoboCtx>();
234+ api.use(api.routes());
235+ api.use(function* fail() {
236+ throw new Error("some error");
237+ });
238+
239+ const action = api.create(`/error`, function* (_, next) {
240+ try {
241+ yield* next();
242+ } catch (_) {
243+ asserts.assert(true);
244+ }
245+ });
246+ const { store, run } = setupStore(api.bootup);
247+ run();
248+ store.dispatch(action());
249+});
250+
251+it(tests, "error handling - error handler", () => {
252+ const api = createPipe<RoboCtx>();
253+ api.use(api.routes());
254+ api.use(function* upstream() {
255+ throw new Error("failure");
256+ });
257+
258+ const action = api.create(`/error`);
259+ const { store, run } = setupStore(api.bootup);
260+ run();
261+ store.dispatch(action());
262+});
263+
264+it(tests, "create fn is an array", () => {
265+ const api = createPipe<RoboCtx>();
266+ api.use(api.routes());
267+ api.use(function* (ctx, next) {
268+ asserts.assertEquals(ctx.request, {
269+ method: "POST",
270+ body: {
271+ test: "me",
272+ },
273+ });
274+ yield* next();
275+ });
276+ const action = api.create("/users", [
277+ function* (ctx, next) {
278+ ctx.request = {
279+ method: "POST",
280+ };
281+ yield* next();
282+ },
283+ function* (ctx, next) {
284+ ctx.request.body = { test: "me" };
285+ yield* next();
286+ },
287+ ]);
288+
289+ const { store, run } = setupStore(api.bootup);
290+ run();
291+ store.dispatch(action());
292+});
293+
294+it(tests, "run() on endpoint action - should run the effect", () => {
295+ const api = createPipe<RoboCtx>();
296+ api.use(api.routes());
297+ let acc = "";
298+ const action1 = api.create("/users", function* (ctx, next) {
299+ yield* next();
300+ ctx.request = { method: "expect this" };
301+ acc += "a";
302+ });
303+ const action2 = api.create("/users2", function* (_, next) {
304+ yield* next();
305+ const curCtx = yield* call(() => action1.run(action1()));
306+ acc += "b";
307+ asserts.assert(acc === "ab");
308+ assertLike(curCtx, {
309+ action: {
310+ type: `@@saga-query${action1}`,
311+ payload: {
312+ name: "/users",
313+ },
314+ },
315+ name: "/users",
316+ request: { method: "expect this" },
317+ });
318+ });
319+
320+ const { store, run } = setupStore(api.bootup);
321+ run();
322+ store.dispatch(action2());
323+});
324+
325+it(tests, "middleware order of execution", async () => {
326+ let acc = "";
327+ const api = createPipe();
328+ api.use(api.routes());
329+
330+ api.use(function* (_, next) {
331+ yield* delay(10);
332+ acc += "b";
333+ yield* next();
334+ yield* delay(10);
335+ acc += "f";
336+ });
337+
338+ api.use(function* (_, next) {
339+ acc += "c";
340+ yield* next();
341+ acc += "d";
342+ yield* delay(30);
343+ acc += "e";
344+ });
345+
346+ const action = api.create("/api", function* (_, next) {
347+ acc += "a";
348+ yield* next();
349+ acc += "g";
350+ });
351+
352+ const { store, run } = setupStore(api.bootup);
353+ run();
354+ store.dispatch(action());
355+
356+ await sleep(150);
357+ asserts.assert(acc === "abcdefg");
358+});
359+
360+it.ignore(tests, "retry with actionFn", async () => {
361+ let acc = "";
362+ let called = false;
363+
364+ const api = createPipe();
365+ api.use(api.routes());
366+
367+ const action = api.create("/api", function* (ctx, next) {
368+ acc += "a";
369+ yield* next();
370+ acc += "g";
371+
372+ if (!called) {
373+ called = true;
374+ yield* put(ctx.actionFn());
375+ }
376+ });
377+
378+ const { store, run } = setupStore(api.bootup);
379+ run();
380+ store.dispatch(action());
381+
382+ await sleep(150);
383+ asserts.assertEquals(acc, "agag");
384+});
385+
386+it.ignore(tests, "retry with actionFn with payload", async () => {
387+ let acc = "";
388+ const api = createPipe();
389+ api.use(api.routes());
390+
391+ api.use(function* (ctx: PipeCtx<{ page: number }>, next) {
392+ yield* next();
393+ if (ctx.payload.page == 1) {
394+ yield* put(ctx.actionFn({ page: 2 }));
395+ }
396+ });
397+
398+ const action = api.create<{ page: number }>("/api", function* (_, next) {
399+ acc += "a";
400+ yield* next();
401+ acc += "g";
402+ });
403+
404+ const { store, run } = setupStore(api.bootup);
405+ const task = run();
406+ store.dispatch(action({ page: 1 }));
407+
408+ await sleep(150);
409+ asserts.assertEquals(acc, "aagg");
410+ await task;
411+});
+242,
-0
1@@ -0,0 +1,242 @@
2+import { call } from "../fx/index.ts";
3+import { takeEvery } from "../redux/index.ts";
4+import { compose } from "../compose.ts";
5+import type { OpFn } from "../types.ts";
6+import { parallel } from "../index.ts";
7+
8+import { isFn, isObject } from "./util.ts";
9+import { createKey } from "./create-key.ts";
10+import type {
11+ ActionWithPayload,
12+ CreateAction,
13+ CreateActionPayload,
14+ CreateActionWithPayload,
15+ Middleware,
16+ MiddlewareCo,
17+ Next,
18+ Payload,
19+ PipeCtx,
20+ Supervisor,
21+} from "./types.ts";
22+import { API_ACTION_PREFIX } from "./constant.ts";
23+
24+export interface SagaApi<Ctx extends PipeCtx> {
25+ use: (fn: Middleware<Ctx>) => void;
26+ routes: () => Middleware<Ctx>;
27+ bootup: OpFn;
28+
29+ /**
30+ * Name only
31+ */
32+ create(name: string): CreateAction<Ctx>;
33+ create<P>(
34+ name: string,
35+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
36+
37+ /**
38+ * Name and options
39+ */
40+ create(name: string, req: { supervisor?: Supervisor }): CreateAction<Ctx>;
41+ create<P>(
42+ name: string,
43+ req: { supervisor?: Supervisor },
44+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
45+
46+ /**
47+ * Name and middleware
48+ */
49+ create(name: string, fn: MiddlewareCo<Ctx>): CreateAction<Ctx>;
50+ create<Gtx extends Ctx = Ctx>(
51+ name: string,
52+ fn: MiddlewareCo<Gtx>,
53+ ): CreateAction<Gtx>;
54+ create<P>(
55+ name: string,
56+ fn: MiddlewareCo<Omit<Ctx, "payload"> & Payload<P>>,
57+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
58+ create<P, Gtx extends Ctx = Ctx>(
59+ name: string,
60+ fn: MiddlewareCo<Gtx>,
61+ ): CreateActionWithPayload<Gtx, P>;
62+
63+ /*
64+ * Name, options, and middleware
65+ */
66+ create(
67+ name: string,
68+ req: { supervisor?: Supervisor },
69+ fn: MiddlewareCo<Ctx>,
70+ ): CreateAction<Ctx>;
71+ create<Gtx extends Ctx = Ctx>(
72+ name: string,
73+ req: { supervisor?: Supervisor },
74+ fn: MiddlewareCo<Gtx>,
75+ ): CreateAction<Gtx>;
76+ create<P>(
77+ name: string,
78+ req: { supervisor?: Supervisor },
79+ fn: MiddlewareCo<Omit<Ctx, "payload"> & Payload<P>>,
80+ ): CreateActionWithPayload<Omit<Ctx, "payload"> & Payload<P>, P>;
81+ create<P, Gtx extends Ctx = Ctx>(
82+ name: string,
83+ req: { supervisor?: Supervisor },
84+ fn: MiddlewareCo<Gtx>,
85+ ): CreateActionWithPayload<Gtx, P>;
86+}
87+
88+/**
89+ * Creates a middleware pipeline.
90+ *
91+ * @remarks
92+ * This middleware pipeline is almost exactly like koa's middleware system.
93+ * See {@link https://koajs.com}
94+ *
95+ * @example
96+ * ```ts
97+ * import { createPipe } from 'saga-query';
98+ *
99+ * const thunk = createPipe();
100+ * thunk.use(function* (ctx, next) {
101+ * console.log('beginning');
102+ * yield next();
103+ * console.log('end');
104+ * });
105+ * thunks.use(thunks.routes());
106+ *
107+ * const doit = thunk.create('do-something', function*(ctx, next) {
108+ * console.log('middle');
109+ * yield next();
110+ * console.log('middle end');
111+ * });
112+ *
113+ * // ...
114+ *
115+ * store.dispatch(doit());
116+ * // beginning
117+ * // middle
118+ * // middle end
119+ * // end
120+ * ```
121+ */
122+export function createPipe<Ctx extends PipeCtx = PipeCtx<any>>(
123+ {
124+ supervisor = takeEvery,
125+ }: {
126+ supervisor?: Supervisor;
127+ } = { supervisor: takeEvery },
128+): SagaApi<Ctx> {
129+ const middleware: Middleware<Ctx>[] = [];
130+ const visors: { [key: string]: OpFn } = {};
131+ const middlewareMap: { [key: string]: Middleware<Ctx> } = {};
132+ const actionMap: {
133+ [key: string]: CreateActionWithPayload<Ctx, any>;
134+ } = {};
135+
136+ function* defaultMiddleware(_: Ctx, next: Next) {
137+ yield* next();
138+ }
139+
140+ const createType = (post: string) =>
141+ `${API_ACTION_PREFIX}${post.startsWith("/") ? "" : "/"}${post}`;
142+
143+ function* onApi<P extends CreateActionPayload>(action: ActionWithPayload<P>) {
144+ const { name, key, options } = action.payload;
145+ const actionFn = actionMap[name];
146+ const ctx = {
147+ action,
148+ name,
149+ key,
150+ payload: options,
151+ actionFn,
152+ } as unknown as Ctx;
153+ const fn = compose(middleware);
154+ yield* call(() => fn(ctx));
155+ return ctx;
156+ }
157+
158+ function create(name: string, ...args: any[]) {
159+ const type = createType(name);
160+ const action = (payload?: any) => {
161+ return { type, payload };
162+ };
163+ let req = null;
164+ let fn = null;
165+ if (args.length === 2) {
166+ req = args[0];
167+ fn = args[1];
168+ }
169+
170+ if (args.length === 1) {
171+ if (isFn(args[0]) || Array.isArray(args[0])) {
172+ fn = args[0];
173+ } else {
174+ req = args[0];
175+ }
176+ }
177+
178+ if (req && !isObject(req)) {
179+ throw new Error("Options must be an object");
180+ }
181+
182+ if (fn && Array.isArray(fn)) {
183+ fn = compose(fn);
184+ }
185+
186+ if (fn && !isFn(fn)) {
187+ throw new Error("Middleware must be a function");
188+ }
189+
190+ if (middlewareMap[name]) {
191+ console.warn(
192+ `[${name}] already exists which means you have two functions with the same name`,
193+ );
194+ }
195+
196+ middlewareMap[name] = fn || defaultMiddleware;
197+
198+ const tt = req ? (req as any).supervisor : supervisor;
199+ function* curVisor() {
200+ const task = yield* tt(type, onApi);
201+ yield* task;
202+ }
203+ visors[name] = curVisor;
204+
205+ const actionFn = (options?: Ctx["payload"]) => {
206+ const key = createKey(name, options);
207+ return action({ name, key, options });
208+ };
209+ actionFn.run = onApi;
210+ actionFn.toString = () => name;
211+ actionMap[name] = actionFn;
212+
213+ return actionFn;
214+ }
215+
216+ function* bootup() {
217+ const results = yield* parallel(Object.values(visors));
218+ yield* results;
219+ }
220+
221+ function routes() {
222+ function* router(ctx: Ctx, next: Next) {
223+ const match = middlewareMap[ctx.name];
224+ if (!match) {
225+ yield* next();
226+ return;
227+ }
228+
229+ yield* call(() => match(ctx, next));
230+ }
231+
232+ return router;
233+ }
234+
235+ return {
236+ use: (fn: Middleware<Ctx>) => {
237+ middleware.push(fn);
238+ },
239+ create,
240+ routes,
241+ bootup,
242+ };
243+}
+125,
-0
1@@ -0,0 +1,125 @@
2+import {
3+ cleanup,
4+ fireEvent,
5+ render,
6+ screen,
7+} from "https://esm.sh/@testing-library/react@14.0.0?pin=v122";
8+
9+import { React } from "../deps.ts";
10+import { asserts, beforeEach, describe, it } from "../test.ts";
11+import {
12+ createAssign,
13+ Provider,
14+ sleep as delay,
15+ useSelector,
16+} from "../deps.ts";
17+
18+import { createApi } from "./api.ts";
19+import { requestMonitor } from "./middleware.ts";
20+import { setupStore } from "./util.ts";
21+import { useApi } from "./react.ts";
22+import { selectDataById } from "./slice.ts";
23+import { createKey } from "./create-key.ts";
24+
25+const h = React.createElement;
26+
27+const mockUser = { id: "1", email: "test@saga-query.com" };
28+
29+const jsonBlob = (data: any) => {
30+ return JSON.stringify(data);
31+};
32+
33+const setupTest = () => {
34+ const slice = createAssign({
35+ name: "user",
36+ initialState: { id: "", email: "" },
37+ });
38+
39+ const api = createApi();
40+ api.use(requestMonitor());
41+ api.use(api.routes());
42+ api.use(function* (ctx, next) {
43+ yield* delay(10);
44+ ctx.json = { ok: true, data: mockUser };
45+ ctx.response = new Response(jsonBlob(mockUser), { status: 200 });
46+ yield next();
47+ });
48+
49+ const fetchUser = api.get<{ id: string }>("/user/:id", function* (ctx, next) {
50+ ctx.cache = true;
51+ yield next();
52+ if (!ctx.json.ok) return;
53+ slice.actions.set(ctx.json.data);
54+ });
55+
56+ const { store, run } = setupStore(
57+ { user: slice.reducer },
58+ {
59+ fx: api.bootup,
60+ },
61+ );
62+ run();
63+ return { store, fetchUser, api };
64+};
65+
66+describe.ignore("useApi()", () => {
67+ beforeEach(() => cleanup());
68+ it("with action", async () => {
69+ const { fetchUser, store } = setupTest();
70+ const App = () => {
71+ const action = fetchUser({ id: "1" });
72+ const query = useApi(action);
73+ const user = useSelector((s: any) =>
74+ selectDataById(s, { id: action.payload.key })
75+ );
76+
77+ return h("div", null, [
78+ h("div", { key: "1" }, user?.email || ""),
79+ h(
80+ "button",
81+ { key: "2", onClick: () => query.trigger() },
82+ query.isLoading ? "loading" : "fetch",
83+ ),
84+ h("div", { key: "3" }, query.isSuccess ? "success" : ""),
85+ ]);
86+ };
87+ render(h(Provider, { store, children: h(App) }));
88+
89+ const button = screen.getByText("fetch");
90+ fireEvent.click(button);
91+
92+ await screen.findByText("loading");
93+ await screen.findByText(mockUser.email);
94+ await screen.findByText("success");
95+ asserts.assert(true);
96+ });
97+
98+ it("with action creator", async () => {
99+ const { fetchUser, store } = setupTest();
100+ const App = () => {
101+ const query = useApi(fetchUser);
102+ const user = useSelector((s: any) => {
103+ const id = createKey(`${fetchUser}`, { id: "1" });
104+ return selectDataById(s, { id });
105+ });
106+ return h("div", null, [
107+ h("div", { key: "1" }, user?.email || "no user"),
108+ h(
109+ "button",
110+ { key: "2", onClick: () => query.trigger({ id: "1" }) },
111+ query.isLoading ? "loading" : "fetch",
112+ ),
113+ h("div", { key: "3" }, query.isSuccess ? "success" : ""),
114+ ]);
115+ };
116+ render(h(Provider, { store, children: h(App) }));
117+
118+ const button = screen.getByText("fetch");
119+ fireEvent.click(button);
120+
121+ await screen.findByText("loading");
122+ await screen.findByText(mockUser.email);
123+ await screen.findByText("success");
124+ asserts.assert(true);
125+ });
126+});
+215,
-0
1@@ -0,0 +1,215 @@
2+import type { LoadingState } from "../deps.ts";
3+import { React, useDispatch, useSelector } from "../deps.ts";
4+const { useState, useEffect } = React;
5+
6+import type { QueryState } from "./slice.ts";
7+import { selectDataById, selectLoaderById } from "./slice.ts";
8+
9+type ActionFn<P = any> = (p: P) => { toString: () => string };
10+type ActionFnSimple = () => { toString: () => string };
11+
12+interface SagaAction<P = any> {
13+ type: string;
14+ payload: { key: string; options: P };
15+}
16+
17+export interface UseApiProps<P = any> extends LoadingState {
18+ trigger: (p: P) => void;
19+ action: ActionFn<P>;
20+}
21+export interface UseApiSimpleProps extends LoadingState {
22+ trigger: () => void;
23+ action: ActionFn;
24+}
25+export interface UseApiAction<A extends SagaAction = SagaAction>
26+ extends LoadingState {
27+ trigger: () => void;
28+ action: A;
29+}
30+export type UseApiResult<P, A extends SagaAction = SagaAction> =
31+ | UseApiProps<P>
32+ | UseApiSimpleProps
33+ | UseApiAction<A>;
34+
35+interface UseCacheResult<D = any, A extends SagaAction = SagaAction>
36+ extends UseApiAction<A> {
37+ data: D | null;
38+}
39+
40+/**
41+ * useLoader will take an action creator or action itself and return the associated
42+ * loader for it.
43+ *
44+ * @returns the loader object for an action creator or action
45+ *
46+ * @example
47+ * ```ts
48+ * import { useLoader } from 'saga-query/react';
49+ *
50+ * import { api } from './api';
51+ *
52+ * const fetchUsers = api.get('/users', function*() {
53+ * // ...
54+ * });
55+ *
56+ * const View = () => {
57+ * const loader = useLoader(fetchUsers);
58+ * // or: const loader = useLoader(fetchUsers());
59+ * return <div>{loader.isLoader ? 'Loading ...' : 'Done!'}</div>
60+ * }
61+ * ```
62+ */
63+export function useLoader<S extends QueryState = QueryState>(
64+ action: SagaAction | ActionFn,
65+) {
66+ const id = typeof action === "function" ? `${action}` : action.payload.key;
67+ return useSelector((s: S) => selectLoaderById(s, { id }));
68+}
69+
70+/**
71+ * useApi will take an action creator or action itself and fetch
72+ * the associated loader and create a `trigger` function that you can call
73+ * later in your react component.
74+ *
75+ * This hook will *not* fetch the data for you because it does not know how to fetch
76+ * data from your redux state.
77+ *
78+ * @example
79+ * ```ts
80+ * import { useApi } from 'saga-query/react';
81+ *
82+ * import { api } from './api';
83+ *
84+ * const fetchUsers = api.get('/users', function*() {
85+ * // ...
86+ * });
87+ *
88+ * const View = () => {
89+ * const { isLoading, trigger } = useApi(fetchUsers);
90+ * useEffect(() => {
91+ * trigger();
92+ * }, []);
93+ * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
94+ * }
95+ * ```
96+ */
97+export function useApi<P = any, A extends SagaAction = SagaAction<P>>(
98+ action: A,
99+): UseApiAction<A>;
100+export function useApi<P = any, A extends SagaAction = SagaAction<P>>(
101+ action: ActionFn<P>,
102+): UseApiProps<P>;
103+export function useApi<A extends SagaAction = SagaAction>(
104+ action: ActionFnSimple,
105+): UseApiSimpleProps;
106+export function useApi(action: any) {
107+ const dispatch = useDispatch();
108+ const loader = useLoader(action);
109+ const trigger = (p: any) => {
110+ if (typeof action === "function") {
111+ dispatch(action(p));
112+ } else {
113+ dispatch(action);
114+ }
115+ };
116+ return { ...loader, trigger, action };
117+}
118+
119+/**
120+ * useQuery uses {@link useApi} and automatically calls `useApi().trigger()`
121+ *
122+ * @example
123+ * ```ts
124+ * import { useQuery } from 'saga-query/react';
125+ *
126+ * import { api } from './api';
127+ *
128+ * const fetchUsers = api.get('/users', function*() {
129+ * // ...
130+ * });
131+ *
132+ * const View = () => {
133+ * const { isLoading } = useQuery(fetchUsers);
134+ * return <div>{isLoading ? : 'Loading' : 'Done!'}</div>
135+ * }
136+ * ```
137+ */
138+export function useQuery<P = any, A extends SagaAction = SagaAction<P>>(
139+ action: A,
140+): UseApiAction<A> {
141+ const api = useApi(action);
142+ useEffect(() => {
143+ api.trigger();
144+ }, [action.payload.key]);
145+ return api;
146+}
147+
148+/**
149+ * useCache uses {@link useQuery} and automatically selects the cached data associated
150+ * with the action creator or action provided.
151+ *
152+ * @example
153+ * ```ts
154+ * import { useCache } from 'saga-query/react';
155+ *
156+ * import { api } from './api';
157+ *
158+ * const fetchUsers = api.get('/users', api.cache());
159+ *
160+ * const View = () => {
161+ * const { isLoading, data } = useCache(fetchUsers());
162+ * return <div>{isLoading ? : 'Loading' : data.length}</div>
163+ * }
164+ * ```
165+ */
166+export function useCache<D = any, A extends SagaAction = SagaAction>(
167+ action: A,
168+): UseCacheResult<D, A> {
169+ const id = action.payload.key;
170+ const data = useSelector((s: any) => selectDataById(s, { id }));
171+ const query = useQuery(action);
172+ return { ...query, data: data || null };
173+}
174+
175+/**
176+ * useLoaderSuccess will activate the callback provided when the loader transitions
177+ * from some state to success.
178+ *
179+ * @example
180+ * ```ts
181+ * import { useLoaderSuccess, useApi } from 'saga-query/react';
182+ *
183+ * import { api } from './api';
184+ *
185+ * const createUser = api.post('/users', function*(ctx, next) {
186+ * // ...
187+ * });
188+ *
189+ * const View = () => {
190+ * const { loader, trigger } = useApi(createUser);
191+ * const onSubmit = () => {
192+ * trigger({ name: 'bob' });
193+ * };
194+ *
195+ * useLoaderSuccess(loader, () => {
196+ * // success!
197+ * // Use this callback to navigate to another view
198+ * });
199+ *
200+ * return <button onClick={onSubmit}>Create user!</button>
201+ * }
202+ * ```
203+ */
204+export function useLoaderSuccess(
205+ cur: Pick<LoadingState, "isLoading" | "isSuccess">,
206+ success: () => any,
207+) {
208+ const [prev, setPrev] = useState(cur);
209+ useEffect(() => {
210+ const curSuccess = !cur.isLoading && cur.isSuccess;
211+ if (prev.isLoading && curSuccess) {
212+ success();
213+ }
214+ setPrev(cur);
215+ }, [cur.isSuccess, cur.isLoading]);
216+}
+66,
-0
1@@ -0,0 +1,66 @@
2+import { call, race } from "../fx/index.ts";
3+import { take } from "../redux/index.ts";
4+import { Operation, sleep, spawn, Task } from "../deps.ts";
5+import type { Action, ActionWPayload, OpFn } from "../types.ts";
6+
7+import type { CreateActionPayload } from "./types.ts";
8+
9+const MS = 1000;
10+const SECONDS = 1 * MS;
11+const MINUTES = 60 * SECONDS;
12+
13+export function poll(parentTimer: number = 5 * 1000, cancelType?: string) {
14+ return function* poller<T>(
15+ actionType: string,
16+ op: (action: Action) => Operation<T>,
17+ ): Operation<T> {
18+ const cancel = cancelType || actionType;
19+ function* fire(action: { type: string }, timer: number) {
20+ while (true) {
21+ yield* call(() => op(action));
22+ yield* sleep(timer);
23+ }
24+ }
25+
26+ while (true) {
27+ const action = yield* take<{ timer?: number }>(actionType);
28+ const timer = action.payload?.timer || parentTimer;
29+ yield* race({
30+ fire: () => call(() => fire(action, timer)),
31+ cancel: () => take(`${cancel}`),
32+ });
33+ }
34+ };
35+}
36+
37+/**
38+ * timer() will create a cache timer for each `key` inside
39+ * of a saga-query api endpoint. `key` is a hash of the action type and payload.
40+ *
41+ * Why do we want this? If we have an api endpoint to fetch a single app: `fetchApp({ id: 1 })`
42+ * if we don't set a timer per key then all calls to `fetchApp` will be on a timer.
43+ * So if we call `fetchApp({ id: 1 })` and then `fetchApp({ id: 2 })` if we use a normal
44+ * cache timer then the second call will not send an http request.
45+ */
46+export function timer(timer: number = 5 * MINUTES) {
47+ return function* onTimer(actionType: string, op: (action: Action) => OpFn) {
48+ const map: { [key: string]: Task<unknown> } = {};
49+
50+ function* activate(action: ActionWPayload<CreateActionPayload>) {
51+ yield* call(() => op(action));
52+ yield* sleep(timer);
53+ delete map[action.payload.key];
54+ }
55+
56+ while (true) {
57+ const action = yield* take<CreateActionPayload>(`${actionType}`);
58+ const key = action.payload.key;
59+ if (!map[key]) {
60+ const task = yield* spawn(function* () {
61+ yield* activate(action);
62+ });
63+ map[key] = task;
64+ }
65+ }
66+ };
67+}
+51,
-0
1@@ -0,0 +1,51 @@
2+import { createLoaderTable, createReducerMap, createTable } from "../deps.ts";
3+import type { LoadingItemState } from "../deps.ts";
4+
5+import { createKey } from "./create-key.ts";
6+
7+export { defaultLoader, defaultLoadingItem } from "../deps.ts";
8+
9+export interface QueryState {
10+ "@@saga-query/loaders": { [key: string]: LoadingItemState };
11+ "@@saga-query/data": { [key: string]: any };
12+}
13+
14+export const LOADERS_NAME = `@@saga-query/loaders`;
15+export const loaders = createLoaderTable({ name: LOADERS_NAME });
16+export const {
17+ loading: setLoaderStart,
18+ error: setLoaderError,
19+ success: setLoaderSuccess,
20+ resetById: resetLoaderById,
21+} = loaders.actions;
22+export const { selectTable: selectLoaders, selectById: selectLoaderById } =
23+ loaders.getSelectors((state: any) => state[LOADERS_NAME] || {});
24+
25+export const DATA_NAME = `@@saga-query/data`;
26+export const data = createTable<any>({ name: DATA_NAME });
27+export const { add: addData, reset: resetData } = data.actions;
28+
29+export const { selectTable: selectData, selectById: selectDataById } = data
30+ .getSelectors((s: any) => s[DATA_NAME] || {});
31+
32+/**
33+ * Returns data from the saga-query slice of redux from an action.
34+ */
35+export const selectDataByName = (
36+ s: any,
37+ p: { name: string; payload?: any },
38+) => {
39+ const id = createKey(p.name, p.payload);
40+ const data = selectDataById(s, { id });
41+ return data;
42+};
43+
44+export const reducers = createReducerMap(loaders, data);
45+
46+export const createQueryState = (s: Partial<QueryState> = {}): QueryState => {
47+ return {
48+ [LOADERS_NAME]: {},
49+ [DATA_NAME]: {},
50+ ...s,
51+ };
52+};
+64,
-0
1@@ -0,0 +1,64 @@
2+import type { Middleware, Reducer, Result, Task } from "../deps.ts";
3+import { combineReducers, enableBatching } from "../deps.ts";
4+import type { OpFn } from "../types.ts";
5+import { createFxMiddleware } from "../redux/index.ts";
6+import { keepAlive } from "../index.ts";
7+
8+import type { QueryState } from "./slice.ts";
9+import { reducers as sagaQueryReducers } from "./slice.ts";
10+
11+export interface PrepareStore<
12+ S extends { [key: string]: any } = { [key: string]: any },
13+> {
14+ reducer: Reducer<S & QueryState>;
15+ middleware: Middleware<any, S, any>[];
16+ run: (...args: any[]) => Task<Result<unknown>>;
17+}
18+
19+interface Props<S extends { [key: string]: any } = { [key: string]: any }> {
20+ reducers: { [key in keyof S]: Reducer<S[key]> };
21+ fx: { [key: string]: OpFn };
22+}
23+
24+/**
25+ * This will setup `redux-batched-actions` to work with `redux-saga`.
26+ * It will also add some reducers to your `redux` store for decoupled loaders
27+ * and a simple data cache.
28+ *
29+ * @example
30+ * ```ts
31+ * import { prepareStore } from 'saga-query';
32+ * import { configureStore } from '@reduxjs/toolkit';
33+ *
34+ * const { middleware, reducer, run } = prepareStore({
35+ * reducers: { users: (state, action) => state },
36+ * fx: { api: api.saga() },
37+ * });
38+ *
39+ * const store = configureStore({
40+ * reducer,
41+ * middleware,
42+ * });
43+ *
44+ * // you must call `.run(...args: any[])` in order for the sagas to bootup.
45+ * run();
46+ * ```
47+ */
48+export function prepareStore<
49+ S extends { [key: string]: unknown } = { [key: string]: unknown },
50+>({ reducers, fx }: Props<S>): PrepareStore<S> {
51+ const middleware: Middleware<unknown, S>[] = [];
52+
53+ const fxMiddleware = createFxMiddleware();
54+ middleware.push(fxMiddleware.middleware);
55+
56+ const reducer = combineReducers({ ...sagaQueryReducers, ...reducers });
57+ const rootReducer = enableBatching(reducer as Reducer);
58+ const run = () => fxMiddleware.run(() => keepAlive(Object.values(fx)));
59+
60+ return {
61+ middleware,
62+ reducer: rootReducer as Reducer,
63+ run,
64+ };
65+}
+129,
-0
1@@ -0,0 +1,129 @@
2+import type {
3+ LoadingItemState,
4+ LoadingMapPayload,
5+ LoadingState,
6+ Operation,
7+} from "../deps.ts";
8+
9+export type { LoadingItemState, LoadingState };
10+
11+type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
12+
13+export interface Payload<P = any> {
14+ payload: P;
15+}
16+
17+export interface PipeCtx<P = any> extends Payload<P> {
18+ name: string;
19+ key: string;
20+ action: ActionWithPayload<CreateActionPayload<P>>;
21+ actionFn: IfAny<
22+ P,
23+ CreateAction<PipeCtx>,
24+ CreateActionWithPayload<PipeCtx<P>, P>
25+ >;
26+}
27+
28+export interface LoaderCtx<P = any> extends PipeCtx<P> {
29+ loader: LoadingMapPayload<Record<string, any>> | null;
30+}
31+
32+export interface ApiFetchSuccess<ApiSuccess = any> {
33+ ok: true;
34+ data: ApiSuccess;
35+}
36+
37+export interface ApiFetchError<ApiError = any> {
38+ ok: false;
39+ data: ApiError;
40+}
41+
42+export type ApiFetchResponse<ApiSuccess = any, ApiError = any> =
43+ | ApiFetchSuccess<ApiSuccess>
44+ | ApiFetchError<ApiError>;
45+
46+export type ApiRequest = Partial<{ url: string } & RequestInit>;
47+export type RequiredApiRequest = {
48+ url: string;
49+ headers: HeadersInit;
50+} & Partial<RequestInit>;
51+
52+export interface FetchCtx<P = any> extends PipeCtx<P> {
53+ request: ApiRequest | null;
54+ req: (r?: ApiRequest) => RequiredApiRequest;
55+ response: Response | null;
56+ bodyType: "arrayBuffer" | "blob" | "formData" | "json" | "text";
57+}
58+
59+export interface FetchJson<ApiSuccess = any, ApiError = any> {
60+ json: ApiFetchResponse<ApiSuccess, ApiError>;
61+}
62+
63+export interface FetchJsonCtx<P = any, ApiSuccess = any, ApiError = any>
64+ extends FetchCtx<P>, FetchJson<ApiSuccess, ApiError> {}
65+
66+export interface ApiCtx<Payload = any, ApiSuccess = any, ApiError = any>
67+ extends FetchJsonCtx<Payload, ApiSuccess, ApiError> {
68+ actions: Action[];
69+ loader: LoadingMapPayload<Record<string, any>> | null;
70+ cache: boolean;
71+ cacheData: any;
72+}
73+
74+export type Middleware<Ctx extends PipeCtx = PipeCtx> = (
75+ ctx: Ctx,
76+ next: Next,
77+) => any;
78+export type MiddlewareCo<Ctx extends PipeCtx = PipeCtx> =
79+ | Middleware<Ctx>
80+ | Middleware<Ctx>[];
81+
82+export type MiddlewareApi<Ctx extends ApiCtx = ApiCtx> = (
83+ ctx: Ctx,
84+ next: Next,
85+) => any;
86+export type MiddlewareApiCo<Ctx extends ApiCtx = ApiCtx> =
87+ | Middleware<Ctx>
88+ | Middleware<Ctx>[];
89+
90+export type Next = () => any;
91+
92+export interface Action {
93+ type: string;
94+}
95+
96+export interface ActionWithPayload<P> extends Action {
97+ payload: P;
98+}
99+
100+export interface CreateActionPayload<P = any> {
101+ name: string;
102+ key: string;
103+ options: P;
104+}
105+
106+export type CreateActionFn = () => ActionWithPayload<
107+ CreateActionPayload<Record<string | number | symbol, never>>
108+>;
109+
110+export interface CreateAction<Ctx> extends CreateActionFn {
111+ run: (
112+ p: ActionWithPayload<
113+ CreateActionPayload<Record<string | number | symbol, never>>
114+ >,
115+ ) => Operation<Ctx>;
116+}
117+
118+export type CreateActionFnWithPayload<P = any> = (
119+ p: P,
120+) => ActionWithPayload<CreateActionPayload<P>>;
121+
122+export interface CreateActionWithPayload<Ctx, P>
123+ extends CreateActionFnWithPayload<P> {
124+ run: (a: ActionWithPayload<CreateActionPayload<P>>) => Operation<Ctx>;
125+}
126+
127+export type Supervisor<T = unknown> = (
128+ pattern: string,
129+ op: (action: Action) => Operation<T>,
130+) => Operation<T>;
+16,
-0
1@@ -0,0 +1,16 @@
2+import { describe, expect, it } from "../test.ts";
3+
4+import { createAction } from "./util.ts";
5+import { API_ACTION_PREFIX } from "./constant.ts";
6+
7+const tests = describe("createAction()");
8+
9+it(tests, "should return action type when stringified", () => {
10+ const undo = createAction("UNDO");
11+ expect(`${API_ACTION_PREFIX}/UNDO`).toEqual(`${undo}`);
12+});
13+
14+it(tests, "return object with type", () => {
15+ const undo = createAction("UNDO");
16+ expect(undo()).toEqual({ type: `${API_ACTION_PREFIX}/UNDO` });
17+});
+70,
-0
1@@ -0,0 +1,70 @@
2+import { configureStore } from "../deps.ts";
3+import type { Reducer } from "../deps.ts";
4+import type { OpFn } from "../types.ts";
5+
6+import type { ApiRequest, RequiredApiRequest } from "./types.ts";
7+import { prepareStore } from "./store.ts";
8+import { API_ACTION_PREFIX } from "./constant.ts";
9+
10+export const noop = () => {};
11+// deno-lint-ignore no-explicit-any
12+export const isFn = (fn?: any) => fn && typeof fn === "function";
13+// deno-lint-ignore no-explicit-any
14+export const isObject = (obj?: any) => typeof obj === "object" && obj !== null;
15+export const createAction = (curType: string) => {
16+ if (!curType) throw new Error("createAction requires non-empty string");
17+ const type = `${API_ACTION_PREFIX}/${curType}`;
18+ const action = () => ({ type });
19+ action.toString = () => type;
20+ return action;
21+};
22+
23+export function setupStore(
24+ reducers: { [key: string]: Reducer } = {},
25+ fx: { [key: string]: OpFn },
26+) {
27+ const fxx = typeof fx === "function" ? { fx } : fx;
28+ const prepared = prepareStore({
29+ reducers,
30+ fx: fxx,
31+ });
32+ const store = configureStore({
33+ reducer: prepared.reducer,
34+ middleware: (getDefaultMiddleware) =>
35+ getDefaultMiddleware().concat(...prepared.middleware),
36+ });
37+ return { store, run: prepared.run };
38+}
39+
40+export const mergeHeaders = (
41+ cur?: { [key: string]: string },
42+ next?: { [key: string]: string },
43+): HeadersInit => {
44+ if (!cur && !next) return {};
45+ if (!cur && next) return next;
46+ if (cur && !next) return cur;
47+ return { ...cur, ...next };
48+};
49+
50+export const mergeRequest = (
51+ cur?: ApiRequest | null,
52+ next?: ApiRequest | null,
53+): RequiredApiRequest => {
54+ const defaultReq = { url: "", method: "GET", headers: mergeHeaders() };
55+ if (!cur && !next) return { ...defaultReq, headers: mergeHeaders() };
56+ if (!cur && next) return { ...defaultReq, ...next };
57+ if (cur && !next) return { ...defaultReq, ...cur };
58+ return {
59+ ...defaultReq,
60+ ...cur,
61+ ...next,
62+ headers: mergeHeaders((cur as any).headers, (next as any).headers),
63+ };
64+};
65+
66+export const sleep = (n: number) =>
67+ new Promise<void>((resolve) => {
68+ setTimeout(() => {
69+ resolve();
70+ }, n);
71+ });
M
react.ts
+3,
-1
1@@ -3,7 +3,9 @@ const { createContext, createElement: h, useContext } = React;
2
3 import type { Operation, Scope } from "./deps.ts";
4 import type { Action } from "./types.ts";
5-import { ActionContext } from "./redux.ts";
6+import { ActionContext } from "./redux/index.ts";
7+
8+export * from "./query/react.ts";
9
10 const ScopeContext = createContext<Scope | null>(null);
11
R redux.ts =>
redux/index.ts
+47,
-16
1@@ -1,6 +1,6 @@
2-import type { Channel, Operation, Scope } from "./deps.ts";
3-import type { Action, OpFn, StoreLike } from "./types.ts";
4-import type { ActionPattern } from "./matcher.ts";
5+import { BATCH, Channel, Instruction, Operation, Scope } from "../deps.ts";
6+import type { Action, ActionWPayload, OpFn, StoreLike } from "../types.ts";
7+import type { ActionPattern } from "../matcher.ts";
8
9 import {
10 configureStore,
11@@ -8,10 +8,10 @@ import {
12 createContext,
13 createScope,
14 spawn,
15-} from "./deps.ts";
16-import { contextualize } from "./context.ts";
17-import { call, cancel, emit, parallel } from "./fx/mod.ts";
18-import { once } from "./iter.ts";
19+} from "../deps.ts";
20+import { contextualize } from "../context.ts";
21+import { call, cancel, emit, parallel } from "../fx/index.ts";
22+import { once } from "../iter.ts";
23
24 export const ActionContext = createContext<Channel<Action, void>>(
25 "redux:action",
26@@ -25,11 +25,18 @@ export function* select<S, R>(selectorFn: (s: S) => R) {
27 return selectorFn(store.getState() as S);
28 }
29
30-export function* take(pattern: ActionPattern) {
31- return yield* once({
32+// https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
33+export function* take<P = never>(
34+ pattern: ActionPattern,
35+): Generator<
36+ Instruction,
37+ [P] extends [never] ? Action : ActionWPayload<P>
38+> {
39+ const action = yield* once({
40 channel: ActionContext,
41 pattern,
42 });
43+ return action as any;
44 }
45
46 export function* takeEvery<T>(
47@@ -76,16 +83,44 @@ export function* takeLeading<T>(
48 }
49
50 export function* put(action: Action | Action[]) {
51+ const store = yield* StoreContext;
52+ if (Array.isArray(action)) {
53+ action.map((act) => store.dispatch(act));
54+ } else {
55+ store.dispatch(action);
56+ }
57 yield* emit({
58 channel: ActionContext,
59 action,
60 });
61 }
62
63+function* send(action: Action) {
64+ if (action.type === BATCH) {
65+ const actions: Action[] = action.payload;
66+ yield* parallel(
67+ actions.map(
68+ (a) =>
69+ function* () {
70+ yield* emit({
71+ channel: ActionContext,
72+ action: a,
73+ });
74+ },
75+ ),
76+ );
77+ } else {
78+ yield* emit({
79+ channel: ActionContext,
80+ action,
81+ });
82+ }
83+}
84+
85 export function createFxMiddleware(scope: Scope = createScope()) {
86 function run<T>(op: OpFn<T>) {
87 const task = scope.run(function* runner() {
88- yield* call(op);
89+ return yield* call(op);
90 });
91
92 return task;
93@@ -99,11 +134,7 @@ export function createFxMiddleware(scope: Scope = createScope()) {
94 return (next: (a: Action) => T) => (action: Action) => {
95 const result = next(action); // hit reducers
96 scope.run(function* () {
97- if (Array.isArray(action)) {
98- yield* parallel(action.map((a) => () => put(a)));
99- } else {
100- yield* put(action);
101- }
102+ yield* send(action);
103 });
104 return result;
105 };
106@@ -112,7 +143,7 @@ export function createFxMiddleware(scope: Scope = createScope()) {
107 return { run, scope, middleware };
108 }
109
110-interface SetupStoreProps<S = any> {
111+interface SetupStoreProps<S = unknown> {
112 reducer: (s: S, _: Action) => S;
113 }
114
R test/middleware.test.ts =>
redux/middleware.test.ts
+3,
-3
1@@ -1,9 +1,9 @@
2 import { describe, expect, it } from "../test.ts";
3-
4-import { call } from "../mod.ts";
5-import { createFxMiddleware, select } from "../redux.ts";
6+import { call } from "../fx/index.ts";
7 import { Action } from "../types.ts";
8
9+import { createFxMiddleware, select } from "./index.ts";
10+
11 const tests = describe("createMiddleware()");
12
13 interface Store<S> {
R test/put.test.ts =>
redux/put.test.ts
+11,
-7
1@@ -1,7 +1,7 @@
2-import { describe, expect, it } from "../test.ts";
3+import { describe, expect, it, setupReduxScope } from "../test.ts";
4+import { sleep, spawn } from "../deps.ts";
5
6-import { run, sleep, spawn } from "../deps.ts";
7-import { ActionContext, put, take } from "../redux.ts";
8+import { ActionContext, put, take } from "./index.ts";
9
10 const putTests = describe("put()");
11
12@@ -27,7 +27,8 @@ it(putTests, "should send actions through channel", async () => {
13 });
14 }
15
16- await run(() => genFn("arg"));
17+ const scope = setupReduxScope();
18+ await scope.run(() => genFn("arg"));
19
20 const expected = ["arg", "2"];
21 expect(actual).toEqual(expected);
22@@ -56,7 +57,8 @@ it(putTests, "should handle nested puts", async () => {
23 yield* spawn(genA);
24 }
25
26- await run(root);
27+ const scope = setupReduxScope();
28+ await scope.run(() => root());
29
30 const expected = ["put b", "put a"];
31 expect(actual).toEqual(expected);
32@@ -73,7 +75,8 @@ it(
33 yield* sleep(0);
34 }
35
36- await run(root);
37+ const scope = setupReduxScope();
38+ await scope.run(() => root());
39 expect(true).toBe(true);
40 },
41 );
42@@ -100,7 +103,8 @@ it(
43 yield* tsk;
44 }
45
46- await run(root);
47+ const scope = setupReduxScope();
48+ await scope.run(() => root());
49 const expected = ["didn't get missed"];
50 expect(actual).toEqual(expected);
51 },
R test/take-helper.test.ts =>
redux/take-helper.test.ts
+3,
-3
1@@ -1,9 +1,9 @@
2 import { describe, expect, it } from "../test.ts";
3-
4-import { setupStore, take, takeEvery } from "../redux.ts";
5-import { cancel } from "../fx/mod.ts";
6+import { cancel } from "../fx/index.ts";
7 import type { Action } from "../types.ts";
8
9+import { setupStore, take, takeEvery } from "./index.ts";
10+
11 const testEvery = describe("takeEvery()");
12
13 it(testEvery, "should work", async () => {
R test/take.test.ts =>
redux/take.test.ts
+14,
-11
1@@ -1,16 +1,16 @@
2-import { describe, expect, it } from "../test.ts";
3-
4-import { run, sleep, spawn } from "../deps.ts";
5-import { put, take } from "../redux.ts";
6+import { describe, expect, it, setupReduxScope } from "../test.ts";
7+import { sleep, spawn } from "../deps.ts";
8 import type { Action } from "../types.ts";
9
10+import { put, take } from "./index.ts";
11+
12 const takeTests = describe("take()");
13
14 it(
15 takeTests,
16 "a put should complete before more `take` are added and then consumed automatically",
17 async () => {
18- const actual: any[] = [];
19+ const actual: Action[] = [];
20
21 function* channelFn() {
22 yield* sleep(10);
23@@ -25,11 +25,13 @@ it(
24 actual.push(yield* take("action-1"));
25 }
26
27- await run(root);
28+ const scope = setupReduxScope();
29+ await scope.run(root);
30+
31 expect(actual).toEqual([
32 { type: "action-1", payload: 1 },
33 { type: "action-1", payload: 2 },
34- ]); // actual: [ { type: "action-1", payload: 1 }, { type: "action-1", payload: 1 } ]
35+ ]);
36 },
37 );
38
39@@ -57,7 +59,7 @@ it(takeTests, "take from default channel", async () => {
40 });
41 }
42
43- const actual: any[] = [];
44+ const actual: Action[] = [];
45 function* genFn() {
46 yield* spawn(channelFn);
47
48@@ -79,11 +81,12 @@ it(takeTests, "take from default channel", async () => {
49 ]),
50 ); // take if match any from the mixed array
51 } finally {
52- actual.push("auto ended");
53+ actual.push({ type: "auto ended" });
54 }
55 }
56
57- await run(genFn);
58+ const scope = setupReduxScope();
59+ await scope.run(genFn);
60
61 const expected = [
62 {
63@@ -110,7 +113,7 @@ it(takeTests, "take from default channel", async () => {
64 {
65 type: "action-3",
66 },
67- "auto ended",
68+ { type: "auto ended" },
69 ];
70 expect(actual).toEqual(expected);
71 });
M
test.ts
+65,
-1
1@@ -1,2 +1,66 @@
2-export { describe, it } from "https://deno.land/std@0.163.0/testing/bdd.ts";
3+export { build, emptyDir } from "https://deno.land/x/dnt@0.35.0/mod.ts";
4+export { assert } from "https://deno.land/std@0.187.0/testing/asserts.ts";
5+export {
6+ beforeEach,
7+ describe,
8+ it,
9+} from "https://deno.land/std@0.163.0/testing/bdd.ts";
10+export * as asserts from "https://deno.land/std@0.185.0/testing/asserts.ts";
11 export { expect } from "https://deno.land/x/expect@v0.3.0/mod.ts";
12+export {
13+ install,
14+ mock,
15+ mockedFetch,
16+} from "https://deno.land/x/mock_fetch@0.3.0/mod.ts";
17+
18+import { contextualize } from "./context.ts";
19+import { configureStore, createScope } from "./deps.ts";
20+
21+export function isLikeSelector(selector: unknown) {
22+ return (
23+ selector !== null &&
24+ typeof selector === "object" &&
25+ Reflect.getPrototypeOf(selector) === Object.prototype &&
26+ Reflect.ownKeys(selector).length > 0
27+ );
28+}
29+
30+export const CIRCULAR_SELECTOR = new Error("Encountered a circular selector");
31+
32+export function assertLike(
33+ lhs: Record<any, any>,
34+ selector: Record<any, any>,
35+ circular = new Set(),
36+) {
37+ if (circular.has(selector)) {
38+ throw CIRCULAR_SELECTOR;
39+ }
40+
41+ circular.add(selector);
42+
43+ if (lhs === null || typeof lhs !== "object") {
44+ return lhs;
45+ }
46+
47+ const comparable: Record<any, any> = {};
48+ for (const [key, rhs] of Object.entries(selector)) {
49+ if (isLikeSelector(rhs)) {
50+ comparable[key] = assertLike(Reflect.get(lhs, key), rhs, circular);
51+ } else {
52+ comparable[key] = Reflect.get(lhs, key);
53+ }
54+ }
55+
56+ return comparable;
57+}
58+
59+export function setupReduxScope() {
60+ const scope = createScope();
61+ const store = configureStore({
62+ reducer: () => null,
63+ });
64+ scope.run(function* () {
65+ yield* contextualize("redux:store", store);
66+ });
67+ return scope;
68+}
M
types.ts
+10,
-2
1@@ -5,10 +5,18 @@ export interface Computation<T = any> {
2 }
3
4 export type ActionType = string;
5-export interface Action<P = any> {
6+export interface Action {
7 type: ActionType;
8- payload?: P;
9+ payload?: any;
10+ meta?: any;
11+ error?: boolean;
12 }
13+export interface ActionWPayload<P> {
14+ type: ActionType;
15+ payload: P;
16+}
17+export type AnyAction = Action | ActionWPayload<unknown>;
18+
19 export type OpFn<T = unknown> =
20 | (() => Operation<T>)
21 | (() => PromiseLike<T>)