@data-client/rest 0.15.7 → 0.16.0
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 +71 -0
- package/dist/index.js +34 -18
- package/dist/index.umd.min.js +1 -1
- package/legacy/RestEndpoint.js +7 -4
- package/legacy/RestEndpointTypes.js +1 -1
- package/legacy/RestHelpers.js +29 -16
- package/legacy/pathTypes.js +1 -1
- package/legacy/resourceTypes.js +1 -1
- package/lib/RestEndpoint.js +7 -4
- package/lib/RestEndpointTypes.d.ts +27 -9
- package/lib/RestEndpointTypes.d.ts.map +1 -1
- package/lib/RestEndpointTypes.js +1 -1
- package/lib/RestHelpers.d.ts +2 -2
- package/lib/RestHelpers.d.ts.map +1 -1
- package/lib/RestHelpers.js +29 -16
- package/lib/pathTypes.d.ts +14 -13
- package/lib/pathTypes.d.ts.map +1 -1
- package/lib/pathTypes.js +1 -1
- package/lib/resourceTypes.d.ts +3 -1
- package/lib/resourceTypes.d.ts.map +1 -1
- package/lib/resourceTypes.js +1 -1
- package/package.json +5 -5
- package/src/RestEndpoint.js +10 -3
- package/src/RestEndpointTypes.ts +9 -9
- package/src/RestHelpers.ts +34 -26
- package/src/__tests__/Resource.test.ts +56 -0
- package/src/__tests__/RestEndpoint.ts +60 -2
- package/src/__tests__/__snapshots__/resource-construction.test.ts.snap +1 -1
- package/src/__tests__/hookifyResource.ts +7 -0
- package/src/__tests__/resource-construction.test.ts +7 -0
- package/src/pathTypes.ts +35 -23
- package/src/resourceTypes.ts +1 -1
- package/ts4.0/RestEndpointTypes.d.ts +45 -9
- package/ts4.0/RestHelpers.d.ts +2 -2
- package/ts4.0/pathTypes.d.ts +1 -1
- package/ts4.0/resourceExtendable.d.ts +1 -1
- package/ts4.0/resourceTypes.d.ts +5 -1
- package/ts4.1/RestEndpointTypes.d.ts +45 -9
- package/ts4.1/RestHelpers.d.ts +2 -2
- package/ts4.1/pathTypes.d.ts +14 -13
- package/ts4.1/resourceExtendable.d.ts +1 -1
- package/ts4.1/resourceTypes.d.ts +5 -1
package/src/RestEndpoint.js
CHANGED
|
@@ -67,14 +67,21 @@ export default class RestEndpoint extends Endpoint {
|
|
|
67
67
|
|
|
68
68
|
/** Get the url */
|
|
69
69
|
url(urlParams = {}) {
|
|
70
|
-
const urlBase = getUrlBase(this.path)(urlParams);
|
|
71
70
|
const tokens = getUrlTokens(this.path);
|
|
71
|
+
const pathParams = {};
|
|
72
72
|
const searchParams = {};
|
|
73
73
|
Object.keys(urlParams).forEach(k => {
|
|
74
|
-
if (
|
|
74
|
+
if (tokens.has(k)) {
|
|
75
|
+
if (urlParams[k] !== undefined)
|
|
76
|
+
pathParams[k] =
|
|
77
|
+
Array.isArray(urlParams[k]) ?
|
|
78
|
+
urlParams[k].map(String)
|
|
79
|
+
: String(urlParams[k]);
|
|
80
|
+
} else {
|
|
75
81
|
searchParams[k] = urlParams[k];
|
|
76
82
|
}
|
|
77
83
|
});
|
|
84
|
+
const urlBase = getUrlBase(this.path)(pathParams);
|
|
78
85
|
if (Object.keys(searchParams).length) {
|
|
79
86
|
return `${this.urlPrefix}${urlBase}?${this.searchToString(searchParams)}`;
|
|
80
87
|
}
|
|
@@ -189,7 +196,7 @@ Response (first 300 characters): ${text.substring(0, 300)}`;
|
|
|
189
196
|
}
|
|
190
197
|
|
|
191
198
|
get pathRegex() {
|
|
192
|
-
return pathToRegexp(this.path);
|
|
199
|
+
return pathToRegexp(this.path).regexp;
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
testKey(key) {
|
package/src/RestEndpointTypes.ts
CHANGED
|
@@ -235,7 +235,7 @@ type OptionsToRestEndpoint<
|
|
|
235
235
|
'path' extends keyof O ?
|
|
236
236
|
RestType<
|
|
237
237
|
'searchParams' extends keyof O ?
|
|
238
|
-
O['searchParams'] extends undefined ?
|
|
238
|
+
[O['searchParams']] extends [undefined] ?
|
|
239
239
|
PathArgs<Exclude<O['path'], undefined>>
|
|
240
240
|
: O['searchParams'] & PathArgs<Exclude<O['path'], undefined>>
|
|
241
241
|
: PathArgs<Exclude<O['path'], undefined>>,
|
|
@@ -263,7 +263,7 @@ type OptionsToRestEndpoint<
|
|
|
263
263
|
: 'body' extends keyof O ?
|
|
264
264
|
RestType<
|
|
265
265
|
'searchParams' extends keyof O ?
|
|
266
|
-
O['searchParams'] extends undefined ?
|
|
266
|
+
[O['searchParams']] extends [undefined] ?
|
|
267
267
|
PathArgs<Exclude<O['path'], undefined>>
|
|
268
268
|
: O['searchParams'] & PathArgs<Exclude<E['path'], undefined>>
|
|
269
269
|
: PathArgs<Exclude<E['path'], undefined>>,
|
|
@@ -290,7 +290,7 @@ type OptionsToRestEndpoint<
|
|
|
290
290
|
>
|
|
291
291
|
: 'searchParams' extends keyof O ?
|
|
292
292
|
RestType<
|
|
293
|
-
O['searchParams'] extends undefined ?
|
|
293
|
+
[O['searchParams']] extends [undefined] ?
|
|
294
294
|
PathArgs<Exclude<O['path'], undefined>>
|
|
295
295
|
: O['searchParams'] & PathArgs<Exclude<E['path'], undefined>>,
|
|
296
296
|
OptionsToBodyArgument<
|
|
@@ -468,7 +468,7 @@ export type AddEndpoint<
|
|
|
468
468
|
> = RestInstanceBase<
|
|
469
469
|
RestFetch<
|
|
470
470
|
'searchParams' extends keyof O ?
|
|
471
|
-
O['searchParams'] extends undefined ?
|
|
471
|
+
[O['searchParams']] extends [undefined] ?
|
|
472
472
|
PathArgs<Exclude<O['path'], undefined>>
|
|
473
473
|
: O['searchParams'] & PathArgs<Exclude<O['path'], undefined>>
|
|
474
474
|
: PathArgs<Exclude<O['path'], undefined>>,
|
|
@@ -490,7 +490,7 @@ export type RemoveEndpoint<
|
|
|
490
490
|
> = RestInstanceBase<
|
|
491
491
|
RestFetch<
|
|
492
492
|
'searchParams' extends keyof O ?
|
|
493
|
-
O['searchParams'] extends undefined ?
|
|
493
|
+
[O['searchParams']] extends [undefined] ?
|
|
494
494
|
PathArgs<Exclude<O['path'], undefined>>
|
|
495
495
|
: O['searchParams'] & PathArgs<Exclude<O['path'], undefined>>
|
|
496
496
|
: PathArgs<Exclude<O['path'], undefined>>,
|
|
@@ -562,7 +562,7 @@ export type RestEndpointConstructorOptions<O extends RestGenerics = any> =
|
|
|
562
562
|
RestEndpointOptions<
|
|
563
563
|
RestFetch<
|
|
564
564
|
'searchParams' extends keyof O ?
|
|
565
|
-
O['searchParams'] extends undefined ?
|
|
565
|
+
[O['searchParams']] extends [undefined] ?
|
|
566
566
|
PathArgs<O['path']>
|
|
567
567
|
: O['searchParams'] & PathArgs<O['path']>
|
|
568
568
|
: PathArgs<O['path']>,
|
|
@@ -587,7 +587,7 @@ export interface RestEndpoint<
|
|
|
587
587
|
> extends RestInstance<
|
|
588
588
|
RestFetch<
|
|
589
589
|
'searchParams' extends keyof O ?
|
|
590
|
-
O['searchParams'] extends undefined ?
|
|
590
|
+
[O['searchParams']] extends [undefined] ?
|
|
591
591
|
PathArgs<O['path']>
|
|
592
592
|
: O['searchParams'] & PathArgs<O['path']>
|
|
593
593
|
: PathArgs<O['path']>,
|
|
@@ -754,7 +754,7 @@ export type GetEndpoint<
|
|
|
754
754
|
},
|
|
755
755
|
> = RestTypeNoBody<
|
|
756
756
|
'searchParams' extends keyof O ?
|
|
757
|
-
O['searchParams'] extends undefined ?
|
|
757
|
+
[O['searchParams']] extends [undefined] ?
|
|
758
758
|
PathArgs<O['path']>
|
|
759
759
|
: O['searchParams'] & PathArgs<O['path']>
|
|
760
760
|
: PathArgs<O['path']>,
|
|
@@ -779,7 +779,7 @@ export type MutateEndpoint<
|
|
|
779
779
|
},
|
|
780
780
|
> = RestTypeWithBody<
|
|
781
781
|
'searchParams' extends keyof O ?
|
|
782
|
-
O['searchParams'] extends undefined ?
|
|
782
|
+
[O['searchParams']] extends [undefined] ?
|
|
783
783
|
PathArgs<O['path']>
|
|
784
784
|
: O['searchParams'] & PathArgs<O['path']>
|
|
785
785
|
: PathArgs<O['path']>,
|
package/src/RestHelpers.ts
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
import { compile, PathFunction, parse } from 'path-to-regexp';
|
|
1
|
+
import { compile, PathFunction, parse, Token, ParamData } from 'path-to-regexp';
|
|
2
2
|
|
|
3
3
|
import { ShortenPath } from './pathTypes.js';
|
|
4
4
|
|
|
5
|
-
const urlBaseCache:
|
|
6
|
-
export function getUrlBase(path: string): PathFunction {
|
|
7
|
-
if (!
|
|
8
|
-
urlBaseCache
|
|
9
|
-
path,
|
|
10
|
-
compile(path, {
|
|
11
|
-
encode: encodeURIComponent,
|
|
12
|
-
validate: false,
|
|
13
|
-
}),
|
|
14
|
-
);
|
|
5
|
+
const urlBaseCache: Record<string, PathFunction<object>> = Object.create(null);
|
|
6
|
+
export function getUrlBase(path: string): PathFunction<ParamData> {
|
|
7
|
+
if (!(path in urlBaseCache)) {
|
|
8
|
+
urlBaseCache[path] = compile(path);
|
|
15
9
|
}
|
|
16
|
-
return urlBaseCache
|
|
10
|
+
return urlBaseCache[path];
|
|
17
11
|
}
|
|
18
12
|
|
|
19
|
-
const urlTokensCache:
|
|
13
|
+
const urlTokensCache: Record<string, Set<string>> = Object.create(null);
|
|
20
14
|
export function getUrlTokens(path: string): Set<string> {
|
|
21
|
-
if (!
|
|
22
|
-
urlTokensCache.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
if (!(path in urlTokensCache)) {
|
|
16
|
+
urlTokensCache[path] = tokenMap(parse(path).tokens);
|
|
17
|
+
}
|
|
18
|
+
return urlTokensCache[path];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function tokenMap(tokens: Token[]): Set<string> {
|
|
22
|
+
const tokenNames = new Set<string>();
|
|
23
|
+
for (const token of tokens) {
|
|
24
|
+
switch (token.type) {
|
|
25
|
+
case 'param':
|
|
26
|
+
case 'wildcard':
|
|
27
|
+
tokenNames.add(token.name);
|
|
28
|
+
break;
|
|
29
|
+
case 'group':
|
|
30
|
+
for (const name of tokenMap(token.tokens)) {
|
|
31
|
+
tokenNames.add(name);
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
|
-
return
|
|
36
|
+
return tokenNames;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
const proto = Object.prototype;
|
|
@@ -40,11 +47,12 @@ export function isPojo(obj: unknown): obj is Record<string, any> {
|
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export function shortenPath<S extends string>(path: S): ShortenPath<S> {
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
throw new Error(
|
|
46
|
-
|
|
47
|
-
|
|
50
|
+
const lastTokenIndex = Math.max(path.lastIndexOf(':'), path.lastIndexOf('*'));
|
|
51
|
+
if (lastTokenIndex === -1)
|
|
52
|
+
throw new Error(
|
|
53
|
+
'Resource path requires at least one :parameter or *wildcard',
|
|
54
|
+
);
|
|
55
|
+
let shortUrlRoot: ShortenPath<S> = path.substring(0, lastTokenIndex) as any;
|
|
48
56
|
if (shortUrlRoot[shortUrlRoot.length - 1] === '/')
|
|
49
57
|
shortUrlRoot = shortUrlRoot.substring(
|
|
50
58
|
0,
|
|
@@ -134,6 +134,18 @@ describe('resource()', () => {
|
|
|
134
134
|
() => UserResource.get.url({ sdf: '5' });
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
it('should omit optional path params when undefined', () => {
|
|
138
|
+
const ep = new RestEndpoint({ path: '/users{/:id}' });
|
|
139
|
+
expect(ep.url({ id: undefined })).toBe('/users');
|
|
140
|
+
expect(ep.url({ id: '5' })).toBe('/users/5');
|
|
141
|
+
expect(ep.url({})).toBe('/users');
|
|
142
|
+
|
|
143
|
+
const ep2 = new RestEndpoint({ path: '/users{/:id}{/:group}' });
|
|
144
|
+
expect(ep2.url({ id: undefined, group: undefined })).toBe('/users');
|
|
145
|
+
expect(ep2.url({ id: '5', group: undefined })).toBe('/users/5');
|
|
146
|
+
expect(ep2.url({ id: undefined })).toBe('/users');
|
|
147
|
+
});
|
|
148
|
+
|
|
137
149
|
it('should handle multiarg urls', () => {
|
|
138
150
|
const MyUserResource = resource({
|
|
139
151
|
path: 'http\\://test.com/groups/:group/users/:id',
|
|
@@ -180,6 +192,50 @@ describe('resource()', () => {
|
|
|
180
192
|
() => useSuspense(MyUserResource.get, { group: 'yay', id: '5' });
|
|
181
193
|
});
|
|
182
194
|
|
|
195
|
+
it('should shorten wildcard (*) paths for getList', () => {
|
|
196
|
+
const FileResource = resource({
|
|
197
|
+
path: '/repos/:owner/*path',
|
|
198
|
+
schema: User,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// get uses the full path with wildcard
|
|
202
|
+
expect(
|
|
203
|
+
FileResource.get.url({ owner: 'john', path: ['src', 'index.ts'] }),
|
|
204
|
+
).toBe('/repos/john/src/index.ts');
|
|
205
|
+
// getList strips the last *wildcard token
|
|
206
|
+
expect(FileResource.getList.url({ owner: 'john' })).toBe('/repos/john');
|
|
207
|
+
// create (push) also uses shortened path
|
|
208
|
+
expect(FileResource.create.url({ owner: 'john' }, {} as any)).toBe(
|
|
209
|
+
'/repos/john',
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// type checks: get requires both owner and path (array)
|
|
213
|
+
// @ts-expect-error - missing path
|
|
214
|
+
() => FileResource.get.url({ owner: 'john' });
|
|
215
|
+
// @ts-expect-error - path must be array
|
|
216
|
+
() => FileResource.get.url({ owner: 'john', path: 'src/index.ts' });
|
|
217
|
+
// getList only requires owner (extra props become search params)
|
|
218
|
+
() => FileResource.getList({ owner: 'john' });
|
|
219
|
+
// @ts-expect-error - missing required owner param
|
|
220
|
+
() => FileResource.getList({});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should shorten path when wildcard comes after multiple params', () => {
|
|
224
|
+
const DeepResource = resource({
|
|
225
|
+
path: '/api/:version/files/*path',
|
|
226
|
+
schema: User,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(
|
|
230
|
+
DeepResource.get.url({ version: 'v2', path: ['docs', 'readme.md'] }),
|
|
231
|
+
).toBe('/api/v2/files/docs/readme.md');
|
|
232
|
+
expect(DeepResource.getList.url({ version: 'v2' })).toBe('/api/v2/files');
|
|
233
|
+
|
|
234
|
+
// @ts-expect-error - missing version
|
|
235
|
+
() => DeepResource.getList({});
|
|
236
|
+
() => DeepResource.getList({ version: 'v2' });
|
|
237
|
+
});
|
|
238
|
+
|
|
183
239
|
it('should automatically name methods', () => {
|
|
184
240
|
expect(PaginatedArticleResource.get.name).toBe('PaginatedArticle.get');
|
|
185
241
|
expect(PaginatedArticleResource.create.name).toBe(
|
|
@@ -237,9 +237,9 @@ describe('RestEndpoint', () => {
|
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
it('only optional path means the arg is not required', () => {
|
|
240
|
-
const ep = new RestEndpoint({ path: '/users/:id
|
|
240
|
+
const ep = new RestEndpoint({ path: '/users{/:id}{/:group}' });
|
|
241
241
|
const epbody = new RestEndpoint({
|
|
242
|
-
path: '/users/:id
|
|
242
|
+
path: '/users{/:id}{/:group}',
|
|
243
243
|
body: { title: '' },
|
|
244
244
|
method: 'POST',
|
|
245
245
|
});
|
|
@@ -255,6 +255,64 @@ describe('RestEndpoint', () => {
|
|
|
255
255
|
() => epbody({ title: 'hi' }, { title: 'hi' });
|
|
256
256
|
});
|
|
257
257
|
|
|
258
|
+
it('should omit optional path params when undefined', () => {
|
|
259
|
+
const ep = new RestEndpoint({ path: '/users{/:id}' });
|
|
260
|
+
expect(ep.url({ id: undefined })).toBe('/users');
|
|
261
|
+
expect(ep.url({ id: '5' })).toBe('/users/5');
|
|
262
|
+
expect(ep.url({})).toBe('/users');
|
|
263
|
+
|
|
264
|
+
const ep2 = new RestEndpoint({ path: '/users{/:id}{/:group}' });
|
|
265
|
+
expect(ep2.url({ id: undefined, group: undefined })).toBe('/users');
|
|
266
|
+
expect(ep2.url({ id: '5', group: undefined })).toBe('/users/5');
|
|
267
|
+
expect(ep2.url({ id: undefined })).toBe('/users');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle wildcard (*) path params as arrays', () => {
|
|
271
|
+
const ep = new RestEndpoint({ path: '/files/*path' });
|
|
272
|
+
expect(ep.url({ path: ['a', 'b', 'c'] })).toBe('/files/a/b/c');
|
|
273
|
+
expect(ep.url({ path: ['single'] })).toBe('/files/single');
|
|
274
|
+
|
|
275
|
+
// @ts-expect-error - path must be an array, not a string
|
|
276
|
+
() => ep.url({ path: 'a/b/c' });
|
|
277
|
+
// @ts-expect-error - missing required wildcard param
|
|
278
|
+
() => ep.url({});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should handle wildcard with regular params', () => {
|
|
282
|
+
const ep = new RestEndpoint({ path: '/users/:id/*rest' });
|
|
283
|
+
expect(ep.url({ id: '5', rest: ['docs', 'file'] })).toBe(
|
|
284
|
+
'/users/5/docs/file',
|
|
285
|
+
);
|
|
286
|
+
expect(ep.url({ id: '42', rest: ['a'] })).toBe('/users/42/a');
|
|
287
|
+
|
|
288
|
+
// @ts-expect-error - missing id
|
|
289
|
+
() => ep.url({ rest: ['a'] });
|
|
290
|
+
// @ts-expect-error - missing rest
|
|
291
|
+
() => ep.url({ id: '5' });
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should handle optional wildcard params', () => {
|
|
295
|
+
const ep = new RestEndpoint({ path: '/files{/*path}' });
|
|
296
|
+
expect(ep.url({ path: ['a', 'b'] })).toBe('/files/a/b');
|
|
297
|
+
expect(ep.url({})).toBe('/files');
|
|
298
|
+
expect(ep.url({ path: undefined })).toBe('/files');
|
|
299
|
+
() => ep();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('should handle optional wildcard with required param', () => {
|
|
303
|
+
const ep = new RestEndpoint({ path: '/users/:id{/*rest}' });
|
|
304
|
+
expect(ep.url({ id: '5', rest: ['docs', 'file'] })).toBe(
|
|
305
|
+
'/users/5/docs/file',
|
|
306
|
+
);
|
|
307
|
+
expect(ep.url({ id: '5' })).toBe('/users/5');
|
|
308
|
+
expect(ep.url({ id: '5', rest: undefined })).toBe('/users/5');
|
|
309
|
+
|
|
310
|
+
// @ts-expect-error - missing required param id
|
|
311
|
+
() => ep.url({});
|
|
312
|
+
// @ts-expect-error - rest should be array, not string
|
|
313
|
+
() => ep.url({ id: '5', rest: 'docs/file' });
|
|
314
|
+
});
|
|
315
|
+
|
|
258
316
|
it('should allow sideEffect overrides', () => {
|
|
259
317
|
const weirdGetUser = new RestEndpoint({
|
|
260
318
|
path: 'http\\://test.com/user/:id',
|
|
@@ -4,7 +4,7 @@ exports[`resource() UserResource.delete should work with 1`] = `"not found"`;
|
|
|
4
4
|
|
|
5
5
|
exports[`resource() UserResource.delete should work with {"id": 5} 1`] = `"not found"`;
|
|
6
6
|
|
|
7
|
-
exports[`resource() should not allow paths without at least one argument 1`] = `"Resource path requires at least one :parameter"`;
|
|
7
|
+
exports[`resource() should not allow paths without at least one argument 1`] = `"Resource path requires at least one :parameter or *wildcard"`;
|
|
8
8
|
|
|
9
9
|
exports[`resource() warnings should warn when mis-capitalizing options 1`] = `
|
|
10
10
|
[
|
|
@@ -63,7 +63,14 @@ describe('hookifyResource()', () => {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
let renderDataClient: ReturnType<typeof makeRenderDataClient>;
|
|
66
|
+
let errorSpy: jest.SpyInstance;
|
|
66
67
|
|
|
68
|
+
beforeAll(() => {
|
|
69
|
+
errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
70
|
+
});
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
errorSpy.mockRestore();
|
|
73
|
+
});
|
|
67
74
|
beforeEach(() => {
|
|
68
75
|
renderDataClient = makeRenderDataClient(CacheProvider);
|
|
69
76
|
|
|
@@ -64,6 +64,13 @@ describe('resource()', () => {
|
|
|
64
64
|
isAdmin: true,
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
+
let errorSpy: jest.SpyInstance;
|
|
68
|
+
beforeAll(() => {
|
|
69
|
+
errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
70
|
+
});
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
errorSpy.mockRestore();
|
|
73
|
+
});
|
|
67
74
|
beforeEach(() => {
|
|
68
75
|
nock(/.*/)
|
|
69
76
|
.persist()
|
package/src/pathTypes.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
type CleanKey<S extends string> = S extends `"${infer K}"` ? K : S;
|
|
2
|
+
|
|
3
|
+
type KeyName<K extends string> = CleanKey<
|
|
4
|
+
K extends `*${infer N}}` ? N
|
|
5
|
+
: K extends `*${infer N}` ? N
|
|
6
|
+
: K extends `${infer N}}` ? N
|
|
7
|
+
: K
|
|
8
|
+
>;
|
|
9
|
+
|
|
10
|
+
type KeyVal<K extends string> =
|
|
11
|
+
K extends `*${string}` ? string[] : string | number;
|
|
6
12
|
|
|
7
13
|
/** Parameters for a given path */
|
|
8
14
|
export type PathArgs<S extends string> =
|
|
@@ -14,43 +20,49 @@ export type PathArgs<S extends string> =
|
|
|
14
20
|
/** Computes the union of keys for a path string */
|
|
15
21
|
export type PathKeys<S extends string> =
|
|
16
22
|
string extends S ? string
|
|
17
|
-
: S extends `${infer A}\\${':' | '
|
|
23
|
+
: S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
|
|
18
24
|
PathKeys<A> | PathKeys<B>
|
|
19
|
-
:
|
|
25
|
+
: Splits<S, ':'> | Splits<S, '*'>;
|
|
20
26
|
|
|
21
|
-
type
|
|
22
|
-
S extends
|
|
23
|
-
|
|
27
|
+
type Splits<S extends string, M extends ':' | '*'> =
|
|
28
|
+
S extends `${string}${M}${infer K}${M}${infer R}` ?
|
|
29
|
+
Splits<`${M}${K}`, M> | Splits<`${M}${R}`, M>
|
|
30
|
+
: S extends (
|
|
31
|
+
`${string}${M}${infer K}${'/' | '\\' | '%' | '&' | '*' | ':' | '{' | ';' | ',' | '!' | '@'}${infer R}`
|
|
24
32
|
) ?
|
|
25
|
-
|
|
26
|
-
: S extends `${string}
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
Splits<`${M}${K}`, M> | Splits<R, M>
|
|
34
|
+
: S extends `${string}${M}${infer K}` ?
|
|
35
|
+
M extends '*' ?
|
|
36
|
+
`*${K}`
|
|
37
|
+
: K
|
|
29
38
|
: never;
|
|
30
39
|
|
|
31
40
|
export type KeysToArgs<Key extends string> = {
|
|
32
|
-
[K in Key as
|
|
33
|
-
} & (
|
|
41
|
+
[K in Key as K extends `${string}}` ? KeyName<K> : never]?: KeyVal<K>;
|
|
42
|
+
} & (Exclude<Key, `${string}}`> extends never ? unknown
|
|
34
43
|
: {
|
|
35
|
-
[K in Key as
|
|
44
|
+
[K in Key as K extends `${string}}` ? never : KeyName<K>]: KeyVal<K>;
|
|
36
45
|
});
|
|
37
46
|
|
|
38
47
|
export type PathArgsAndSearch<S extends string> =
|
|
39
|
-
|
|
48
|
+
Exclude<PathKeys<S>, `${string}}`> extends never ?
|
|
40
49
|
Record<string, number | string | boolean> | undefined
|
|
41
50
|
: {
|
|
42
|
-
[K in PathKeys<S> as
|
|
43
|
-
|
|
51
|
+
[K in PathKeys<S> as K extends `${string}}` ? never
|
|
52
|
+
: KeyName<K>]: KeyVal<K>;
|
|
53
|
+
} & Record<string, number | string | string[]>;
|
|
44
54
|
|
|
45
|
-
/** Removes the last :token */
|
|
55
|
+
/** Removes the last :param or *wildcard token */
|
|
46
56
|
export type ShortenPath<S extends string> =
|
|
47
57
|
string extends S ? string
|
|
48
|
-
: S extends `${infer B}:${infer R}` ?
|
|
58
|
+
: S extends `${infer B}:${infer R}` ? TrimToken<`${B}:${ShortenPath<R>}`>
|
|
59
|
+
: S extends `${infer B}*${infer R}` ? TrimToken<`${B}*${ShortenPath<R>}`>
|
|
49
60
|
: '';
|
|
50
61
|
|
|
51
|
-
type
|
|
62
|
+
type TrimToken<S extends string> =
|
|
52
63
|
string extends S ? string
|
|
53
64
|
: S extends `${infer R}:` ? R
|
|
65
|
+
: S extends `${infer R}*` ? R
|
|
54
66
|
: S;
|
|
55
67
|
|
|
56
68
|
export type ResourcePath = string; // `${string}:${string}`; TODO: Maybe do this in the future? Seems to hard to understand for now
|
package/src/resourceTypes.ts
CHANGED
|
@@ -84,7 +84,7 @@ export interface Resource<
|
|
|
84
84
|
schema: schema.Collection<
|
|
85
85
|
[O['schema']],
|
|
86
86
|
ParamToArgs<
|
|
87
|
-
O['searchParams'] extends undefined ?
|
|
87
|
+
[O['searchParams']] extends [undefined] ?
|
|
88
88
|
KeysToArgs<ShortenPath<O['path']>>
|
|
89
89
|
: O['searchParams'] & PathArgs<ShortenPath<O['path']>>
|
|
90
90
|
>
|
|
@@ -134,19 +134,31 @@ export type RestEndpointExtendOptions<O extends PartialRestGenerics, E extends {
|
|
|
134
134
|
type OptionsToRestEndpoint<O extends PartialRestGenerics, E extends RestInstanceBase & {
|
|
135
135
|
body?: any;
|
|
136
136
|
paginationField?: string;
|
|
137
|
-
}, F extends FetchFunction> = 'path' extends keyof O ? RestType<'searchParams' extends keyof O ?
|
|
137
|
+
}, F extends FetchFunction> = 'path' extends keyof O ? RestType<'searchParams' extends keyof O ? [
|
|
138
|
+
O['searchParams']
|
|
139
|
+
] extends [
|
|
140
|
+
undefined
|
|
141
|
+
] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, OptionsToBodyArgument<'body' extends keyof O ? O : E, 'method' extends keyof O ? O['method'] : E['method']>, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : ResolveType<F>, {
|
|
138
142
|
path: Exclude<O['path'], undefined>;
|
|
139
143
|
body: 'body' extends keyof O ? O['body'] : E['body'];
|
|
140
144
|
searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams'];
|
|
141
145
|
method: 'method' extends keyof O ? O['method'] : E['method'];
|
|
142
146
|
paginationField: 'paginationField' extends keyof O ? O['paginationField'] : E['paginationField'];
|
|
143
|
-
}> : 'body' extends keyof O ? RestType<'searchParams' extends keyof O ?
|
|
147
|
+
}> : 'body' extends keyof O ? RestType<'searchParams' extends keyof O ? [
|
|
148
|
+
O['searchParams']
|
|
149
|
+
] extends [
|
|
150
|
+
undefined
|
|
151
|
+
] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<E['path'], undefined>> : PathArgs<Exclude<E['path'], undefined>>, OptionsToBodyArgument<O, 'method' extends keyof O ? O['method'] : E['method']>, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : ResolveType<F>, {
|
|
144
152
|
path: E['path'];
|
|
145
153
|
body: O['body'];
|
|
146
154
|
searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams'];
|
|
147
155
|
method: 'method' extends keyof O ? O['method'] : E['method'];
|
|
148
156
|
paginationField: 'paginationField' extends keyof O ? O['paginationField'] : Extract<E['paginationField'], string>;
|
|
149
|
-
}> : 'searchParams' extends keyof O ? RestType<
|
|
157
|
+
}> : 'searchParams' extends keyof O ? RestType<[
|
|
158
|
+
O['searchParams']
|
|
159
|
+
] extends [
|
|
160
|
+
undefined
|
|
161
|
+
] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<E['path'], undefined>>, OptionsToBodyArgument<E, 'method' extends keyof O ? O['method'] : E['method']>, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : ResolveType<F>, {
|
|
150
162
|
path: E['path'];
|
|
151
163
|
body: E['body'];
|
|
152
164
|
searchParams: O['searchParams'];
|
|
@@ -238,7 +250,11 @@ export type AddEndpoint<F extends FetchFunction = FetchFunction, S extends Schem
|
|
|
238
250
|
} = {
|
|
239
251
|
path: string;
|
|
240
252
|
body: any;
|
|
241
|
-
}> = RestInstanceBase<RestFetch<'searchParams' extends keyof O ?
|
|
253
|
+
}> = RestInstanceBase<RestFetch<'searchParams' extends keyof O ? [
|
|
254
|
+
O['searchParams']
|
|
255
|
+
] extends [
|
|
256
|
+
undefined
|
|
257
|
+
] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F>>, S, true, Omit<O, 'method'> & {
|
|
242
258
|
method: 'POST';
|
|
243
259
|
}>;
|
|
244
260
|
export type RemoveEndpoint<F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends {
|
|
@@ -248,7 +264,11 @@ export type RemoveEndpoint<F extends FetchFunction = FetchFunction, S extends Sc
|
|
|
248
264
|
} = {
|
|
249
265
|
path: string;
|
|
250
266
|
body: any;
|
|
251
|
-
}> = RestInstanceBase<RestFetch<'searchParams' extends keyof O ?
|
|
267
|
+
}> = RestInstanceBase<RestFetch<'searchParams' extends keyof O ? [
|
|
268
|
+
O['searchParams']
|
|
269
|
+
] extends [
|
|
270
|
+
undefined
|
|
271
|
+
] ? PathArgs<Exclude<O['path'], undefined>> : O['searchParams'] & PathArgs<Exclude<O['path'], undefined>> : PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F>>, S, true, Omit<O, 'method'> & {
|
|
252
272
|
method: 'PATCH';
|
|
253
273
|
}>;
|
|
254
274
|
export type MoveEndpoint<F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends {
|
|
@@ -293,14 +313,22 @@ export interface RestEndpointOptions<F extends FetchFunction = FetchFunction, S
|
|
|
293
313
|
url?(...args: Parameters<F>): string;
|
|
294
314
|
update?: EndpointUpdateFunction<F, S>;
|
|
295
315
|
}
|
|
296
|
-
export type RestEndpointConstructorOptions<O extends RestGenerics = any> = RestEndpointOptions<RestFetch<'searchParams' extends keyof O ?
|
|
316
|
+
export type RestEndpointConstructorOptions<O extends RestGenerics = any> = RestEndpointOptions<RestFetch<'searchParams' extends keyof O ? [
|
|
317
|
+
O['searchParams']
|
|
318
|
+
] extends [
|
|
319
|
+
undefined
|
|
320
|
+
] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, OptionsToBodyArgument<O, 'method' extends keyof O ? O['method'] : O extends {
|
|
297
321
|
sideEffect: true;
|
|
298
322
|
} ? 'POST' : 'GET'>, O['process'] extends {} ? ReturnType<O['process']> : any>, O['schema']>;
|
|
299
323
|
/** Simplifies endpoint definitions that follow REST patterns
|
|
300
324
|
*
|
|
301
325
|
* @see https://dataclient.io/rest/api/RestEndpoint
|
|
302
326
|
*/
|
|
303
|
-
export interface RestEndpoint<O extends RestGenerics = any> extends RestInstance<RestFetch<'searchParams' extends keyof O ?
|
|
327
|
+
export interface RestEndpoint<O extends RestGenerics = any> extends RestInstance<RestFetch<'searchParams' extends keyof O ? [
|
|
328
|
+
O['searchParams']
|
|
329
|
+
] extends [
|
|
330
|
+
undefined
|
|
331
|
+
] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, OptionsToBodyArgument<O, 'method' extends keyof O ? O['method'] : O extends {
|
|
304
332
|
sideEffect: true;
|
|
305
333
|
} ? 'POST' : 'GET'>, O['process'] extends {} ? ReturnType<O['process']> : any>, 'schema' extends keyof O ? O['schema'] : undefined, 'sideEffect' extends keyof O ? Extract<O['sideEffect'], boolean | undefined> : MethodToSide<O['method']>, 'method' extends keyof O ? O : O & {
|
|
306
334
|
method: O extends {
|
|
@@ -379,7 +407,11 @@ export type GetEndpoint<O extends {
|
|
|
379
407
|
} = {
|
|
380
408
|
path: string;
|
|
381
409
|
schema: Schema;
|
|
382
|
-
}> = RestTypeNoBody<'searchParams' extends keyof O ?
|
|
410
|
+
}> = RestTypeNoBody<'searchParams' extends keyof O ? [
|
|
411
|
+
O['searchParams']
|
|
412
|
+
] extends [
|
|
413
|
+
undefined
|
|
414
|
+
] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], undefined, any, O & {
|
|
383
415
|
method: 'GET';
|
|
384
416
|
}>;
|
|
385
417
|
export type MutateEndpoint<O extends {
|
|
@@ -393,7 +425,11 @@ export type MutateEndpoint<O extends {
|
|
|
393
425
|
path: string;
|
|
394
426
|
body: any;
|
|
395
427
|
schema: Schema;
|
|
396
|
-
}> = RestTypeWithBody<'searchParams' extends keyof O ?
|
|
428
|
+
}> = RestTypeWithBody<'searchParams' extends keyof O ? [
|
|
429
|
+
O['searchParams']
|
|
430
|
+
] extends [
|
|
431
|
+
undefined
|
|
432
|
+
] ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], true, O['body'], any, O & {
|
|
397
433
|
body: any;
|
|
398
434
|
method: 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
399
435
|
}>;
|
package/ts4.0/RestHelpers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { PathFunction } from 'path-to-regexp';
|
|
1
|
+
import { PathFunction, ParamData } from 'path-to-regexp';
|
|
2
2
|
import { ShortenPath } from './pathTypes.js';
|
|
3
|
-
export declare function getUrlBase(path: string): PathFunction
|
|
3
|
+
export declare function getUrlBase(path: string): PathFunction<ParamData>;
|
|
4
4
|
export declare function getUrlTokens(path: string): Set<string>;
|
|
5
5
|
export declare function isPojo(obj: unknown): obj is Record<string, any>;
|
|
6
6
|
export declare function shortenPath<S extends string>(path: S): ShortenPath<S>;
|
package/ts4.0/pathTypes.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare type KeysToArgs<Key extends string> = {
|
|
|
11
11
|
};
|
|
12
12
|
export declare type PathArgsAndSearch<S extends string> =
|
|
13
13
|
PathKeys<S> extends never
|
|
14
|
-
? Record<string, number | string> | undefined
|
|
14
|
+
? Record<string, number | string | boolean> | undefined
|
|
15
15
|
: {
|
|
16
16
|
[K in PathKeys<S>]: string | number;
|
|
17
17
|
} & Record<string, number | string>;
|
|
@@ -15,7 +15,7 @@ export interface Extendable<O extends ResourceGenerics = {
|
|
|
15
15
|
}, ExtendKey extends string, ExtendOptions extends PartialRestGenerics | {}>(this: R, key: ExtendKey, options: Readonly<RestEndpointExtendOptions<ExtendOptions, R['get'], EndpointToFunction<R['get']>> & ExtendOptions>): R & {
|
|
16
16
|
[key in ExtendKey]: RestExtendedEndpoint<ExtendOptions, R['get']>;
|
|
17
17
|
};
|
|
18
|
-
extend<R extends ResourceInterface, Get extends PartialRestGenerics =
|
|
18
|
+
extend<R extends ResourceInterface, Get extends PartialRestGenerics = {}, GetList extends PartialRestGenerics = {}, Update extends PartialRestGenerics = {}, PartialUpdate extends PartialRestGenerics = {}, Delete extends PartialRestGenerics = {}>(this: R, options: ResourceEndpointExtensions<R, Get, GetList, Update, PartialUpdate, Delete>): CustomResource<R, O, Get, GetList, Update, PartialUpdate, Delete>;
|
|
19
19
|
extend<R extends ResourceInterface, T extends Record<string, EndpointInterface>>(this: R, extender: (baseResource: R) => T): ExtendedResource<R, T>;
|
|
20
20
|
}
|
|
21
21
|
//# sourceMappingURL=resourceExtendable.d.ts.map
|
package/ts4.0/resourceTypes.d.ts
CHANGED
|
@@ -71,7 +71,11 @@ export interface Resource<O extends ResourceGenerics = {
|
|
|
71
71
|
path: ShortenPath<O['path']>;
|
|
72
72
|
schema: schema.Collection<[
|
|
73
73
|
O['schema']
|
|
74
|
-
], ParamToArgs<
|
|
74
|
+
], ParamToArgs<[
|
|
75
|
+
O['searchParams']
|
|
76
|
+
] extends [
|
|
77
|
+
undefined
|
|
78
|
+
] ? KeysToArgs<ShortenPath<O['path']>> : O['searchParams'] & PathArgs<ShortenPath<O['path']>>>>;
|
|
75
79
|
body: 'body' extends keyof O ? O['body'] : Partial<Denormalize<O['schema']>>;
|
|
76
80
|
searchParams: O['searchParams'];
|
|
77
81
|
movePath: O['path'];
|