Skip to content

Commit 91a8114

Browse files
committed
refactor(callapi): further improve type simplification in a few core areas
- Add GetMergedCallApiContext type for better context merging - Simplify Hooks interface by removing redundant generic params - Update Middlewares interface to support context types - Remove type-preserving overload from toFormData utility - Reorganize imports in test files
1 parent 081998f commit 91a8114

File tree

13 files changed

+353
-262
lines changed

13 files changed

+353
-262
lines changed

.changeset/warm-rockets-marry.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@zayne-labs/callapi": patch
3+
---
4+
5+
refactor(callapi): further improve type simplification in a few core areas
6+
7+
- Add GetMergedCallApiContext type for better context merging
8+
- Simplify Hooks interface by removing redundant generic params
9+
- Update Middlewares interface to support context types
10+
- Remove type-preserving overload from toFormData utility
11+
- Reorganize imports in test files

apps/dev/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@
2020
"@zayne-labs/tsconfig": "0.11.5",
2121
"tsx": "^4.20.6",
2222
"typescript": "5.9.3",
23-
"vite": "npm:rolldown-vite@7.1.19"
23+
"vite": "npm:rolldown-vite@7.2.8"
2424
}
2525
}

apps/docs/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@
2626
"@zayne-labs/ui-react": "^0.10.37",
2727
"ai": "^5.0.104",
2828
"clsx": "^2.1.1",
29-
"fumadocs-core": "16.1.0",
29+
"fumadocs-core": "16.2.0",
3030
"fumadocs-docgen": "3.0.4",
31-
"fumadocs-mdx": "14.0.3",
31+
"fumadocs-mdx": "14.0.4",
3232
"fumadocs-twoslash": "3.1.10",
3333
"fumadocs-typescript": "4.0.13",
34-
"fumadocs-ui": "16.1.0",
34+
"fumadocs-ui": "16.2.0",
3535
"geist": "^1.5.1",
3636
"hast-util-to-jsx-runtime": "^2.3.6",
3737
"lucide-react": "^0.555.0",
@@ -40,7 +40,7 @@
4040
"radix-ui": "^1.4.3",
4141
"react": "19.2.0",
4242
"react-dom": "19.2.0",
43-
"react-remove-scroll": "^2.7.1",
43+
"react-remove-scroll": "^2.7.2",
4444
"remark": "^15.0.1",
4545
"remark-gfm": "^4.0.1",
4646
"remark-mdx": "^3.1.1",

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"dev:callapi-plugins": "turbo run --filter=./packages/callapi-plugins dev",
1919
"dev:dev": "turbo run --filter=./apps/dev dev",
2020
"dev:docs": "turbo run --filter=./apps/docs dev",
21+
"dev:packages": "turbo run --filter=./packages/* dev",
2122
"inspect:eslint-config": "pnpx @eslint/config-inspector@latest",
2223
"lint:attw": "turbo run --filter=./packages/* lint:attw",
2324
"lint:eslint": "turbo run --filter=./packages/* lint:eslint",
@@ -56,7 +57,7 @@
5657
"eslint-plugin-react-you-might-not-need-an-effect": "^0.7.0",
5758
"husky": "9.1.7",
5859
"lint-staged": "16.2.7",
59-
"prettier": "3.6.2",
60+
"prettier": "3.7.2",
6061
"tailwindcss": "^4.1.17",
6162
"turbo": "^2.6.1",
6263
"typescript": "^5.9.3"

