Skip to content

Commit

Permalink
test: plugin schema
Browse files Browse the repository at this point in the history
  • Loading branch information
Bekacru committed Jul 22, 2024
1 parent 442fd28 commit 5393c1a
Show file tree
Hide file tree
Showing 16 changed files with 453 additions and 159 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
node_modules
*.log
.DS_Store
coverage
dist
types
.conf*
.env
1 change: 0 additions & 1 deletion doc/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Inter } from "next/font/google";
import type { ReactNode } from "react";
import "fumadocs-ui/twoslash.css";
import { baseUrl, createMetadata } from "@/lib/metadata";
import { Viewport } from "next";

const inter = Inter({
subsets: ["latin"],
Expand Down
2 changes: 1 addition & 1 deletion doc/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "better-fetch-doc",
"name": "doc",
"version": "0.0.0",
"private": true,
"scripts": {
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@
"name": "@better-fetch/root",
"private": true,
"scripts": {
"build": "pnpm -F fetch build",
"test": "pnpm -F fetch test",
"build": "pnpm --filter \"./packages/*\" build",
"dev": "pnpm -F fetch dev",
"test": "pnpm --filter \"./packages/*\" test",
"bump": "pnpm -F fetch bump",
"typecheck": "pnpm -r typecheck",
"lint": "biome check .",
"format": "biome check . --apply"
},
"dependencies": {
"@biomejs/biome": "1.7.3",
"simple-git-hooks": "^2.11.1"
"simple-git-hooks": "^2.11.1",
"vitest": "^1.5.0"
},
"simple-git-hooks": {
"pre-push": "pnpm typecheck"
},
"devDependencies": {
"tsup": "^8.0.2"
}
}
8 changes: 0 additions & 8 deletions packages/better-fetch/.gitignore

This file was deleted.

2 changes: 2 additions & 0 deletions packages/better-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"listhen": "^1.7.2",
"mocha": "^10.4.0",
"tsup": "^8.0.2",
"type-fest": "^4.23.0",
"typescript": "^5.4.5",
"vitest": "^1.5.0",
"zod": "^3.23.6"
Expand All @@ -30,6 +31,7 @@
},
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest",
"bump": "bumpp",
"test:watch": "vitest watch",
Expand Down
41 changes: 26 additions & 15 deletions packages/better-fetch/src/create-fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,35 @@ const applySchemaPlugin = (config: CreateFetchOption) =>
name: "Apply Schema",
version: "1.0.0",
async init(url, options) {
let method = "";
if (url.startsWith("@")) {
const pMethod = url.split("@")[1]?.split("/")[0];
if (methods.includes(pMethod)) {
method = pMethod;
const schema =
config.plugins?.find((plugin) =>
plugin.schema?.config
? url.startsWith(plugin.schema.config.baseURL || "") ||
url.startsWith(plugin.schema.config.prefix || "")
: false,
)?.schema || config.schema;
if (schema) {
let urlKey = url;
if (schema.config?.prefix) {
if (urlKey.startsWith(schema.config.prefix)) {
urlKey = urlKey.replace(schema.config.prefix, "");
if (schema.config.baseURL) {
url = url.replace(schema.config.prefix, schema.config.baseURL);
}
}
}
}
if (config.schema) {
const schema = config.schema.schema[url];
if (schema) {
if (schema.config?.baseURL) {
if (urlKey.startsWith(schema.config.baseURL)) {
urlKey = urlKey.replace(schema.config.baseURL, "");
}
}
const keySchema = schema.schema[urlKey];
if (keySchema) {
return {
url,
options: {
method: schema.method ?? method,
output: schema.output,
method: keySchema.method,
output: keySchema.output,
...options,
},
};
Expand All @@ -44,10 +58,7 @@ export const createFetch = <Option extends CreateFetchOption>(
const opts = {
...config,
...options,
plugins: [
...(config?.plugins || []),
config?.schema ? applySchemaPlugin(config) : [],
],
plugins: [...(config?.plugins || []), applySchemaPlugin(config || {})],
} as BetterFetchOption;

if (config?.catchAllError) {
Expand Down
56 changes: 21 additions & 35 deletions packages/better-fetch/src/create-fetch/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,50 +24,36 @@ export type FetchSchemaRoutes = {

export const createSchema = <
F extends FetchSchemaRoutes,
S extends {
strict?: boolean;
},
S extends SchemaConfig,
>(
schema: F,
config?: S,
) => {
return {
schema,
schema: schema as F,
config: config as S,
};
};

export type SchemaConfig = {
strict?: boolean;
/**
* A prefix that will be prepended when it's
* calling the schema.
*
* NOTE: Make sure to handle converting
* the prefix to the baseURL in the init
* function if you you are defining for a
* plugin.
*/
prefix?: "" | (string & Record<never, never>);
/**
* The base url of the schema. By default it's the baseURL of the fetch instance.
*/
baseURL?: "" | (string & Record<never, never>);
};

export type Schema = {
schema: FetchSchemaRoutes;
config: {
strict: boolean;
/**
* The base url of the schema. By default it's the baseURL of the fetch instance.
*/
baseURL?: "" | (string & Record<never, never>);
prefix?: "" | (string & Record<never, never>);
};
config: SchemaConfig;
};

export type InferQuery<Q> = Q extends z.ZodSchema ? z.infer<Q> : any;

export type IsFieldOptional<T> = T extends z.ZodSchema
? T extends z.ZodOptional<any>
? true
: false
: true;

export type IsOptionRequired<T extends FetchSchema> = IsFieldOptional<
T["input"]
> extends false
? true
: IsFieldOptional<T["query"]> extends false
? true
: IsFieldOptional<T["params"]> extends false
? true
: false;

export type RequiredOptionKeys<T extends FetchSchema> =
| (IsFieldOptional<T["input"]> extends false ? "body" : never)
| (IsFieldOptional<T["query"]> extends false ? "query" : never)
| (IsFieldOptional<T["params"]> extends false ? "params" : never);
84 changes: 61 additions & 23 deletions packages/better-fetch/src/create-fetch/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import type { ZodObject, ZodSchema, z } from "zod";
import type { StringLiteralUnion } from "../type-utils";
import type { BetterFetchOption, BetterFetchResponse } from "../types";
import type {
FetchSchema,
IsOptionRequired,
RequiredOptionKeys,
Schema,
} from "./schema";
import type { FetchSchema, Schema } from "./schema";
import { BetterFetchPlugin } from "../plugins";

import { IsEmptyObject } from "type-fest";
export interface CreateFetchOption extends BetterFetchOption {
schema?: Schema;
/**
Expand All @@ -22,38 +17,81 @@ export interface CreateFetchOption extends BetterFetchOption {

type WithRequired<T, K extends keyof T | never> = T & { [P in K]-?: T[P] };
type InferBody<T> = T extends ZodSchema ? z.infer<T> : any;
type InferQuery<T> = T extends ZodSchema ? z.infer<T> : any;

type InferParamPath<Path> =
Path extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof InferParamPath<Rest>]: string }
: Path extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: Path extends `${infer _Start}/${infer Rest}`
? InferParamPath<Rest>
: {};

export type InferParam<Path, Param> = Param extends ZodSchema
? z.infer<Param>
: Path extends `${infer _}:${infer P}`
? P extends `${infer _2}:${infer _P}`
? Array<string>
: {
[key in P]: string;
}
: never;
: InferParamPath<Path>;

export type InferOptions<T extends FetchSchema, Key> = WithRequired<
BetterFetchOption<
InferBody<T["input"]>,
InferQuery<T["query"]>,
InferParam<Key, T["params"]>
>,
RequiredOptionKeys<T> extends keyof BetterFetchOption
? RequiredOptionKeys<T>
RequiredOptionKeys<T, Key> extends keyof BetterFetchOption
? RequiredOptionKeys<T, Key>
: never
>;

export type InferQuery<Q> = Q extends z.ZodSchema ? z.infer<Q> : any;

export type IsFieldOptional<T> = T extends z.ZodSchema
? T extends z.ZodOptional<any>
? true
: false
: true;

type IsParamOptional<T, K> = IsFieldOptional<T> extends false
? false
: IsEmptyObject<InferParamPath<K>> extends false
? false
: true;

export type IsOptionRequired<T extends FetchSchema, Key> = IsFieldOptional<
T["input"]
> extends false
? true
: IsFieldOptional<T["query"]> extends false
? true
: IsParamOptional<T["params"], Key> extends false
? true
: false;

export type RequiredOptionKeys<T extends FetchSchema, Key> =
| (IsFieldOptional<T["input"]> extends false ? "body" : never)
| (IsFieldOptional<T["query"]> extends false ? "query" : never)
| (IsParamOptional<T["params"], Key> extends false ? "params" : never);

export type InferKey<S> = S extends Schema
? S["config"]["strict"] extends true
? keyof S["schema"]
? S["config"]["prefix"] extends string
? `${S["config"]["prefix"]}${keyof S["schema"] extends string
? keyof S["schema"]
: never}`
: S["config"]["baseURL"] extends string
? `${S["config"]["baseURL"]}${keyof S["schema"] extends string
? keyof S["schema"]
: never}`
: keyof S["schema"] extends string
? keyof S["schema"]
: never
: S["config"]["prefix"] extends string
? StringLiteralUnion<`${S["config"]["prefix"]}${keyof S["schema"] extends string
? keyof S["schema"]
: never}`>
: S["config"]["baseURL"] extends string
? `${S["config"]["baseURL"]}${keyof S["schema"] extends string
? StringLiteralUnion<`${S["config"]["baseURL"]}${keyof S["schema"] extends string
? keyof S["schema"]
: never}`
: never}`>
: StringLiteralUnion<
keyof S["schema"] extends string ? keyof S["schema"] : never
>
Expand Down Expand Up @@ -82,8 +120,8 @@ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (
export type PluginSchema<P> = P extends BetterFetchPlugin
? P["schema"] extends Schema
? P["schema"]
: never
: never;
: undefined
: undefined;

export type MergeSchema<Options extends CreateFetchOption> =
Options["plugins"] extends Array<infer P>
Expand Down Expand Up @@ -119,7 +157,7 @@ export type BetterFetch<
>(
url: U,
...options: F extends FetchSchema
? IsOptionRequired<F> extends true
? IsOptionRequired<F, K> extends true
? [InferOptions<F, K>]
: [InferOptions<F, K>?]
: [O?]
Expand Down
Loading

0 comments on commit 5393c1a

Please sign in to comment.