@data-client/rest 0.15.5 → 0.15.7

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.
@@ -818,6 +818,542 @@ describe('resource()', () => {
818
818
  );
819
819
  });
820
820
 
821
+ it('UserResource.getList.move should work', async () => {
822
+ mynock.patch(`/groups/five/users/2`).reply(200, (uri, body: any) => ({
823
+ id: 2,
824
+ username: 'user2',
825
+ email: 'user2@example.com',
826
+ group: 'ten',
827
+ ...body,
828
+ }));
829
+
830
+ const { result, controller } = renderDataClient(
831
+ () => {
832
+ return {
833
+ user2: useQuery(User, { id: 2 }),
834
+ groupFive: useCache(UserResource.getList, { group: 'five' }),
835
+ groupTen: useCache(UserResource.getList, { group: 'ten' }),
836
+ };
837
+ },
838
+ {
839
+ initialFixtures: [
840
+ {
841
+ endpoint: UserResource.getList,
842
+ args: [{ group: 'five' }],
843
+ response: [
844
+ {
845
+ id: 1,
846
+ username: 'user1',
847
+ email: 'user1@example.com',
848
+ group: 'five',
849
+ },
850
+ {
851
+ id: 2,
852
+ username: 'user2',
853
+ email: 'user2@example.com',
854
+ group: 'five',
855
+ },
856
+ ],
857
+ },
858
+ {
859
+ endpoint: UserResource.getList,
860
+ args: [{ group: 'ten' }],
861
+ response: [
862
+ {
863
+ id: 3,
864
+ username: 'user3',
865
+ email: 'user3@example.com',
866
+ group: 'ten',
867
+ },
868
+ ],
869
+ },
870
+ ],
871
+ },
872
+ );
873
+
874
+ expect(result.current.groupFive).toHaveLength(2);
875
+ expect(result.current.groupTen).toHaveLength(1);
876
+
877
+ // PATCH /groups/five/users/2 - moves user 2 from 'five' to 'ten'
878
+ await act(async () => {
879
+ const response = await controller.fetch(
880
+ UserResource.getList.move,
881
+ { group: 'five', id: '2' },
882
+ {
883
+ id: 2,
884
+ group: 'ten',
885
+ },
886
+ );
887
+ expect(response.id).toEqual(2);
888
+ });
889
+
890
+ // user should be removed from group 'five'
891
+ expect(result.current.groupFive).toHaveLength(1);
892
+ expect(result.current.groupFive?.[0]?.username).toBe('user1');
893
+
894
+ // user should be added to group 'ten'
895
+ expect(result.current.groupTen).toHaveLength(2);
896
+ expect(result.current.groupTen?.map((u: any) => u.username)).toEqual(
897
+ expect.arrayContaining(['user3', 'user2']),
898
+ );
899
+
900
+ // entity should be updated
901
+ expect(result.current.user2?.group).toEqual('ten');
902
+
903
+ // move uses full path params (group + id), no searchParams
904
+ () =>
905
+ controller.fetch(
906
+ UserResource.getList.move,
907
+ { group: 'five', id: 2 },
908
+ { id: 1, group: 'ten' },
909
+ );
910
+ () =>
911
+ controller.fetch(
912
+ UserResource.getList.move,
913
+ // @ts-expect-error - missing required group
914
+ { id: 2 },
915
+ { id: 1 },
916
+ );
917
+ // @ts-expect-error
918
+ () => controller.fetch(UserResource.getList.move, { username: 'never' });
919
+ // @ts-expect-error
920
+ () => controller.fetch(UserResource.getList.move, 1, 'hi');
921
+ () =>
922
+ controller.fetch(
923
+ UserResource.getList.move,
924
+ { group: 'five', id: 2 },
925
+ // @ts-expect-error
926
+ { sdf: 'never' },
927
+ );
928
+ () =>
929
+ controller.fetch(
930
+ UserResource.getList.move,
931
+ // @ts-expect-error
932
+ { sdf: 'five', id: 2 },
933
+ { id: 1 },
934
+ );
935
+ });
936
+
937
+ it('getList.move should work when entity lacks path param field', async () => {
938
+ // Entity has 'status' but NOT 'team' - team is only a URL path param
939
+ // Collection keys include both team (from path) and status (from searchParams)
940
+ class Task extends Entity {
941
+ readonly id: number | undefined = undefined;
942
+ readonly title: string = '';
943
+ readonly status: string = 'backlog';
944
+
945
+ pk() {
946
+ return this.id?.toString();
947
+ }
948
+ }
949
+
950
+ const TeamTaskResource = resource({
951
+ path: 'http\\://test.com/teams/:team/tasks/:id',
952
+ searchParams: {} as { status: string },
953
+ schema: Task,
954
+ });
955
+
956
+ mynock.patch(`/teams/alpha/tasks/3`).reply(200, (uri, body: any) => ({
957
+ id: 3,
958
+ title: 'My Task',
959
+ status: 'in-progress',
960
+ ...body,
961
+ }));
962
+
963
+ const { result, controller } = renderDataClient(
964
+ () => {
965
+ return {
966
+ task3: useQuery(Task, { id: 3 }),
967
+ backlog: useCache(TeamTaskResource.getList, {
968
+ team: 'alpha',
969
+ status: 'backlog',
970
+ }),
971
+ inProgress: useCache(TeamTaskResource.getList, {
972
+ team: 'alpha',
973
+ status: 'in-progress',
974
+ }),
975
+ };
976
+ },
977
+ {
978
+ initialFixtures: [
979
+ {
980
+ endpoint: TeamTaskResource.getList,
981
+ args: [{ team: 'alpha', status: 'backlog' }],
982
+ response: [
983
+ { id: 1, title: 'Task 1', status: 'backlog' },
984
+ { id: 3, title: 'My Task', status: 'backlog' },
985
+ ],
986
+ },
987
+ {
988
+ endpoint: TeamTaskResource.getList,
989
+ args: [{ team: 'alpha', status: 'in-progress' }],
990
+ response: [{ id: 2, title: 'Task 2', status: 'in-progress' }],
991
+ },
992
+ ],
993
+ },
994
+ );
995
+
996
+ expect(result.current.backlog).toHaveLength(2);
997
+ expect(result.current.inProgress).toHaveLength(1);
998
+
999
+ // PATCH /teams/alpha/tasks/3 - move task 3 from backlog to in-progress
1000
+ // 'team' is only in URL params (not on entity); 'status' is in body and on entity
1001
+ await act(async () => {
1002
+ const response = await controller.fetch(
1003
+ TeamTaskResource.getList.move,
1004
+ { team: 'alpha', id: '3' },
1005
+ { id: 3, status: 'in-progress' },
1006
+ );
1007
+ expect(response.id).toEqual(3);
1008
+ });
1009
+
1010
+ // task should be removed from backlog
1011
+ expect(result.current.backlog).toHaveLength(1);
1012
+ expect(result.current.backlog?.[0]?.title).toBe('Task 1');
1013
+
1014
+ // task should be added to in-progress
1015
+ expect(result.current.inProgress).toHaveLength(2);
1016
+ expect(result.current.inProgress?.map((t: any) => t.title)).toEqual(
1017
+ expect.arrayContaining(['Task 2', 'My Task']),
1018
+ );
1019
+
1020
+ // entity should be updated
1021
+ expect(result.current.task3?.status).toEqual('in-progress');
1022
+ });
1023
+
1024
+ it('getList.move should work with searchParams-based collections', async () => {
1025
+ class Task extends Entity {
1026
+ readonly id: number | undefined = undefined;
1027
+ readonly title: string = '';
1028
+ readonly status: string = 'backlog';
1029
+
1030
+ pk() {
1031
+ return this.id?.toString();
1032
+ }
1033
+ }
1034
+
1035
+ const TaskResource = resource({
1036
+ path: 'http\\://test.com/tasks/:id',
1037
+ searchParams: {} as { status: string },
1038
+ schema: Task,
1039
+ });
1040
+
1041
+ mynock.patch(`/tasks/3`).reply(200, (uri, body: any) => ({
1042
+ id: 3,
1043
+ title: 'My Task',
1044
+ status: 'in-progress',
1045
+ ...body,
1046
+ }));
1047
+
1048
+ const { result, controller } = renderDataClient(
1049
+ () => {
1050
+ return {
1051
+ task3: useQuery(Task, { id: 3 }),
1052
+ backlog: useCache(TaskResource.getList, { status: 'backlog' }),
1053
+ inProgress: useCache(TaskResource.getList, {
1054
+ status: 'in-progress',
1055
+ }),
1056
+ };
1057
+ },
1058
+ {
1059
+ initialFixtures: [
1060
+ {
1061
+ endpoint: TaskResource.getList,
1062
+ args: [{ status: 'backlog' }],
1063
+ response: [
1064
+ { id: 1, title: 'Task 1', status: 'backlog' },
1065
+ { id: 3, title: 'My Task', status: 'backlog' },
1066
+ ],
1067
+ },
1068
+ {
1069
+ endpoint: TaskResource.getList,
1070
+ args: [{ status: 'in-progress' }],
1071
+ response: [{ id: 2, title: 'Task 2', status: 'in-progress' }],
1072
+ },
1073
+ ],
1074
+ },
1075
+ );
1076
+
1077
+ expect(result.current.backlog).toHaveLength(2);
1078
+ expect(result.current.inProgress).toHaveLength(1);
1079
+
1080
+ // PATCH /tasks/3 - moves task 3 from 'backlog' to 'in-progress'
1081
+ await act(async () => {
1082
+ const response = await controller.fetch(
1083
+ TaskResource.getList.move,
1084
+ { id: '3' },
1085
+ { id: 3, status: 'in-progress' },
1086
+ );
1087
+ expect(response.id).toEqual(3);
1088
+ });
1089
+
1090
+ // task should be removed from backlog
1091
+ expect(result.current.backlog).toHaveLength(1);
1092
+ expect(result.current.backlog?.[0]?.title).toBe('Task 1');
1093
+
1094
+ // task should be added to in-progress
1095
+ expect(result.current.inProgress).toHaveLength(2);
1096
+ expect(result.current.inProgress?.map((t: any) => t.title)).toEqual(
1097
+ expect.arrayContaining(['Task 2', 'My Task']),
1098
+ );
1099
+
1100
+ // entity should be updated
1101
+ expect(result.current.task3?.status).toEqual('in-progress');
1102
+ });
1103
+
1104
+ it('getList.move should work with FormData body', async () => {
1105
+ class Task extends Entity {
1106
+ readonly id: number | undefined = undefined;
1107
+ readonly title: string = '';
1108
+ readonly status: string = 'backlog';
1109
+
1110
+ pk() {
1111
+ return this.id?.toString();
1112
+ }
1113
+ }
1114
+
1115
+ const TaskResource = resource({
1116
+ path: 'http\\://test.com/tasks/:id',
1117
+ searchParams: {} as { status: string },
1118
+ schema: Task,
1119
+ });
1120
+
1121
+ mynock.patch(`/tasks/3`).reply(200, (uri, body: any) => ({
1122
+ id: 3,
1123
+ title: 'My Task',
1124
+ status: 'in-progress',
1125
+ ...(body instanceof FormData ? Object.fromEntries(body.entries()) : body),
1126
+ }));
1127
+
1128
+ const { result, controller } = renderDataClient(
1129
+ () => {
1130
+ return {
1131
+ task3: useQuery(Task, { id: 3 }),
1132
+ backlog: useCache(TaskResource.getList, { status: 'backlog' }),
1133
+ inProgress: useCache(TaskResource.getList, {
1134
+ status: 'in-progress',
1135
+ }),
1136
+ };
1137
+ },
1138
+ {
1139
+ initialFixtures: [
1140
+ {
1141
+ endpoint: TaskResource.getList,
1142
+ args: [{ status: 'backlog' }],
1143
+ response: [
1144
+ { id: 1, title: 'Task 1', status: 'backlog' },
1145
+ { id: 3, title: 'My Task', status: 'backlog' },
1146
+ ],
1147
+ },
1148
+ {
1149
+ endpoint: TaskResource.getList,
1150
+ args: [{ status: 'in-progress' }],
1151
+ response: [{ id: 2, title: 'Task 2', status: 'in-progress' }],
1152
+ },
1153
+ ],
1154
+ },
1155
+ );
1156
+
1157
+ expect(result.current.backlog).toHaveLength(2);
1158
+ expect(result.current.inProgress).toHaveLength(1);
1159
+
1160
+ // Use FormData as the body (simulates multipart form submission)
1161
+ const formData = new FormData();
1162
+ formData.append('id', '3');
1163
+ formData.append('status', 'in-progress');
1164
+
1165
+ await act(async () => {
1166
+ const response = await controller.fetch(
1167
+ TaskResource.getList.move,
1168
+ { id: '3' },
1169
+ formData,
1170
+ );
1171
+ expect(response.id).toEqual(3);
1172
+ });
1173
+
1174
+ // task should be removed from backlog
1175
+ expect(result.current.backlog).toHaveLength(1);
1176
+ expect(result.current.backlog?.[0]?.title).toBe('Task 1');
1177
+
1178
+ // task should be added to in-progress
1179
+ expect(result.current.inProgress).toHaveLength(2);
1180
+ expect(result.current.inProgress?.map((t: any) => t.title)).toEqual(
1181
+ expect.arrayContaining(['Task 2', 'My Task']),
1182
+ );
1183
+
1184
+ // entity should be updated
1185
+ expect(result.current.task3?.status).toEqual('in-progress');
1186
+ });
1187
+
1188
+ it('getList.move should work optimistically with path-based collections', async () => {
1189
+ const OptUserResource = resource({
1190
+ path: 'http\\://test.com/groups/:group/users/:id',
1191
+ schema: User,
1192
+ optimistic: true,
1193
+ });
1194
+
1195
+ mynock.patch(`/groups/five/users/2`).reply(200, () => ({
1196
+ id: 2,
1197
+ username: 'user2',
1198
+ email: 'user2@example.com',
1199
+ group: 'ten',
1200
+ }));
1201
+
1202
+ const { result, controller } = renderDataClient(
1203
+ () => {
1204
+ return {
1205
+ user2: useQuery(User, { id: 2 }),
1206
+ groupFive: useCache(OptUserResource.getList, { group: 'five' }),
1207
+ groupTen: useCache(OptUserResource.getList, { group: 'ten' }),
1208
+ };
1209
+ },
1210
+ {
1211
+ initialFixtures: [
1212
+ {
1213
+ endpoint: OptUserResource.getList,
1214
+ args: [{ group: 'five' }],
1215
+ response: [
1216
+ {
1217
+ id: 1,
1218
+ username: 'user1',
1219
+ email: 'user1@example.com',
1220
+ group: 'five',
1221
+ },
1222
+ {
1223
+ id: 2,
1224
+ username: 'user2',
1225
+ email: 'user2@example.com',
1226
+ group: 'five',
1227
+ },
1228
+ ],
1229
+ },
1230
+ {
1231
+ endpoint: OptUserResource.getList,
1232
+ args: [{ group: 'ten' }],
1233
+ response: [
1234
+ {
1235
+ id: 3,
1236
+ username: 'user3',
1237
+ email: 'user3@example.com',
1238
+ group: 'ten',
1239
+ },
1240
+ ],
1241
+ },
1242
+ ],
1243
+ },
1244
+ );
1245
+
1246
+ expect(OptUserResource.getList.move.getOptimisticResponse).toBeDefined();
1247
+ expect(result.current.groupFive).toHaveLength(2);
1248
+ expect(result.current.groupTen).toHaveLength(1);
1249
+
1250
+ let promise: any;
1251
+ act(() => {
1252
+ promise = controller.fetch(
1253
+ OptUserResource.getList.move,
1254
+ { group: 'five', id: '2' },
1255
+ { id: 2, group: 'ten' },
1256
+ );
1257
+ });
1258
+
1259
+ // optimistic: should update immediately before network responds
1260
+ expect(result.current.groupFive).toHaveLength(1);
1261
+ expect(result.current.groupFive?.[0]?.username).toBe('user1');
1262
+ expect(result.current.groupTen).toHaveLength(2);
1263
+ expect(result.current.groupTen?.map((u: any) => u.username)).toEqual(
1264
+ expect.arrayContaining(['user3', 'user2']),
1265
+ );
1266
+ expect(result.current.user2?.group).toEqual('ten');
1267
+
1268
+ // after server response, should still be correct
1269
+ await act(() => promise);
1270
+ expect(result.current.groupFive).toHaveLength(1);
1271
+ expect(result.current.groupTen).toHaveLength(2);
1272
+ expect(result.current.user2?.group).toEqual('ten');
1273
+ });
1274
+
1275
+ it('getList.move should work optimistically with searchParams-based collections', async () => {
1276
+ class Task extends Entity {
1277
+ readonly id: number | undefined = undefined;
1278
+ readonly title: string = '';
1279
+ readonly status: string = 'backlog';
1280
+
1281
+ pk() {
1282
+ return this.id?.toString();
1283
+ }
1284
+ }
1285
+
1286
+ const TaskResource = resource({
1287
+ path: 'http\\://test.com/tasks/:id',
1288
+ searchParams: {} as { status: string },
1289
+ schema: Task,
1290
+ optimistic: true,
1291
+ });
1292
+
1293
+ mynock.patch(`/tasks/3`).reply(200, () => ({
1294
+ id: 3,
1295
+ title: 'My Task',
1296
+ status: 'in-progress',
1297
+ }));
1298
+
1299
+ const { result, controller } = renderDataClient(
1300
+ () => {
1301
+ return {
1302
+ task3: useQuery(Task, { id: 3 }),
1303
+ backlog: useCache(TaskResource.getList, { status: 'backlog' }),
1304
+ inProgress: useCache(TaskResource.getList, {
1305
+ status: 'in-progress',
1306
+ }),
1307
+ };
1308
+ },
1309
+ {
1310
+ initialFixtures: [
1311
+ {
1312
+ endpoint: TaskResource.getList,
1313
+ args: [{ status: 'backlog' }],
1314
+ response: [
1315
+ { id: 1, title: 'Task 1', status: 'backlog' },
1316
+ { id: 3, title: 'My Task', status: 'backlog' },
1317
+ ],
1318
+ },
1319
+ {
1320
+ endpoint: TaskResource.getList,
1321
+ args: [{ status: 'in-progress' }],
1322
+ response: [{ id: 2, title: 'Task 2', status: 'in-progress' }],
1323
+ },
1324
+ ],
1325
+ },
1326
+ );
1327
+
1328
+ expect(TaskResource.getList.move.getOptimisticResponse).toBeDefined();
1329
+ expect(result.current.backlog).toHaveLength(2);
1330
+ expect(result.current.inProgress).toHaveLength(1);
1331
+
1332
+ let promise: any;
1333
+ act(() => {
1334
+ promise = controller.fetch(
1335
+ TaskResource.getList.move,
1336
+ { id: '3' },
1337
+ { id: 3, status: 'in-progress' },
1338
+ );
1339
+ });
1340
+
1341
+ // optimistic: should update immediately before network responds
1342
+ expect(result.current.backlog).toHaveLength(1);
1343
+ expect(result.current.backlog?.[0]?.title).toBe('Task 1');
1344
+ expect(result.current.inProgress).toHaveLength(2);
1345
+ expect(result.current.inProgress?.map((t: any) => t.title)).toEqual(
1346
+ expect.arrayContaining(['Task 2', 'My Task']),
1347
+ );
1348
+ expect(result.current.task3?.status).toEqual('in-progress');
1349
+
1350
+ // after server response, should still be correct
1351
+ await act(() => promise);
1352
+ expect(result.current.backlog).toHaveLength(1);
1353
+ expect(result.current.inProgress).toHaveLength(2);
1354
+ expect(result.current.task3?.status).toEqual('in-progress');
1355
+ });
1356
+
821
1357
  it.each([
822
1358
  {
823
1359
  response: {
package/src/resource.ts CHANGED
@@ -96,6 +96,7 @@ This warning will not show in production.`,
96
96
  const getList = new Endpoint({
97
97
  ...extraMutateOptions,
98
98
  paginationField: paginationField as string,
99
+ movePath: path,
99
100
  path: shortenedPath,
100
101
  schema: new Collection([schema as any]),
101
102
  name: getName('getList'),
@@ -112,6 +113,7 @@ This warning will not show in production.`,
112
113
  method: 'PUT',
113
114
  name: getName('update'),
114
115
  }),
116
+ // TODO(breaking): Move to getList.move
115
117
  partialUpdate: new Endpoint({
116
118
  ...extraPartialOptions,
117
119
  path,
@@ -92,6 +92,7 @@ export interface Resource<
92
92
  body: 'body' extends keyof O ? O['body']
93
93
  : Partial<Denormalize<O['schema']>>;
94
94
  searchParams: O['searchParams'];
95
+ movePath: O['path'];
95
96
  } & Pick<O, 'paginationField'>
96
97
  >
97
98
  : GetEndpoint<
@@ -107,6 +108,7 @@ export interface Resource<
107
108
  body: 'body' extends keyof O ? O['body']
108
109
  : Partial<Denormalize<O['schema']>>;
109
110
  searchParams: Record<string, number | string | boolean> | undefined;
111
+ movePath: O['path'];
110
112
  } & Pick<O, 'paginationField'>
111
113
  >;
112
114
  /** Create a new item (POST)
@@ -7,5 +7,17 @@ export default class NetworkError extends Error {
7
7
  response: Response;
8
8
  name: string;
9
9
  constructor(response: Response);
10
+ /** Serialize the error for logging and debugging.
11
+ *
12
+ * Error properties are non-enumerable by default, so `JSON.stringify()`
13
+ * on a plain Error produces `{}`. This ensures status, message, and the
14
+ * request URL are always included in serialized output.
15
+ */
16
+ toJSON(): {
17
+ name: string;
18
+ status: number;
19
+ message: string;
20
+ url: string;
21
+ };
10
22
  }
11
23
  //# sourceMappingURL=NetworkError.d.ts.map
@@ -117,6 +117,13 @@ export interface RestInstance<F extends FetchFunction = FetchFunction, S extends
117
117
  remove: RemoveEndpoint<F, ExtractCollection<S>['remove'], Exclude<O, 'body' | 'method'> & {
118
118
  body: OptionsToAdderBodyArgument<O> | OptionsToAdderBodyArgument<O>[] | FormData;
119
119
  }>;
120
+ /** Move item between collections (PATCH) - removes from old, adds to new
121
+ * @see https://dataclient.io/rest/api/RestEndpoint#move
122
+ */
123
+ move: MoveEndpoint<F, ExtractCollection<S>['move'], {
124
+ path: 'movePath' extends keyof O ? O['movePath'] & string : O['path'];
125
+ body: OptionsToAdderBodyArgument<O> | FormData;
126
+ }>;
120
127
  }
