synapse-storage 3.0.8 → 3.0.10
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/README.md +281 -221
- package/dist/api.cjs +1 -890
- package/dist/api.js +1 -859
- package/dist/core.cjs +1 -2420
- package/dist/core.js +1 -2387
- package/dist/index.cjs +1 -4179
- package/dist/index.js +1 -4124
- package/dist/react.cjs +1 -267
- package/dist/react.js +1 -238
- package/dist/reactive.cjs +1 -642
- package/dist/reactive.js +1 -603
- package/dist/utils.cjs +1 -600
- package/dist/utils.js +1 -573
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# Synapse Storage
|
|
2
2
|
|
|
3
|
+
Набор инструментов для управления состоянием + API-клиент
|
|
4
|
+
|
|
3
5
|
[](https://badge.fury.io/js/synapse-storage)
|
|
4
6
|
[](https://bundlephobia.com/package/synapse-storage)
|
|
5
7
|
[](https://www.typescriptlang.org/)
|
|
6
|
-
|
|
7
|
-
Synapse — это набор инструментов для управления состоянием + API-клиент.
|
|
8
|
+
[](https://rxjs.dev/)
|
|
8
9
|
|
|
9
10
|
## Особенности
|
|
10
11
|
|
|
@@ -21,18 +22,23 @@ Synapse — это набор инструментов для управлени
|
|
|
21
22
|
|
|
22
23
|
## Автор
|
|
23
24
|
|
|
24
|
-
**Владислав** — Senior Frontend Developer (React, TypeScript)
|
|
25
|
+
**Владислав** — Senior Frontend Developer (React, TypeScript)
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
> ### 🔎 Нахожусь в поиске новых карьерных возможностей!
|
|
28
29
|
>
|
|
29
30
|
> [GitHub](https://github.com/Vlad92msk/) | [LinkedIn](https://www.linkedin.com/in/vlad-firsov/)
|
|
30
31
|
|
|
32
|
+
> ### Подробные примеры:
|
|
33
|
+
>[GitHub](https://github.com/Vlad92msk/synapse-examples)
|
|
34
|
+
> |
|
|
35
|
+
>[YouTube](https://www.youtube.com/channel/UCGENI_i4qmBkPp98P2HvvGw)
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
---
|
|
38
|
+
*PS: Пока не рекоммендую использовать в production т.к разработкой занимаюсь в свободное время.
|
|
39
|
+
Библиотека в целом работает, но дать гарантии смогу после полной интеграции ее в свой пет-проект Социальная сеть.
|
|
40
|
+
Но произойдет это не раньше смены моего текущего места работы и страны проживания*
|
|
41
|
+
---
|
|
36
42
|
|
|
37
43
|
## 📦 Установка
|
|
38
44
|
|
|
@@ -40,8 +46,6 @@ Synapse — это набор инструментов для управлени
|
|
|
40
46
|
npm install synapse-storage
|
|
41
47
|
```
|
|
42
48
|
|
|
43
|
-
### Дополнительные зависимости (по необходимости)
|
|
44
|
-
|
|
45
49
|
```bash
|
|
46
50
|
# Для реактивных возможностей
|
|
47
51
|
npm install rxjs
|
|
@@ -55,7 +59,6 @@ npm install synapse-storage rxjs react react-dom
|
|
|
55
59
|
|
|
56
60
|
## 🚀 Быстрый старт
|
|
57
61
|
|
|
58
|
-
|
|
59
62
|
| Модуль | Описание | Зависимости |
|
|
60
63
|
|--------|-----------------------|-------------|
|
|
61
64
|
| `synapse-storage/core` | Хранилища состояния | Нет |
|
|
@@ -133,159 +136,272 @@ import { useStorageSubscribe, useSelector, createSynapseCtx } from 'synapse-stor
|
|
|
133
136
|
import { createSynapse } from 'synapse-storage/utils'
|
|
134
137
|
```
|
|
135
138
|
|
|
136
|
-
Вот простой пример использования Synapse с React:
|
|
137
|
-
|
|
138
|
-
```tsx
|
|
139
|
-
import { IndexedDBStorage, LocalStorage, MemoryStorage } from 'synapse-storage/core'
|
|
140
|
-
import { useEffect, useState } from 'react'
|
|
141
|
-
|
|
142
|
-
// Создаем экземпляр хранилища (MemoryStorage / LocalStorage / IndexedDBStorage)
|
|
143
|
-
const counterStorage = await new MemoryStorage({
|
|
144
|
-
name: 'counter',
|
|
145
|
-
initialState: { value: 0 }
|
|
146
|
-
}).initialize();
|
|
147
|
-
|
|
148
|
-
function Counter() {
|
|
149
|
-
const [count, setCount] = useState(0);
|
|
150
|
-
|
|
151
|
-
useEffect(() => {
|
|
152
|
-
// Подписываемся на изменения
|
|
153
|
-
return counterStorage.subscribe('value', setCount);
|
|
154
|
-
}, []);
|
|
155
|
-
|
|
156
|
-
const increment = () => {
|
|
157
|
-
counterStorage.update(state => {
|
|
158
|
-
state.value++;
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
return (
|
|
163
|
-
<div>
|
|
164
|
-
<p>Счетчик: {count}</p>
|
|
165
|
-
<button onClick={increment}>Увеличить</button>
|
|
166
|
-
</div>
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
Более подробные примеры можно найти в examples:
|
|
171
|
-
- расширенный пример комплексного использования, включая реактивный подход (full-example)
|
|
172
|
-
- пример использования api-client(api-example.md)
|
|
173
|
-
- пример комбинированного использования хранилищ всех типов, демонтсрация возможностей подписок и работы базовых middlewares(base-storage-example.md)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
## Адаптеры хранилищ
|
|
177
|
-
|
|
178
|
-
Synapse предоставляет три типа адаптеров хранилищ:
|
|
179
139
|
|
|
180
|
-
|
|
140
|
+
## Базовое использование
|
|
181
141
|
|
|
182
|
-
|
|
142
|
+
### Создание зранилищ
|
|
183
143
|
|
|
184
144
|
```typescript
|
|
185
|
-
const
|
|
186
|
-
name: '
|
|
187
|
-
|
|
145
|
+
const counter1 = await new MemoryStorage<Counter>({
|
|
146
|
+
name: 'counter1',
|
|
147
|
+
initialState: {
|
|
148
|
+
value: 100,
|
|
149
|
+
},
|
|
150
|
+
}).initialize()
|
|
188
151
|
```
|
|
189
152
|
|
|
190
|
-
### LocalStorage
|
|
191
|
-
|
|
192
|
-
Хранилище на основе Web Storage API для небольших объемов данных, которые сохраняются между сессиями.
|
|
193
153
|
|
|
194
154
|
```typescript
|
|
195
|
-
const
|
|
196
|
-
name: '
|
|
197
|
-
}
|
|
155
|
+
const counter2 = await new LocalStorage<Counter>({
|
|
156
|
+
name: 'counter2',
|
|
157
|
+
initialState: { value: 100 },
|
|
158
|
+
}).initialize()
|
|
198
159
|
```
|
|
199
160
|
|
|
200
|
-
### IndexedDBStorage
|
|
201
161
|
|
|
202
|
-
Хранилище на основе IndexedDB для больших объемов данных и сложных структур.
|
|
203
|
-
Создается немного иначе, но довольно похожим образом
|
|
204
162
|
```typescript
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
export const { CORE, API } = await IndexedDBStorage.createStorages<{
|
|
209
|
-
CORE: IDBCore
|
|
210
|
-
API: IDBApi
|
|
211
|
-
}>(
|
|
212
|
-
'social-network', // Название базы данных в indexDB
|
|
163
|
+
const { counter3 } = await IndexedDBStorage.createStorages<{ counter3: Counter }>(
|
|
164
|
+
'example1', // Название базы данных в indexDB
|
|
213
165
|
// Таблицы:
|
|
214
166
|
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
167
|
+
counter3: {
|
|
168
|
+
name: 'counter3',
|
|
169
|
+
initialState: { value: 99 },
|
|
218
170
|
// eventEmitter: ,
|
|
219
171
|
// initialState: ,
|
|
220
172
|
// middlewares: ,
|
|
221
173
|
// pluginExecutor: ,
|
|
222
174
|
},
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
name: 'core',
|
|
226
|
-
initialState: {
|
|
227
|
-
currentUserProfile: undefined,
|
|
228
|
-
},
|
|
229
|
-
//...
|
|
230
|
-
},
|
|
231
|
-
// Другие объекты (хранилища)
|
|
232
|
-
},
|
|
233
|
-
console, // logger (может быть любой, который имплементируют интерфейс ILogger)
|
|
175
|
+
// Другие объекты (хранилища в текущей базе данных)
|
|
176
|
+
}
|
|
234
177
|
)
|
|
235
178
|
```
|
|
236
179
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Селекторы предоставляют удобный способ доступа к данным в хранилище:
|
|
240
|
-
|
|
241
|
-
### Базовые подписки на свойства хранилища
|
|
180
|
+
### Способы изменения значений (основные)
|
|
242
181
|
|
|
243
182
|
```typescript
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
})
|
|
183
|
+
const updateCounter1 = async () => {
|
|
184
|
+
await counter1.update((state) => {
|
|
185
|
+
state.value = state.value + 1
|
|
186
|
+
})
|
|
187
|
+
}
|
|
248
188
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
189
|
+
const updateCounter2 = async () => {
|
|
190
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1)
|
|
191
|
+
}
|
|
253
192
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
});
|
|
193
|
+
const updateCounter3 = async () => {
|
|
194
|
+
counter3.set('value', counter3ValueSelectorValue! + 1)
|
|
195
|
+
}
|
|
258
196
|
```
|
|
259
197
|
|
|
260
|
-
### Селекторы для вычисляемых значений (в стиле Redux)
|
|
261
198
|
|
|
199
|
+
### Создание подписок
|
|
200
|
+
|
|
201
|
+
> **💡 Совет:**
|
|
202
|
+
При создании подписок с помощью subscribe или subscribeToAll лучше не забывать вызывать функцию отписки
|
|
203
|
+
>
|
|
204
|
+
```jsx
|
|
205
|
+
const [counter1Value, setCounter1Value] = useState(0)
|
|
206
|
+
const [counter2Value, setCounter2Value] = useState(0)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
// Подписка через колбэк
|
|
211
|
+
counter1.subscribe((state) => state.value, (value) => {
|
|
212
|
+
setCounter1Value(value)
|
|
213
|
+
})
|
|
214
|
+
// Подписка через путь (может быть типа 'user.settings.theme')
|
|
215
|
+
counter2.subscribe('value', (value) => {
|
|
216
|
+
setCounter2Value(value)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
// Подписка на все события
|
|
220
|
+
counter1.subscribeToAll((event) => {
|
|
221
|
+
console.log('event', event)
|
|
222
|
+
// Здесь мы получим объект:
|
|
223
|
+
// changedPaths:['value'] // все пути по которым были вызваны изменения (['prop1.prop2', 'prop44.prop.555.prop.666'])
|
|
224
|
+
// key:['value'] // Корневые ключи в хранилище которые вкоторых были изменения
|
|
225
|
+
// type:"storage:update" // Тип операции
|
|
226
|
+
// value: {value: 101} // Новый state
|
|
227
|
+
})
|
|
228
|
+
}, [])
|
|
229
|
+
// Для React через специальный селектор
|
|
230
|
+
const counter3Value = useStorageSubscribe(counter3, (state) => state.value)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Создание вычисляемых подписок в стиле Redux
|
|
262
234
|
```typescript
|
|
263
|
-
|
|
264
|
-
const
|
|
235
|
+
const counter1Selector = new SelectorModule(counter1)
|
|
236
|
+
const counter2Selector = new SelectorModule(counter2)
|
|
237
|
+
const counter3Selector = new SelectorModule(counter3)
|
|
265
238
|
|
|
266
|
-
|
|
267
|
-
const
|
|
239
|
+
const counter1ValueSelector = counter1Selector.createSelector((s) => s.value)
|
|
240
|
+
const counter2ValueSelector = counter2Selector.createSelector((s) => s.value)
|
|
241
|
+
const counter3ValueSelector = counter3Selector.createSelector((s) => s.value)
|
|
268
242
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
count => count * 2,
|
|
243
|
+
const sum = counter3Selector.createSelector(
|
|
244
|
+
[counter1ValueSelector, counter2ValueSelector, counter3ValueSelector],
|
|
245
|
+
(a,b,c) => a + b + c,
|
|
273
246
|
// Опционально:
|
|
274
247
|
// {
|
|
275
248
|
// equals: , // Функция сравнения
|
|
276
249
|
// name: 'doubledCountSelector' // Имя селектора
|
|
277
250
|
// }
|
|
278
|
-
)
|
|
251
|
+
)
|
|
252
|
+
```
|
|
279
253
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
});
|
|
254
|
+
### Получение значений из вычисляемых селекторов
|
|
255
|
+
```jsx
|
|
256
|
+
// Нативный способ
|
|
284
257
|
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
258
|
+
// Единоразовое получение
|
|
259
|
+
const sumValueSelector = sum.select().then(value => value)
|
|
260
|
+
|
|
261
|
+
// Подписка на изменение
|
|
262
|
+
counter2ValueSelector.subscribe({
|
|
263
|
+
notify: (value) => {
|
|
264
|
+
console.log('counter2ValueSelector', value)
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
counter3ValueSelector.subscribe({
|
|
269
|
+
notify: (value) => {
|
|
270
|
+
console.log('counter3ValueSelector', value)
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Для React через специальный селектор
|
|
275
|
+
const counter1ValueSelectorValue = useSelector(counter1ValueSelector)
|
|
276
|
+
const counter2ValueSelectorValue = useSelector(counter2ValueSelector)
|
|
277
|
+
const counter3ValueSelectorValue = useSelector(counter3ValueSelector,
|
|
278
|
+
// Можно указать доп опции
|
|
279
|
+
{
|
|
280
|
+
initialValue: 99,
|
|
281
|
+
withLoading: true,
|
|
282
|
+
equals: (a, b) => a !== b
|
|
283
|
+
})
|
|
284
|
+
// Тогда получать значение так
|
|
285
|
+
counter3ValueSelectorValue.data
|
|
286
|
+
counter3ValueSelectorValue.isLoading
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
### Middlewares
|
|
291
|
+
|
|
292
|
+
> **💡 Важно:**
|
|
293
|
+
Порядок имеет значение!<br/>
|
|
294
|
+
Action → BroadcastMiddleware → ShallowCompare → Batching → Base Operation
|
|
295
|
+
>
|
|
296
|
+
```typescript
|
|
297
|
+
const counter1 = await new MemoryStorage<Counter>({
|
|
298
|
+
name: 'counter1',
|
|
299
|
+
initialState: {
|
|
300
|
+
value: 100,
|
|
301
|
+
},
|
|
302
|
+
middlewares: () => {
|
|
303
|
+
const broadcast = broadcastMiddleware({
|
|
304
|
+
storageType: 'memory', // <-- Важно правильно указывать тип хранилища
|
|
305
|
+
storageName: 'counter1' // <-- Желательно правильно указывать имя хранилища
|
|
306
|
+
})
|
|
307
|
+
return [broadcast]
|
|
308
|
+
}
|
|
309
|
+
}).initialize()
|
|
310
|
+
|
|
311
|
+
const counter2 = await new LocalStorage<Counter>({
|
|
312
|
+
name: 'counter2',
|
|
313
|
+
initialState: { value: 100 },
|
|
314
|
+
middlewares: (getDefaultMiddleware) => {
|
|
315
|
+
const { shallowCompare } = getDefaultMiddleware()
|
|
316
|
+
|
|
317
|
+
const broadcast = broadcastMiddleware({
|
|
318
|
+
storageType: 'localStorage',
|
|
319
|
+
storageName: 'counter2'
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
return [broadcast, shallowCompare()]
|
|
323
|
+
}
|
|
324
|
+
}).initialize()
|
|
325
|
+
|
|
326
|
+
const { counter3 } = await IndexedDBStorage.createStorages<{ counter3: Counter }>(
|
|
327
|
+
'example1', {
|
|
328
|
+
counter3: {
|
|
329
|
+
name: 'counter3',
|
|
330
|
+
initialState: { value: 99 },
|
|
331
|
+
middlewares: (getDefaultMiddleware) => {
|
|
332
|
+
const { batching } = getDefaultMiddleware()
|
|
333
|
+
|
|
334
|
+
const broadcast = broadcastMiddleware({
|
|
335
|
+
storageType: 'indexedDB',
|
|
336
|
+
storageName: 'counter3'
|
|
337
|
+
})
|
|
338
|
+
return [
|
|
339
|
+
broadcast,
|
|
340
|
+
batching({
|
|
341
|
+
batchSize: 20,
|
|
342
|
+
batchDelay: 200
|
|
343
|
+
})
|
|
344
|
+
]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Поверхностное сравнение
|
|
353
|
+
const updateCounter2 = async () => {
|
|
354
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1) // Это будет применено
|
|
355
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1) // |
|
|
356
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1) // | Не будут вызваны так как payload не изменился
|
|
357
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1) // |
|
|
358
|
+
await counter2.set('value', counter2ValueSelectorValue! + 1) // |
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Батчинг
|
|
362
|
+
// !! работает только для методов без await
|
|
363
|
+
const updateCounter3 = async () => {
|
|
364
|
+
counter3.set('value', counter3ValueSelectorValue! + 1) // | игнорируется
|
|
365
|
+
counter3.set('value', counter3ValueSelectorValue! + 1) // | игнорируется
|
|
366
|
+
counter3.set('value', counter3ValueSelectorValue! + 1) // | игнорируется
|
|
367
|
+
counter3.set('value', counter3ValueSelectorValue! + 1) // | игнорируется
|
|
368
|
+
counter3.set('value', counter3ValueSelectorValue! + 10)// | < --- будет применено только это
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Иное
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const counter1 = await new MemoryStorage<Counter>({
|
|
376
|
+
name: 'counter1',
|
|
377
|
+
initialState: {
|
|
378
|
+
value: 100,
|
|
379
|
+
},
|
|
380
|
+
middlewares: () => {
|
|
381
|
+
const broadcast = broadcastMiddleware({
|
|
382
|
+
storageType: 'memory',
|
|
383
|
+
storageName: 'counter1'
|
|
384
|
+
})
|
|
385
|
+
return [broadcast]
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
undefined, // Менеджер плагинов имплементирующий IPluginExecutor
|
|
389
|
+
{
|
|
390
|
+
emit: async (event: StorageEvent) => { // любой EventEmitter имплементирующий IEventEmitter
|
|
391
|
+
console.log('event', event)
|
|
392
|
+
// event будет содержать следующую инф:
|
|
393
|
+
// type: "storage:update"
|
|
394
|
+
// metadata:
|
|
395
|
+
// storageName:"counter1"
|
|
396
|
+
// timestamp: 1748581282102
|
|
397
|
+
// payload:
|
|
398
|
+
// changedPaths:['value']
|
|
399
|
+
// key: ['value']
|
|
400
|
+
// state: {value: 101}
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
console // любой логгер имплементирующий ILogger
|
|
404
|
+
).initialize()
|
|
289
405
|
```
|
|
290
406
|
|
|
291
407
|
## API-клиент
|
|
@@ -411,11 +527,10 @@ export function createPokemonDispatcher(storage: PokemonStorage) {
|
|
|
411
527
|
storage,
|
|
412
528
|
middlewares: [loggerMiddleware, alertM],
|
|
413
529
|
}, (storage, { createWatcher, createAction }) => ({
|
|
414
|
-
// watcher
|
|
530
|
+
// watcher`s
|
|
415
531
|
watchCurrentId: createWatcher({...}),
|
|
416
|
-
//
|
|
532
|
+
// сСобытия
|
|
417
533
|
loadPokemon: createAction<number, { id: number }>({...}),
|
|
418
|
-
|
|
419
534
|
loadPokemonRequest: createAction<number, { id: number }>({...}),
|
|
420
535
|
// Успешное получение данных
|
|
421
536
|
success: createAction<{ data?: Pokemon}, { data?: Pokemon }>({...}, {
|
|
@@ -458,17 +573,26 @@ import { PokemonDispatcher } from '../pokemon.dispatcher'
|
|
|
458
573
|
import { Pokemon, PokemonState } from '../types'
|
|
459
574
|
|
|
460
575
|
// Определяем типы для наших эффектов
|
|
461
|
-
type
|
|
462
|
-
|
|
576
|
+
type DispatcherType = {
|
|
577
|
+
pokemonDispatcher: PokemonDispatcher
|
|
578
|
+
}
|
|
579
|
+
type ApiType = {
|
|
580
|
+
pokemonApi: typeof pokemonEndpoints
|
|
581
|
+
}
|
|
582
|
+
type ExternalStorages = {
|
|
583
|
+
core$: typeof coreSynapseIDB.state$
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
type Effect = ReturnType<typeof createEffect<
|
|
587
|
+
AboutUserUserInfo, // Тип текущего хранилища
|
|
588
|
+
DispatcherType, // Типы диспетчеров
|
|
589
|
+
ApiType, // Типы api
|
|
590
|
+
Record<string, void>, // Тип конфигурации
|
|
591
|
+
ExternalStorages // Типы внешних хранилищ потоков
|
|
592
|
+
>>
|
|
463
593
|
|
|
464
594
|
// Эффект для навигации
|
|
465
|
-
export const navigationEffect = createEffect
|
|
466
|
-
PokemonState,
|
|
467
|
-
PokemonDispatcherType,
|
|
468
|
-
PokemonApiType,
|
|
469
|
-
AppConfig,
|
|
470
|
-
any //ExternalStorages
|
|
471
|
-
>((action$, state$, externalStorages, { pokemonDispatcher }, _, config) =>
|
|
595
|
+
export const navigationEffect: Effect = createEffect((action$, state$, externalStorages, { pokemonDispatcher }, _, config) =>
|
|
472
596
|
action$.pipe(
|
|
473
597
|
ofTypes([pokemonDispatcher.dispatch.next, pokemonDispatcher.dispatch.prev]),
|
|
474
598
|
switchMap((action) => {
|
|
@@ -479,33 +603,21 @@ export const navigationEffect = createEffect<
|
|
|
479
603
|
)
|
|
480
604
|
|
|
481
605
|
// Эффект для отслеживания изменений ID
|
|
482
|
-
export const watchIdEffect = createEffect
|
|
483
|
-
PokemonState,
|
|
484
|
-
PokemonDispatcherType,
|
|
485
|
-
PokemonApiType,
|
|
486
|
-
AppConfig,
|
|
487
|
-
any //ExternalStorages
|
|
488
|
-
>((action$, state$, externalStorages, { pokemonDispatcher }) =>
|
|
606
|
+
export const watchIdEffect: Effect = createEffect((action$, state$, externalStorages, { pokemonDispatcher }) =>
|
|
489
607
|
action$.pipe(
|
|
490
608
|
ofType(pokemonDispatcher.watchers.watchCurrentId),
|
|
491
609
|
withLatestFrom(
|
|
492
610
|
selectorMap(state$,
|
|
611
|
+
(state) => state.value
|
|
493
612
|
//... selectors
|
|
494
613
|
),
|
|
495
614
|
),
|
|
496
|
-
// tap(([action, [loading, currentId]]) => {...}),
|
|
497
615
|
mapTo(null),
|
|
498
616
|
),
|
|
499
617
|
)
|
|
500
618
|
|
|
501
619
|
// Эффект для загрузки данных покемона
|
|
502
|
-
export const loadPokemonEffect = createEffect
|
|
503
|
-
PokemonState,
|
|
504
|
-
PokemonDispatcherType,
|
|
505
|
-
PokemonApiType,
|
|
506
|
-
AppConfig,
|
|
507
|
-
any //ExternalStorages
|
|
508
|
-
>((
|
|
620
|
+
export const loadPokemonEffect: Effect = createEffect((
|
|
509
621
|
action$, // Поток событий
|
|
510
622
|
state$, // Поток состояния
|
|
511
623
|
externalStorages, // Потоки внешних хранилищ
|
|
@@ -678,16 +790,21 @@ type CurrentDispatchers = {
|
|
|
678
790
|
type CurrentApis = {
|
|
679
791
|
userInfoAPi: typeof userInfoEndpoints
|
|
680
792
|
};
|
|
793
|
+
type ExternalStorages = {
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
type Effect = ReturnType<typeof createEffect<
|
|
797
|
+
AboutUserUserInfo, // Тип текущего хранилища
|
|
798
|
+
CurrentDispatchers, // Типы диспетчеров
|
|
799
|
+
CurrentApis, // Типы api
|
|
800
|
+
Record<string, void>, // Тип конфигурации
|
|
801
|
+
ExternalStorages // Типы внешних хранилищ потоков
|
|
802
|
+
>>
|
|
681
803
|
|
|
682
804
|
/**
|
|
683
805
|
* Добавляем полученный профиль пользователя в текущий СТор
|
|
684
806
|
*/
|
|
685
|
-
const loadUserInfoById = createEffect
|
|
686
|
-
AboutUserUserInfo,
|
|
687
|
-
CurrentDispatchers,
|
|
688
|
-
CurrentApis,
|
|
689
|
-
any
|
|
690
|
-
>((action$, state$, { userInfoDispatcher, coreIdbDispatcher }) => action$.pipe(
|
|
807
|
+
const loadUserInfoById: Effect = createEffect((action$, state$, { userInfoDispatcher, coreIdbDispatcher }) => action$.pipe(
|
|
691
808
|
// Подписываемся на изменения в стороннем Synapse
|
|
692
809
|
ofType(coreIdbDispatcher.watchers.watchCurrentUserProfile),
|
|
693
810
|
map((s) => {
|
|
@@ -697,12 +814,7 @@ const loadUserInfoById = createEffect<
|
|
|
697
814
|
}),
|
|
698
815
|
))
|
|
699
816
|
|
|
700
|
-
const updateUserProfile = createEffect
|
|
701
|
-
AboutUserUserInfo,
|
|
702
|
-
CurrentDispatchers,
|
|
703
|
-
CurrentApis,
|
|
704
|
-
any
|
|
705
|
-
>((action$, state$, { userInfoDispatcher }, { userInfoAPi }) => action$.pipe(
|
|
817
|
+
const updateUserProfile: Effect = createEffect((action$, state$, { userInfoDispatcher }, { userInfoAPi }) => action$.pipe(
|
|
706
818
|
ofType(userInfoDispatcher.dispatch.submit),
|
|
707
819
|
validateMap({
|
|
708
820
|
// Валидация перед запросом
|
|
@@ -768,6 +880,10 @@ export const userInfoSynapse = await createSynapse({
|
|
|
768
880
|
createDispatcherFn: createUserInfoDispatcher,
|
|
769
881
|
// Функция создания селекторов (Опционально)
|
|
770
882
|
createSelectorsFn: createUserInfoSelectors,
|
|
883
|
+
// Внешние селекторы (Опционально)
|
|
884
|
+
externalSelectors: {
|
|
885
|
+
// externalSelectors1: ...
|
|
886
|
+
},
|
|
771
887
|
// Конфигурация для эффектов (Опционально)
|
|
772
888
|
createEffectConfig: (userInfoDispatcher) => ({
|
|
773
889
|
// Диспетчеры для эффектов
|
|
@@ -815,77 +931,21 @@ export const {
|
|
|
815
931
|
)
|
|
816
932
|
```
|
|
817
933
|
|
|
818
|
-
Вы можете связывать Synapse между собой
|
|
819
|
-
|
|
820
|
-
```typescript
|
|
821
|
-
// core.synapse.ts
|
|
822
|
-
export const coreSynapseIDB = await createSynapse({
|
|
823
|
-
storage: CORE,
|
|
824
|
-
createSelectorsFn: (selectorModule) => {
|
|
825
|
-
const currentUserProfile = selectorModule.createSelector((s) => s.currentUserProfile, { name: 'currentUserProfile' })
|
|
826
|
-
|
|
827
|
-
return ({
|
|
828
|
-
currentUserProfile,
|
|
829
|
-
})
|
|
830
|
-
},
|
|
831
|
-
createDispatcherFn: createCoreDispatcher,
|
|
832
|
-
})
|
|
833
|
-
|
|
834
|
-
// user-info.synapse.ts
|
|
835
|
-
import { createSynapse } from 'synapse-storage/utils'
|
|
836
|
-
import { coreSynapseIDB } from '../core/core.synapse'
|
|
837
|
-
|
|
838
|
-
export const userInfoSynapse = await createSynapse({
|
|
839
|
-
// Передаем внешие селекторы
|
|
840
|
-
externalSelectors: {
|
|
841
|
-
coreSelectors: coreSynapseIDB.selectors
|
|
842
|
-
},
|
|
843
|
-
// TypeScript подскажет интерфейс
|
|
844
|
-
// createSelectorsFn: (currentSelectorModule, { coreSelectors }) => {...},
|
|
845
|
-
})
|
|
846
|
-
```
|
|
847
|
-
|
|
848
934
|
Таким образом вы можете резделить функционал на слои
|
|
849
935
|
|
|
850
936
|
|
|
851
937
|
---
|
|
852
938
|
|
|
853
939
|
|
|
854
|
-
Полноценный рабочий пример можно найти в папке src/examples/pokemons
|
|
855
|
-
Там показано еще больше возможностей которые дает этот подход.
|
|
856
940
|
|
|
857
|
-
##
|
|
941
|
+
## Создание пользовательского middleware
|
|
858
942
|
|
|
859
943
|
Synapse предоставляет две системы расширения функциональности: middleware и плагины. Они выполняют разные роли и имеют разную область применения.
|
|
860
944
|
|
|
861
|
-
### Middleware
|
|
862
|
-
|
|
863
945
|
Middleware в Synapse работают по принципу "цепочки обработчиков" и позволяют перехватывать любые операции хранилища. Каждое middleware может модифицировать действия до и после их обработки базовым хранилищем.
|
|
864
946
|
|
|
865
|
-
```typescript
|
|
866
|
-
const storage = await new MemoryStorage({
|
|
867
|
-
name: 'appState',
|
|
868
|
-
middlewares: (getDefaultMiddleware) => {
|
|
869
|
-
const { shallowCompare, batching } = getDefaultMiddleware();
|
|
870
|
-
return [
|
|
871
|
-
// Синхронизация между вкладками браузера
|
|
872
|
-
broadcastMiddleware({
|
|
873
|
-
storageName: 'appState',
|
|
874
|
-
storageType: 'memory',
|
|
875
|
-
}),
|
|
876
|
-
// Предотвращает ненужные обновления при одинаковых значениях
|
|
877
|
-
shallowCompare(),
|
|
878
|
-
// Группирует операции для оптимизации
|
|
879
|
-
batching({
|
|
880
|
-
batchSize: 10, // Максимальное количество операций в батче
|
|
881
|
-
batchDelay: 300, // Задержка перед обработкой батча (мс)
|
|
882
|
-
}),
|
|
883
|
-
];
|
|
884
|
-
},
|
|
885
|
-
}).initialize();
|
|
886
|
-
```
|
|
887
947
|
|
|
888
|
-
|
|
948
|
+
### Порядок выполнения middleware
|
|
889
949
|
|
|
890
950
|
Middleware выполняются в порядке их объявления в массиве:
|
|
891
951
|
1. Действие проходит через все middleware сверху вниз
|