@data-client/rest 0.5.0 → 0.7.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.
@@ -46,6 +46,7 @@ export interface RestInstanceBase<
46
46
  /** @see https://resthooks.io/rest/api/RestEndpoint#method */
47
47
  readonly method: (O & { method: string })['method'];
48
48
  readonly signal: AbortSignal | undefined;
49
+ readonly paginationField?: string;
49
50
 
50
51
  /* fetch lifecycles */
51
52
  /* before-fetch */
@@ -77,10 +78,15 @@ export interface RestInstanceBase<
77
78
  /** Creates a child endpoint that inherits from this while overriding provided `options`.
78
79
  * @see https://dataclient.io/rest/api/RestEndpoint#extend
79
80
  */
80
- extend<E extends RestInstanceBase, O extends PartialRestGenerics | {}>(
81
+ extend<
82
+ E extends RestInstanceBase,
83
+ ExtendOptions extends PartialRestGenerics | {},
84
+ >(
81
85
  this: E,
82
- options: Readonly<RestEndpointExtendOptions<O, E, F> & O>,
83
- ): RestExtendedEndpoint<O, E>;
86
+ options: Readonly<
87
+ RestEndpointExtendOptions<ExtendOptions, E, F> & ExtendOptions
88
+ >,
89
+ ): RestExtendedEndpoint<ExtendOptions, E>;
84
90
  }
85
91
 
86
92
  export interface RestInstance<
@@ -92,9 +98,10 @@ export interface RestInstance<
92
98
  body?: any;
93
99
  searchParams?: any;
94
100
  method?: string;
101
+ paginationField?: string;
95
102
  } = { path: string },
96
103
  > extends RestInstanceBase<F, S, M, O> {
97
- /** Endpoint to append the next page extending a list for pagination
104
+ /** Creates an Endpoint to append the next page extending a list for pagination
98
105
  * @see https://dataclient.io/rest/api/RestEndpoint#paginated
99
106
  */
100
107
  paginated<
@@ -111,6 +118,17 @@ export interface RestInstance<
111
118
  this: E,
112
119
  cursorField: C,
113
120
  ): PaginationFieldEndpoint<E, C>;
121
+ /** Endpoint to append the next page extending a list for pagination
122
+ * @see https://dataclient.io/rest/api/RestEndpoint#getPage
123
+ */
124
+ getPage: 'paginationField' extends keyof O
125
+ ? O['paginationField'] extends string
126
+ ? PaginationFieldEndpoint<
127
+ F & { schema: S; sideEffect: M } & O,
128
+ O['paginationField']
129
+ >
130
+ : undefined
131
+ : undefined;
114
132
  /** Endpoint that pushes (place at end) a newly created entity to this Collection
115
133
  * @see https://dataclient.io/rest/api/RestEndpoint#push
116
134
  */
@@ -159,11 +177,16 @@ export type RestEndpointExtendOptions<
159
177
  ? Extract<O['schema'], Schema | undefined>
160
178
  : E['schema']
161
179
  > &
162
- Partial<Omit<E, KeyofRestEndpoint | keyof PartialRestGenerics>>;
180
+ Partial<
181
+ Omit<
182
+ E,
183
+ KeyofRestEndpoint | keyof PartialRestGenerics | keyof RestEndpointOptions
184
+ >
185
+ >;
163
186
 
164
187
  type OptionsToRestEndpoint<
165
188
  O extends PartialRestGenerics,
166
- E extends RestInstanceBase & { body?: any },
189
+ E extends RestInstanceBase & { body?: any; paginationField?: string },
167
190
  F extends FetchFunction,
168
191
  > = 'path' extends keyof O
169
192
  ? RestType<
@@ -186,6 +209,9 @@ type OptionsToRestEndpoint<
186
209
  ? O['searchParams']
187
210
  : E['searchParams'];
188
211
  method: 'method' extends keyof O ? O['method'] : E['method'];
