@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/dist/index.js +15 -6
  3. package/dist/index.umd.min.js +1 -1
  4. package/legacy/RestEndpoint.js +5 -4
  5. package/legacy/RestEndpointTypes.js +1 -1
  6. package/legacy/extractCollection.js +3 -1
  7. package/legacy/paramsToString.js +1 -1
  8. package/legacy/paramsToString.native.js +1 -1
  9. package/legacy/pathTypes.js +1 -1
  10. package/legacy/resource.js +10 -4
  11. package/legacy/resourceExtendable.js +1 -1
  12. package/legacy/resourceExtensionTypes.js +1 -1
  13. package/legacy/resourceTypes.js +1 -1
  14. package/lib/NetworkError.d.ts.map +1 -1
  15. package/lib/RestEndpoint.js +5 -4
  16. package/lib/RestEndpointTypes.d.ts +49 -20
  17. package/lib/RestEndpointTypes.d.ts.map +1 -1
  18. package/lib/RestEndpointTypes.js +1 -1
  19. package/lib/extractCollection.d.ts +8 -1
  20. package/lib/extractCollection.d.ts.map +1 -1
  21. package/lib/extractCollection.js +3 -1
  22. package/lib/paramsToString.d.ts.map +1 -1
  23. package/lib/paramsToString.js +1 -1
  24. package/lib/paramsToString.native.d.ts.map +1 -1
  25. package/lib/paramsToString.native.js +1 -1
  26. package/lib/pathTypes.d.ts +33 -7
  27. package/lib/pathTypes.d.ts.map +1 -1
  28. package/lib/pathTypes.js +1 -1
  29. package/lib/resource.d.ts +1 -1
  30. package/lib/resource.d.ts.map +1 -1
  31. package/lib/resource.js +10 -4
  32. package/lib/resourceExtendable.d.ts +4 -4
  33. package/lib/resourceExtendable.d.ts.map +1 -1
  34. package/lib/resourceExtendable.js +1 -1
  35. package/lib/resourceExtensionTypes.d.ts +7 -7
  36. package/lib/resourceExtensionTypes.d.ts.map +1 -1
  37. package/lib/resourceExtensionTypes.js +1 -1
  38. package/lib/resourceTypes.d.ts +8 -2
  39. package/lib/resourceTypes.d.ts.map +1 -1
  40. package/lib/resourceTypes.js +1 -1
  41. package/lib/test-fixtures.d.ts +7 -7
  42. package/lib/test-fixtures.d.ts.map +1 -1
  43. package/package.json +6 -6
  44. package/src/RestEndpoint.js +7 -6
  45. package/src/RestEndpointTypes.ts +116 -53
  46. package/src/__tests__/RestEndpoint.ts +23 -7
  47. package/src/__tests__/resource-construction.test.ts +47 -0
  48. package/src/extractCollection.ts +3 -0
  49. package/src/paramsToString.native.ts +1 -5
  50. package/src/paramsToString.ts +1 -5
  51. package/src/pathTypes.ts +105 -31
  52. package/src/resource.ts +11 -6
  53. package/src/resourceExtendable.ts +15 -11
  54. package/src/resourceExtensionTypes.ts +16 -10
  55. package/src/resourceTypes.ts +6 -4
  56. package/ts4.0/RestEndpointTypes.d.ts +57 -22
  57. package/ts4.0/extractCollection.d.ts +8 -1
  58. package/ts4.0/mapCollection.d.ts +11 -16
  59. package/ts4.0/resource.d.ts +1 -1
  60. package/ts4.0/resourceExtendable.d.ts +4 -4
  61. package/ts4.0/resourceExtensionTypes.d.ts +7 -7
  62. package/ts4.0/resourceTypes.d.ts +8 -2
  63. package/ts4.0/test-fixtures.d.ts +7 -7
  64. package/ts4.1/RestEndpointTypes.d.ts +57 -22
  65. package/ts4.1/extractCollection.d.ts +8 -1
  66. package/ts4.1/mapCollection.d.ts +11 -16
  67. package/ts4.1/pathTypes.d.ts +33 -7
  68. package/ts4.1/resource.d.ts +1 -1
  69. package/ts4.1/resourceExtendable.d.ts +4 -4
  70. package/ts4.1/resourceExtensionTypes.d.ts +7 -7
  71. package/ts4.1/resourceTypes.d.ts +8 -2
  72. 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
- | Record<string, Partial<PaginatedArticle>>
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);
@@ -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 {
@@ -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
- type CleanKey<S extends string> = S extends `"${infer K}"` ? K : S;
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> = CleanKey<
4
- K extends `*${infer N}}` ? N
5
- : K extends `*${infer N}` ? N
6
- : K extends `${infer N}}` ? N
7
- : K
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
- : S extends `${infer A}\\${':' | '*' | '}'}${infer B}` ?
32
- PathKeys<A> | PathKeys<B>
33
- : Splits<S, ':'> | Splits<S, '*'>;
34
-
35
- type Splits<S extends string, M extends ':' | '*'> =
36
- S extends `${string}${M}${infer K}${M}${infer R}` ?
37
- Splits<`${M}${K}`, M> | Splits<`${M}${R}`, M>
38
- : S extends (
39
- `${string}${M}${infer K}${'/' | '\\' | '%' | '&' | '*' | ':' | '{' | ';' | ',' | '!' | '@'}${infer R}`
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
- Splits<`${M}${K}`, M> | Splits<R, M>
42
- : S extends `${string}${M}${infer K}` ?
43
- M extends '*' ?
44
- `*${K}`
45
- : K
46
- : never;
47
-
48
- export type KeysToArgs<Key extends string> = {
49
- [K in Key as K extends `${string}}` ? KeyName<K> : never]?: KeyVal<K>;
50
- } & (Exclude<Key, `${string}}`> extends never ? unknown
51
- : {
52
- [K in Key as K extends `${string}}` ? never : KeyName<K>]: KeyVal<K>;
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
- [K in PathKeys<S> as K extends `${string}}` ? never
61
- : KeyName<K>]: KeyVal<K>;
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
- const extraMutateOptions = { ...extraOptions };
82
- const extraPartialOptions = { ...extraOptions };
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
- ...extraOptions,
90
+ ...extraBaseOptions,
86
91
  path,
87
92
  schema,
88
93
  name: getName('get'),
89
94
  }) as any;
90
95
  if (optimistic) {
91
- (extraMutateOptions as any).getOptimisticResponse = optimisticUpdate;
96
+ extraMutateOptions.getOptimisticResponse = optimisticUpdate;
92
97
  // TODO: Check that schema is a queryable, otherwise this doesn't make sense
93
- (extraPartialOptions as any).getOptimisticResponse = optimisticPartial(
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
- PartialRestGenerics,
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 PartialRestGenerics | {},
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 PartialRestGenerics | {},
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 PartialRestGenerics = {},
73
- GetList extends PartialRestGenerics = {},
74
- Update extends PartialRestGenerics = {},
75
- PartialUpdate extends PartialRestGenerics = {},
76
- Delete extends PartialRestGenerics = {},
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 PartialRestGenerics = {},
63
- GetList extends PartialRestGenerics = {},
64
- Update extends PartialRestGenerics = {},
65
- PartialUpdate extends PartialRestGenerics = {},
66
- Delete extends PartialRestGenerics = {},
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
  }
@@ -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: 'body' extends keyof O ? O['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: 'body' extends keyof O ? O['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']>>;