121
128
  export type RestEndpointExtendOptions<O extends PartialRestGenerics, E extends {
122
129
  body?: any;
@@ -127,25 +134,25 @@ export type RestEndpointExtendOptions<O extends PartialRestGenerics, E extends {
127
134
  type OptionsToRestEndpoint<O extends PartialRestGenerics, E extends RestInstanceBase & {
128
135
  body?: any;
129
136
  paginationField?: string;
130
- }, 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'], undefined | true> : '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 ? 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>, {
131
138
  path: Exclude<O['path'], undefined>;
132
139
  body: 'body' extends keyof O ? O['body'] : E['body'];
133
140
  searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams'];
134
141
  method: 'method' extends keyof O ? O['method'] : E['method'];
135
142
  paginationField: 'paginationField' extends keyof O ? O['paginationField'] : E['paginationField'];
136
- }> : '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'], undefined | true> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : ResolveType<F>, {
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>, {
137
144
  path: E['path'];
138
145
  body: O['body'];
139
146
  searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams'];
140
147
  method: 'method' extends keyof O ? O['method'] : E['method'];
141
148
  paginationField: 'paginationField' extends keyof O ? O['paginationField'] : Extract<E['paginationField'], string>;
142
- }> : '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'], undefined | true> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], O['process'] extends {} ? ReturnType<O['process']> : ResolveType<F>, {
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>, {
143
150
  path: E['path'];
144
151
  body: E['body'];
145
152
  searchParams: O['searchParams'];
146
153
  method: 'method' extends keyof O ? O['method'] : E['method'];
147
154
  paginationField: 'paginationField' extends keyof O ? O['paginationField'] : Extract<E['paginationField'], string>;
148
- }> : RestInstance<F, 'schema' extends keyof O ? O['schema'] : E['schema'], 'sideEffect' extends keyof O ? Extract<O['sideEffect'], undefined | true> : 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect'], {
155
+ }> : RestInstance<F, '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'], {
149
156
  path: 'path' extends keyof O ? Exclude<O['path'], undefined> : E['path'];
150
157
  body: 'body' extends keyof O ? O['body'] : E['body'];
151
158
  searchParams: 'searchParams' extends keyof O ? O['searchParams'] : E['searchParams'];
@@ -160,7 +167,7 @@ export type RestExtendedEndpoint<O extends PartialRestGenerics, E extends RestIn
160
167
  };