212
+ paginationField: 'paginationField' extends keyof O
213
+ ? O['paginationField']
214
+ : E['paginationField'];
189
215
  }
190
216
  >
191
217
  : 'body' extends keyof O
@@ -209,6 +235,9 @@ type OptionsToRestEndpoint<
209
235
  ? O['searchParams']
210
236
  : E['searchParams'];
211
237
  method: 'method' extends keyof O ? O['method'] : E['method'];
238
+ paginationField: 'paginationField' extends keyof O
239
+ ? O['paginationField']
240
+ : Extract<E['paginationField'], string>;
212
241
  }
213
242
  >
214
243
  : 'searchParams' extends keyof O
@@ -228,6 +257,9 @@ type OptionsToRestEndpoint<
228
257
  body: E['body'];
229
258
  searchParams: O['searchParams'];
230
259
  method: 'method' extends keyof O ? O['method'] : E['method'];
260
+ paginationField: 'paginationField' extends keyof O
261
+ ? O['paginationField']
262
+ : Extract<E['paginationField'], string>;
231
263
  }
232
264
  >
233
265
  : RestInstance<
@@ -243,15 +275,21 @@ type OptionsToRestEndpoint<
243
275
  ? O['searchParams']
244
276
  : E['searchParams'];
245
277
  method: 'method' extends keyof O ? O['method'] : E['method'];
278
+ paginationField: 'paginationField' extends keyof O
279
+ ? O['paginationField']
280
+ : E['paginationField'];
246
281
  }
247
282
  >;
248
283
 
249
284
  export type RestExtendedEndpoint<
250
285
  O extends PartialRestGenerics,
251
- E extends RestInstanceBase,
286
+ E extends RestInstanceBase & { getPage?: unknown },
252
287
  > = OptionsToRestEndpoint<
253
288
  O,
254
- E,
289
+ E &
290
+ (E extends { getPage: { paginationField: string } }
291
+ ? { paginationField: E['getPage']['paginationField'] }
292
+ : unknown),
255
293
  RestInstance<
256
294
  (
257
295
  ...args: Parameters<E>
@@ -266,13 +304,20 @@ export type RestExtendedEndpoint<
266
304
  Omit<E, KeyofRestEndpoint | keyof O>;
267
305
 
268
306
  export interface PartialRestGenerics {
307
+ /** @see https://resthooks.io/rest/api/RestEndpoint#path */
269
308
  readonly path?: string;
309
+ /** @see https://resthooks.io/rest/api/RestEndpoint#schema */
270
310
  readonly schema?: Schema | undefined;
311
+ /** @see https://resthooks.io/rest/api/RestEndpoint#method */
271
312
  readonly method?: string;
272
313
  /** Only used for types */
314
+ /** @see https://dataclient.io/rest/api/RestEndpoint#body */
273
315
  body?: any;
274
316
  /** Only used for types */
317
+ /** @see https://dataclient.io/rest/api/RestEndpoint#searchParams */
275
318
  searchParams?: any;
319
+ /** @see https://dataclient.io/rest/api/RestEndpoint#paginationfield */
320
+ readonly paginationField?: string;
276
321
  /** @see https://resthooks.io/rest/api/RestEndpoint#process */
277
322
  process?(value: any, ...args: any): any;
278
323
  }
@@ -283,7 +328,7 @@ export interface RestGenerics extends PartialRestGenerics {
283
328
  export type PaginationEndpoint<
284
329
  E extends FetchFunction & RestGenerics & { sideEffect?: true | undefined },
285
330
  A extends any[],
286
- > = RestInstance<
331
+ > = RestInstanceBase<
287
332
  ParamFetchNoBody<A[0], ResolveType<E>>,
288
333
  E['schema'],
289
334
  E['sideEffect'],
@@ -294,7 +339,7 @@ export type PaginationEndpoint<
294
339
  export type PaginationFieldEndpoint<
295
340
  E extends FetchFunction & RestGenerics & { sideEffect?: true | undefined },
296
341
  C extends string,
297
- > = RestInstance<
342
+ > = RestInstanceBase<
298
343
  ParamFetchNoBody<
299
344
  { [K in C]: string | number | boolean } & E['searchParams'] &
300
345
  PathArgs<Exclude<E['path'], undefined>>,
@@ -305,7 +350,7 @@ export type PaginationFieldEndpoint<
305
350
  Pick<E, 'path' | 'searchParams' | 'body'> & {
306
351
  searchParams: { [K in C]: string | number | boolean } & E['searchParams'];
307
352
  }
308
- >;
353
+ > & { paginationField: C };
309
354
  export type AddEndpoint<
310
355
  F extends FetchFunction = FetchFunction,
311
356
  S extends Schema | undefined = any,
@@ -443,7 +488,8 @@ export type RestType<
443
488
  path: string;
444
489
  body?: any;
445
490
  searchParams?: any;
446
- } = { path: string },
491
+ paginationField?: string;
492
+ } = { path: string; paginationField: string },
447
493
  // eslint-disable-next-line @typescript-eslint/ban-types
