modify MediaApi to support custom fetch implementation, defaults to native fetch (#158)

* modify MediaApi to support custom fetch implementation, defaults to native fetch

added an optional `fetcher` parameter to allow usage of a custom fetch function in both `upload` and `fetcher` methods. Defaults to the standard `fetch` if none is provided.

* fix tests and improve api fetcher types
This commit is contained in:
dswbx
2025-04-22 15:44:55 +02:00
committed by GitHub
parent 5763a6e150
commit a1be1b4bf4
4 changed files with 27 additions and 10 deletions
+8 -4
View File
@@ -1,8 +1,8 @@
/// <reference types="@types/bun" /> /// <reference types="@types/bun" />
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { Hono } from "hono"; import { Hono } from "hono";
import { getFileFromContext, isFile, isReadableStream } from "../../src/core/utils"; import { getFileFromContext, isFile, isReadableStream } from "core/utils";
import { MediaApi } from "../../src/media/api/MediaApi"; import { MediaApi } from "media/api/MediaApi";
import { assetsPath, assetsTmpPath } from "../helper"; import { assetsPath, assetsTmpPath } from "../helper";
const mockedBackend = new Hono() const mockedBackend = new Hono()
@@ -121,8 +121,12 @@ describe("MediaApi", () => {
}); });
it("should upload file in various ways", async () => { it("should upload file in various ways", async () => {
// @ts-ignore tests const api = new MediaApi(
const api = new MediaApi({}, mockedBackend.request); {
upload_fetcher: mockedBackend.request,
},
mockedBackend.request,
);
const file = Bun.file(`${assetsPath}/image.png`); const file = Bun.file(`${assetsPath}/image.png`);
async function matches(req: Promise<any>, filename: string) { async function matches(req: Promise<any>, filename: string) {
+6 -1
View File
@@ -8,6 +8,11 @@ import { omitKeys } from "core/utils";
export type TApiUser = SafeUser; export type TApiUser = SafeUser;
export type ApiFetcher = (
input: RequestInfo | URL,
init?: RequestInit,
) => Response | Promise<Response>;
declare global { declare global {
interface Window { interface Window {
__BKND__: { __BKND__: {
@@ -21,7 +26,7 @@ export type ApiOptions = {
headers?: Headers; headers?: Headers;
key?: string; key?: string;
localStorage?: boolean; localStorage?: boolean;
fetcher?: typeof fetch; fetcher?: ApiFetcher;
verbose?: boolean; verbose?: boolean;
verified?: boolean; verified?: boolean;
} & ( } & (
+9 -2
View File
@@ -6,13 +6,17 @@ import {
type TInput, type TInput,
} from "modules/ModuleApi"; } from "modules/ModuleApi";
import type { FileWithPath } from "ui/elements/media/file-selector"; import type { FileWithPath } from "ui/elements/media/file-selector";
import type { ApiFetcher } from "Api";
export type MediaApiOptions = BaseModuleApiOptions & {}; export type MediaApiOptions = BaseModuleApiOptions & {
upload_fetcher: ApiFetcher;
};
export class MediaApi extends ModuleApi<MediaApiOptions> { export class MediaApi extends ModuleApi<MediaApiOptions> {
protected override getDefaultOptions(): Partial<MediaApiOptions> { protected override getDefaultOptions(): Partial<MediaApiOptions> {
return { return {
basepath: "/api/media", basepath: "/api/media",
upload_fetcher: fetch,
}; };
} }
@@ -109,10 +113,12 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
filename?: string; filename?: string;
_init?: Omit<RequestInit, "body">; _init?: Omit<RequestInit, "body">;
path?: TInput; path?: TInput;
fetcher?: ApiFetcher;
} = {}, } = {},
) { ) {
if (item instanceof Request || typeof item === "string") { if (item instanceof Request || typeof item === "string") {
const res = await this.fetcher(item); const fetcher = opts.fetcher ?? this.options.upload_fetcher;
const res = await fetcher(item);
if (!res.ok || !res.body) { if (!res.ok || !res.body) {
throw new Error("Failed to fetch file"); throw new Error("Failed to fetch file");
} }
@@ -143,6 +149,7 @@ export class MediaApi extends ModuleApi<MediaApiOptions> {
item: Request | Response | string | File | ReadableStream, item: Request | Response | string | File | ReadableStream,
opts?: { opts?: {
_init?: Omit<RequestInit, "body">; _init?: Omit<RequestInit, "body">;
fetcher?: typeof fetch;
}, },
) { ) {
return this.upload(item, { return this.upload(item, {
+4 -3
View File
@@ -1,6 +1,7 @@
import { $console, type PrimaryFieldType } from "core"; import { $console, type PrimaryFieldType } from "core";
import { isDebug } from "core/env"; import { isDebug } from "core/env";
import { encodeSearch } from "core/utils/reqres"; import { encodeSearch } from "core/utils/reqres";
import type { ApiFetcher } from "Api";
export type { PrimaryFieldType }; export type { PrimaryFieldType };
export type BaseModuleApiOptions = { export type BaseModuleApiOptions = {
@@ -24,11 +25,11 @@ export type ApiResponse<Data = any> = {
export type TInput = string | (string | number | PrimaryFieldType)[]; export type TInput = string | (string | number | PrimaryFieldType)[];
export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModuleApiOptions> { export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModuleApiOptions> {
protected fetcher: typeof fetch; protected fetcher: ApiFetcher;
constructor( constructor(
protected readonly _options: Partial<Options> = {}, protected readonly _options: Partial<Options> = {},
fetcher?: typeof fetch, fetcher?: ApiFetcher,
) { ) {
this.fetcher = fetcher ?? fetch; this.fetcher = fetcher ?? fetch;
} }
@@ -221,7 +222,7 @@ export class FetchPromise<T = ApiResponse<any>> implements Promise<T> {
constructor( constructor(
public request: Request, public request: Request,
protected options?: { protected options?: {
fetcher?: typeof fetch; fetcher?: ApiFetcher;
verbose?: boolean; verbose?: boolean;
}, },
// keep "any" here, it gets inferred correctly with the "refine" fn // keep "any" here, it gets inferred correctly with the "refine" fn