Skip to content

Commit 026a76c

Browse files
committed
refactor(callapi): improve type inference and global declarations ♻️
fix(callapi): move `ReadableStream` global declaration to `reset.d.ts` to prevent tsdown for always bundling it 🚚 feat(callapi): use `NoInferUnMasked` helper for over NoInfer for better tooltips ✨ fix(callapi, types): add `NoInfer` usage to sharedOptions type to prevent middlewares and hooks from inferring the data and error as unknown🔧
1 parent c2e26ab commit 026a76c

File tree

8 files changed

+43
-17
lines changed

8 files changed

+43
-17
lines changed

.changeset/fifty-chairs-film.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): improve type inference and global declarations ♻️
6+
7+
fix(callapi): move `ReadableStream` global declaration to `reset.d.ts` to prevent tsdown for always bundling it 🚚
8+
9+
feat(callapi): use `NoInferUnMasked` helper for over NoInfer for better tooltips ✨
10+
11+
fix(callapi, types): add `NoInfer` usage to sharedOptions type to prevent middlewares and hooks from inferring the data and error as unknown🔧

packages/callapi/reset.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
import "@total-typescript/ts-reset";
22
import "@total-typescript/ts-reset/dom";
3+
4+
declare global {
5+
// eslint-disable-next-line ts-eslint/consistent-type-definitions -- Ignore
6+
interface ReadableStream<R> {
7+
[Symbol.asyncIterator]: () => AsyncIterableIterator<R>;
8+
}
9+
}

packages/callapi/src/hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ export interface Hooks<TCallApiContext extends CallApiContext = DefaultCallApiCo
154154
onValidationError?: (context: ValidationErrorContext<TCallApiContext>) => Awaitable<unknown>;
155155
}
156156

157-
export type HooksOrHooksArray<TCallApiContext extends CallApiContext = DefaultCallApiContext> = {
157+
export type HooksOrHooksArray<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> = {
158158
[Key in keyof Hooks<TCallApiContext>]:
159159
| Hooks<TCallApiContext>[Key]
160160
// eslint-disable-next-line perfectionist/sort-union-types -- I need arrays to be last
@@ -224,7 +224,7 @@ export type SuccessContext<
224224
TCallApiContext extends Pick<CallApiContext, "Data" | "InferredExtraOptions" | "Meta"> =
225225
DefaultCallApiContext,
226226
> = RequestContext<TCallApiContext> & {
227-
data: NoInfer<TCallApiContext["Data"]>;
227+
data: TCallApiContext["Data"];
228228
response: Response;
229229
};
230230

packages/callapi/src/middlewares.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type FetchImpl = UnmaskType<
77
(input: string | Request | URL, init?: RequestInit) => Promise<Response>
88
>;
99

10-
export interface Middlewares<TCallApiContext extends CallApiContext = DefaultCallApiContext> {
10+
export interface Middlewares<TCallApiContext extends NoInfer<CallApiContext> = DefaultCallApiContext> {
1111
/**
1212
* Wraps the fetch implementation to intercept requests at the network layer.
1313
*

packages/callapi/src/result.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { extraOptionDefaults } from "./constants/defaults";
22
import type { CallApiExtraOptions } from "./types/common";
33
import type { ThrowOnErrorUnion } from "./types/conditional-types";
44
import type { DefaultDataType, DefaultThrowOnError } from "./types/default-types";
5-
import type { AnyString, Awaitable, DistributiveOmit, UnmaskType } from "./types/type-helpers";
5+
import type {
6+
AnyString,
7+
Awaitable,
8+
DistributiveOmit,
9+
NoInferUnMasked,
10+
UnmaskType,
11+
} from "./types/type-helpers";
612
import { omitKeys } from "./utils/common";
713
import type { HTTPError, ValidationError } from "./utils/external/error";
814
import { isHTTPErrorInstance, isValidationErrorInstance } from "./utils/external/guards";
@@ -85,7 +91,7 @@ export const resolveResponseData = <TResponse>(
8591
};
8692

8793
export type CallApiResultSuccessVariant<TData> = {
88-
data: NoInfer<TData>;
94+
data: NoInferUnMasked<TData>;
8995
error: null;
9096
response: Response;
9197
};
@@ -97,7 +103,7 @@ export type PossibleJavaScriptError = UnmaskType<{
97103
}>;
98104

99105
export type PossibleHTTPError<TErrorData> = UnmaskType<{
100-
errorData: NoInfer<TErrorData>;
106+
errorData: NoInferUnMasked<TErrorData>;
101107
message: string;
102108
name: "HTTPError";
103109
originalError: HTTPError;

packages/callapi/src/stream.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ export type StreamProgressEvent = {
2525
transferredBytes: number;
2626
};
2727

28-
declare global {
29-
// eslint-disable-next-line ts-eslint/no-explicit-any -- Ignore
30-
interface ReadableStream<R = any> {
31-
[Symbol.asyncIterator]: () => AsyncIterableIterator<R>;
32-
}
33-
}
34-
3528
const createProgressEvent = (options: {
3629
chunk: Uint8Array;
3730
totalBytes: number;

packages/callapi/src/types/common.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import type {
3434
DefaultPluginArray,
3535
DefaultThrowOnError,
3636
} from "./default-types";
37-
import type { Awaitable, Prettify, Writeable } from "./type-helpers";
37+
import type { Awaitable, NoInferUnMasked, Prettify, Writeable } from "./type-helpers";
3838

3939
// eslint-disable-next-line ts-eslint/no-empty-object-type -- This needs to be empty to allow users to register their own meta
4040
export interface Register {
@@ -108,8 +108,8 @@ type SharedExtraOptions<
108108
>,
109109
> = DedupeOptions
110110
& HookConfigOptions
111-
& HooksOrHooksArray<TComputedCallApiContext>
112-
& Middlewares<TComputedCallApiContext>
111+
& HooksOrHooksArray<NoInferUnMasked<TComputedCallApiContext>>
112+
& Middlewares<NoInferUnMasked<TComputedCallApiContext>>
113113
& ResultModeOption<TErrorData, TResultMode>
114114
& RetryOptions<TErrorData>
115115
& TComputedMergedPluginExtraOptions

packages/callapi/src/types/type-helpers.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,16 @@ export type UnionToIntersection<TUnion> =
4343
: never;
4444

4545
// == Using this Immediately Indexed Mapped type helper to help show computed type of anything passed to it instead of just the type name
46-
export type UnmaskType<TValue> = { _: TValue }["_"];
46+
export type UnmaskType<TValue> = { value: TValue }["value"];
47+
48+
/**
49+
* @description Userland implementation of NoInfer intrinsic type, but this one doesn't show up on hover like the intrinsic one
50+
*
51+
* Prevents TypeScript from inferring `TGeneric` at this position by creating a circular dependency.
52+
* The tuple index `[TGeneric extends unknown ? 0 : never]` depends on `TGeneric`, forcing TS to
53+
* skip this site for inference and use other arguments or defaults instead.
54+
*/
55+
export type NoInferUnMasked<TGeneric> = [TGeneric][TGeneric extends unknown ? 0 : never];
4756

4857
export type RemovePrefix<TPrefix extends "dedupe" | "retry", TKey extends string> =
4958
TKey extends `${TPrefix}${infer TRest}` ? Uncapitalize<TRest> : TKey;

0 commit comments

Comments
 (0)