448
494
  > = IfTypeScriptLooseNull<
449
495
  RestInstance<
@@ -576,6 +622,7 @@ export type GetEndpoint<
576
622
  readonly schema: Schema;
577
623
  /** Only used for types */
578
624
  readonly searchParams?: any;
625
+ readonly paginationField?: string;
579
626
  } = {
580
627
  path: string;
581
628
  schema: Schema;
@@ -231,6 +231,10 @@ Response (first 300 characters): ${text.substring(0, 300)}`;
231
231
  return this.extend({ update: paginationUpdate(this, removeCursor) });
232
232
  }
233
233
 
234
+ get getPage() {
235
+ return this.paginated(this.paginationField);
236
+ }
237
+
234
238
  get push() {
235
239
  return this.extend({
236
240
  method: 'POST',
@@ -1,4 +1,4 @@
1
- import { Entity } from '@data-client/endpoint';
1
+ import { Entity, schema } from '@data-client/endpoint';
2
2
  import { useController } from '@data-client/react';
3
3
  import { useSuspense } from '@data-client/react';
4
4
  import { CacheProvider } from '@data-client/react';
@@ -86,6 +86,62 @@ const getNextPage2 = getArticleList2.paginated(
86
86
  }) => [rest],
87
87
  );
88
88
 
89
+ const getArticleList3 = new RestEndpoint({
90
+ urlPrefix: 'http://test.com',
91
+ path: '/article-paginated',
92
+ schema: {
93
+ nextPage: '',
94
+ data: { results: new schema.Collection([PaginatedArticle]) },
95
+ },
96
+ method: 'GET',
97
+ searchParams: {} as { group: string | number },
98
+ paginationField: 'cursor',
99
+ }).extend({
100
+ dataExpiryLength: 10000,
101
+ });
102
+ const getNextPage3 = getArticleList3.getPage;
103
+
104
+ // type tests
105
+ () => {
106
+ const base = new RestEndpoint({
107
+ urlPrefix: 'http://test.com',
108
+ path: '/article-paginated',
109
+ schema: {
110
+ nextPage: '',
111
+ data: { results: new schema.Collection([PaginatedArticle]) },
112
+ },
113
+ method: 'GET',
114
+ });
115
+ // @ts-expect-error
116
+ () => base.getPage();
117
+ () =>
118
+ // @ts-expect-error
119
+ base
120
+ .extend({
121
+ path: '',
122
+ dataExpiryLength: 10000,
123
+ })
124
+ .getPage();
125
+ const a = new RestEndpoint({
126
+ urlPrefix: 'http://test.com',
127
+ path: '/article-paginated',
128
+ schema: {
129
+ nextPage: '',
130
+ data: { results: new schema.Collection([PaginatedArticle]) },
131
+ },
132
+ method: 'GET',
133
+ searchParams: {} as { group: string | number },
134
+ }).extend({
135
+ path: ':blob',
136
+ searchParams: {} as { isAdmin?: boolean },
137
+ method: 'GET',
138
+ paginationField: 'cursor',
139
+ });
140
+ a.getPage({ cursor: 'hi', blob: 'ho' });
141
+ // @ts-expect-error
142
+ a.getPage({ blob: 'ho' });
143
+ };
144
+
89
145
  describe('RestEndpoint', () => {
90
146
  const renderRestHook: ReturnType<typeof makeRenderRestHook> =
91
147
  makeRenderRestHook(CacheProvider);
@@ -277,26 +333,56 @@ describe('RestEndpoint', () => {
277
333
  .get(`/article-paginated/happy?cursor=2`)
278
334
  .reply(200, paginatedSecondPage);
279
335
 
280
- const { result, waitForNextUpdate } = renderRestHook(() => {
281
- const { fetch } = useController();
336
+ const { result, waitForNextUpdate, controller } = renderRestHook(() => {
282
337
  const {
283
338
  data: { results: articles },
284
339
  nextPage,
285
340
  } = useSuspense(getArticleList2, {
286
341
  group: 'happy',
287
342
  });
288
- return { articles, nextPage, fetch };
343
+ return { articles, nextPage };
344
+ });
345
+ await waitForNextUpdate();
346
+ // @ts-expect-error
347
+ () => controller.fetch(getNextPage2);
348
+ // @ts-expect-error
349
+ () => controller.fetch(getNextPage2, { fake: 5 });
350
+ // @ts-expect-error
351
+ () => controller.fetch(getNextPage2, { group: 'happy' });
352
+ // @ts-expect-error
353
+ () => controller.fetch(getNextPage2, { cursor: 2 });
354
+ await controller.fetch(getNextPage2, {
355
+ group: 'happy',
356
+ cursor: 2,
357
+ });
358
+ expect(result.current.articles.map(({ id }) => id)).toEqual([5, 3, 7, 8]);
359
+ });
360
+
361
+ it('getPage: should update on get for a paginated resource with parameter in path', async () => {
362
+ mynock.get(`/article-paginated?group=happy`).reply(200, paginatedFirstPage);
363
+ mynock
364
+ .get(`/article-paginated?cursor=2&group=happy`)
365
+ .reply(200, paginatedSecondPage);
366
+
367
+ const { result, waitForNextUpdate, controller } = renderRestHook(() => {
368
+ const {
369
+ data: { results: articles },
370
+ nextPage,
371
+ } = useSuspense(getArticleList3, {
372
+ group: 'happy',
373
+ });
374
+ return { articles, nextPage };
289
375
  });
290
376
  await waitForNextUpdate();
291
377
  // @ts-expect-error
292
- () => result.current.fetch(getNextPage2);
378
+ () => controller.fetch(getNextPage3);
293
379
  // @ts-expect-error
294
- () => result.current.fetch(getNextPage2, { fake: 5 });
380
+ () => controller.fetch(getNextPage3, { fake: 5 });
295
381
  // @ts-expect-error
296
- () => result.current.fetch(getNextPage2, { group: 'happy' });
382
+ () => controller.fetch(getNextPage3, { group: 'happy' });
297
383
  // @ts-expect-error
298
- () => result.current.fetch(getNextPage2, { cursor: 2 });
299
- await result.current.fetch(getNextPage2, {
384
+ () => controller.fetch(getNextPage3, { cursor: 2 });
385
+ await controller.fetch(getNextPage3, {
300
386
  group: 'happy',
301
387
  cursor: 2,
302
388
  });
@@ -84,9 +84,12 @@ describe('createResource()', () => {
84
84
  path: 'http\\://test.com/groups/:group/users/:id',
85
85
  schema: User,
86
86
  });
87
+ // @ts-expect-error
88
+ UserResourceBase.getList.getPage.paginationField;
87
89
  const UserResource = UserResourceBase.extend('getList', {
88
90
  path: ':blob',
89
91
  searchParams: {} as { isAdmin?: boolean },
92
+ paginationField: 'cursor',
90
93
  getOptimisticResponse(snap, params) {
91
94
  params.isAdmin;
92
95
  params.blob;
@@ -94,9 +97,10 @@ describe('createResource()', () => {
94
97
  params.nothere;
95
98
  return [] as User[];
96
99
  },
97
- /*process(users: User[]) {
98
- return users.slice(0, 7);
99
- }, TODO: why doesn't this work?*/
100
+ process(users: User[]) {
101
+ if (!Array.isArray(users)) return users;
102
+ return users.slice(0, 7);
103
+ },
100
104
  })
101
105
  .extend('partialUpdate', {
102
106
  getOptimisticResponse(snap, params, body) {
@@ -128,19 +132,18 @@ describe('createResource()', () => {
128
132
 
129
133
  () => UserResource.getList({ blob: '5', isAdmin: true });
130
134
  () =>
131
- UserResource.getList.paginated('cursor')({
135
+ UserResource.getList.getPage({
132
136
  blob: '5',
133
137
  isAdmin: true,
134
138
  cursor: 'next',
135
139
  });
136
- () =>
137
- UserResource.getList.paginated('cursor')({ blob: '5', cursor: 'next' });
140
+ () => UserResource.getList.getPage({ blob: '5', cursor: 'next' });
138
141
  () =>
139
142
  // @ts-expect-error
140
- UserResource.getList.paginated('cursor')({ blob: '5', isAdmin: true });
143
+ UserResource.getList.getPage({ blob: '5', isAdmin: true });
141
144
  () =>
142
145
  // @ts-expect-error
143
- UserResource.getList.paginated('cursor')({
146
+ UserResource.getList.getPage({
144
147
  cursor: 'next',
145
148
  isAdmin: true,
146
149
  });
@@ -208,6 +211,7 @@ describe('createResource()', () => {
208
211
  const UserResourceBase = createResource({
209
212
  path: 'http\\://test.com/groups/:group/users/:id',
210
213
  schema: User,
214
+ paginationField: 'cursor',
211
215
  });
212
216
  const UserResource = UserResourceBase.extend({
213
217
  getList: {
@@ -263,17 +267,16 @@ describe('createResource()', () => {
263
267
 
264
268
  () => UserResource.getList({ blob: '5', isAdmin: true });
265
269
  () =>
266
- UserResource.getList.paginated('cursor')({
270
+ UserResource.getList.getPage({
267
271
  blob: '5',
268
272
  isAdmin: true,
269
273
  cursor: 'next',
270
274
  });
271
- () =>
272
- UserResource.getList.paginated('cursor')({ blob: '5', cursor: 'next' });
275
+ () => UserResource.getList.getPage({ blob: '5', cursor: 'next' });
273
276
  // @ts-expect-error
274
- () => UserResource.getNextPage({ blob: '5', isAdmin: true });
277
+ () => UserResource.getList.getPage({ blob: '5', isAdmin: true });
275
278
  // @ts-expect-error
276
- () => UserResource.getNextPage({ cursor: 'next', isAdmin: true });
279
+ () => UserResource.getList.getPage({ cursor: 'next', isAdmin: true });
277
280
  () => UserResource.get({ group: '1', id: '5' });
278
281
  () => UserResource.getList.push({ blob: '5' }, { username: 'bob' });
279
282
  () =>
@@ -357,69 +360,71 @@ describe('createResource()', () => {
357
360
  path: 'http\\://test.com/groups/:group/users/:id',
358
361
  schema: User,
359
362
  paginationField: 'cursor',
360
- })
361
- .extend(resourceBase => ({
362
- getList: resourceBase.getList.extend({
363
- path: ':blob',
364
- searchParams: {} as { isAdmin?: boolean },
365
- getOptimisticResponse(snap, params) {
366
- params.isAdmin;
367
- params.blob;
368
- // @ts-expect-error
369
- params.nothere;
370
- return [] as User[];
371
- },
372
- /*process(users: User[]) {
373
- return users.slice(0, 7);
374
- }, TODO: why doesn't this work?*/
375
- }),
376
- partialUpdate: resourceBase.partialUpdate.extend({
377
- getOptimisticResponse(snap, params, body) {
378
- params.id;
379
- params.group;
380
- // @ts-expect-error
381
- params.nothere;
382
- return {
383
- id: params.id,
384
- ...body,
385
- };
386
- },
387
- }),
388
- delete: resourceBase.delete.extend({
389
- getOptimisticResponse(snap, params) {
390
- return params;
391
- },
392
- }),
393
- justget: resourceBase.get,
394
- current: resourceBase.get.extend({
395
- path: '/current',
396
- searchParams: {} as { isAdmin?: boolean },
397
- }),
398
- toggleAdmin: resourceBase.get.extend({
399
- path: '/toggle/:id',
400
- method: 'POST',
401
- body: undefined,
402
- getOptimisticResponse(snap, params) {
403
- params.id;
404
- // @ts-expect-error
405
- params.group;
406
- return {
407
- id: params.id,
408
- };
409
- },
410
- }),
411
- }))
412
- .extend(resourceBase => ({
413
- getNextPage: resourceBase.getList.paginated('cursor'),
414
- }));
363
+ }).extend(resourceBase => ({
364
+ getList: resourceBase.getList.extend({
365
+ path: ':blob',
366
+ searchParams: {} as { isAdmin?: boolean },
367
+ getOptimisticResponse(snap, params) {
368
+ params.isAdmin;
369
+ params.blob;
370
+ // @ts-expect-error
371
+ params.nothere;
372
+ return [] as User[];
373
+ },
374
+ process(users: User[]) {
375
+ if (!Array.isArray(users)) return users;
376
+ return users.slice(0, 7);
377
+ },
378
+ }),
379
+ partialUpdate: resourceBase.partialUpdate.extend({
380
+ getOptimisticResponse(snap, params, body) {
381
+ params.id;
382
+ params.group;
383
+ // @ts-expect-error
384
+ params.nothere;
385
+ return {
386
+ id: params.id,
387
+ ...body,
388
+ };
389
+ },
390
+ }),
391
+ delete: resourceBase.delete.extend({
392
+ getOptimisticResponse(snap, params) {
393
+ return params;
394
+ },
395
+ }),
396
+ justget: resourceBase.get,
397
+ current: resourceBase.get.extend({
398
+ path: '/current',
399
+ searchParams: {} as { isAdmin?: boolean },
400
+ }),
401
+ toggleAdmin: resourceBase.get.extend({
402
+ path: '/toggle/:id',
403
+ method: 'POST',
404
+ body: undefined,
405
+ getOptimisticResponse(snap, params) {
406
+ params.id;
407
+ // @ts-expect-error
408
+ params.group;
409
+ return {
410
+ id: params.id,
411
+ };
412
+ },
413
+ }),
414
+ }));
415
+
415
416
  () => UserResource.getList({ blob: '5', isAdmin: true });
416
417
  () =>
417
- UserResource.getNextPage({ blob: '5', isAdmin: true, cursor: 'next' });
418
- () => UserResource.getNextPage({ blob: '5', cursor: 'next' });
418
+ UserResource.getList.getPage({
419
+ blob: '5',
420
+ isAdmin: true,
421
+ cursor: 'next',
422
+ });
423
+ () => UserResource.getList.getPage({ blob: '5', cursor: 'next' });
419
424
  // @ts-expect-error
420
- () => UserResource.getNextPage({ blob: '5', isAdmin: true });
425
+ () => UserResource.getList.getPage({ blob: '5', isAdmin: true });
421
426
  // @ts-expect-error
422
- () => UserResource.getNextPage({ cursor: 'next', isAdmin: true });
427
+ () => UserResource.getList.getPage({ cursor: 'next', isAdmin: true });
423
428
  () => UserResource.get({ group: '1', id: '5' });
424
429
  () => UserResource.getList.unshift({ blob: '5' }, { username: 'bob' });
425
430
  () =>
@@ -30,6 +30,7 @@ export default function createResource<O extends ResourceGenerics>({
30
30
  schema,
31
31
  Endpoint = RestEndpoint,
32
32
  optimistic,
33
+ paginationField,
33
34
  ...extraOptions
34
35
  }: Readonly<O> & ResourceOptions): Resource<O> {
35
36
  const shortenedPath = shortenPath(path);
@@ -50,10 +51,13 @@ export default function createResource<O extends ResourceGenerics>({
50
51
  }) as any;
51
52
  if (optimistic) {
52
53
  (extraMutateOptions as any).getOptimisticResponse = optimisticUpdate;
53
- (extraPartialOptions as any).getOptimisticResponse = optimisticPartial(get);
54
+ (extraPartialOptions as any).getOptimisticResponse = optimisticPartial(
55
+ get as any,
56
+ );
54
57
  }
55
58
  const getList = new Endpoint({
56
59
  ...extraMutateOptions,
60
+ paginationField: paginationField as string,
57
61
  path: shortenedPath,
58
62
  schema: new Collection([schema as any]),
59
63
  name: getName('getList'),
@@ -133,7 +137,7 @@ export default function createResource<O extends ResourceGenerics>({
133
137
  function optimisticUpdate(snap: SnapshotInterface, params: any, body: any) {
134
138
  return {
135
139
  ...params,
136
- ...body,
140
+ ...ensureBodyPojo(body),
137
141
  };
138
142
  }
139
143
  function optimisticPartial(
@@ -146,10 +150,15 @@ function optimisticPartial(
146
150
  ...params,
147
151
  ...data,
148
152
  // even tho we don't always have two arguments, the extra one will simply be undefined which spreads fine
149
- ...body,
153
+ ...ensureBodyPojo(body),
150
154
  };
151
155
  };
152
156
  }
153
157
  function optimisticDelete(snap: SnapshotInterface, params: any) {
154
158
  return params;
155
159
  }
160
+ function ensureBodyPojo(body: any) {
161
+ return body instanceof FormData
162
+ ? Object.fromEntries((body as any).entries())
163
+ : body;
164
+ }
@@ -9,9 +9,9 @@ export function paginatedMerge(existing: any[], incoming: any[]) {
9
9
  export const paginatedFilter =
10
10
  <C extends (...args: readonly any[]) => readonly any[]>(removeCursor: C) =>
11
11
  (...args: Parameters<C>) => {
12
- const noCursorArgs = removeCursor(...args);
12
+ const noCursorArgs = removeCursor(...args)[0] ?? {};
13
13
  return (collectionKey: Record<string, string>) =>
14
- shallowEqual(collectionKey, noCursorArgs[0] ?? {});
14
+ shallowEqual(collectionKey, noCursorArgs);
15
15
  };
16
16
 
17
17
  function shallowEqual(
@@ -20,13 +20,17 @@ function shallowEqual(
20
20
  ) {
21
21
  const keys1 = Object.keys(object1);
22
22
  const keys2 = Object.keys(object2);
23
- if (keys1.length !== keys2.length) {
24
- return false;
25
- }
26
- for (const key of keys1) {
27
- if (object1[key] != object2[key]) {
23
+ let length2 = 0;
24
+ for (const key of keys2) {
25
+ // we should ignore any members with value undefined
26
+ if (object2[key] === undefined) continue;
27
+ if (object1[key] !== `${object2[key]}`) {
28
28
  return false;
29
29
  }
30
+ length2++;
31
+ }
32
+ if (keys1.length !== length2) {
33
+ return false;
30
34
  }
31
35
  return true;
32
36
  }