packages/callapi/src/createFetchClient.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
CallApiResult,
3030
GetBaseSchemaConfig,
3131
GetBaseSchemaRoutes,
32+
GetMergedCallApiContext,
3233
} from "./types/common";
3334
import type {
3435
GetCurrentRouteSchema,
@@ -83,6 +84,10 @@ export const createFetchClientWithContext = <
8384
TBaseResponseType extends ResponseTypeType = ResponseTypeType,
8485
const TBaseSchemaAndConfig extends BaseCallApiSchemaAndConfig = BaseCallApiSchemaAndConfig,
8586
const TBasePluginArray extends CallApiPlugin[] = DefaultPluginArray,
87+
TComputedBaseCallApiContext extends CallApiContext = GetMergedCallApiContext<
88+
TBaseCallApiContext,
89+
{ Data: TBaseData; ErrorData: TBaseErrorData; ResultMode: TBaseResultMode }
90+
>,
8691
TComputedBaseSchemaConfig extends CallApiSchemaConfig = GetBaseSchemaConfig<TBaseSchemaAndConfig>,
8792
TComputedBaseSchemaRoutes extends
8893
BaseCallApiSchemaRoutes = GetBaseSchemaRoutes<TBaseSchemaAndConfig>,
@@ -104,7 +109,10 @@ export const createFetchClientWithContext = <
104109
TData = TBaseData,
105110
TErrorData = TBaseErrorData,
106111
TResultMode extends ResultModeType = TBaseResultMode,
107-
TCallApiContext extends CallApiContext = TBaseCallApiContext,
112+
TCallApiContext extends CallApiContext = GetMergedCallApiContext<
113+
TComputedBaseCallApiContext,
114+
{ Data: TData; ErrorData: TErrorData; ResultMode: TResultMode }
115+
>,
108116
TThrowOnError extends ThrowOnErrorUnion = TBaseThrowOnError,
109117
TResponseType extends ResponseTypeType = TBaseResponseType,
110118
const TSchemaConfig extends CallApiSchemaConfig = TComputedBaseSchemaConfig,

packages/callapi/src/hooks.ts

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,7 @@ import type {
1919
import type { DefaultCallApiContext } from "./types/default-types";
2020
import type { AnyFunction, Awaitable, Prettify, UnmaskType } from "./types/type-helpers";
2121

22-
export interface Hooks<
23-
TCallApiContext extends Pick<
24-
CallApiContext,
25-
"Data" | "ErrorData" | "InferredPluginOptions" | "Meta"
26-
> = DefaultCallApiContext,
27-
TData = TCallApiContext["Data"],
28-
TErrorData = TCallApiContext["ErrorData"],
29-
> {
22+
export interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
3023
/**
3124
* Hook called when any error occurs within the request/response lifecycle.
3225
*
@@ -37,7 +30,7 @@ export interface Hooks<
3730
* @param context - Error context containing error details, request info, and response (if available)
3831
* @returns Promise or void - Hook can be async or sync
3932
*/
40-
onError?: (context: ErrorContext<TCallApiContext, TErrorData>) => Awaitable<unknown>;
33+
onError?: (context: ErrorContext<TCallApiContext>) => Awaitable<unknown>;
4134

4235
/**
4336
* Hook called before the HTTP request is sent and before any internal processing of the request object begins.
@@ -94,7 +87,7 @@ export interface Hooks<
9487
* @returns Promise or void - Hook can be async or sync
9588
*
9689
*/
97-
onResponse?: (context: ResponseContext<TCallApiContext, TData, TErrorData>) => Awaitable<unknown>;
90+
onResponse?: (context: ResponseContext<TCallApiContext>) => Awaitable<unknown>;
9891

9992
/**
10093
* Hook called when an HTTP error response (4xx, 5xx) is received from the API.
@@ -106,7 +99,7 @@ export interface Hooks<
10699
* @param context - Response error context with HTTP error details and response
107100
* @returns Promise or void - Hook can be async or sync
108101
*/
109-
onResponseError?: (context: ResponseErrorContext<TCallApiContext, TErrorData>) => Awaitable<unknown>;
102+
onResponseError?: (context: ResponseErrorContext<TCallApiContext>) => Awaitable<unknown>;
110103

111104
/**
112105
* Hook called during download stream progress tracking.
@@ -132,7 +125,7 @@ export interface Hooks<
132125
* @returns Promise or void - Hook can be async or sync
133126
*
134127
*/
135-
onRetry?: (response: RetryContext<TCallApiContext, TErrorData>) => Awaitable<unknown>;
128+
onRetry?: (response: RetryContext<TCallApiContext>) => Awaitable<unknown>;
136129

137130
/**
138131
* Hook called when a successful response (2xx status) is received from the API.
@@ -145,7 +138,7 @@ export interface Hooks<
145138
* @returns Promise or void - Hook can be async or sync
146139
*
147140
*/
148-
onSuccess?: (context: SuccessContext<TCallApiContext, TData>) => Awaitable<unknown>;
141+
onSuccess?: (context: SuccessContext<TCallApiContext>) => Awaitable<unknown>;
149142

150143
/**
151144
* Hook called when a validation error occurs.
@@ -161,15 +154,11 @@ export interface Hooks<
161154
onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
162155
}
163156

164-
export type HooksOrHooksArray<
165-
TCallApiContext extends CallApiContext = DefaultCallApiContext,
166-
TData = TCallApiContext["Data"],
167-
TErrorData = TCallApiContext["ErrorData"],
168-
> = {
169-
[Key in keyof Hooks<TCallApiContext, TData, TErrorData>]:
170-
| Hooks<TCallApiContext, TData, TErrorData>[Key]
157+
export type HooksOrHooksArray<TCallApiContext extends CallApiContext = DefaultCallApiContext> = {
158+
[Key in keyof Hooks<TCallApiContext>]:
159+
| Hooks<TCallApiContext>[Key]
171160
// eslint-disable-next-line perfectionist/sort-union-types -- I need arrays to be last
172-
| Array<Hooks<TCallApiContext, TData, TErrorData>[Key]>;
161+
| Array<Hooks<TCallApiContext>[Key]>;
173162
};
174163

175164
export interface HookConfigOptions {
@@ -238,10 +227,9 @@ export type SuccessContext<
238227
CallApiContext,
239228
"Data" | "InferredPluginOptions" | "Meta"
240229
> = DefaultCallApiContext,
241-
TData = TCallApiContext["Data"],
242230
> = UnmaskType<
243231
RequestContext<TCallApiContext> & {
244-
data: NoInfer<TData>;
232+
data: NoInfer<TCallApiContext["Data"]>;
245233
response: Response;
246234
}
247235
>;
@@ -251,14 +239,15 @@ export type ResponseContext<
251239
CallApiContext,
252240
"Data" | "ErrorData" | "InferredPluginOptions" | "Meta"
253241
> = DefaultCallApiContext,
254-
TData = TCallApiContext["Data"],
255-
TErrorData = TCallApiContext["ErrorData"],
256242
> = UnmaskType<
257243
RequestContext<TCallApiContext>
258244
& (
259-
| Prettify<CallApiResultSuccessVariant<TData>>
245+
| Prettify<CallApiResultSuccessVariant<TCallApiContext["Data"]>>
260246
| Prettify<
261-
Extract<CallApiResultErrorVariant<TErrorData>, { error: PossibleHTTPError<TErrorData> }>
247+
Extract<
248+
CallApiResultErrorVariant<TCallApiContext["ErrorData"]>,
249+
{ error: PossibleHTTPError<TCallApiContext["ErrorData"]> }
250+
>
262251
>
263252
)
264253
>;
@@ -275,12 +264,11 @@ export type ErrorContext<
275264
CallApiContext,
276265
"ErrorData" | "InferredPluginOptions" | "Meta"
277266
> = DefaultCallApiContext,
278-
TErrorData = TCallApiContext["ErrorData"],
279267
> = UnmaskType<
280268
RequestContext<TCallApiContext>
281269
& (
282270
| {
283-
error: PossibleHTTPError<TErrorData>;
271+
error: PossibleHTTPError<TCallApiContext["ErrorData"]>;
284272
response: Response;
285273
}
286274
| {
@@ -295,9 +283,8 @@ export type ResponseErrorContext<
295283
CallApiContext,
296284
"ErrorData" | "InferredPluginOptions" | "Meta"
297285
> = DefaultCallApiContext,
298-
TErrorData = TCallApiContext["ErrorData"],
299286
> = UnmaskType<
300-
Extract<ErrorContext<TCallApiContext, TErrorData>, { error: PossibleHTTPError<TErrorData> }>
287+
Extract<ErrorContext<TCallApiContext>, { error: PossibleHTTPError<TCallApiContext["ErrorData"]> }>
301288
& RequestContext<TCallApiContext>
302289
>;
303290

@@ -306,9 +293,8 @@ export type RetryContext<
306293
CallApiContext,
307294
"ErrorData" | "InferredPluginOptions" | "Meta"
308295
> = DefaultCallApiContext,
309-
TErrorData = TCallApiContext["ErrorData"],
310296
> = UnmaskType<
311-
ErrorContext<TCallApiContext, TErrorData> & {
297+
ErrorContext<TCallApiContext> & {
312298
retryAttemptCount: number;
313299
}
314300
>;

packages/callapi/src/middlewares.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type { RequestContext } from "./hooks";
2+
import type { CallApiContext } from "./types/common";
3+
import type { DefaultCallApiContext } from "./types/default-types";
24
import type { UnmaskType } from "./types/type-helpers";
35

46
export type FetchImpl = UnmaskType<
57
(input: string | Request | URL, init?: RequestInit) => Promise<Response>
68
>;
79

8-
export interface Middlewares {
10+
export interface Middlewares<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
911
/**
1012
* Wraps the fetch implementation to intercept requests at the network layer.
1113
*
@@ -37,7 +39,7 @@ export interface Middlewares {
3739
* }
3840
* ```
3941
*/
40-
fetchMiddleware?: (context: RequestContext & { fetchImpl: FetchImpl }) => FetchImpl;
42+
fetchMiddleware?: (context: RequestContext<TCallApiContext> & { fetchImpl: FetchImpl }) => FetchImpl;
4143
}
4244

4345
type MiddlewareRegistries = Required<{

packages/callapi/src/plugins.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,13 @@ import {
1111
getMiddlewareRegistriesAndKeys,
1212
type Middlewares,
1313
} from "./middlewares";
14-
import type { CallApiContext, CallApiRequestOptions, CallApiRequestOptionsForHooks } from "./types/common";
15-
import type { DefaultCallApiContext } from "./types/default-types";
14+
import type {
15+
CallApiContext,
16+
CallApiRequestOptions,
17+
CallApiRequestOptionsForHooks,
18+
GetMergedCallApiContext,
19+
} from "./types/common";
20+
import type { DefaultCallApiContext, DefaultDataType } from "./types/default-types";
1621
import type { Awaitable } from "./types/type-helpers";
1722
import type { InitURLOrURLObject } from "./url";
1823
import { isArray, isFunction, isPlainObject, isString } from "./utils/guards";
@@ -21,22 +26,26 @@ import { type BaseCallApiSchemaAndConfig, getCurrentRouteSchemaKeyAndMainInitURL
2126
export type PluginSetupContext<TCallApiContext extends CallApiContext = DefaultCallApiContext> =
2227
RequestContext<TCallApiContext> & { initURL: string };
2328

24-
export type PluginInitResult = Partial<
25-
Omit<PluginSetupContext, "initURL" | "request"> & {
29+
export type PluginInitResult<TCallApiContext extends CallApiContext = DefaultCallApiContext> = Partial<
30+
Omit<PluginSetupContext<TCallApiContext>, "initURL" | "request"> & {
2631
initURL: InitURLOrURLObject;
2732
request: CallApiRequestOptions;
2833
}
2934
>;
3035

31-
export type PluginHooks<
32-
TCallApiContext extends CallApiContext = DefaultCallApiContext,
33-
TData = unknown extends TCallApiContext["Data"] ? never : TCallApiContext["Data"],
34-
TErrorData = unknown extends TCallApiContext["ErrorData"] ? never : TCallApiContext["ErrorData"],
35-
> = HooksOrHooksArray<TCallApiContext, TData, TErrorData>;
36-
37-
export type PluginMiddlewares = Middlewares;
38-
39-
export interface CallApiPlugin {
36+
export type PluginHooks<TCallApiContext extends CallApiContext = DefaultCallApiContext> =
37+
HooksOrHooksArray<
38+
GetMergedCallApiContext<
39+
TCallApiContext,
40+
{
41+
Data: DefaultDataType extends TCallApiContext["Data"] ? never : TCallApiContext["Data"];
42+
ErrorData: DefaultDataType extends TCallApiContext["ErrorData"] ? never
43+
: TCallApiContext["ErrorData"];
44+
}
45+
>
46+
>;
47+
48+
export interface CallApiPlugin<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
4049
/**
4150
* Defines additional options that can be passed to callApi
4251
*/
@@ -50,7 +59,9 @@ export interface CallApiPlugin {
5059
/**
5160
* Hooks for the plugin
5261
*/
53-
hooks?: PluginHooks | ((context: PluginSetupContext) => Awaitable<PluginHooks>);
62+
hooks?:
63+
| PluginHooks<TCallApiContext>
64+
| ((context: PluginSetupContext<TCallApiContext>) => Awaitable<PluginHooks<TCallApiContext>>);
5465

5566
/**
5667
* A unique id for the plugin
@@ -60,7 +71,9 @@ export interface CallApiPlugin {
6071
/**
6172
* Middlewares that for the plugin
6273
*/
63-
middlewares?: Middlewares | ((context: PluginSetupContext) => Awaitable<Middlewares>);
74+
middlewares?:
75+
| Middlewares<TCallApiContext>
76+
| ((context: PluginSetupContext<TCallApiContext>) => Awaitable<Middlewares<TCallApiContext>>);
6477

6578
/**
6679
* A name for the plugin
@@ -75,7 +88,9 @@ export interface CallApiPlugin {
7588
/**
7689
* A function that will be called when the plugin is initialized. This will be called before the any of the other internal functions.
7790
*/
78-
setup?: (context: PluginSetupContext) => Awaitable<PluginInitResult> | Awaitable<void>;
91+
setup?: (
92+
context: PluginSetupContext<TCallApiContext>
93+
) => Awaitable<PluginInitResult<TCallApiContext>> | Awaitable<void>;
7994

8095
/**
8196
* A version for the plugin
@@ -224,7 +239,7 @@ const setupHooksAndMiddlewares = (
224239
}
225240
};
226241

227-
const addPluginMiddlewares = (pluginMiddlewares: PluginMiddlewares) => {
242+
const addPluginMiddlewares = (pluginMiddlewares: Middlewares) => {
228243
for (const middlewareName of middlewareRegistryKeys) {
229244
const pluginMiddleware = pluginMiddlewares[middlewareName];
230245

packages/callapi/src/types/common.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ export type CallApiContext = {
5252
ResultMode?: ResultModeType;
5353
};
5454

55+
export type GetMergedCallApiContext<
56+
TFullCallApiContext extends CallApiContext,
57+
TOverrideCallApiContext extends CallApiContext,
58+
> = Omit<TFullCallApiContext, keyof TOverrideCallApiContext> & TOverrideCallApiContext;
59+
5560
type FetchSpecificKeysUnion = Exclude<(typeof fetchSpecificKeys)[number], "body" | "headers" | "method">;
5661

5762
export type ModifiedRequestInit = RequestInit & { duplex?: "half" };
@@ -87,17 +92,18 @@ type SharedExtraOptions<
8792
TThrowOnError extends ThrowOnErrorUnion = DefaultThrowOnError,
8893
TResponseType extends ResponseTypeType = ResponseTypeType,
8994
TPluginArray extends CallApiPlugin[] = DefaultPluginArray,
90-
TComputedPluginOptions = InferPluginOptions<TPluginArray> & TCallApiContext["InferredPluginOptions"],
91-
TComputedInferredPluginOptions extends Pick<Required<CallApiContext>, "InferredPluginOptions"> = {
92-
InferredPluginOptions: TComputedPluginOptions;
93-
},
95+
TComputedMergedPluginOptions = Partial<
96+
InferPluginOptions<TPluginArray> & TCallApiContext["InferredPluginOptions"]
97+
>,
9498
> = DedupeOptions
9599
& HookConfigOptions
96-
& HooksOrHooksArray<TComputedInferredPluginOptions, TData, TErrorData>
100+
& HooksOrHooksArray<
101+
GetMergedCallApiContext<TCallApiContext, { InferredPluginOptions: TComputedMergedPluginOptions }>
102+
>
97103
& Middlewares
98-
& Partial<TComputedInferredPluginOptions["InferredPluginOptions"]>
99104
& ResultModeOption<TErrorData, TResultMode>
100105
& RetryOptions<TErrorData>
106+
& TComputedMergedPluginOptions
101107
& ThrowOnErrorOption<TErrorData, TThrowOnError>
102108
& URLOptions & {
103109
/**

0 commit comments

Comments
 (0)