161
168
  } ? {
162
169
  paginationField: E['getPage']['paginationField'];
163
- } : unknown), RestInstance<(...args: Parameters<E>) => O['process'] extends {} ? Promise<ReturnType<O['process']>> : ReturnType<E>, 'schema' extends keyof O ? O['schema'] : E['schema'], 'method' extends keyof O ? MethodToSide<O['method']> : E['sideEffect']>> & Omit<O, KeyofRestEndpoint> & Omit<E, KeyofRestEndpoint | keyof O>;
170
+ } : unknown), RestInstance<(...args: Parameters<E>) => O['process'] extends {} ? Promise<ReturnType<O['process']>> : ReturnType<E>, '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']>> & Omit<O, KeyofRestEndpoint> & Omit<E, KeyofRestEndpoint | keyof O>;
164
171
  export interface PartialRestGenerics {
165
172
  /** @see https://dataclient.io/rest/api/RestEndpoint#path */
166
173
  readonly path?: string;
@@ -244,6 +251,15 @@ export type RemoveEndpoint<F extends FetchFunction = FetchFunction, S extends Sc
244
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'> & {
245
252
  method: 'PATCH';
246
253
  }>;
254
+ export type MoveEndpoint<F extends FetchFunction = FetchFunction, S extends Schema | undefined = any, O extends {
255
+ path: string;
256
+ body: any;
257
+ } = {
258
+ path: string;
259
+ body: any;
260
+ }> = RestInstanceBase<RestFetch<PathArgs<Exclude<O['path'], undefined>>, O['body'], ResolveType<F>>, S, true, Omit<O, 'method' | 'searchParams'> & {
261
+ method: 'PATCH';
262
+ }>;
247
263
  type OptionsToAdderBodyArgument<O extends {
248
264
  body?: any;
249
265
  }> = 'body' extends keyof O ? O['body'] : any;
@@ -286,7 +302,7 @@ export type RestEndpointConstructorOptions<O extends RestGenerics = any> = RestE
286
302
  */
287
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 {
288
304
  sideEffect: true;
289
- } ? 'POST' : 'GET'>, O['process'] extends {} ? ReturnType<O['process']> : any>, 'schema' extends keyof O ? O['schema'] : undefined, 'sideEffect' extends keyof O ? Extract<O['sideEffect'], undefined | true> : MethodToSide<O['method']>, 'method' extends keyof O ? O : O & {
305
+ } ? '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 & {
290
306
  method: O extends {
291
307
  sideEffect: true;
292
308
  } ? 'POST' : 'GET';