-
Notifications
You must be signed in to change notification settings - Fork 2
feat(fetch): add fetchApi with middleware support #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8b87416
bea8bd0
f9e693a
871f161
a246570
3baf2dd
9dda7e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,10 +14,11 @@ import { | |
| call, | ||
| each, | ||
| ensure, | ||
| spawn, | ||
| withResolvers, | ||
| } from "effection"; | ||
|
|
||
| import { HttpError, fetch } from "./fetch.ts"; | ||
| import { type FetchResponse, HttpError, fetch, fetchApi } from "./fetch.ts"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, verify the file exists and check for createMockResponse export
find . -name "fetch.ts" -o -name "fetch.test.ts" | head -20Repository: thefrontside/effectionx Length of output: 105 🏁 Script executed: #!/bin/bash
# Search for createMockResponse export in fetch module
rg -n "createMockResponse" --type tsRepository: thefrontside/effectionx Length of output: 637 🏁 Script executed: #!/bin/bash
# Check the structure of fetch directory
fd -t f . fetch/Repository: thefrontside/effectionx Length of output: 169 🏁 Script executed: #!/bin/bash
# Check the import statement at line 21 of fetch.test.ts
sed -n '15,30p' fetch/fetch.test.tsRepository: thefrontside/effectionx Length of output: 433 🏁 Script executed: #!/bin/bash
# Check lines around 220-253 to see the manual mock response
sed -n '215,260p' fetch/fetch.test.tsRepository: thefrontside/effectionx Length of output: 1322 🏁 Script executed: #!/bin/bash
# Check the createMockResponse function signature and implementation
sed -n '340,360p' fetch/fetch.tsRepository: thefrontside/effectionx Length of output: 636 🏁 Script executed: #!/bin/bash
# Check if FetchResponse type is used elsewhere in the test file
rg -n "FetchResponse" fetch/fetch.test.tsRepository: thefrontside/effectionx Length of output: 194 🏁 Script executed: #!/bin/bash
# Check the full createMockResponse implementation to understand what it returns
sed -n '347,370p' fetch/fetch.tsRepository: thefrontside/effectionx Length of output: 671 Use This keeps tests aligned with the public helper and avoids duplicating the FetchResponse surface. ♻️ Suggested refactor-import { type FetchResponse, HttpError, fetch, fetchApi } from "./fetch.ts";
+import { HttpError, fetch, fetchApi, createMockResponse } from "./fetch.ts";
@@
- // Create a mock response
- const mockResponse: FetchResponse = {
- raw: new Response(JSON.stringify({ mocked: true })),
- bodyUsed: false,
- ok: true,
- status: 200,
- statusText: "OK",
- headers: new Headers(),
- url: "mock://test",
- redirected: false,
- type: "basic",
- *json<T>(): Operation<T> {
- return { mocked: true } as T;
- },
- *text(): Operation<string> {
- return '{"mocked": true}';
- },
- *arrayBuffer(): Operation<ArrayBuffer> {
- return new ArrayBuffer(0);
- },
- *blob(): Operation<Blob> {
- return new Blob();
- },
- *formData(): Operation<FormData> {
- return new FormData();
- },
- body() {
- throw new Error("Not implemented");
- },
- *expect() {
- return this;
- },
- };
+ const mockResponse = createMockResponse({ mocked: true });Also applies to: 220-253 🤖 Prompt for AI Agents |
||
|
|
||
| function box<T>(content: () => Operation<T>): Operation<Result<T>> { | ||
| return { | ||
|
|
@@ -197,4 +198,117 @@ describe("fetch()", () => { | |
| expect(data).toEqual({ id: 1, title: "do things" }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("middleware API", () => { | ||
| it("can intercept requests with logging", function* () { | ||
| let requestedUrls: string[] = []; | ||
|
|
||
| yield* fetchApi.around({ | ||
| *fetch(args, next) { | ||
| let [input] = args; | ||
| requestedUrls.push(String(input)); | ||
| return yield* next(...args); | ||
| }, | ||
| }); | ||
|
|
||
| yield* fetch(`${url}/json`).json(); | ||
| yield* fetch(`${url}/text`).text(); | ||
|
|
||
| expect(requestedUrls).toEqual([`${url}/json`, `${url}/text`]); | ||
| }); | ||
|
|
||
| it("can mock responses", function* () { | ||
| // Create a mock response | ||
| const mockResponse: FetchResponse = { | ||
| raw: new Response(JSON.stringify({ mocked: true })), | ||
| bodyUsed: false, | ||
| ok: true, | ||
| status: 200, | ||
| statusText: "OK", | ||
| headers: new Headers(), | ||
| url: "mock://test", | ||
| redirected: false, | ||
| type: "basic", | ||
| *json<T>(): Operation<T> { | ||
| return { mocked: true } as T; | ||
| }, | ||
| *text(): Operation<string> { | ||
| return '{"mocked": true}'; | ||
| }, | ||
| *arrayBuffer(): Operation<ArrayBuffer> { | ||
| return new ArrayBuffer(0); | ||
| }, | ||
| *blob(): Operation<Blob> { | ||
| return new Blob(); | ||
| }, | ||
| *formData(): Operation<FormData> { | ||
| return new FormData(); | ||
| }, | ||
| body() { | ||
| throw new Error("Not implemented"); | ||
| }, | ||
| *expect() { | ||
| return this; | ||
| }, | ||
| }; | ||
|
|
||
| yield* fetchApi.around({ | ||
| *fetch(args, next) { | ||
| let [input] = args; | ||
| if (String(input).includes("/mocked")) { | ||
| return mockResponse; | ||
| } | ||
| return yield* next(...args); | ||
| }, | ||
| }); | ||
|
|
||
| // This should be mocked | ||
| let mockedData = yield* fetch(`${url}/mocked`).json<{ | ||
| mocked: boolean; | ||
| }>(); | ||
| expect(mockedData).toEqual({ mocked: true }); | ||
|
|
||
| // This should still hit the real server | ||
| let realData = yield* fetch(`${url}/json`).json<{ | ||
| id: number; | ||
| title: string; | ||
| }>(); | ||
| expect(realData).toEqual({ id: 1, title: "do things" }); | ||
| }); | ||
|
|
||
| it("middleware is scoped and does not leak", function* () { | ||
| let outerCalls: string[] = []; | ||
| let innerCalls: string[] = []; | ||
|
|
||
| yield* fetchApi.around({ | ||
| *fetch(args, next) { | ||
| outerCalls.push("outer"); | ||
| return yield* next(...args); | ||
| }, | ||
| }); | ||
|
|
||
| // Make a request in outer scope | ||
| yield* fetch(`${url}/json`).json(); | ||
|
|
||
| // Spawn a child scope with additional middleware | ||
| let task = yield* spawn(function* () { | ||
| yield* fetchApi.around({ | ||
| *fetch(args, next) { | ||
| innerCalls.push("inner"); | ||
| return yield* next(...args); | ||
| }, | ||
| }); | ||
|
|
||
| // Make request in inner scope - should hit both middlewares | ||
| yield* fetch(`${url}/json`).json(); | ||
| }); | ||
|
|
||
| yield* task; | ||
|
|
||
| // Outer scope should only have outer middleware call | ||
| expect(outerCalls).toEqual(["outer", "outer"]); | ||
| // Inner scope should have one call | ||
| expect(innerCalls).toEqual(["inner"]); | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider using consistent Effection entry point across examples.
The middleware examples use
run()(lines 183, 203) while the earlier usage example usesmain()(line 23). Both are correct, but usingmain()consistently would improve uniformity.📝 Suggested consistency improvement
Note:
run()is perfectly valid—this is purely for stylistic consistency.Also applies to: 203-203
🤖 Prompt for AI Agents