@data-client/rest 0.18.1 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +191 -0
- package/dist/index.js +15 -6
- package/dist/index.umd.min.js +1 -1
- package/legacy/RestEndpoint.js +5 -4
- package/legacy/RestEndpointTypes.js +1 -1
- package/legacy/extractCollection.js +3 -1
- package/legacy/paramsToString.js +1 -1
- package/legacy/paramsToString.native.js +1 -1
- package/legacy/pathTypes.js +1 -1
- package/legacy/resource.js +10 -4
- package/legacy/resourceExtendable.js +1 -1
- package/legacy/resourceExtensionTypes.js +1 -1
- package/legacy/resourceTypes.js +1 -1
- package/lib/NetworkError.d.ts.map +1 -1
- package/lib/RestEndpoint.js +5 -4
- package/lib/RestEndpointTypes.d.ts +49 -20
- package/lib/RestEndpointTypes.d.ts.map +1 -1
- package/lib/RestEndpointTypes.js +1 -1
- package/lib/extractCollection.d.ts +8 -1
- package/lib/extractCollection.d.ts.map +1 -1
- package/lib/extractCollection.js +3 -1
- package/lib/paramsToString.d.ts.map +1 -1
- package/lib/paramsToString.js +1 -1
- package/lib/paramsToString.native.d.ts.map +1 -1
- package/lib/paramsToString.native.js +1 -1
- package/lib/pathTypes.d.ts +33 -7
- package/lib/pathTypes.d.ts.map +1 -1
- package/lib/pathTypes.js +1 -1
- package/lib/resource.d.ts +1 -1
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +10 -4
- package/lib/resourceExtendable.d.ts +4 -4
- package/lib/resourceExtendable.d.ts.map +1 -1
- package/lib/resourceExtendable.js +1 -1
- package/lib/resourceExtensionTypes.d.ts +7 -7
- package/lib/resourceExtensionTypes.d.ts.map +1 -1
- package/lib/resourceExtensionTypes.js +1 -1
- package/lib/resourceTypes.d.ts +8 -2
- package/lib/resourceTypes.d.ts.map +1 -1
- package/lib/resourceTypes.js +1 -1
- package/lib/test-fixtures.d.ts +7 -7
- package/lib/test-fixtures.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/RestEndpoint.js +7 -6
- package/src/RestEndpointTypes.ts +116 -53
- package/src/__tests__/RestEndpoint.ts +23 -7
- package/src/__tests__/resource-construction.test.ts +47 -0
- package/src/extractCollection.ts +3 -0
- package/src/paramsToString.native.ts +1 -5
- package/src/paramsToString.ts +1 -5
- package/src/pathTypes.ts +105 -31
- package/src/resource.ts +11 -6
- package/src/resourceExtendable.ts +15 -11
- package/src/resourceExtensionTypes.ts +16 -10
- package/src/resourceTypes.ts +6 -4
- package/ts4.0/RestEndpointTypes.d.ts +57 -22
- package/ts4.0/extractCollection.d.ts +8 -1
- package/ts4.0/mapCollection.d.ts +11 -16
- package/ts4.0/resource.d.ts +1 -1
- package/ts4.0/resourceExtendable.d.ts +4 -4
- package/ts4.0/resourceExtensionTypes.d.ts +7 -7
- package/ts4.0/resourceTypes.d.ts +8 -2
- package/ts4.0/test-fixtures.d.ts +7 -7
- package/ts4.1/RestEndpointTypes.d.ts +57 -22
- package/ts4.1/extractCollection.d.ts +8 -1
- package/ts4.1/mapCollection.d.ts +11 -16
- package/ts4.1/pathTypes.d.ts +33 -7
- package/ts4.1/resource.d.ts +1 -1
- package/ts4.1/resourceExtendable.d.ts +4 -4
- package/ts4.1/resourceExtensionTypes.d.ts +7 -7
- package/ts4.1/resourceTypes.d.ts +8 -2
- package/ts4.1/test-fixtures.d.ts +7 -7
|
@@ -197,10 +197,7 @@ const getNextPage3 = getArticleList3.getPage;
|
|
|
197
197
|
// @ts-expect-error - remove body should not be any
|
|
198
198
|
withBody.remove.body satisfies number;
|
|
199
199
|
withBody.remove.body satisfies
|
|
200
|
-
| Partial<Body>
|
|
201
|
-
| Partial<Body>[]
|
|
202
|
-
| FormData
|
|
203
|
-
| undefined;
|
|
200
|
+
Partial<Body> | Partial<Body>[] | FormData | undefined;
|
|
204
201
|
// push body requires full Body (not partial)
|
|
205
202
|
() => withBody.push({ id: 'a' }, { title: 'hi', content: 'there' });
|
|
206
203
|
// @ts-expect-error - push rejects partial body
|
|
@@ -221,9 +218,7 @@ const getNextPage3 = getArticleList3.getPage;
|
|
|
221
218
|
// @ts-expect-error - assign body should not be any
|
|
222
219
|
valuesEndpoint.assign.body satisfies number;
|
|
223
220
|
valuesEndpoint.assign.body satisfies
|
|
224
|
-
|
|
225
|
-
| FormData
|
|
226
|
-
| undefined;
|
|
221
|
+
Record<string, Partial<PaginatedArticle>> | FormData | undefined;
|
|
227
222
|
() => valuesEndpoint.assign({ myKey: { id: 5, title: 'hi', content: 'ho' } });
|
|
228
223
|
};
|
|
229
224
|
|
|
@@ -1560,6 +1555,27 @@ describe('RestEndpoint.fetch()', () => {
|
|
|
1560
1555
|
expect(article).toMatchObject(payload2);
|
|
1561
1556
|
});
|
|
1562
1557
|
|
|
1558
|
+
it('getRequestInit() should JSON-encode objects and arrays, but pass other bodies through', async () => {
|
|
1559
|
+
const endpoint = new RestEndpoint({ path: '/items', method: 'POST' });
|
|
1560
|
+
const json = { 'Content-Type': 'application/json' };
|
|
1561
|
+
for (const body of [{ a: 1 }, [{ a: 1 }, { a: 2 }], [], ['a', 5]]) {
|
|
1562
|
+
const init = await endpoint.getRequestInit(body);
|
|
1563
|
+
expect(init.body).toBe(JSON.stringify(body));
|
|
1564
|
+
expect(init.headers).toEqual(json);
|
|
1565
|
+
}
|
|
1566
|
+
for (const body of [
|
|
1567
|
+
new FormData(),
|
|
1568
|
+
new URLSearchParams('a=1'),
|
|
1569
|
+
new Blob(['a']),
|
|
1570
|
+
new Uint8Array([1, 2]),
|
|
1571
|
+
'raw',
|
|
1572
|
+
]) {
|
|
1573
|
+
const init = await endpoint.getRequestInit(body);
|
|
1574
|
+
expect(init.body).toBe(body);
|
|
1575
|
+
expect(init.headers).toBeUndefined();
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1563
1579
|
it('should PUT with multipart form data', async () => {
|
|
1564
1580
|
const payload2 = { id: 500, content: 'another' };
|
|
1565
1581
|
let lastRequest: any;
|
|
@@ -1108,6 +1108,50 @@ describe('resource()', () => {
|
|
|
1108
1108
|
expect(result.current.task3?.status).toEqual('in-progress');
|
|
1109
1109
|
});
|
|
1110
1110
|
|
|
1111
|
+
it('getList.push should send an array of items as JSON', async () => {
|
|
1112
|
+
const BulkUserResource = resource({
|
|
1113
|
+
path: 'http\\://test.com/groups/:group/users/:id',
|
|
1114
|
+
schema: User,
|
|
1115
|
+
});
|
|
1116
|
+
let contentType: unknown;
|
|
1117
|
+
mynock.post(`/groups/five/users`).reply(201, function (uri, body: any) {
|
|
1118
|
+
contentType = this.req.headers['content-type'];
|
|
1119
|
+
return body.map((user: any, i: number) => ({ id: 10 + i, ...user }));
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
const { result, waitForNextUpdate } = renderDataClient(() => {
|
|
1123
|
+
return [
|
|
1124
|
+
useSuspense(BulkUserResource.getList, { group: 'five' }),
|
|
1125
|
+
useController(),
|
|
1126
|
+
] as const;
|
|
1127
|
+
});
|
|
1128
|
+
await waitForNextUpdate();
|
|
1129
|
+
const controller = result.current[1];
|
|
1130
|
+
|
|
1131
|
+
await act(async () => {
|
|
1132
|
+
const created = await controller.fetch(
|
|
1133
|
+
BulkUserResource.getList.push,
|
|
1134
|
+
{ group: 'five' },
|
|
1135
|
+
[{ username: 'a' }, { username: 'b' }],
|
|
1136
|
+
);
|
|
1137
|
+
// push's type resolves one item even for an array body (CollectionArrayAdder TODO)
|
|
1138
|
+
expect((created as unknown as User[]).map(user => user.username)).toEqual(
|
|
1139
|
+
['a', 'b'],
|
|
1140
|
+
);
|
|
1141
|
+
});
|
|
1142
|
+
expect(contentType).toBe('application/json');
|
|
1143
|
+
expect(result.current[0].map(user => user.username)).toEqual([
|
|
1144
|
+
'ntucker',
|
|
1145
|
+
'a',
|
|
1146
|
+
'b',
|
|
1147
|
+
]);
|
|
1148
|
+
// create is getList.push, so it takes arrays too
|
|
1149
|
+
() =>
|
|
1150
|
+
controller.fetch(BulkUserResource.create, { group: 'five' }, [
|
|
1151
|
+
{ username: 'c' },
|
|
1152
|
+
]);
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1111
1155
|
it('getList.move should work with FormData body', async () => {
|
|
1112
1156
|
class Task extends Entity {
|
|
1113
1157
|
readonly id: number | undefined = undefined;
|
|
@@ -1177,6 +1221,9 @@ describe('resource()', () => {
|
|
|
1177
1221
|
);
|
|
1178
1222
|
expect(response.id).toEqual(3);
|
|
1179
1223
|
});
|
|
1224
|
+
// create takes FormData too
|
|
1225
|
+
() =>
|
|
1226
|
+
controller.fetch(TaskResource.create, { status: 'backlog' }, formData);
|
|
1180
1227
|
|
|
1181
1228
|
// task should be removed from backlog
|
|
1182
1229
|
expect(result.current.backlog).toHaveLength(1);
|
package/src/extractCollection.ts
CHANGED
|
@@ -20,6 +20,7 @@ export default function extractCollection<
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Searches wrappers like Query and Lazy through their `schema` (Entity classes have a `prototype`) */
|
|
23
24
|
export type ExtractCollection<S extends Schema | undefined> =
|
|
24
25
|
S extends (
|
|
25
26
|
{
|
|
@@ -32,6 +33,8 @@ export type ExtractCollection<S extends Schema | undefined> =
|
|
|
32
33
|
) ?
|
|
33
34
|
S
|
|
34
35
|
: S extends schema.Object<infer T> ? ExtractObject<T>
|
|
36
|
+
: S extends { schema: infer Q; denormalize: any; prototype?: undefined } ?
|
|
37
|
+
ExtractObject<{ schema: Q }>
|
|
35
38
|
: S extends Exclude<Schema, { [K: string]: any }> ? never
|
|
36
39
|
: S extends { [K: string]: Schema } ? ExtractObject<S>
|
|
37
40
|
: never;
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export default function paramsToString(
|
|
2
2
|
searchParams?:
|
|
3
|
-
| string
|
|
4
|
-
| URLSearchParams
|
|
5
|
-
| Record<string, string>
|
|
6
|
-
| string[][]
|
|
7
|
-
| undefined,
|
|
3
|
+
string | URLSearchParams | Record<string, string> | string[][] | undefined,
|
|
8
4
|
) {
|
|
9
5
|
const params = new URLSearchParams(searchParams as any);
|
|
10
6
|
try {
|
package/src/paramsToString.ts
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
export default function paramsToString(
|
|
2
2
|
searchParams?:
|
|
3
|
-
| string
|
|
4
|
-
| URLSearchParams
|
|
5
|
-
| Record<string, string>
|
|
6
|
-
| string[][]
|
|
7
|
-
| undefined,
|
|
3
|
+
string | URLSearchParams | Record<string, string> | string[][] | undefined,
|
|
8
4
|
) {
|
|
9
5
|
const params = new URLSearchParams(searchParams);
|
|
10
6
|
params.sort();
|
package/src/pathTypes.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
// The non-`infer` pre-checks are matched without instantiating any types,
|
|
2
|
+
// so plain keys (the common case) skip the inferring templates entirely.
|
|
3
|
+
type CleanKey<S extends string> =
|
|
4
|
+
S extends `"${string}"` ?
|
|
5
|
+
S extends `"${infer K}"` ?
|
|
6
|
+
K
|
|
7
|
+
: S
|
|
8
|
+
: S;
|
|
2
9
|
|
|
3
|
-
type KeyName<K extends string> =
|
|
4
|
-
K extends `*${
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
type KeyName<K extends string> =
|
|
11
|
+
K extends `*${string}` | `${string}}` ?
|
|
12
|
+
CleanKey<
|
|
13
|
+
K extends `*${infer N}}` ? N
|
|
14
|
+
: K extends `*${infer N}` ? N
|
|
15
|
+
: K extends `${infer N}}` ? N
|
|
16
|
+
: K
|
|
17
|
+
>
|
|
18
|
+
: CleanKey<K>;
|
|
9
19
|
|
|
10
20
|
type KeyVal<K extends string> =
|
|
11
21
|
K extends `*${string}` ? string[] : string | number;
|
|
@@ -28,37 +38,101 @@ export type SoftPathArgs<P extends string> =
|
|
|
28
38
|
/** Computes the union of keys for a path string */
|
|
29
39
|
export type PathKeys<S extends string> =
|
|
30
40
|
string extends S ? string
|
|
31
|
-
:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
S
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
: // cheap (non-inferring) pre-check before the 3-way escape split
|
|
42
|
+
S extends `${string}\\${string}` ?
|
|
43
|
+
S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
|
|
44
|
+
PathKeys<A> | PathKeys<B>
|
|
45
|
+
: ColonSplits<S> | StarSplits<S>
|
|
46
|
+
: ColonSplits<S> | StarSplits<S>;
|
|
47
|
+
|
|
48
|
+
/** Characters that end a :param or *wildcard token */
|
|
49
|
+
type PathDelimiter =
|
|
50
|
+
'/' | '\\' | '%' | '&' | '*' | ':' | '{' | ';' | ',' | '!' | '@';
|
|
51
|
+
|
|
52
|
+
/** Token after every ':' in S */
|
|
53
|
+
type ColonSplits<S extends string> =
|
|
54
|
+
S extends `${string}:${infer K}` ? PathToken<K> | ColonSplits<K> : never;
|
|
55
|
+
|
|
56
|
+
/** `*`-prefixed token after every '*' in S */
|
|
57
|
+
type StarSplits<S extends string> =
|
|
58
|
+
S extends `${string}*${infer K}` ? `*${PathToken<K>}` | StarSplits<K> : never;
|
|
59
|
+
|
|
60
|
+
/** Prefix of K up to (excluding) its first PathDelimiter.
|
|
61
|
+
*
|
|
62
|
+
* Fast path: no delimiter at all, or the first '/' ends a delimiter-free token.
|
|
63
|
+
* The delimiter-union templates without `infer` are matched without instantiation. */
|
|
64
|
+
type PathToken<K extends string> =
|
|
65
|
+
K extends `${string}${PathDelimiter}${string}` ?
|
|
66
|
+
K extends `${infer H}/${string}` ?
|
|
67
|
+
H extends `${string}${PathDelimiter}${string}` ?
|
|
68
|
+
PathTokenSlow<H>
|
|
69
|
+
: H
|
|
70
|
+
: PathTokenSlow<K>
|
|
71
|
+
: K;
|
|
72
|
+
|
|
73
|
+
/** Cuts at the first occurrence of each delimiter (union); recursing on each
|
|
74
|
+
* candidate converges on the shortest, delimiter-free prefix. */
|
|
75
|
+
type PathTokenSlow<K extends string> =
|
|
76
|
+
K extends `${infer H}${PathDelimiter}${string}` ? PathToken<H> : K;
|
|
77
|
+
|
|
78
|
+
export type KeysToArgs<Key extends string> = OptionalArgs<Key> &
|
|
79
|
+
(RequiredPathKeys<Key> extends never ? unknown : RequiredArgs<Key>);
|
|
80
|
+
|
|
81
|
+
/** Wide keys (`string`, template patterns) keep the original key-remapping
|
|
82
|
+
* form so index signatures (and their `keyof`) stay exactly the same. */
|
|
83
|
+
type HasWideKey<Key extends string> =
|
|
84
|
+
true extends (
|
|
85
|
+
Key extends string ?
|
|
86
|
+
{} extends { [P in Key]: 1 } ?
|
|
87
|
+
true
|
|
88
|
+
: never
|
|
89
|
+
: never
|
|
40
90
|
) ?
|
|
41
|
-
|
|
42
|
-
:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
:
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
true
|
|
92
|
+
: false;
|
|
93
|
+
|
|
94
|
+
// Literal keys: mapped over the computed names without an `as` clause.
|
|
95
|
+
// `as` clauses get re-instantiated every time TypeScript asks whether the
|
|
96
|
+
// mapped type is generic (on every relation check of hook/fetch params).
|
|
97
|
+
// Each value is the KeyVal of the key(s) named N (inlined so errors show the
|
|
98
|
+
// resolved type rather than an alias).
|
|
99
|
+
type OptionalArgs<Key extends string> =
|
|
100
|
+
HasWideKey<Key> extends true ?
|
|
101
|
+
{ [K in Key as K extends `${string}}` ? KeyName<K> : never]?: KeyVal<K> }
|
|
102
|
+
: {
|
|
103
|
+
[N in KeyName<OptionalPathKeys<Key>>]?:
|
|
104
|
+
| (N extends KeyName<Extract<OptionalPathKeys<Key>, `*${string}`>> ?
|
|
105
|
+
string[]
|
|
106
|
+
: never)
|
|
107
|
+
| (N extends KeyName<Exclude<OptionalPathKeys<Key>, `*${string}`>> ?
|
|
108
|
+
string | number
|
|
109
|
+
: never);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
type RequiredArgs<Key extends string> =
|
|
113
|
+
HasWideKey<Key> extends true ?
|
|
114
|
+
{ [K in Key as K extends `${string}}` ? never : KeyName<K>]: KeyVal<K> }
|
|
115
|
+
: {
|
|
116
|
+
[N in KeyName<RequiredPathKeys<Key>>]:
|
|
117
|
+
| (N extends KeyName<Extract<RequiredPathKeys<Key>, `*${string}`>> ?
|
|
118
|
+
string[]
|
|
119
|
+
: never)
|
|
120
|
+
| (N extends KeyName<Exclude<RequiredPathKeys<Key>, `*${string}`>> ?
|
|
121
|
+
string | number
|
|
122
|
+
: never);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
type OptionalPathKeys<Key extends string> = Extract<Key, `${string}}`>;
|
|
126
|
+
type RequiredPathKeys<Key extends string> = Exclude<Key, `${string}}`>;
|
|
54
127
|
|
|
55
128
|
export type PathArgsAndSearch<S extends string> =
|
|
56
129
|
unknown extends S ? any
|
|
57
130
|
: Exclude<PathKeys<S>, `${string}}`> extends never ?
|
|
58
131
|
Record<string, number | string | boolean> | undefined
|
|
59
132
|
: {
|
|
60
|
-
[
|
|
61
|
-
|
|
133
|
+
[
|
|
134
|
+
K in PathKeys<S> as K extends `${string}}` ? never : KeyName<K>
|
|
135
|
+
]: KeyVal<K>;
|
|
62
136
|
} & Record<string, number | string | string[]>;
|
|
63
137
|
|
|
64
138
|
/** Removes the last :param or *wildcard token */
|
package/src/resource.ts
CHANGED
|
@@ -30,7 +30,7 @@ export default function resource<O extends ResourceGenerics>({
|
|
|
30
30
|
optimistic,
|
|
31
31
|
paginationField,
|
|
32
32
|
...extraOptions
|
|
33
|
-
}: Readonly<O> & ResourceOptions): Resource<O> {
|
|
33
|
+
}: Readonly<O> & O & ResourceOptions): Resource<O> {
|
|
34
34
|
if (process.env.NODE_ENV !== 'production') {
|
|
35
35
|
// if they lowercase and it looks like they meant to use upper-case version
|
|
36
36
|
if (
|
|
@@ -78,19 +78,24 @@ This warning will not show in production.`,
|
|
|
78
78
|
extended[key] = extended[key].extend(options);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Loosely typed: the public signature infers O from the naked `O` position,
|
|
82
|
+
// so spreading the generic rest into `new Endpoint()` would otherwise leave
|
|
83
|
+
// ContentSchemaGuard<O> as an unresolvable (deferred) conditional.
|
|
84
|
+
// Retype this if a future TypeScript resolves that conditional here.
|
|
85
|
+
const extraBaseOptions: Record<string, any> = extraOptions;
|
|
86
|
+
const extraMutateOptions = { ...extraBaseOptions };
|
|
87
|
+
const extraPartialOptions = { ...extraBaseOptions };
|
|
83
88
|
const get: GetEndpoint<{ path: O['path']; schema: O['schema'] }> =
|
|
84
89
|
new Endpoint({
|
|
85
|
-
...
|
|
90
|
+
...extraBaseOptions,
|
|
86
91
|
path,
|
|
87
92
|
schema,
|
|
88
93
|
name: getName('get'),
|
|
89
94
|
}) as any;
|
|
90
95
|
if (optimistic) {
|
|
91
|
-
|
|
96
|
+
extraMutateOptions.getOptimisticResponse = optimisticUpdate;
|
|
92
97
|
// TODO: Check that schema is a queryable, otherwise this doesn't make sense
|
|
93
|
-
|
|
98
|
+
extraPartialOptions.getOptimisticResponse = optimisticPartial(
|
|
94
99
|
schema as any,
|
|
95
100
|
);
|
|
96
101
|
}
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
ResourceInterface,
|
|
17
17
|
} from './resourceTypes.js';
|
|
18
18
|
import type {
|
|
19
|
-
|
|
19
|
+
ExtendableRestGenerics,
|
|
20
20
|
RestEndpointExtendOptions,
|
|
21
21
|
RestExtendedEndpoint,
|
|
22
22
|
RestInstanceBase,
|
|
@@ -35,7 +35,7 @@ export interface Extendable<
|
|
|
35
35
|
},
|
|
36
36
|
const ExtendKey extends Exclude<Extract<keyof R, string>, 'extend'>,
|
|
37
37
|
// TODO: see RestEndpoint.extend TODO
|
|
38
|
-
ExtendOptions extends
|
|
38
|
+
ExtendOptions extends ExtendableRestGenerics | {},
|
|
39
39
|
>(
|
|
40
40
|
this: R,
|
|
41
41
|
key: ExtendKey,
|
|
@@ -46,13 +46,14 @@ export interface Extendable<
|
|
|
46
46
|
EndpointToFunction<R[ExtendKey]>
|
|
47
47
|
> &
|
|
48
48
|
ExtendOptions
|
|
49
|
-
|
|
49
|
+
> &
|
|
50
|
+
ExtendOptions,
|
|
50
51
|
): ResourceExtension<R, ExtendKey, ExtendOptions>;
|
|
51
52
|
extend<
|
|
52
53
|
R extends { get: RestInstanceBase },
|
|
53
54
|
const ExtendKey extends string,
|
|
54
55
|
// TODO: see RestEndpoint.extend TODO
|
|
55
|
-
ExtendOptions extends
|
|
56
|
+
ExtendOptions extends ExtendableRestGenerics | {},
|
|
56
57
|
>(
|
|
57
58
|
this: R,
|
|
58
59
|
key: ExtendKey,
|
|
@@ -63,17 +64,18 @@ export interface Extendable<
|
|
|
63
64
|
EndpointToFunction<R['get']>
|
|
64
65
|
> &
|
|
65
66
|
ExtendOptions
|
|
66
|
-
|
|
67
|
+
> &
|
|
68
|
+
ExtendOptions,
|
|
67
69
|
): R & {
|
|
68
70
|
[key in ExtendKey]: RestExtendedEndpoint<ExtendOptions, R['get']>;
|
|
69
71
|
};
|
|
70
72
|
extend<
|
|
71
73
|
R extends ResourceInterface,
|
|
72
|
-
Get extends
|
|
73
|
-
GetList extends
|
|
74
|
-
Update extends
|
|
75
|
-
PartialUpdate extends
|
|
76
|
-
Delete extends
|
|
74
|
+
Get extends ExtendableRestGenerics = {},
|
|
75
|
+
GetList extends ExtendableRestGenerics = {},
|
|
76
|
+
Update extends ExtendableRestGenerics = {},
|
|
77
|
+
PartialUpdate extends ExtendableRestGenerics = {},
|
|
78
|
+
Delete extends ExtendableRestGenerics = {},
|
|
77
79
|
>(
|
|
78
80
|
this: R,
|
|
79
81
|
options: ResourceEndpointExtensions<
|
|
@@ -84,7 +86,9 @@ export interface Extendable<
|
|
|
84
86
|
PartialUpdate,
|
|
85
87
|
Delete
|
|
86
88
|
>,
|
|
87
|
-
): CustomResource<R, O, Get, GetList, Update, PartialUpdate, Delete
|
|
89
|
+
): CustomResource<R, O, Get, GetList, Update, PartialUpdate, Delete> &
|
|
90
|
+
// keeps members like those added by extend('key', options)
|
|
91
|
+
Omit<R, keyof ResourceInterface | 'extend'>;
|
|
88
92
|
extend<
|
|
89
93
|
R extends ResourceInterface,
|
|
90
94
|
T extends Record<string, EndpointInterface>,
|
|
@@ -8,6 +8,7 @@ import type { ResourcePath } from './pathTypes.js';
|
|
|
8
8
|
import { Extendable } from './resourceExtendable.js';
|
|
9
9
|
import { ResourceGenerics, ResourceInterface } from './resourceTypes.js';
|
|
10
10
|
import type {
|
|
11
|
+
ExtendableRestGenerics,
|
|
11
12
|
PartialRestGenerics,
|
|
12
13
|
RestExtendedEndpoint,
|
|
13
14
|
RestInstanceBase,
|
|
@@ -59,18 +60,19 @@ export type ExtendedResource<
|
|
|
59
60
|
|
|
60
61
|
export interface ResourceEndpointExtensions<
|
|
61
62
|
R extends ResourceInterface,
|
|
62
|
-
Get extends
|
|
63
|
-
GetList extends
|
|
64
|
-
Update extends
|
|
65
|
-
PartialUpdate extends
|
|
66
|
-
Delete extends
|
|
63
|
+
Get extends ExtendableRestGenerics = {},
|
|
64
|
+
GetList extends ExtendableRestGenerics = {},
|
|
65
|
+
Update extends ExtendableRestGenerics = {},
|
|
66
|
+
PartialUpdate extends ExtendableRestGenerics = {},
|
|
67
|
+
Delete extends ExtendableRestGenerics = {},
|
|
67
68
|
> {
|
|
68
69
|
readonly get?: RestEndpointOptions<
|
|
69
70
|
unknown extends Get ? EndpointToFunction<R['get']>
|
|
70
71
|
: OptionsToFunction<Get, R['get'], EndpointToFunction<R['get']>>,
|
|
71
72
|
R['get']['schema']
|
|
72
73
|
> &
|
|
73
|
-
Readonly<Get
|
|
74
|
+
Readonly<Get> &
|
|
75
|
+
Get;
|
|
74
76
|
readonly getList?: RestEndpointOptions<
|
|
75
77
|
unknown extends GetList ? EndpointToFunction<R['getList']>
|
|
76
78
|
: OptionsToFunction<
|
|
@@ -80,13 +82,15 @@ export interface ResourceEndpointExtensions<
|
|
|
80
82
|
>,
|
|
81
83
|
R['getList']['schema']
|
|
82
84
|
> &
|
|
83
|
-
Readonly<GetList
|
|
85
|
+
Readonly<GetList> &
|
|
86
|
+
GetList;
|
|
84
87
|
readonly update?: RestEndpointOptions<
|
|
85
88
|
unknown extends Update ? EndpointToFunction<R['update']>
|
|
86
89
|
: OptionsToFunction<Update, R['update'], EndpointToFunction<R['update']>>,
|
|
87
90
|
R['update']['schema']
|
|
88
91
|
> &
|
|
89
|
-
Readonly<Update
|
|
92
|
+
Readonly<Update> &
|
|
93
|
+
Update;
|
|
90
94
|
readonly partialUpdate?: RestEndpointOptions<
|
|
91
95
|
unknown extends PartialUpdate ? EndpointToFunction<R['partialUpdate']>
|
|
92
96
|
: OptionsToFunction<
|
|
@@ -96,11 +100,13 @@ export interface ResourceEndpointExtensions<
|
|
|
96
100
|
>,
|
|
97
101
|
R['partialUpdate']['schema']
|
|
98
102
|
> &
|
|
99
|
-
Readonly<PartialUpdate
|
|
103
|
+
Readonly<PartialUpdate> &
|
|
104
|
+
PartialUpdate;
|
|
100
105
|
readonly delete?: RestEndpointOptions<
|
|
101
106
|
unknown extends Delete ? EndpointToFunction<R['delete']>
|
|
102
107
|
: OptionsToFunction<Delete, R['delete'], EndpointToFunction<R['delete']>>,
|
|
103
108
|
R['delete']['schema']
|
|
104
109
|
> &
|
|
105
|
-
Readonly<Delete
|
|
110
|
+
Readonly<Delete> &
|
|
111
|
+
Delete;
|
|
106
112
|
}
|
package/src/resourceTypes.ts
CHANGED
|
@@ -121,15 +121,13 @@ export interface Resource<
|
|
|
121
121
|
MutateEndpoint<{
|
|
122
122
|
path: ShortenPath<O['path']>;
|
|
123
123
|
schema: schema.Collection<[O['schema']]>['push'];
|
|
124
|
-
body:
|
|
125
|
-
: Partial<Denormalize<O['schema']>>;
|
|
124
|
+
body: CreateBody<O> | CreateBody<O>[] | FormData;
|
|
126
125
|
searchParams: O['searchParams'];
|
|
127
126
|
}>
|
|
128
127
|
: MutateEndpoint<{
|
|
129
128
|
path: ShortenPath<O['path']>;
|
|
130
129
|
schema: schema.Collection<[O['schema']]>['push'];
|
|
131
|
-
body:
|
|
132
|
-
: Partial<Denormalize<O['schema']>>;
|
|
130
|
+
body: CreateBody<O> | CreateBody<O>[] | FormData;
|
|
133
131
|
}>;
|
|
134
132
|
/** Update an item (PUT)
|
|
135
133
|
*
|
|
@@ -185,3 +183,7 @@ export interface ResourceInterface {
|
|
|
185
183
|
partialUpdate: RestInstanceBase;
|
|
186
184
|
delete: RestInstanceBase;
|
|
187
185
|
}
|
|
186
|
+
|
|
187
|
+
/** Matches getList.push's body, since create is getList.push at runtime */
|
|
188
|
+
type CreateBody<O extends { body?: any; schema: Schema }> =
|
|
189
|
+
'body' extends keyof O ? O['body'] : Partial<Denormalize<O['schema']>>;
|