@astral/mobx-query 1.18.3 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AuxiliaryQuery/AuxiliaryQuery.d.ts +2 -7
- package/AuxiliaryQuery/AuxiliaryQuery.js +13 -29
- package/InfiniteQuery/InfiniteQuery.d.ts +74 -7
- package/InfiniteQuery/InfiniteQuery.js +93 -30
- package/MobxQuery/MobxQuery.js +8 -4
- package/README.md +248 -1
- package/Sets/InfiniteQuerySet/InfiniteQuerySet.d.ts +1 -1
- package/StatusStorage/StatusStorage.d.ts +4 -0
- package/StatusStorage/StatusStorage.js +18 -0
- package/node/AuxiliaryQuery/AuxiliaryQuery.d.ts +2 -7
- package/node/AuxiliaryQuery/AuxiliaryQuery.js +13 -29
- package/node/InfiniteQuery/InfiniteQuery.d.ts +74 -7
- package/node/InfiniteQuery/InfiniteQuery.js +93 -30
- package/node/MobxQuery/MobxQuery.js +8 -4
- package/node/Sets/InfiniteQuerySet/InfiniteQuerySet.d.ts +1 -1
- package/node/StatusStorage/StatusStorage.d.ts +4 -0
- package/node/StatusStorage/StatusStorage.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- ⚡️️️️ Фоновая подгрузка данных для работы с WebSocket
|
|
11
11
|
- ⚡️️️️ Возможность тестирования
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
## Table of contents
|
|
14
15
|
|
|
15
16
|
- [Installation](#installation)
|
|
@@ -25,7 +26,13 @@
|
|
|
25
26
|
- [Создание InfiniteQuerySet](#создание-infinitequerygroups)
|
|
26
27
|
- [Использование InfiniteQuerySet](#использование-infinitequerygroups)
|
|
27
28
|
- [Загрузка данных](#загрузка-данных)
|
|
29
|
+
- [Статус дозагрузки fetchMore](#статус-дозагрузки-fetchmore)
|
|
30
|
+
- [Старт не с начала списка](#старт-не-с-начала-списка)
|
|
31
|
+
- [Запоминание последней успешной позиции](#запоминание-последней-успешной-позиции)
|
|
32
|
+
- [Обратная дозагрузка данных](#обратная-дозагрузка-данных)
|
|
33
|
+
- [Курсорная пагинация](#курсорная-пагинация)
|
|
28
34
|
- [isEndReached. Определение конца списка](#isendreached-определение-конца-списка)
|
|
35
|
+
- [isStartReached. Определение начала списка](#isstartreached-определение-начала-списка)
|
|
29
36
|
- [Изменение количества запрашиваемых записей](#изменение-количества-запрашиваемых-записей)
|
|
30
37
|
- [MutationSet](#mutationset)
|
|
31
38
|
- [Создание mutations](#создание-и-использование-mutations)
|
|
@@ -340,6 +347,189 @@ await when(() => !query.isLoading);
|
|
|
340
347
|
docListQuery.data.length; // 60 записей
|
|
341
348
|
```
|
|
342
349
|
|
|
350
|
+
#### Статус дозагрузки `fetchMore`
|
|
351
|
+
|
|
352
|
+
`fetchMoreStatus` содержит отдельные статусы дозагрузки следующей страницы. Это позволяет отдельно отслеживать состояние `fetchMore`, не смешивая его с общим статусом `InfiniteQuery`.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { when } from 'mobx';
|
|
356
|
+
|
|
357
|
+
const createDocListInfiniteQuery = (filters: Omit<DocsDTO.DocListFilters, 'offset' | 'count'>) =>
|
|
358
|
+
cacheService.createInfiniteQuery(['docList', filters],
|
|
359
|
+
(params: { offset: number; count: number }) => docsEndpoints.getDocList({ offset: params.offset, count: params.count, ...filters })
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const docListQuery = createDocListInfiniteQuery({ search: 'test' });
|
|
363
|
+
|
|
364
|
+
await docListQuery.async();
|
|
365
|
+
|
|
366
|
+
docListQuery.fetchMore();
|
|
367
|
+
|
|
368
|
+
docListQuery.fetchMoreStatus.isLoading; // true
|
|
369
|
+
docListQuery.fetchMoreStatus.isSuccess; // false
|
|
370
|
+
docListQuery.fetchMoreStatus.isError; // false
|
|
371
|
+
|
|
372
|
+
await when(() => !docListQuery.fetchMoreStatus.isLoading);
|
|
373
|
+
|
|
374
|
+
docListQuery.fetchMoreStatus.isLoading; // false
|
|
375
|
+
docListQuery.fetchMoreStatus.isSuccess; // true
|
|
376
|
+
docListQuery.fetchMoreStatus.error; // undefined
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Если `fetchMore` завершится ошибкой, `fetchMoreStatus.isError` станет `true`, а `fetchMoreStatus.error` будет содержать ошибку. Значение `offset` будет возвращено к предыдущему успешному состоянию, поэтому повторный вызов `fetchMore` повторит запрос той же страницы.
|
|
380
|
+
|
|
381
|
+
### Старт не с начала списка
|
|
382
|
+
|
|
383
|
+
Для сценариев восстановления позиции списка можно передать `initialOffset` или `initialCursor`.
|
|
384
|
+
Эти значения используются только для самого первого запроса нового инстанса `InfiniteQuery`.
|
|
385
|
+
После `invalidate()` следующий запрос снова начнется с начала списка.
|
|
386
|
+
|
|
387
|
+
Это полезно, когда нужно восстановить список с определенной позиции, например после возврата на страницу в SPA или при открытии ссылки, в которой уже закодирована нужная точка входа в список.
|
|
388
|
+
|
|
389
|
+
#### Offset-пагинация
|
|
390
|
+
|
|
391
|
+
Если API использует смещение, передайте `initialOffset`.
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
395
|
+
{
|
|
396
|
+
initialOffset: 120,
|
|
397
|
+
},
|
|
398
|
+
{ search: 'test' },
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
await docListQuery.async(); // первый запрос будет выполнен с offset=120
|
|
402
|
+
|
|
403
|
+
docListQuery.invalidate();
|
|
404
|
+
await docListQuery.async(); // после invalidate запрос снова начнется с offset=0
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### Курсорная пагинация
|
|
408
|
+
|
|
409
|
+
Если API использует курсор, передайте `initialCursor`.
|
|
410
|
+
|
|
411
|
+
```ts
|
|
412
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
413
|
+
{
|
|
414
|
+
initialCursor: { id: 'doc-120' },
|
|
415
|
+
},
|
|
416
|
+
{ search: 'test' },
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
await docListQuery.async(); // первый запрос будет выполнен с cursor={ id: 'doc-120' }
|
|
420
|
+
|
|
421
|
+
docListQuery.invalidate();
|
|
422
|
+
await docListQuery.async(); // после invalidate запрос снова начнется без курсора
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Запоминание последней успешной позиции
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
await docListQuery.async(); // запрос на получение первых 30 записей
|
|
429
|
+
|
|
430
|
+
docListQuery.fetchMore({
|
|
431
|
+
onSuccess: ({ offset, cursor }) => {
|
|
432
|
+
savedOffset = offset; // запоминаем offset последней успешной дозагрузки
|
|
433
|
+
savedCursor = cursor; // запоминаем cursor последней успешной дозагрузки
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
438
|
+
{ search: 'test' },
|
|
439
|
+
{
|
|
440
|
+
initialOffset: savedOffset,
|
|
441
|
+
initialCursor: savedCursor,
|
|
442
|
+
},
|
|
443
|
+
); // новый инстанс продолжит загрузку с сохраненной позиции
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
448
|
+
|
|
449
|
+
await docListQuery.async();
|
|
450
|
+
|
|
451
|
+
docListQuery.fetchMore({
|
|
452
|
+
onSuccess: ({ offset, cursor }) => {
|
|
453
|
+
searchParams.set('offset', String(offset));
|
|
454
|
+
searchParams.set('cursorId', cursor.id);
|
|
455
|
+
|
|
456
|
+
window.history.replaceState(
|
|
457
|
+
null,
|
|
458
|
+
'',
|
|
459
|
+
`?${searchParams.toString()}`,
|
|
460
|
+
);
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const restoredQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
465
|
+
{
|
|
466
|
+
initialOffset: Number(searchParams.get('offset') ?? 0),
|
|
467
|
+
initialCursor: searchParams.get('cursorId')
|
|
468
|
+
? { id: searchParams.get('cursorId')! }
|
|
469
|
+
: undefined,
|
|
470
|
+
},
|
|
471
|
+
{ search: 'test' },
|
|
472
|
+
);
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Обратная дозагрузка данных
|
|
476
|
+
|
|
477
|
+
По умолчанию `fetchMore()` загружает следующую страницу и добавляет ее в конец списка.
|
|
478
|
+
Вызов `fetchMore({ direction: 'backward' })` загружает предыдущую страницу и добавляет ее в начало списка.
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
482
|
+
{ initialOffset: 60 },
|
|
483
|
+
{ search: 'test' },
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
await docListQuery.async(); // будут загружены записи начиная с offset=60
|
|
487
|
+
|
|
488
|
+
docListQuery.fetchMore({ direction: 'backward' }); // будет запрошена предыдущая страница
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
При `fetchMore({ direction: 'backward' })` библиотека уменьшает `offset`, передает в `execute` первый элемент текущего списка как `cursor` и указывает `direction: 'backward'`.
|
|
492
|
+
|
|
493
|
+
### Курсорная пагинация
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
type Doc = {
|
|
497
|
+
id: string;
|
|
498
|
+
title: string;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const docsFetcher = {
|
|
502
|
+
infiniteQueries: {
|
|
503
|
+
docList: mobxQuery.createInfiniteQuerySet(
|
|
504
|
+
(filters: Omit<DocsDTO.DocListFilters, 'count'>) => ({
|
|
505
|
+
execute: ({ count, cursor, direction }) =>
|
|
506
|
+
docsEndpoint
|
|
507
|
+
.getDocList({
|
|
508
|
+
count,
|
|
509
|
+
cursorId: cursor?.id,
|
|
510
|
+
direction,
|
|
511
|
+
...filters,
|
|
512
|
+
})
|
|
513
|
+
.then(({ data }) => data.list as Doc[]),
|
|
514
|
+
}),
|
|
515
|
+
),
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const docListQuery = docsFetcher.infiniteQueries.docList.createWithConfig(
|
|
520
|
+
{ initialCursor: { id: 'doc-120' } },
|
|
521
|
+
{ search: 'test' },
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
await docListQuery.async(); // при первом запросе execute получит initialCursor, если он задан
|
|
525
|
+
|
|
526
|
+
docListQuery.fetchMore(); // execute получит последний элемент списка и direction: 'forward'
|
|
527
|
+
|
|
528
|
+
docListQuery.fetchMore({ direction: 'backward' }); // execute получит первый элемент списка и direction: 'backward'
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Во всех остальных запросах `cursor` будет `undefined`, включая первый запрос без `initialCursor` и повторный запрос после `invalidate()`.
|
|
532
|
+
|
|
343
533
|
### `isEndReached`. Определение конца списка
|
|
344
534
|
|
|
345
535
|
Флаг `isEndReached` будет установлен в `true`, если записи для загрузки закончились:
|
|
@@ -364,6 +554,11 @@ docListQuery.isEndReached; // true
|
|
|
364
554
|
|
|
365
555
|
Флаг `isEndReached` устанавливается в `true`, когда количество полученных элементов меньше запрошенного количества, что означает отсутствие дополнительных данных на сервере.
|
|
366
556
|
|
|
557
|
+
### `isStartReached`. Определение начала списка
|
|
558
|
+
|
|
559
|
+
Флаг `isStartReached` будет установлен в `true`, если при `fetchMore({ direction: 'backward' })` сервер вернул меньше элементов, чем было запрошено.
|
|
560
|
+
Это означает, что более ранних данных больше нет, и повторные вызовы `fetchMore({ direction: 'backward' })` не будут запускать новые запросы.
|
|
561
|
+
|
|
367
562
|
### Изменение количества запрашиваемых записей
|
|
368
563
|
|
|
369
564
|
Для изменения конфигурации InfiniteQuery необходимо использовать метод `createWithConfig`:
|
|
@@ -520,6 +715,9 @@ const docQueryWithCache = docsFetcher.queries.doc.createWithConfig(({ fetchPolic
|
|
|
520
715
|
await docQueryWithCache.async(); // запрос не будет выполнен потому что прежде данные были получены с политикой `network-only` и записаны в кэш
|
|
521
716
|
```
|
|
522
717
|
|
|
718
|
+
Для `InfiniteQuery` параметры `initialOffset` и `initialCursor` позволяют при первом запросе нового инстанса начать загрузку с нужной позиции.
|
|
719
|
+
После `invalidate()` эти стартовые значения больше не применяются, и следующий запрос начинается с начала списка.
|
|
720
|
+
|
|
523
721
|
### Как работает кэш
|
|
524
722
|
|
|
525
723
|
Все данные, возвращаемые `QuerySet` и `InfiniteQuerySet`, кэшируются в едином хранилище @astral/mobx-query.
|
|
@@ -716,6 +914,27 @@ docsFetcher.queries.document.createWithConfig({
|
|
|
716
914
|
})
|
|
717
915
|
```
|
|
718
916
|
|
|
917
|
+
## Последовательное получение данных
|
|
918
|
+
|
|
919
|
+
Для последовательного получения данных используйте флаг `enabledAutoFetch`.
|
|
920
|
+
|
|
921
|
+
```ts
|
|
922
|
+
private get fileQuery() {
|
|
923
|
+
const fileId = this._documentStore.data?.FileId;
|
|
924
|
+
|
|
925
|
+
return this._docsFetcher.queries.file.createWithConfig(
|
|
926
|
+
{
|
|
927
|
+
enabledAutoFetch: Boolean(fileId),
|
|
928
|
+
},
|
|
929
|
+
fileId,
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
public get file() {
|
|
934
|
+
return this.fileQuery.data; // пока enabledAutoFetch==false, запроса данных не будет
|
|
935
|
+
}
|
|
936
|
+
```
|
|
937
|
+
|
|
719
938
|
## Тестирование Fetcher на сонове QuerySet и InfiniteQuerySet.
|
|
720
939
|
|
|
721
940
|
Для мокинга QuerySet и InfiniteQuerySet необходимо использовать mock следующего вида:
|
|
@@ -1037,6 +1256,10 @@ const createDocListInfiniteQuery = (filters: Omit<DocsDTO.DocListFilters, 'offse
|
|
|
1037
1256
|
const docListQuery = createDocListInfiniteQuery({ search: 'test' });
|
|
1038
1257
|
```
|
|
1039
1258
|
|
|
1259
|
+
Executor `InfiniteQuery` получает объект `{ offset, count, cursor, direction }`.
|
|
1260
|
+
Если нужна только offset-пагинация, можно использовать только `offset` и `count`.
|
|
1261
|
+
`cursor` передается только при `fetchMore()` или при самом первом запросе, если задан `initialCursor`. В обычном первом запросе без `initialCursor` и после `invalidate()` значение `cursor` будет `undefined`.
|
|
1262
|
+
|
|
1040
1263
|
#### Загрузка данных
|
|
1041
1264
|
|
|
1042
1265
|
```ts
|
|
@@ -1061,6 +1284,17 @@ await when(() => !query.isLoading);
|
|
|
1061
1284
|
docListQuery.data.length; // 60 записей
|
|
1062
1285
|
```
|
|
1063
1286
|
|
|
1287
|
+
#### Статус дозагрузки `fetchMore`
|
|
1288
|
+
|
|
1289
|
+
- [Статус дозагрузки fetchMore](#статус-дозагрузки-fetchmore)
|
|
1290
|
+
|
|
1291
|
+
#### Дополнительные возможности
|
|
1292
|
+
|
|
1293
|
+
- [Старт не с начала списка](#старт-не-с-начала-списка)
|
|
1294
|
+
- [Запоминание последней успешной позиции](#запоминание-последней-успешной-позиции)
|
|
1295
|
+
- [Обратная дозагрузка данных](#обратная-дозагрузка-данных)
|
|
1296
|
+
- [Курсорная пагинация](#курсорная-пагинация)
|
|
1297
|
+
|
|
1064
1298
|
#### `isEndReached`. Определение конца списка
|
|
1065
1299
|
|
|
1066
1300
|
Флаг `isEndReached` будет установлен в `true`, если записи для загрузки закончились:
|
|
@@ -1090,6 +1324,10 @@ docListQuery.isEndReached; // true
|
|
|
1090
1324
|
|
|
1091
1325
|
Флаг `isEndReached` устанавливается в `true`, когда количество полученных элементов меньше запрошенного количества, что означает отсутствие дополнительных данных на сервере.
|
|
1092
1326
|
|
|
1327
|
+
#### `isStartReached`. Определение начала списка
|
|
1328
|
+
|
|
1329
|
+
- [isStartReached. Определение начала списка](#isstartreached-определение-начала-списка)
|
|
1330
|
+
|
|
1093
1331
|
#### Изменение количества запрашиваемых записей
|
|
1094
1332
|
|
|
1095
1333
|
Для изменения конфигурации InfiniteQuery необходимо использовать третий параметр `config`:
|
|
@@ -1103,7 +1341,7 @@ const createDocListInfiniteQuery = (filters: Omit<DocsDTO.DocListFilters, 'offse
|
|
|
1103
1341
|
|
|
1104
1342
|
#### Интерфейс InfiniteQueryConfig
|
|
1105
1343
|
```ts
|
|
1106
|
-
export type InfiniteQueryConfig = {
|
|
1344
|
+
export type InfiniteQueryConfig<TResult = unknown> = {
|
|
1107
1345
|
/**
|
|
1108
1346
|
* Количество записей, которое будет загружено при первом и следующих запросах
|
|
1109
1347
|
* @default 30
|
|
@@ -1121,6 +1359,15 @@ export type InfiniteQueryConfig = {
|
|
|
1121
1359
|
* Политика кэширования данных.
|
|
1122
1360
|
*/
|
|
1123
1361
|
fetchPolicy?: FetchPolicy;
|
|
1362
|
+
/**
|
|
1363
|
+
* Стартовое значение offset, от которого требуется сделать самый первый запрос.
|
|
1364
|
+
* @default 0
|
|
1365
|
+
*/
|
|
1366
|
+
initialOffset?: number;
|
|
1367
|
+
/**
|
|
1368
|
+
* Стартовый элемент, от которого требуется сделать самый первый запрос.
|
|
1369
|
+
*/
|
|
1370
|
+
initialCursor?: Partial<TResult>;
|
|
1124
1371
|
/**
|
|
1125
1372
|
* Режим фонового обновления
|
|
1126
1373
|
* @default false
|
|
@@ -7,7 +7,7 @@ export type InfiniteQuerySetConfig = {
|
|
|
7
7
|
};
|
|
8
8
|
export type InfiniteQuerySetConfigurator<TParams extends any[], TResponse> = (...params: TParams) => {
|
|
9
9
|
keys?: CacheKey[];
|
|
10
|
-
execute: (infiniteParams: InfiniteParams) => Promise<TResponse[]>;
|
|
10
|
+
execute: (infiniteParams: InfiniteParams<TResponse>) => Promise<TResponse[]>;
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
* Набор infinite queries под одним ключем с целью повышения читаемости кода и удобства инвалидации по общему ключу
|
|
@@ -24,6 +24,10 @@ export declare class StatusStorage<TError> implements Statuses<TError> {
|
|
|
24
24
|
*/
|
|
25
25
|
isSuccess: boolean;
|
|
26
26
|
setStatues: (data: Statuses<TError>) => void;
|
|
27
|
+
setStartLoading: () => void;
|
|
28
|
+
setError: (error: TError) => void;
|
|
29
|
+
setSuccess: () => void;
|
|
30
|
+
setEndLoading: () => void;
|
|
27
31
|
get statuses(): Statuses<TError>;
|
|
28
32
|
}
|
|
29
33
|
export declare class StatusStorageFactory extends StorageFactory<StatusStorage<any>> {
|
|
@@ -24,6 +24,24 @@ export class StatusStorage {
|
|
|
24
24
|
this.error = data.error;
|
|
25
25
|
this.isSuccess = data.isSuccess;
|
|
26
26
|
};
|
|
27
|
+
this.setStartLoading = () => {
|
|
28
|
+
this.isLoading = true;
|
|
29
|
+
this.isError = false;
|
|
30
|
+
this.isSuccess = false;
|
|
31
|
+
};
|
|
32
|
+
this.setError = (error) => {
|
|
33
|
+
this.isSuccess = false;
|
|
34
|
+
this.isError = true;
|
|
35
|
+
this.error = error;
|
|
36
|
+
};
|
|
37
|
+
this.setSuccess = () => {
|
|
38
|
+
this.error = undefined;
|
|
39
|
+
this.isError = false;
|
|
40
|
+
this.isSuccess = true;
|
|
41
|
+
};
|
|
42
|
+
this.setEndLoading = () => {
|
|
43
|
+
this.isLoading = false;
|
|
44
|
+
};
|
|
27
45
|
makeAutoObservable(this);
|
|
28
46
|
}
|
|
29
47
|
get statuses() {
|
|
@@ -31,22 +31,17 @@ export declare class AuxiliaryQuery<TResult, TError = void> {
|
|
|
31
31
|
* для устранения гонки запросов
|
|
32
32
|
*/
|
|
33
33
|
getUnifiedPromise: (executor: Executor<TResult>, onSuccess?: (data: TResult) => void) => Promise<TResult>;
|
|
34
|
-
private setSuccess;
|
|
35
34
|
private checkBackgroundAndSet;
|
|
36
35
|
/**
|
|
37
36
|
* Обработчик успешного ответа
|
|
38
37
|
*/
|
|
39
38
|
submitSuccess: () => void;
|
|
40
|
-
private
|
|
41
|
-
/**
|
|
42
|
-
* Обработчик ошибки
|
|
43
|
-
*/
|
|
44
|
-
submitError: (error: TError) => void;
|
|
45
|
-
private setLoading;
|
|
39
|
+
private submitError;
|
|
46
40
|
/**
|
|
47
41
|
* Метод, вызываемый в самом начале запроса, чтобы сбросить флаги в соответствующее значение
|
|
48
42
|
*/
|
|
49
43
|
startLoading: () => void;
|
|
44
|
+
private endLoading;
|
|
50
45
|
/**
|
|
51
46
|
* Метод для инвалидации данных
|
|
52
47
|
*/
|
|
@@ -41,19 +41,10 @@ class AuxiliaryQuery {
|
|
|
41
41
|
});
|
|
42
42
|
throw e;
|
|
43
43
|
})
|
|
44
|
-
.finally(
|
|
45
|
-
this.unifiedPromise = undefined;
|
|
46
|
-
(0, mobx_1.runInAction)(() => {
|
|
47
|
-
this.statusStorage.isLoading = false;
|
|
48
|
-
});
|
|
49
|
-
});
|
|
44
|
+
.finally(this.endLoading);
|
|
50
45
|
}
|
|
51
46
|
return this.unifiedPromise;
|
|
52
47
|
};
|
|
53
|
-
this.setSuccess = (storage) => {
|
|
54
|
-
storage.isError = false;
|
|
55
|
-
storage.isSuccess = true;
|
|
56
|
-
};
|
|
57
48
|
this.checkBackgroundAndSet = (setStorage) => {
|
|
58
49
|
if (this.backgroundStatusStorage && this.statusStorage.isSuccess) {
|
|
59
50
|
setStorage(this.backgroundStatusStorage);
|
|
@@ -66,32 +57,27 @@ class AuxiliaryQuery {
|
|
|
66
57
|
* Обработчик успешного ответа
|
|
67
58
|
*/
|
|
68
59
|
this.submitSuccess = () => {
|
|
69
|
-
this.checkBackgroundAndSet(
|
|
60
|
+
this.checkBackgroundAndSet((storage) => storage.setSuccess());
|
|
70
61
|
this.isInvalid = false;
|
|
71
62
|
};
|
|
72
|
-
this.setError = (storage, error) => {
|
|
73
|
-
storage.isSuccess = false;
|
|
74
|
-
storage.isError = true;
|
|
75
|
-
storage.error = error;
|
|
76
|
-
};
|
|
77
|
-
/**
|
|
78
|
-
* Обработчик ошибки
|
|
79
|
-
*/
|
|
80
63
|
this.submitError = (error) => {
|
|
81
|
-
this.checkBackgroundAndSet((storage) =>
|
|
64
|
+
this.checkBackgroundAndSet((storage) => storage.setError(error));
|
|
82
65
|
this.isInvalid = false;
|
|
83
66
|
};
|
|
84
|
-
this.setLoading = (storage) => {
|
|
85
|
-
storage.isLoading = true;
|
|
86
|
-
storage.isError = false;
|
|
87
|
-
storage.isSuccess = false;
|
|
88
|
-
};
|
|
89
67
|
/**
|
|
90
68
|
* Метод, вызываемый в самом начале запроса, чтобы сбросить флаги в соответствующее значение
|
|
91
69
|
*/
|
|
92
70
|
this.startLoading = () => {
|
|
93
71
|
this.isIdle = false;
|
|
94
|
-
this.checkBackgroundAndSet(
|
|
72
|
+
this.checkBackgroundAndSet((storage) => storage.setStartLoading());
|
|
73
|
+
};
|
|
74
|
+
this.endLoading = () => {
|
|
75
|
+
var _a;
|
|
76
|
+
this.unifiedPromise = undefined;
|
|
77
|
+
this.statusStorage.setEndLoading();
|
|
78
|
+
if (this.statusStorage.isSuccess) {
|
|
79
|
+
(_a = this.backgroundStatusStorage) === null || _a === void 0 ? void 0 : _a.setEndLoading();
|
|
80
|
+
}
|
|
95
81
|
};
|
|
96
82
|
/**
|
|
97
83
|
* Метод для инвалидации данных
|
|
@@ -104,12 +90,10 @@ class AuxiliaryQuery {
|
|
|
104
90
|
isIdle: mobx_1.observable,
|
|
105
91
|
isInvalid: mobx_1.observable,
|
|
106
92
|
submitSuccess: mobx_1.action,
|
|
107
|
-
setSuccess: mobx_1.action,
|
|
108
|
-
setError: mobx_1.action,
|
|
109
|
-
setLoading: mobx_1.action,
|
|
110
93
|
submitError: mobx_1.action,
|
|
111
94
|
startLoading: mobx_1.action,
|
|
112
95
|
invalidate: mobx_1.action,
|
|
96
|
+
endLoading: mobx_1.action,
|
|
113
97
|
});
|
|
114
98
|
}
|
|
115
99
|
}
|
|
@@ -1,23 +1,61 @@
|
|
|
1
1
|
import { AuxiliaryQuery } from '../AuxiliaryQuery';
|
|
2
2
|
import { type DataStorage } from '../DataStorage';
|
|
3
3
|
import { QueryContainer } from '../QueryContainer';
|
|
4
|
-
import {
|
|
4
|
+
import { StatusStorage } from '../StatusStorage';
|
|
5
5
|
import { type FetchPolicy, type QueryBaseActions, type Sync, type SyncParams } from '../types';
|
|
6
6
|
export declare const DEFAULT_INFINITE_ITEMS_COUNT = 30;
|
|
7
|
-
export type
|
|
7
|
+
export type FetchMoreDirection = 'forward' | 'backward';
|
|
8
|
+
export type InfiniteFetchMoreOptions<TResult> = {
|
|
9
|
+
/**
|
|
10
|
+
* Направление для дозапроса данных
|
|
11
|
+
* @default 'forward'
|
|
12
|
+
*/
|
|
13
|
+
direction?: FetchMoreDirection;
|
|
14
|
+
/**
|
|
15
|
+
* Колбэк, вызываемый при успешном запросе, подразумевается использование, для запоминания последней успешной позиции
|
|
16
|
+
*/
|
|
17
|
+
onSuccess?: (params: {
|
|
18
|
+
offset: number;
|
|
19
|
+
cursor: TResult;
|
|
20
|
+
}) => void;
|
|
21
|
+
};
|
|
22
|
+
export type InfiniteParams<TResult> = {
|
|
23
|
+
/**
|
|
24
|
+
* Смещение, от которого требуется сделать запрос
|
|
25
|
+
* @default 0
|
|
26
|
+
*/
|
|
8
27
|
offset: number;
|
|
28
|
+
/**
|
|
29
|
+
* Количество запрашиваемых элементов
|
|
30
|
+
* @default 30
|
|
31
|
+
*/
|
|
9
32
|
count: number;
|
|
33
|
+
/**
|
|
34
|
+
* Элемент использующийся в качестве точки отсчета при курсорной пагинации.
|
|
35
|
+
* При движении вперед (direction: 'forward') это последний элемент текущего набора данных
|
|
36
|
+
* При движении назад (direction: 'backward') это первый элемент текущего набора данных.
|
|
37
|
+
* Передается в двух случаях:
|
|
38
|
+
* - при использовании fetchMore.
|
|
39
|
+
* - при самом первом запросе, если указан initialCursor.
|
|
40
|
+
* Во всех остальных запросах, включая первый запрос без initialCursor и повторный запрос после invalidate(), cursor будет undefined.
|
|
41
|
+
*/
|
|
42
|
+
cursor?: Partial<TResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Направление запроса, присутствует только при использовании fetchMore
|
|
45
|
+
*/
|
|
46
|
+
direction?: FetchMoreDirection;
|
|
10
47
|
};
|
|
11
48
|
export type InfiniteDataStorage<TResult> = DataStorage<{
|
|
12
49
|
data: TResult[];
|
|
13
50
|
offset: number;
|
|
14
51
|
isEndReached: boolean;
|
|
52
|
+
isStartReached: boolean;
|
|
15
53
|
}>;
|
|
16
54
|
/**
|
|
17
55
|
* Исполнитель запроса, ожидается,
|
|
18
56
|
* что будет использоваться что-то возвращающее массив данных
|
|
19
57
|
*/
|
|
20
|
-
export type InfiniteExecutor<TResult> = (params: InfiniteParams) => Promise<TResult[]>;
|
|
58
|
+
export type InfiniteExecutor<TResult> = (params: InfiniteParams<TResult>) => Promise<TResult[]>;
|
|
21
59
|
export type InfiniteQueryParams<TResult, TError, TIsBackground extends boolean = false> = {
|
|
22
60
|
/**
|
|
23
61
|
* Количество запрашиваемых элементов
|
|
@@ -54,6 +92,15 @@ export type InfiniteQueryParams<TResult, TError, TIsBackground extends boolean =
|
|
|
54
92
|
* Колбэк, вызываемый при успешном завершении запроса, подразумевается использование, для подтверждения валидности данных, чтобы квери не был удален из памяти
|
|
55
93
|
*/
|
|
56
94
|
submitValidity?: () => void;
|
|
95
|
+
/**
|
|
96
|
+
* Стартовое значение offset, от которого требуется сделать самый первый запрос.
|
|
97
|
+
* @default 0
|
|
98
|
+
*/
|
|
99
|
+
initialOffset?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Стартовый элемент, от которого требуется сделать самый первый запрос
|
|
102
|
+
*/
|
|
103
|
+
initialCursor?: Partial<TResult>;
|
|
57
104
|
};
|
|
58
105
|
/**
|
|
59
106
|
* Квери для работы с инфинити запросами,
|
|
@@ -85,7 +132,20 @@ export declare class InfiniteQuery<TResult, TError = void, TIsBackground extends
|
|
|
85
132
|
* Колбэк, вызываемый при успешном завершении запроса, подразумевается использование, для подтверждения валидности данных, чтобы квери не был удален из памяти
|
|
86
133
|
*/
|
|
87
134
|
private readonly submitValidity?;
|
|
88
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Хранилище статусов для процесса дозапроса данных
|
|
137
|
+
*/
|
|
138
|
+
private fetchMoreStatusStorage;
|
|
139
|
+
/**
|
|
140
|
+
* Стартовый offset для запроса,
|
|
141
|
+
*/
|
|
142
|
+
private initialOffset;
|
|
143
|
+
/**
|
|
144
|
+
* Стартовое значение курсора, от которого должен произойти первый запрос.
|
|
145
|
+
* Предполагается, что этот элемент должен содержать только id сущности.
|
|
146
|
+
*/
|
|
147
|
+
private initialCursor?;
|
|
148
|
+
constructor(executor: InfiniteExecutor<TResult>, { incrementCount, initialOffset, initialCursor, onError, enabledAutoFetch, fetchPolicy, dataStorage, statusStorage, backgroundStatusStorage, submitValidity, }: InfiniteQueryParams<TResult, TError, TIsBackground>);
|
|
89
149
|
private get isNetworkOnly();
|
|
90
150
|
/**
|
|
91
151
|
* Счетчик отступа для инфинити запроса
|
|
@@ -99,7 +159,11 @@ export declare class InfiniteQuery<TResult, TError = void, TIsBackground extends
|
|
|
99
159
|
* Флаг того, что мы достигли предела запрашиваемых элементов
|
|
100
160
|
*/
|
|
101
161
|
get isEndReached(): boolean;
|
|
102
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Флаг того, что мы достигли самого начала запрашиваемых элементов
|
|
164
|
+
*/
|
|
165
|
+
get isStartReached(): boolean;
|
|
166
|
+
private calcIsLimitReachedByResult;
|
|
103
167
|
/**
|
|
104
168
|
* Форс метод для установки данных
|
|
105
169
|
*/
|
|
@@ -107,15 +171,18 @@ export declare class InfiniteQuery<TResult, TError = void, TIsBackground extends
|
|
|
107
171
|
/**
|
|
108
172
|
* Метод для обогащения параметров текущими значениями для инфинити
|
|
109
173
|
*/
|
|
110
|
-
private
|
|
174
|
+
private makeInfiniteExecutor;
|
|
111
175
|
/**
|
|
112
176
|
* Метод для инвалидации данных
|
|
113
177
|
*/
|
|
114
178
|
invalidate: () => void;
|
|
179
|
+
private setOffset;
|
|
180
|
+
get fetchMoreStatus(): import("../StatusStorage").Statuses<TError>;
|
|
115
181
|
/**
|
|
116
182
|
* Метод для запроса следующего набора данных
|
|
183
|
+
* @param options - параметры дозапроса данных
|
|
117
184
|
*/
|
|
118
|
-
fetchMore: () => void;
|
|
185
|
+
fetchMore: (options?: InfiniteFetchMoreOptions<TResult>) => void;
|
|
119
186
|
/**
|
|
120
187
|
* Синхронный метод получения данных
|
|
121
188
|
*/
|