Skip to content

Commit 5b64cbf

Browse files
authored
enhance: Improve argmatch typing (#3043)
* enhance: Improve argmatch typing * enhance: Change NoInfer location * enhance: Remove NoInfer for parameters
1 parent fd7ef1b commit 5b64cbf

31 files changed

+379
-173
lines changed

.changeset/famous-moose-hammer.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@data-client/react": patch
3+
"@data-client/core": patch
4+
---
5+
6+
Improve [controller](https://dataclient.io/docs/api/Controller) type matching for its methods

.changeset/healthy-suns-love.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@data-client/react": patch
3+
---
4+
5+
Improve [useFetch()](https://dataclient.io/docs/api/useFetch) argtype matching similar to [useSuspense()](https://dataclient.io/docs/api/useSuspense)

packages/core/src/actions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ export type SetAction<E extends EndpointAndUpdate<E> = EndpointDefault> =
5656
| SetActionError<E>;
5757

5858
/* FETCH */
59-
export interface FetchMeta {
60-
args: readonly any[];
59+
export interface FetchMeta<A extends readonly any[] = readonly any[]> {
60+
args: A;
6161
key: string;
6262
throttle: boolean;
6363
resolve: (value?: any | PromiseLike<any>) => void;
@@ -71,7 +71,7 @@ export interface FetchMeta {
7171
export interface FetchAction<E extends EndpointAndUpdate<E> = EndpointDefault> {
7272
type: typeof FETCH_TYPE;
7373
endpoint: E;
74-
meta: FetchMeta;
74+
meta: FetchMeta<readonly [...Parameters<E>]>;
7575
payload: () => ReturnType<E>;
7676
}
7777

packages/core/src/controller/Controller.ts

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
Denormalize,
66
Queryable,
77
SchemaArgs,
8+
NI,
89
} from '@data-client/normalizr';
910
import {
1011
ExpiryStatus,
@@ -154,7 +155,7 @@ export default class Controller<
154155
args[0] !== null ?
155156
this.dispatch(
156157
createInvalidate(endpoint, {
157-
args: args as readonly [...Parameters<E>],
158+
args: args as Parameters<E>,
158159
}),
159160
)
160161
: Promise.resolve();
@@ -314,17 +315,28 @@ export default class Controller<
314315
* Gets the error, if any, for a given endpoint. Returns undefined for no errors.
315316
* @see https://dataclient.io/docs/api/Controller#getError
316317
*/
317-
getError = <
318-
E extends Pick<EndpointInterface, 'key'>,
319-
Args extends readonly [...Parameters<E['key']>] | readonly [null],
320-
>(
318+
getError<E extends EndpointInterface>(
319+
endpoint: E,
320+
...rest:
321+
| readonly [null, State<unknown>]
322+
| readonly [...Parameters<E>, State<unknown>]
323+
): ErrorTypes | undefined;
324+
325+
getError<E extends Pick<EndpointInterface, 'key'>>(
321326
endpoint: E,
322-
...rest: [...Args, State<unknown>]
323-
): ErrorTypes | undefined => {
327+
...rest:
328+
| readonly [null, State<unknown>]
329+
| readonly [...Parameters<E['key']>, State<unknown>]
330+
): ErrorTypes | undefined;
331+
332+
getError(
333+
endpoint: EndpointInterface,
334+
...rest: readonly [...unknown[], State<unknown>]
335+
): ErrorTypes | undefined {
324336
if (rest[0] === null) return;
325337
const state = rest[rest.length - 1] as State<unknown>;
326338
// this is typescript generics breaking
327-
const args: any = rest.slice(0, rest.length - 1) as Parameters<E['key']>;
339+
const args: any = rest.slice(0, rest.length - 1);
328340
const key = endpoint.key(...args);
329341

330342
const meta = selectMeta(state, key);
@@ -333,24 +345,17 @@ export default class Controller<
333345
if (error !== undefined && meta?.errorPolicy === 'soft') return;
334346

335347
return meta?.error as any;
336-
};
348+
}
337349

338350
/**
339351
* Gets the (globally referentially stable) response for a given endpoint/args pair from state given.
340352
* @see https://dataclient.io/docs/api/Controller#getResponse
341353
*/
342354
getResponse<E extends EndpointInterface>(
343355
endpoint: E,
344-
...rest: readonly [null, State<unknown>]
345-
): {
346-
data: DenormalizeNullable<E['schema']>;
347-
expiryStatus: ExpiryStatus;
348-
expiresAt: number;
349-
};
350-
351-
getResponse<E extends EndpointInterface>(
352-
endpoint: E,
353-
...rest: readonly [...Parameters<E>, State<unknown>]
356+
...rest:
357+
| readonly [null, State<unknown>]
358+
| readonly [...Parameters<E>, State<unknown>]
354359
): {
355360
data: DenormalizeNullable<E['schema']>;
356361
expiryStatus: ExpiryStatus;
@@ -613,15 +618,22 @@ class Snapshot<T = unknown> implements SnapshotInterface {
613618
}
614619

615620
/** @see https://dataclient.io/docs/api/Snapshot#getError */
616-
getError = <
617-
E extends Pick<EndpointInterface, 'key'>,
618-
Args extends readonly [...Parameters<E['key']>],
619-
>(
621+
getError<E extends EndpointInterface>(
622+
endpoint: E,
623+
...args: readonly [...Parameters<E>] | readonly [null]
624+
): ErrorTypes | undefined;
625+
626+
getError<E extends Pick<EndpointInterface, 'key'>>(
620627
endpoint: E,
621-
...args: Args
622-
): ErrorTypes | undefined => {
628+
...args: readonly [...Parameters<E['key']>] | readonly [null]
629+
): ErrorTypes | undefined;
630+
631+
getError<E extends Pick<EndpointInterface, 'key'>>(
632+
endpoint: E,
633+
...args: readonly [...Parameters<E['key']>] | readonly [null]
634+
): ErrorTypes | undefined {
623635
return this.controller.getError(endpoint, ...args, this.state);
624-
};
636+
}
625637

626638
/**
627639
* Retrieved memoized value for any Querable schema

packages/core/src/controller/createFetch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { EndpointInterface } from '@data-client/normalizr';
1+
import type { EndpointInterface, NI } from '@data-client/normalizr';
22

33
import { EndpointUpdateFunction } from './types.js';
44
import { FETCH_TYPE } from '../actionTypes.js';
@@ -19,7 +19,7 @@ export default function createFetch<
1919
const promise = new Promise<any>((a, b) => {
2020
[resolve, reject] = [a, b];
2121
});
22-
const meta: FetchMeta = {
22+
const meta: FetchMeta<typeof args> = {
2323
args,
2424
key,
2525
throttle: !endpoint.sideEffect,

packages/core/src/manager/NetworkManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export default class NetworkManager implements Manager {
7171
if (error) {
7272
this.handleSet(
7373
createSet(action.endpoint, {
74-
args: action.meta.args as any,
74+
args: action.meta.args,
7575
response: error,
7676
fetchedAt: action.meta.fetchedAt,
7777
error: true,
@@ -192,7 +192,7 @@ export default class NetworkManager implements Manager {
192192
// don't update state with promises started before last clear
193193
if (createdAt >= lastReset) {
194194
this.controller.resolve(action.endpoint, {
195-
args: action.meta.args as any,
195+
args: action.meta.args,
196196
response: data,
197197
fetchedAt: createdAt,
198198
});
@@ -204,7 +204,7 @@ export default class NetworkManager implements Manager {
204204
// don't update state with promises started before last clear
205205
if (createdAt >= lastReset) {
206206
this.controller.resolve(action.endpoint, {
207-
args: action.meta.args as any,
207+
args: action.meta.args,
208208
response: error,
209209
fetchedAt: createdAt,
210210
error: true,

packages/core/src/manager/__tests__/networkManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ describe('NetworkManager', () => {
5454
(v: { id: number }) => Promise.resolve({ id: 5, title: 'hi' }),
5555
{
5656
schema: Article,
57+
name: 'detailEndpoint',
5758
},
5859
);
5960
const fetchResolveAction = createFetch(detailEndpoint, {

packages/core/src/manager/__tests__/pollingSubscription.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,9 @@ describe('PollingSubscription', () => {
288288
afterEach(() => {
289289
warnSpy.mockRestore();
290290
});
291-
beforeEach(() => (warnSpy = jest.spyOn(console, 'warn')));
291+
beforeEach(() =>
292+
(warnSpy = jest.spyOn(console, 'warn')).mockImplementation(() => {}),
293+
);
292294

293295
it('should stop all timers', () => {
294296
dispatch.mockClear();

packages/endpoint/src/schemas/__tests__/Collection.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ describe(`${schema.Collection.name} normalization`, () => {
5454
afterEach(() => {
5555
warnSpy.mockRestore();
5656
});
57-
beforeEach(() => (warnSpy = jest.spyOn(console, 'warn')));
57+
beforeEach(() =>
58+
(warnSpy = jest.spyOn(console, 'warn')).mockImplementation(() => {}),
59+
);
5860

5961
test('should throw a custom error if data loads with string unexpected value', () => {
6062
function normalizeBad() {

packages/endpoint/src/schemas/__tests__/Entity.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ describe(`${Entity.name} normalization`, () => {
6060
afterEach(() => {
6161
warnSpy.mockRestore();
6262
});
63-
beforeEach(() => (warnSpy = jest.spyOn(console, 'warn')));
63+
beforeEach(() =>
64+
(warnSpy = jest.spyOn(console, 'warn')).mockImplementation(() => {}),
65+
);
6466

6567
test('normalizes an entity', () => {
6668
class MyEntity extends IDEntity {}

0 commit comments

Comments
 (0)