@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.
@@ -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';
@@ -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
+ }
@@ -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 {};