@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/dist/index.js +34 -18
  3. package/dist/index.umd.min.js +1 -1
  4. package/legacy/RestEndpoint.js +7 -4
  5. package/legacy/RestEndpointTypes.js +1 -1
  6. package/legacy/RestHelpers.js +29 -16
  7. package/legacy/pathTypes.js +1 -1
  8. package/legacy/resourceTypes.js +1 -1
  9. package/lib/RestEndpoint.js +7 -4
  10. package/lib/RestEndpointTypes.d.ts +27 -9
  11. package/lib/RestEndpointTypes.d.ts.map +1 -1
  12. package/lib/RestEndpointTypes.js +1 -1
  13. package/lib/RestHelpers.d.ts +2 -2
  14. package/lib/RestHelpers.d.ts.map +1 -1
  15. package/lib/RestHelpers.js +29 -16
  16. package/lib/pathTypes.d.ts +14 -13
  17. package/lib/pathTypes.d.ts.map +1 -1
  18. package/lib/pathTypes.js +1 -1
  19. package/lib/resourceTypes.d.ts +3 -1
  20. package/lib/resourceTypes.d.ts.map +1 -1
  21. package/lib/resourceTypes.js +1 -1
  22. package/package.json +5 -5
  23. package/src/RestEndpoint.js +10 -3
  24. package/src/RestEndpointTypes.ts +9 -9
  25. package/src/RestHelpers.ts +34 -26
  26. package/src/__tests__/Resource.test.ts +56 -0
  27. package/src/__tests__/RestEndpoint.ts +60 -2
  28. package/src/__tests__/__snapshots__/resource-construction.test.ts.snap +1 -1
  29. package/src/__tests__/hookifyResource.ts +7 -0
  30. package/src/__tests__/resource-construction.test.ts +7 -0
  31. package/src/pathTypes.ts +35 -23
  32. package/src/resourceTypes.ts +1 -1
  33. package/ts4.0/RestEndpointTypes.d.ts +45 -9
  34. package/ts4.0/RestHelpers.d.ts +2 -2
  35. package/ts4.0/pathTypes.d.ts +1 -1
  36. package/ts4.0/resourceExtendable.d.ts +1 -1
  37. package/ts4.0/resourceTypes.d.ts +5 -1
  38. package/ts4.1/RestEndpointTypes.d.ts +45 -9
  39. package/ts4.1/RestHelpers.d.ts +2 -2
  40. package/ts4.1/pathTypes.d.ts +14 -13
  41. package/ts4.1/resourceExtendable.d.ts +1 -1
  42. package/ts4.1/resourceTypes.d.ts +5 -1
@@ -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 (!tokens.has(k)) {
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) {
@@ -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']>,
@@ -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: Map<string, PathFunction<object>> = new Map();
6
- export function getUrlBase(path: string): PathFunction {
7
- if (!urlBaseCache.has(path)) {
8
- urlBaseCache.set(
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.get(path) as PathFunction;
10
+ return urlBaseCache[path];
17
11
  }
18
12
 
19
- const urlTokensCache: Map<string, Set<string>> = new Map();
13
+ const urlTokensCache: Record<string, Set<string>> = Object.create(null);
20
14
  export function getUrlTokens(path: string): Set<string> {
21
- if (!urlTokensCache.has(path)) {
22
- urlTokensCache.set(
23
- path,
24
- new Set(
25
- parse(path).map(t => (typeof t === 'string' ? t : `${t['name']}`)),
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 urlTokensCache.get(path) as Set<string>;
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 lastColonIndex = path.lastIndexOf(':');
44
- if (lastColonIndex === -1)
45
- throw new Error('Resource path requires at least one :parameter');
46
- // this is for when not specifying a specific item like create/list
47
- let shortUrlRoot: ShortenPath<S> = path.substring(0, lastColonIndex) as any;
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?/:group?' });
240
+ const ep = new RestEndpoint({ path: '/users{/:id}{/:group}' });
241
241
  const epbody = new RestEndpoint({
242
- path: '/users/:id?/:group?',
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 OnlyOptional<S extends string> =
2
- S extends `${infer K}}?` ? K
3
- : S extends `${infer K}?` ? K
4
- : never;
5
- type OnlyRequired<S extends string> = S extends `${string}?` ? never : S;
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}\\${':' | '?' | '+' | '*' | '{' | '}'}${infer B}` ?
23
+ : S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
18
24
  PathKeys<A> | PathKeys<B>
19
- : PathSplits<S>;
25
+ : Splits<S, ':'> | Splits<S, '*'>;
20
26
 
21
- type PathSplits<S extends string> =
22
- S extends (
23
- `${string}:${infer K}${'/' | ',' | '%' | '&' | '+' | '*' | '{'}${infer R}`
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
- PathSplits<`:${K}`> | PathSplits<R>
26
- : S extends `${string}:${infer K}:${infer R}` ?
27
- PathSplits<`:${K}`> | PathSplits<`:${R}`>
28
- : S extends `${string}:${infer K}` ? K
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 OnlyOptional<K>]?: string | number;
33
- } & (OnlyRequired<Key> extends never ? unknown
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 OnlyRequired<K>]: string | number;
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
- OnlyRequired<PathKeys<S>> extends never ?
48
+ Exclude<PathKeys<S>, `${string}}`> extends never ?
40
49
  Record<string, number | string | boolean> | undefined
41
50
  : {
42
- [K in PathKeys<S> as OnlyRequired<K>]: string | number;
43
- } & Record<string, number | string>;
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}` ? TrimColon<`${B}:${ShortenPath<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 TrimColon<S extends string> =
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
@@ -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 ? O['searchParams'] extends undefined ? 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>, {
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 ? O['searchParams'] extends undefined ? 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>, {
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<O['searchParams'] extends undefined ? 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>, {
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 ? O['searchParams'] extends undefined ? 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'> & {
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 ? O['searchParams'] extends undefined ? 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'> & {
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 ? O['searchParams'] extends undefined ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, OptionsToBodyArgument<O, 'method' extends keyof O ? O['method'] : O extends {
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 ? O['searchParams'] extends undefined ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, OptionsToBodyArgument<O, 'method' extends keyof O ? O['method'] : O extends {
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 ? O['searchParams'] extends undefined ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], undefined, any, 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 ? O['searchParams'] extends undefined ? PathArgs<O['path']> : O['searchParams'] & PathArgs<O['path']> : PathArgs<O['path']>, O['schema'], true, O['body'], any, 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
  }>;
@@ -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>;
@@ -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 = any, GetList extends PartialRestGenerics = any, Update extends PartialRestGenerics = any, PartialUpdate extends PartialRestGenerics = any, Delete extends PartialRestGenerics = any>(this: R, options: ResourceEndpointExtensions<R, Get, GetList, Update, PartialUpdate, Delete>): CustomResource<R, O, Get, GetList, Update, PartialUpdate, Delete>;
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
@@ -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<O['searchParams'] extends undefined ? KeysToArgs<ShortenPath<O['path']>> : O['searchParams'] & PathArgs<ShortenPath<O['path']>>>>;
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'];