@astral/mobx-query 0.1.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 +52 -0
- package/AuxiliaryQuery/AuxiliaryQuery.js +76 -0
- package/AuxiliaryQuery/index.d.ts +1 -0
- package/AuxiliaryQuery/index.js +1 -0
- package/DataStorage/DataStorage.d.ts +36 -0
- package/DataStorage/DataStorage.js +48 -0
- package/DataStorage/index.d.ts +1 -0
- package/DataStorage/index.js +1 -0
- package/InfiniteQuery/InfiniteQuery.d.ts +135 -0
- package/InfiniteQuery/InfiniteQuery.js +183 -0
- package/InfiniteQuery/index.d.ts +1 -0
- package/InfiniteQuery/index.js +1 -0
- package/LICENSE +21 -0
- package/MobxQuery/MobxQuery.d.ts +79 -0
- package/MobxQuery/MobxQuery.js +85 -0
- package/MobxQuery/index.d.ts +1 -0
- package/MobxQuery/index.js +1 -0
- package/MutationQuery/MutationQuery.d.ts +56 -0
- package/MutationQuery/MutationQuery.js +68 -0
- package/MutationQuery/index.d.ts +1 -0
- package/MutationQuery/index.js +1 -0
- package/Query/Query.d.ts +103 -0
- package/Query/Query.js +122 -0
- package/Query/index.d.ts +1 -0
- package/Query/index.js +1 -0
- package/README.md +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +25 -0
- package/types.d.ts +51 -0
- package/types.js +1 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description испольнитель запроса
|
|
3
|
+
*/
|
|
4
|
+
type Executor<TResult> = () => Promise<TResult>;
|
|
5
|
+
/**
|
|
6
|
+
* @description вспомогательное хранилище данных, для композиции в Query сторах,
|
|
7
|
+
* содержащее флаги загрузки и ошибки,
|
|
8
|
+
* колбэки на успешный запрос и на ошибку,
|
|
9
|
+
* данные последней ошибки,
|
|
10
|
+
* а так же, управляющее синглтон промисом.
|
|
11
|
+
*/
|
|
12
|
+
export declare class AuxiliaryQuery<TResult, TError = void> {
|
|
13
|
+
/**
|
|
14
|
+
* @description флаг обозначающий загрузку данных
|
|
15
|
+
*/
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
19
|
+
*/
|
|
20
|
+
isError: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* @description данные о последней ошибке
|
|
23
|
+
*/
|
|
24
|
+
error?: TError;
|
|
25
|
+
/**
|
|
26
|
+
* @description флаг, обозначающий успешность завершения последнего запроса
|
|
27
|
+
*/
|
|
28
|
+
isSuccess: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* @description единый промис, для устранения гонки запросов
|
|
31
|
+
*/
|
|
32
|
+
private unifiedPromise?;
|
|
33
|
+
constructor();
|
|
34
|
+
/**
|
|
35
|
+
* @description метод ответственный за создание единого промиса,
|
|
36
|
+
* для устранения гонки запросов
|
|
37
|
+
*/
|
|
38
|
+
getUnifiedPromise: (executor: Executor<TResult>) => Promise<TResult>;
|
|
39
|
+
/**
|
|
40
|
+
* @description обработчик успешного ответа
|
|
41
|
+
*/
|
|
42
|
+
submitSuccess: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* @description обработчик ошибки
|
|
45
|
+
*/
|
|
46
|
+
submitError: (e: TError) => void;
|
|
47
|
+
/**
|
|
48
|
+
* @description метод, вызываемый в самом начале запроса, чтобы сбросить флаги в соответствующее значение
|
|
49
|
+
*/
|
|
50
|
+
startLoading: () => void;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { makeAutoObservable, runInAction } from 'mobx';
|
|
2
|
+
/**
|
|
3
|
+
* @description вспомогательное хранилище данных, для композиции в Query сторах,
|
|
4
|
+
* содержащее флаги загрузки и ошибки,
|
|
5
|
+
* колбэки на успешный запрос и на ошибку,
|
|
6
|
+
* данные последней ошибки,
|
|
7
|
+
* а так же, управляющее синглтон промисом.
|
|
8
|
+
*/
|
|
9
|
+
export class AuxiliaryQuery {
|
|
10
|
+
constructor() {
|
|
11
|
+
/**
|
|
12
|
+
* @description флаг обозначающий загрузку данных
|
|
13
|
+
*/
|
|
14
|
+
this.isLoading = false;
|
|
15
|
+
/**
|
|
16
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
17
|
+
*/
|
|
18
|
+
this.isError = false;
|
|
19
|
+
/**
|
|
20
|
+
* @description флаг, обозначающий успешность завершения последнего запроса
|
|
21
|
+
*/
|
|
22
|
+
this.isSuccess = false;
|
|
23
|
+
/**
|
|
24
|
+
* @description метод ответственный за создание единого промиса,
|
|
25
|
+
* для устранения гонки запросов
|
|
26
|
+
*/
|
|
27
|
+
this.getUnifiedPromise = (executor) => {
|
|
28
|
+
// проверяем, если синглтона нет, то надо создать
|
|
29
|
+
if (!Boolean(this.unifiedPromise)) {
|
|
30
|
+
this.startLoading();
|
|
31
|
+
this.unifiedPromise = executor()
|
|
32
|
+
.then((resData) => {
|
|
33
|
+
runInAction(this.submitSuccess);
|
|
34
|
+
return resData;
|
|
35
|
+
})
|
|
36
|
+
.catch((e) => {
|
|
37
|
+
runInAction(() => {
|
|
38
|
+
this.submitError(e);
|
|
39
|
+
});
|
|
40
|
+
throw e;
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
this.unifiedPromise = undefined;
|
|
44
|
+
runInAction(() => {
|
|
45
|
+
this.isLoading = false;
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return this.unifiedPromise;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* @description обработчик успешного ответа
|
|
53
|
+
*/
|
|
54
|
+
this.submitSuccess = () => {
|
|
55
|
+
this.isError = false;
|
|
56
|
+
this.isSuccess = true;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* @description обработчик ошибки
|
|
60
|
+
*/
|
|
61
|
+
this.submitError = (e) => {
|
|
62
|
+
this.isSuccess = false;
|
|
63
|
+
this.isError = true;
|
|
64
|
+
this.error = e;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* @description метод, вызываемый в самом начале запроса, чтобы сбросить флаги в соответствующее значение
|
|
68
|
+
*/
|
|
69
|
+
this.startLoading = () => {
|
|
70
|
+
this.isLoading = true;
|
|
71
|
+
this.isError = false;
|
|
72
|
+
this.isSuccess = false;
|
|
73
|
+
};
|
|
74
|
+
makeAutoObservable(this);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AuxiliaryQuery';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AuxiliaryQuery';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CacheKey } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* @description хранилище данных, предназначено для обеспечения единого интерфейса при работе с данными
|
|
4
|
+
*/
|
|
5
|
+
export declare class DataStorage<TData> {
|
|
6
|
+
/**
|
|
7
|
+
* @description поле, отвечающее за непосредственное хранение данных
|
|
8
|
+
*/
|
|
9
|
+
private internalData?;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* @description флаг, отображающий наличие данных
|
|
13
|
+
*/
|
|
14
|
+
get hasData(): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* @description метод для установки данных
|
|
17
|
+
*/
|
|
18
|
+
setData: (value: TData) => void;
|
|
19
|
+
/**
|
|
20
|
+
* @description геттер данных
|
|
21
|
+
*/
|
|
22
|
+
get data(): TData | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @description фабрика ответственная за создание и хранение экземляров хранилищ
|
|
26
|
+
*/
|
|
27
|
+
export declare class DataStorageFactory {
|
|
28
|
+
/**
|
|
29
|
+
* @description Map хранящий инстансы хранилищ по хэшу ключа
|
|
30
|
+
*/
|
|
31
|
+
private storageMap;
|
|
32
|
+
/**
|
|
33
|
+
* @description фабричный метод получения/создания инстанса хранилища по ключу
|
|
34
|
+
*/
|
|
35
|
+
getStorage: <TData>(key: CacheKey[]) => DataStorage<TData>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { makeAutoObservable } from 'mobx';
|
|
2
|
+
/**
|
|
3
|
+
* @description хранилище данных, предназначено для обеспечения единого интерфейса при работе с данными
|
|
4
|
+
*/
|
|
5
|
+
export class DataStorage {
|
|
6
|
+
constructor() {
|
|
7
|
+
/**
|
|
8
|
+
* @description метод для установки данных
|
|
9
|
+
*/
|
|
10
|
+
this.setData = (value) => {
|
|
11
|
+
this.internalData = value;
|
|
12
|
+
};
|
|
13
|
+
makeAutoObservable(this);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @description флаг, отображающий наличие данных
|
|
17
|
+
*/
|
|
18
|
+
get hasData() {
|
|
19
|
+
return Boolean(this.internalData);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @description геттер данных
|
|
23
|
+
*/
|
|
24
|
+
get data() {
|
|
25
|
+
return this.internalData;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* @description фабрика ответственная за создание и хранение экземляров хранилищ
|
|
30
|
+
*/
|
|
31
|
+
export class DataStorageFactory {
|
|
32
|
+
constructor() {
|
|
33
|
+
/**
|
|
34
|
+
* @description Map хранящий инстансы хранилищ по хэшу ключа
|
|
35
|
+
*/
|
|
36
|
+
this.storageMap = new Map();
|
|
37
|
+
/**
|
|
38
|
+
* @description фабричный метод получения/создания инстанса хранилища по ключу
|
|
39
|
+
*/
|
|
40
|
+
this.getStorage = (key) => {
|
|
41
|
+
const keyHash = JSON.stringify(key);
|
|
42
|
+
if (!this.storageMap.has(keyHash)) {
|
|
43
|
+
this.storageMap.set(keyHash, new DataStorage());
|
|
44
|
+
}
|
|
45
|
+
return this.storageMap.get(keyHash);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DataStorage';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './DataStorage';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { FetchPolicy, QueryBaseActions, Sync, SyncParams } from '../types';
|
|
2
|
+
import { DataStorage } from '../DataStorage';
|
|
3
|
+
export declare const DEFAULT_INFINITE_ITEMS_COUNT = 30;
|
|
4
|
+
export type InfiniteParams = {
|
|
5
|
+
offset: number;
|
|
6
|
+
count: number;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* @description исполнитель запроса, ожидается,
|
|
10
|
+
* что будет использоваться что-то возвращающее массив данных
|
|
11
|
+
*/
|
|
12
|
+
export type InfiniteExecutor<TResult> = (params: InfiniteParams) => Promise<Array<TResult>>;
|
|
13
|
+
export type InfiniteQueryParams<TResult, TError> = {
|
|
14
|
+
/**
|
|
15
|
+
* @description количество запрашиваемых элементов
|
|
16
|
+
* @default 30
|
|
17
|
+
*/
|
|
18
|
+
incrementCount?: number;
|
|
19
|
+
/**
|
|
20
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
21
|
+
*/
|
|
22
|
+
onError?: SyncParams<TResult, TError>['onError'];
|
|
23
|
+
/**
|
|
24
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
25
|
+
*/
|
|
26
|
+
enabledAutoFetch?: boolean;
|
|
27
|
+
fetchPolicy?: FetchPolicy;
|
|
28
|
+
/**
|
|
29
|
+
* @description инстанс хранилища данных
|
|
30
|
+
*/
|
|
31
|
+
dataStorage: DataStorage<TResult[]>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @description стор для работы с инфинити запросами,
|
|
35
|
+
* которые должны быть закешированы,
|
|
36
|
+
*/
|
|
37
|
+
export declare class InfiniteQuery<TResult, TError = void> implements QueryBaseActions<Array<TResult>, TError> {
|
|
38
|
+
/**
|
|
39
|
+
* @description инстанс вспомогательного стора
|
|
40
|
+
*/
|
|
41
|
+
private auxiliary;
|
|
42
|
+
/**
|
|
43
|
+
* @description счетчик отступа для инфинити запроса
|
|
44
|
+
*/
|
|
45
|
+
private offset;
|
|
46
|
+
/**
|
|
47
|
+
* @description количество запрашиваемых элементов
|
|
48
|
+
*/
|
|
49
|
+
private readonly incrementCount;
|
|
50
|
+
/**
|
|
51
|
+
* @description исполнитель запроса, ожидается,
|
|
52
|
+
* что будет использоваться что-то из слоя sources, возвращающее массив данных
|
|
53
|
+
*/
|
|
54
|
+
private executor;
|
|
55
|
+
/**
|
|
56
|
+
* @description хранилище данных, для обеспечения возможности синхронизации данных между разными инстансами
|
|
57
|
+
*/
|
|
58
|
+
private storage;
|
|
59
|
+
/**
|
|
60
|
+
* @description флаг того, что мы достигли предела запрашиваемых элементов
|
|
61
|
+
*/
|
|
62
|
+
isEndReached: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* @description флаг необходимости выполнить обновления данных
|
|
65
|
+
*/
|
|
66
|
+
private isInvalid;
|
|
67
|
+
/**
|
|
68
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
69
|
+
*/
|
|
70
|
+
private defaultOnError?;
|
|
71
|
+
/**
|
|
72
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
73
|
+
*/
|
|
74
|
+
private enabledAutoFetch?;
|
|
75
|
+
/**
|
|
76
|
+
* @description стандартное поведение политики кеширования
|
|
77
|
+
*/
|
|
78
|
+
private readonly defaultFetchPolicy?;
|
|
79
|
+
constructor(executor: InfiniteExecutor<TResult>, { incrementCount, onError, enabledAutoFetch, fetchPolicy, dataStorage, }: InfiniteQueryParams<TResult, TError>);
|
|
80
|
+
private get isNetworkOnly();
|
|
81
|
+
/**
|
|
82
|
+
* @description обработчик успешного запроса, проверяет что мы достигли предела
|
|
83
|
+
*/
|
|
84
|
+
private submitSuccess;
|
|
85
|
+
/**
|
|
86
|
+
* @description метод для обогащения параметров текущими значениями для инфинити
|
|
87
|
+
*/
|
|
88
|
+
private get infiniteExecutor();
|
|
89
|
+
/**
|
|
90
|
+
* @description метод для инвалидации данных
|
|
91
|
+
*/
|
|
92
|
+
invalidate: () => void;
|
|
93
|
+
/**
|
|
94
|
+
* @description метод для запроса следующего набора данных
|
|
95
|
+
*/
|
|
96
|
+
fetchMore: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* @description синхронный метод получения данных
|
|
99
|
+
*/
|
|
100
|
+
sync: Sync<Array<TResult>, TError>;
|
|
101
|
+
/**
|
|
102
|
+
* @description метод для переиспользования синхронной логики запроса
|
|
103
|
+
*/
|
|
104
|
+
private proceedSync;
|
|
105
|
+
/**
|
|
106
|
+
* @description aсинхронный метод получения данных,
|
|
107
|
+
* подходит для изменения параметров запроса(фильтров),
|
|
108
|
+
* при котором будет сброшен offset,
|
|
109
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
110
|
+
*/
|
|
111
|
+
async: () => Promise<TResult[]>;
|
|
112
|
+
/**
|
|
113
|
+
* @description вычисляемое свойство, содержащее реактивные данные,
|
|
114
|
+
* благодаря mobx, при изменении isInvalid, свойство будет вычисляться заново,
|
|
115
|
+
* следовательно, стриггерится условие невалидности,
|
|
116
|
+
* и начнется запрос, в результате которого, данные обновятся
|
|
117
|
+
*/
|
|
118
|
+
get data(): TResult[] | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* @description флаг загрузки данных
|
|
121
|
+
*/
|
|
122
|
+
get isLoading(): boolean;
|
|
123
|
+
/**
|
|
124
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
125
|
+
*/
|
|
126
|
+
get isError(): boolean;
|
|
127
|
+
/**
|
|
128
|
+
* @description данные о последней ошибке
|
|
129
|
+
*/
|
|
130
|
+
get error(): TError | undefined;
|
|
131
|
+
/**
|
|
132
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
133
|
+
*/
|
|
134
|
+
get isSuccess(): boolean;
|
|
135
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { makeAutoObservable } from 'mobx';
|
|
2
|
+
import { AuxiliaryQuery } from '../AuxiliaryQuery';
|
|
3
|
+
export const DEFAULT_INFINITE_ITEMS_COUNT = 30;
|
|
4
|
+
/**
|
|
5
|
+
* @description стор для работы с инфинити запросами,
|
|
6
|
+
* которые должны быть закешированы,
|
|
7
|
+
*/
|
|
8
|
+
export class InfiniteQuery {
|
|
9
|
+
constructor(executor, { incrementCount = DEFAULT_INFINITE_ITEMS_COUNT, onError, enabledAutoFetch, fetchPolicy, dataStorage, }) {
|
|
10
|
+
/**
|
|
11
|
+
* @description инстанс вспомогательного стора
|
|
12
|
+
*/
|
|
13
|
+
this.auxiliary = new AuxiliaryQuery();
|
|
14
|
+
/**
|
|
15
|
+
* @description счетчик отступа для инфинити запроса
|
|
16
|
+
*/
|
|
17
|
+
this.offset = 0;
|
|
18
|
+
/**
|
|
19
|
+
* @description флаг того, что мы достигли предела запрашиваемых элементов
|
|
20
|
+
*/
|
|
21
|
+
this.isEndReached = false;
|
|
22
|
+
/**
|
|
23
|
+
* @description флаг необходимости выполнить обновления данных
|
|
24
|
+
*/
|
|
25
|
+
this.isInvalid = false;
|
|
26
|
+
/**
|
|
27
|
+
* @description обработчик успешного запроса, проверяет что мы достигли предела
|
|
28
|
+
*/
|
|
29
|
+
this.submitSuccess = (result, onSuccess, isIncrement) => {
|
|
30
|
+
this.auxiliary.submitSuccess();
|
|
31
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(result);
|
|
32
|
+
if (isIncrement && this.storage.hasData) {
|
|
33
|
+
this.storage.setData([...this.storage.data, ...result]);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.storage.setData(result);
|
|
37
|
+
}
|
|
38
|
+
this.isInvalid = false;
|
|
39
|
+
// убеждаемся что результат запроса действительно массив,
|
|
40
|
+
// и если количество элементов в ответе меньше,
|
|
41
|
+
// чем запрашивалось, значит у бэка их больше нет,
|
|
42
|
+
// другими словами мы допускаем что, может произойти лишний запрос,
|
|
43
|
+
// когда последняя отданная страница содержит ровно то количество,
|
|
44
|
+
// сколько может содержать страница, а следующая уже просто пустая.
|
|
45
|
+
if (Array.isArray(result) && result.length < this.incrementCount) {
|
|
46
|
+
// включаем флаг достижения предела
|
|
47
|
+
this.isEndReached = true;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* @description метод для инвалидации данных
|
|
52
|
+
*/
|
|
53
|
+
this.invalidate = () => {
|
|
54
|
+
this.isInvalid = true;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* @description метод для запроса следующего набора данных
|
|
58
|
+
*/
|
|
59
|
+
this.fetchMore = () => {
|
|
60
|
+
// если мы еще не достигли предела
|
|
61
|
+
if (!this.isEndReached && this.storage.data) {
|
|
62
|
+
// прибавляем к офсету число запрашиваемых элементов
|
|
63
|
+
this.offset += this.incrementCount;
|
|
64
|
+
// запускаем запрос с последними параметрами, и флагом необходимости инкремента
|
|
65
|
+
this.auxiliary
|
|
66
|
+
.getUnifiedPromise(this.infiniteExecutor)
|
|
67
|
+
.then((resData) => {
|
|
68
|
+
this.submitSuccess(resData, undefined, true);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* @description синхронный метод получения данных
|
|
74
|
+
*/
|
|
75
|
+
this.sync = (params) => {
|
|
76
|
+
const isInstanceAllow = !(this.isLoading || this.isSuccess);
|
|
77
|
+
if (this.isNetworkOnly || this.isInvalid || isInstanceAllow) {
|
|
78
|
+
this.proceedSync(params);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* @description метод для переиспользования синхронной логики запроса
|
|
83
|
+
*/
|
|
84
|
+
this.proceedSync = ({ onSuccess, onError, } = {}) => {
|
|
85
|
+
this.offset = 0;
|
|
86
|
+
this.isEndReached = false;
|
|
87
|
+
this.auxiliary
|
|
88
|
+
.getUnifiedPromise(this.infiniteExecutor)
|
|
89
|
+
.then((resData) => {
|
|
90
|
+
this.submitSuccess(resData, onSuccess);
|
|
91
|
+
})
|
|
92
|
+
.catch((e) => {
|
|
93
|
+
var _a;
|
|
94
|
+
if (onError) {
|
|
95
|
+
onError(e);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
(_a = this.defaultOnError) === null || _a === void 0 ? void 0 : _a.call(this, e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* @description aсинхронный метод получения данных,
|
|
104
|
+
* подходит для изменения параметров запроса(фильтров),
|
|
105
|
+
* при котором будет сброшен offset,
|
|
106
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
107
|
+
*/
|
|
108
|
+
this.async = () => {
|
|
109
|
+
if (!this.isNetworkOnly && this.isSuccess && !this.isInvalid) {
|
|
110
|
+
return Promise.resolve(this.storage.data);
|
|
111
|
+
}
|
|
112
|
+
this.offset = 0;
|
|
113
|
+
this.isEndReached = false;
|
|
114
|
+
return this.auxiliary
|
|
115
|
+
.getUnifiedPromise(this.infiniteExecutor)
|
|
116
|
+
.then((resData) => {
|
|
117
|
+
this.submitSuccess(resData);
|
|
118
|
+
return resData;
|
|
119
|
+
})
|
|
120
|
+
.catch((e) => {
|
|
121
|
+
this.auxiliary.submitError(e);
|
|
122
|
+
throw e;
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
this.storage = dataStorage;
|
|
126
|
+
this.incrementCount = incrementCount;
|
|
127
|
+
this.executor = executor;
|
|
128
|
+
this.defaultOnError = onError;
|
|
129
|
+
this.enabledAutoFetch = enabledAutoFetch;
|
|
130
|
+
this.defaultFetchPolicy = fetchPolicy;
|
|
131
|
+
makeAutoObservable(this);
|
|
132
|
+
}
|
|
133
|
+
get isNetworkOnly() {
|
|
134
|
+
return this.defaultFetchPolicy === 'network-only';
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* @description метод для обогащения параметров текущими значениями для инфинити
|
|
138
|
+
*/
|
|
139
|
+
get infiniteExecutor() {
|
|
140
|
+
return () => this.executor({
|
|
141
|
+
offset: this.offset,
|
|
142
|
+
count: this.incrementCount,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @description вычисляемое свойство, содержащее реактивные данные,
|
|
147
|
+
* благодаря mobx, при изменении isInvalid, свойство будет вычисляться заново,
|
|
148
|
+
* следовательно, стриггерится условие невалидности,
|
|
149
|
+
* и начнется запрос, в результате которого, данные обновятся
|
|
150
|
+
*/
|
|
151
|
+
get data() {
|
|
152
|
+
const shouldSync = this.enabledAutoFetch && !this.isSuccess && !this.isLoading;
|
|
153
|
+
if (this.isInvalid || shouldSync) {
|
|
154
|
+
this.proceedSync();
|
|
155
|
+
}
|
|
156
|
+
// возвращаем имеющиеся данные
|
|
157
|
+
return this.storage.data;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @description флаг загрузки данных
|
|
161
|
+
*/
|
|
162
|
+
get isLoading() {
|
|
163
|
+
return this.auxiliary.isLoading;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
167
|
+
*/
|
|
168
|
+
get isError() {
|
|
169
|
+
return this.auxiliary.isError;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* @description данные о последней ошибке
|
|
173
|
+
*/
|
|
174
|
+
get error() {
|
|
175
|
+
return this.auxiliary.error;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
179
|
+
*/
|
|
180
|
+
get isSuccess() {
|
|
181
|
+
return this.auxiliary.isSuccess;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './InfiniteQuery';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './InfiniteQuery';
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Astral.Soft
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Query, QueryExecutor, QueryParams } from '../Query';
|
|
2
|
+
import { InfiniteExecutor, InfiniteQuery, InfiniteQueryParams } from '../InfiniteQuery';
|
|
3
|
+
import { MutationExecutor, MutationQuery, MutationQueryParams } from '../MutationQuery';
|
|
4
|
+
import { CacheKey, FetchPolicy } from '../types';
|
|
5
|
+
/**
|
|
6
|
+
* @description стандартный обработчик ошибки запроса,
|
|
7
|
+
* будет вызван, если при вызове sync не был передан отдельный onError параметр
|
|
8
|
+
*/
|
|
9
|
+
type OnError<TError = unknown> = (error: TError) => void;
|
|
10
|
+
type MobxQueryParams = {
|
|
11
|
+
fetchPolicy?: FetchPolicy;
|
|
12
|
+
onError?: OnError;
|
|
13
|
+
/**
|
|
14
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
enabledAutoFetch?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type CreateCacheableQueryParams<TResult, TError> = Omit<QueryParams<TResult, TError>, 'dataStorage'>;
|
|
20
|
+
type CreateInfiniteQueryParams<TResult, TError> = Omit<InfiniteQueryParams<TResult, TError>, 'dataStorage'>;
|
|
21
|
+
/**
|
|
22
|
+
* @description Сервис, позволяющий кэшировать данные.
|
|
23
|
+
*/
|
|
24
|
+
export declare class MobxQuery {
|
|
25
|
+
/**
|
|
26
|
+
* @description объект соответствия хешей ключей и их значений
|
|
27
|
+
*/
|
|
28
|
+
private keys;
|
|
29
|
+
/**
|
|
30
|
+
* @description Map соответствия хешей ключей к запомненным сторам
|
|
31
|
+
*/
|
|
32
|
+
private cacheableStores;
|
|
33
|
+
/**
|
|
34
|
+
* @description фабрика создания хранилищ данных для обычного Query
|
|
35
|
+
*/
|
|
36
|
+
private queryDataStorageFactory;
|
|
37
|
+
/**
|
|
38
|
+
* @description фабрика создания хранилищ данных для Infinite Query
|
|
39
|
+
*/
|
|
40
|
+
private infiniteQueryDataStorageFactory;
|
|
41
|
+
/**
|
|
42
|
+
* @description стандартный обработчик ошибок, будет использован, если не передан другой
|
|
43
|
+
*/
|
|
44
|
+
private readonly defaultErrorHandler?;
|
|
45
|
+
/**
|
|
46
|
+
* @description стандартное поведение политики кеширования
|
|
47
|
+
*/
|
|
48
|
+
private readonly defaultFetchPolicy;
|
|
49
|
+
/**
|
|
50
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
private readonly defaultEnabledAutoFetch;
|
|
54
|
+
private serialize;
|
|
55
|
+
constructor({ onError, fetchPolicy, enabledAutoFetch, }?: MobxQueryParams);
|
|
56
|
+
/**
|
|
57
|
+
* @description метод для инвалидации по списку ключей,
|
|
58
|
+
* предполагается использование из домена
|
|
59
|
+
*/
|
|
60
|
+
invalidate: (keysParts: CacheKey[]) => void;
|
|
61
|
+
/**
|
|
62
|
+
* @description метод, который занимается проверкой наличия стора по ключу,
|
|
63
|
+
* и если нет, создает новый, добавляет его к себе в память, и возвращает его пользователю
|
|
64
|
+
*/
|
|
65
|
+
private getCachedQuery;
|
|
66
|
+
/**
|
|
67
|
+
* @description метод создания стора, кешируется
|
|
68
|
+
*/
|
|
69
|
+
createQuery: <TResult, TError>(key: CacheKey[], executor: QueryExecutor<TResult>, params?: CreateCacheableQueryParams<TResult, TError> | undefined) => Query<TResult, TError>;
|
|
70
|
+
/**
|
|
71
|
+
* @description метод создания инфинит стора, кешируется
|
|
72
|
+
*/
|
|
73
|
+
createInfiniteQuery: <TResult, TError>(key: CacheKey[], executor: InfiniteExecutor<TResult>, params?: CreateInfiniteQueryParams<TResult, TError> | undefined) => InfiniteQuery<TResult, TError>;
|
|
74
|
+
/**
|
|
75
|
+
* @description метод создания мутации, не кешируется
|
|
76
|
+
*/
|
|
77
|
+
createMutationQuery: <TResult, TError, TExecutorParams>(executor: MutationExecutor<TResult, TExecutorParams>, params?: MutationQueryParams<TResult, TError> | undefined) => MutationQuery<TResult, TError, TExecutorParams>;
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Query } from '../Query';
|
|
2
|
+
import { InfiniteQuery, } from '../InfiniteQuery';
|
|
3
|
+
import { MutationQuery, } from '../MutationQuery';
|
|
4
|
+
import { DataStorageFactory } from '../DataStorage';
|
|
5
|
+
/**
|
|
6
|
+
* @description Сервис, позволяющий кэшировать данные.
|
|
7
|
+
*/
|
|
8
|
+
export class MobxQuery {
|
|
9
|
+
constructor({ onError, fetchPolicy = 'cache-first', enabledAutoFetch = false, } = {}) {
|
|
10
|
+
/**
|
|
11
|
+
* @description объект соответствия хешей ключей и их значений
|
|
12
|
+
*/
|
|
13
|
+
this.keys = {};
|
|
14
|
+
/**
|
|
15
|
+
* @description Map соответствия хешей ключей к запомненным сторам
|
|
16
|
+
*/
|
|
17
|
+
this.cacheableStores = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* @description фабрика создания хранилищ данных для обычного Query
|
|
20
|
+
*/
|
|
21
|
+
this.queryDataStorageFactory = new DataStorageFactory();
|
|
22
|
+
/**
|
|
23
|
+
* @description фабрика создания хранилищ данных для Infinite Query
|
|
24
|
+
*/
|
|
25
|
+
this.infiniteQueryDataStorageFactory = new DataStorageFactory();
|
|
26
|
+
this.serialize = (data) => JSON.stringify(data);
|
|
27
|
+
/**
|
|
28
|
+
* @description метод для инвалидации по списку ключей,
|
|
29
|
+
* предполагается использование из домена
|
|
30
|
+
*/
|
|
31
|
+
this.invalidate = (keysParts) => {
|
|
32
|
+
// сет сериализовонных ключей
|
|
33
|
+
const keysSet = new Set(keysParts.map(this.serialize));
|
|
34
|
+
Object.keys(this.keys).forEach((key) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const value = this.keys[key];
|
|
37
|
+
// проверяем, есть ли пересечение между закешированными ключами и набором ключей для инвалидации
|
|
38
|
+
const hasTouchedElement = value.some((valuePart) => keysSet.has(this.serialize(valuePart)));
|
|
39
|
+
if (hasTouchedElement) {
|
|
40
|
+
(_a = this.cacheableStores.get(key)) === null || _a === void 0 ? void 0 : _a.invalidate();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* @description метод, который занимается проверкой наличия стора по ключу,
|
|
46
|
+
* и если нет, создает новый, добавляет его к себе в память, и возвращает его пользователю
|
|
47
|
+
*/
|
|
48
|
+
this.getCachedQuery = (key, createStore, fetchPolicy) => {
|
|
49
|
+
if (fetchPolicy === 'network-only') {
|
|
50
|
+
return createStore();
|
|
51
|
+
}
|
|
52
|
+
const keyHash = this.serialize(key);
|
|
53
|
+
if (this.cacheableStores.has(keyHash)) {
|
|
54
|
+
return this.cacheableStores.get(keyHash);
|
|
55
|
+
}
|
|
56
|
+
const store = createStore();
|
|
57
|
+
this.cacheableStores.set(keyHash, store);
|
|
58
|
+
this.keys[keyHash] = key;
|
|
59
|
+
return store;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* @description метод создания стора, кешируется
|
|
63
|
+
*/
|
|
64
|
+
this.createQuery = (key, executor, params) => {
|
|
65
|
+
const fetchPolicy = (params === null || params === void 0 ? void 0 : params.fetchPolicy) || this.defaultFetchPolicy;
|
|
66
|
+
return this.getCachedQuery(key, () => new Query(executor, Object.assign(Object.assign({}, params), { onError: ((params === null || params === void 0 ? void 0 : params.onError) ||
|
|
67
|
+
this.defaultErrorHandler), enabledAutoFetch: (params === null || params === void 0 ? void 0 : params.enabledAutoFetch) || this.defaultEnabledAutoFetch, fetchPolicy: fetchPolicy, dataStorage: this.queryDataStorageFactory.getStorage(key) })), fetchPolicy);
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* @description метод создания инфинит стора, кешируется
|
|
71
|
+
*/
|
|
72
|
+
this.createInfiniteQuery = (key, executor, params) => {
|
|
73
|
+
const fetchPolicy = (params === null || params === void 0 ? void 0 : params.fetchPolicy) || this.defaultFetchPolicy || '';
|
|
74
|
+
return this.getCachedQuery(key, () => new InfiniteQuery(executor, Object.assign(Object.assign({}, params), { onError: ((params === null || params === void 0 ? void 0 : params.onError) ||
|
|
75
|
+
this.defaultErrorHandler), enabledAutoFetch: (params === null || params === void 0 ? void 0 : params.enabledAutoFetch) || this.defaultEnabledAutoFetch, dataStorage: this.infiniteQueryDataStorageFactory.getStorage(key), fetchPolicy: fetchPolicy })), fetchPolicy);
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* @description метод создания мутации, не кешируется
|
|
79
|
+
*/
|
|
80
|
+
this.createMutationQuery = (executor, params) => new MutationQuery(executor, Object.assign(Object.assign({}, params), { onError: (params === null || params === void 0 ? void 0 : params.onError) || this.defaultErrorHandler }));
|
|
81
|
+
this.defaultErrorHandler = onError;
|
|
82
|
+
this.defaultFetchPolicy = fetchPolicy;
|
|
83
|
+
this.defaultEnabledAutoFetch = enabledAutoFetch;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MobxQuery';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MobxQuery';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { QueryBaseActions, Sync, SyncParams } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* @description исполнитель запроса
|
|
4
|
+
*/
|
|
5
|
+
export type MutationExecutor<TResult, TParams> = (params: TParams) => Promise<TResult>;
|
|
6
|
+
export type MutationQueryParams<TResult, TError> = {
|
|
7
|
+
/**
|
|
8
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
9
|
+
*/
|
|
10
|
+
onError?: SyncParams<TResult, TError>['onError'];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @description простой стор для запросов, которые не требуют кэширования,
|
|
14
|
+
* пример - POST запросы
|
|
15
|
+
*/
|
|
16
|
+
export declare class MutationQuery<TResult, TError = void, TExecutorParams = void> implements QueryBaseActions<TResult, TError, TExecutorParams> {
|
|
17
|
+
/**
|
|
18
|
+
* @description инстанс вспомогательного стора
|
|
19
|
+
*/
|
|
20
|
+
private auxiliary;
|
|
21
|
+
/**
|
|
22
|
+
* @description исполнитель запроса, ожидается,
|
|
23
|
+
* что будет использоваться что-то из слоя sources
|
|
24
|
+
*/
|
|
25
|
+
private executor;
|
|
26
|
+
/**
|
|
27
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
28
|
+
*/
|
|
29
|
+
private defaultOnError?;
|
|
30
|
+
constructor(executor: MutationExecutor<TResult, TExecutorParams>, { onError }?: MutationQueryParams<TResult, TError>);
|
|
31
|
+
/**
|
|
32
|
+
* @description синхронный метод получения/отправки данных
|
|
33
|
+
*/
|
|
34
|
+
sync: Sync<TResult, TError, TExecutorParams>;
|
|
35
|
+
/**
|
|
36
|
+
* @description асинхронный метод получения/отправки данных,
|
|
37
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
38
|
+
*/
|
|
39
|
+
async: (params: TExecutorParams) => Promise<TResult>;
|
|
40
|
+
/**
|
|
41
|
+
* @description флаг загрузки данных
|
|
42
|
+
*/
|
|
43
|
+
get isLoading(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
46
|
+
*/
|
|
47
|
+
get isError(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* @description данные о последней ошибке
|
|
50
|
+
*/
|
|
51
|
+
get error(): TError | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
54
|
+
*/
|
|
55
|
+
get isSuccess(): boolean;
|
|
56
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { makeAutoObservable } from 'mobx';
|
|
2
|
+
import { AuxiliaryQuery } from '../AuxiliaryQuery';
|
|
3
|
+
/**
|
|
4
|
+
* @description простой стор для запросов, которые не требуют кэширования,
|
|
5
|
+
* пример - POST запросы
|
|
6
|
+
*/
|
|
7
|
+
export class MutationQuery {
|
|
8
|
+
constructor(executor, { onError } = {}) {
|
|
9
|
+
/**
|
|
10
|
+
* @description инстанс вспомогательного стора
|
|
11
|
+
*/
|
|
12
|
+
this.auxiliary = new AuxiliaryQuery();
|
|
13
|
+
/**
|
|
14
|
+
* @description синхронный метод получения/отправки данных
|
|
15
|
+
*/
|
|
16
|
+
this.sync = (options) => {
|
|
17
|
+
const { onSuccess, onError, params } = options || {};
|
|
18
|
+
this.auxiliary
|
|
19
|
+
.getUnifiedPromise(() => this.executor(params))
|
|
20
|
+
.then((resData) => {
|
|
21
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(resData);
|
|
22
|
+
})
|
|
23
|
+
.catch((e) => {
|
|
24
|
+
var _a;
|
|
25
|
+
if (onError) {
|
|
26
|
+
onError(e);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
(_a = this.defaultOnError) === null || _a === void 0 ? void 0 : _a.call(this, e);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @description асинхронный метод получения/отправки данных,
|
|
35
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
36
|
+
*/
|
|
37
|
+
this.async = (params) => {
|
|
38
|
+
return this.auxiliary.getUnifiedPromise(() => this.executor(params));
|
|
39
|
+
};
|
|
40
|
+
this.executor = executor;
|
|
41
|
+
this.defaultOnError = onError;
|
|
42
|
+
makeAutoObservable(this);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* @description флаг загрузки данных
|
|
46
|
+
*/
|
|
47
|
+
get isLoading() {
|
|
48
|
+
return this.auxiliary.isLoading;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
52
|
+
*/
|
|
53
|
+
get isError() {
|
|
54
|
+
return this.auxiliary.isError;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* @description данные о последней ошибке
|
|
58
|
+
*/
|
|
59
|
+
get error() {
|
|
60
|
+
return this.auxiliary.error;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
64
|
+
*/
|
|
65
|
+
get isSuccess() {
|
|
66
|
+
return this.auxiliary.isSuccess;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MutationQuery';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MutationQuery';
|
package/Query/Query.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { FetchPolicy, QueryBaseActions, Sync, SyncParams } from '../types';
|
|
2
|
+
import { DataStorage } from '../DataStorage';
|
|
3
|
+
/**
|
|
4
|
+
* @description исполнитель запроса
|
|
5
|
+
*/
|
|
6
|
+
export type QueryExecutor<TResult> = () => Promise<TResult>;
|
|
7
|
+
export type QueryParams<TResult, TError> = {
|
|
8
|
+
/**
|
|
9
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
10
|
+
*/
|
|
11
|
+
onError?: SyncParams<TResult, TError>['onError'];
|
|
12
|
+
/**
|
|
13
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
14
|
+
*/
|
|
15
|
+
enabledAutoFetch?: boolean;
|
|
16
|
+
fetchPolicy?: FetchPolicy;
|
|
17
|
+
/**
|
|
18
|
+
* @description инстанс хранилища данных
|
|
19
|
+
*/
|
|
20
|
+
dataStorage: DataStorage<TResult>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @description стор для работы с запросами,
|
|
24
|
+
* которые должны быть закешированы,
|
|
25
|
+
* но им не требуется усложнение в виде работы с фильтрами и инфинити запросами
|
|
26
|
+
*/
|
|
27
|
+
export declare class Query<TResult, TError = void> implements QueryBaseActions<TResult, TError, undefined> {
|
|
28
|
+
/**
|
|
29
|
+
* @description инстанс вспомогательного стора
|
|
30
|
+
*/
|
|
31
|
+
private auxiliary;
|
|
32
|
+
/**
|
|
33
|
+
* @description флаг, по которому реактивно определяется необходимость запуска инвалидации
|
|
34
|
+
*/
|
|
35
|
+
private isInvalid;
|
|
36
|
+
/**
|
|
37
|
+
* @description хранилище данных, для обеспечения возможности синхронизации данных между разными инстансами
|
|
38
|
+
*/
|
|
39
|
+
private storage;
|
|
40
|
+
/**
|
|
41
|
+
* @description исполнитель запроса, ожидается,
|
|
42
|
+
* что будет использоваться что-то из слоя sources
|
|
43
|
+
*/
|
|
44
|
+
private executor;
|
|
45
|
+
/**
|
|
46
|
+
* @description обработчик ошибки, вызываемый по умолчанию
|
|
47
|
+
*/
|
|
48
|
+
private defaultOnError?;
|
|
49
|
+
/**
|
|
50
|
+
* @description флаг, отвечающий за автоматический запрос данных при обращении к полю data
|
|
51
|
+
*/
|
|
52
|
+
private enabledAutoFetch?;
|
|
53
|
+
/**
|
|
54
|
+
* @description стандартное поведение политики кеширования
|
|
55
|
+
*/
|
|
56
|
+
private readonly defaultFetchPolicy?;
|
|
57
|
+
constructor(executor: QueryExecutor<TResult>, { onError, enabledAutoFetch, fetchPolicy, dataStorage, }: QueryParams<TResult, TError>);
|
|
58
|
+
private get isNetworkOnly();
|
|
59
|
+
/**
|
|
60
|
+
* @description метод для инвалидации данных
|
|
61
|
+
*/
|
|
62
|
+
invalidate: () => void;
|
|
63
|
+
/**
|
|
64
|
+
* @description синхронный метод получения данных
|
|
65
|
+
*/
|
|
66
|
+
sync: Sync<TResult, TError, undefined>;
|
|
67
|
+
/**
|
|
68
|
+
* @description обработчик успешного ответа
|
|
69
|
+
*/
|
|
70
|
+
private submitSuccess;
|
|
71
|
+
/**
|
|
72
|
+
* @description метод для переиспользования синхронной логики запроса
|
|
73
|
+
*/
|
|
74
|
+
private proceedSync;
|
|
75
|
+
/**
|
|
76
|
+
* @description асинхронный метод получения данных,
|
|
77
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
78
|
+
*/
|
|
79
|
+
async: () => Promise<TResult>;
|
|
80
|
+
/**
|
|
81
|
+
* @description содержит реактивные данные
|
|
82
|
+
* благодаря mobx, при изменении isInvalid, свойство будет вычисляться заново,
|
|
83
|
+
* следовательно, стриггерится условие невалидности,
|
|
84
|
+
* и начнется запрос, в результате которого, данные обновятся
|
|
85
|
+
*/
|
|
86
|
+
get data(): TResult | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* @description флаг загрузки данных
|
|
89
|
+
*/
|
|
90
|
+
get isLoading(): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
93
|
+
*/
|
|
94
|
+
get isError(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* @description данные о последней ошибке
|
|
97
|
+
*/
|
|
98
|
+
get error(): TError | undefined;
|
|
99
|
+
/**
|
|
100
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
101
|
+
*/
|
|
102
|
+
get isSuccess(): boolean;
|
|
103
|
+
}
|
package/Query/Query.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { makeAutoObservable } from 'mobx';
|
|
2
|
+
import { AuxiliaryQuery } from '../AuxiliaryQuery';
|
|
3
|
+
/**
|
|
4
|
+
* @description стор для работы с запросами,
|
|
5
|
+
* которые должны быть закешированы,
|
|
6
|
+
* но им не требуется усложнение в виде работы с фильтрами и инфинити запросами
|
|
7
|
+
*/
|
|
8
|
+
export class Query {
|
|
9
|
+
constructor(executor, { onError, enabledAutoFetch, fetchPolicy, dataStorage, }) {
|
|
10
|
+
/**
|
|
11
|
+
* @description инстанс вспомогательного стора
|
|
12
|
+
*/
|
|
13
|
+
this.auxiliary = new AuxiliaryQuery();
|
|
14
|
+
/**
|
|
15
|
+
* @description флаг, по которому реактивно определяется необходимость запуска инвалидации
|
|
16
|
+
*/
|
|
17
|
+
this.isInvalid = false;
|
|
18
|
+
/**
|
|
19
|
+
* @description метод для инвалидации данных
|
|
20
|
+
*/
|
|
21
|
+
this.invalidate = () => {
|
|
22
|
+
this.isInvalid = true;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* @description синхронный метод получения данных
|
|
26
|
+
*/
|
|
27
|
+
this.sync = (params) => {
|
|
28
|
+
const isInstanceAllow = !(this.isLoading || this.isSuccess);
|
|
29
|
+
if (this.isNetworkOnly || this.isInvalid || isInstanceAllow) {
|
|
30
|
+
this.proceedSync(params);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* @description обработчик успешного ответа
|
|
35
|
+
*/
|
|
36
|
+
this.submitSuccess = (resData) => {
|
|
37
|
+
this.storage.setData(resData);
|
|
38
|
+
this.isInvalid = false;
|
|
39
|
+
return resData;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* @description метод для переиспользования синхронной логики запроса
|
|
43
|
+
*/
|
|
44
|
+
this.proceedSync = (options) => {
|
|
45
|
+
const { onSuccess, onError } = options || {};
|
|
46
|
+
this.auxiliary
|
|
47
|
+
.getUnifiedPromise(this.executor)
|
|
48
|
+
.then((res) => {
|
|
49
|
+
this.submitSuccess(res);
|
|
50
|
+
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(res);
|
|
51
|
+
})
|
|
52
|
+
.catch((e) => {
|
|
53
|
+
var _a;
|
|
54
|
+
if (onError) {
|
|
55
|
+
onError(e);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
(_a = this.defaultOnError) === null || _a === void 0 ? void 0 : _a.call(this, e);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* @description асинхронный метод получения данных,
|
|
64
|
+
* предполагается, что нужно будет самостоятельно обрабатывать ошибку
|
|
65
|
+
*/
|
|
66
|
+
this.async = () => {
|
|
67
|
+
if (!this.isNetworkOnly && this.isSuccess && !this.isInvalid) {
|
|
68
|
+
return Promise.resolve(this.storage.data);
|
|
69
|
+
}
|
|
70
|
+
return this.auxiliary
|
|
71
|
+
.getUnifiedPromise(this.executor)
|
|
72
|
+
.then(this.submitSuccess);
|
|
73
|
+
};
|
|
74
|
+
this.executor = executor;
|
|
75
|
+
this.defaultOnError = onError;
|
|
76
|
+
this.enabledAutoFetch = enabledAutoFetch;
|
|
77
|
+
this.defaultFetchPolicy = fetchPolicy;
|
|
78
|
+
this.storage = dataStorage;
|
|
79
|
+
makeAutoObservable(this);
|
|
80
|
+
}
|
|
81
|
+
get isNetworkOnly() {
|
|
82
|
+
return this.defaultFetchPolicy === 'network-only';
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @description содержит реактивные данные
|
|
86
|
+
* благодаря mobx, при изменении isInvalid, свойство будет вычисляться заново,
|
|
87
|
+
* следовательно, стриггерится условие невалидности,
|
|
88
|
+
* и начнется запрос, в результате которого, данные обновятся
|
|
89
|
+
*/
|
|
90
|
+
get data() {
|
|
91
|
+
const shouldSync = this.enabledAutoFetch && !this.isSuccess && !this.isLoading;
|
|
92
|
+
if (this.isInvalid || shouldSync) {
|
|
93
|
+
this.proceedSync();
|
|
94
|
+
}
|
|
95
|
+
// возвращаем имеющиеся данные
|
|
96
|
+
return this.storage.data;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @description флаг загрузки данных
|
|
100
|
+
*/
|
|
101
|
+
get isLoading() {
|
|
102
|
+
return this.auxiliary.isLoading;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
106
|
+
*/
|
|
107
|
+
get isError() {
|
|
108
|
+
return this.auxiliary.isError;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* @description данные о последней ошибке
|
|
112
|
+
*/
|
|
113
|
+
get error() {
|
|
114
|
+
return this.auxiliary.error;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @description флаг обозначающий, что последний запрос был успешно завершен
|
|
118
|
+
*/
|
|
119
|
+
get isSuccess() {
|
|
120
|
+
return this.auxiliary.isSuccess;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/Query/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Query';
|
package/Query/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Query';
|
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MobxQuery';
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MobxQuery';
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astral/mobx-query",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"browser": "./index.js",
|
|
5
|
+
"main": "./index.js",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"mobx": "^6.9.0"
|
|
8
|
+
},
|
|
9
|
+
"author": "Astral.Soft",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/kaluga-astral/mobx-query"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/kaluga-astral/mobx-query/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"types": "./index.d.ts",
|
|
21
|
+
"module": "./index.js",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./index.js"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type SyncParams<TResult, TError, TExecutorParams = void> = {
|
|
2
|
+
onSuccess?: (res: TResult) => void;
|
|
3
|
+
onError?: (e: TError) => void;
|
|
4
|
+
params?: TExecutorParams;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* @description синхронный метод получения данных
|
|
8
|
+
*/
|
|
9
|
+
export type Sync<TResult, TError, TExecutorParams = void> = (params?: SyncParams<TResult, TError, TExecutorParams>) => void;
|
|
10
|
+
/**
|
|
11
|
+
* @description асинхронный метод получения данных
|
|
12
|
+
*/
|
|
13
|
+
export type Async<TResult, TExecutorParams = void> = (params: TExecutorParams) => Promise<TResult>;
|
|
14
|
+
export type QueryBaseActions<TResult, TError, TExecutorParams = void> = {
|
|
15
|
+
/**
|
|
16
|
+
* @description синхронный метод получения данных
|
|
17
|
+
*/
|
|
18
|
+
sync: Sync<TResult, TError, TExecutorParams>;
|
|
19
|
+
/**
|
|
20
|
+
* @description асинхронный метод получения данных
|
|
21
|
+
*/
|
|
22
|
+
async: Async<TResult, TExecutorParams>;
|
|
23
|
+
/**
|
|
24
|
+
* @description флаг обозначающий загрузку данных
|
|
25
|
+
*/
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* @description флаг обозначающий, что последний запрос был зафейлен
|
|
29
|
+
*/
|
|
30
|
+
isError: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* @description данные о последней ошибке
|
|
33
|
+
*/
|
|
34
|
+
error?: TError;
|
|
35
|
+
/**
|
|
36
|
+
* @description флаг обзначающий, что последний запрос был успешно выполнен
|
|
37
|
+
*/
|
|
38
|
+
isSuccess: boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* @description политика получения данных.
|
|
42
|
+
* @variation 'cache-first' - данные сначала берутся из кеша, если их нет, тогда идет обращение к сети, ответ записывается в кэш
|
|
43
|
+
* @kind 'network-only' - данные всегда беруться из сети, при этом ответ записывается в кэш
|
|
44
|
+
*/
|
|
45
|
+
export type FetchPolicy = 'network-only' | 'cache-first';
|
|
46
|
+
/**
|
|
47
|
+
* @description ключ для кешированя
|
|
48
|
+
*/
|
|
49
|
+
export type CacheKey = string | string[] | number | {
|
|
50
|
+
[key: string]: CacheKey;
|
|
51
|
+
};
|
package/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|