synapse-storage 4.0.0 β†’ 4.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/README.md CHANGED
@@ -1,747 +1,50 @@
1
1
  # Synapse Storage
2
2
 
3
- > **πŸ‡ΊπŸ‡Έ English** | [πŸ“ ChangeLog](./CHANGELOG.md)
4
-
5
- State management toolkit + API client
6
-
7
3
  [![npm version](https://badge.fury.io/js/synapse-storage.svg)](https://badge.fury.io/js/synapse-storage)
8
4
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/synapse-storage)](https://bundlephobia.com/package/synapse-storage)
9
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/)
10
6
  [![RxJS Version](https://img.shields.io/badge/RxJS-%5E7.8.2-red?logo=reactivex)](https://rxjs.dev/)
11
7
 
12
- ## Key Features
13
-
14
- - **Framework Agnostic** β€” works with any framework or standalone
15
- - **Sync & Async Storage** β€” Memory/LocalStorage (fully synchronous) and IndexedDB (async) with type-safe separation
16
- - **Selectors** β€” memoized computed values with dependency tracking (like Reselect)
17
- - **Subscriptions** β€” subscribe to nested paths via selector functions
18
- - **Immer-like Updates** β€” mutate state directly inside `update()` callbacks
19
- - **API Client** β€” HTTP client with caching, tags, and invalidation (like RTK Query)
20
- - **React Integration** β€” hooks built on `useSyncExternalStore` (Concurrent Mode safe)
21
- - **RxJS Reactive** β€” Redux-Observable style effects, dispatchers, and watchers
22
- - **Middleware & Plugins** β€” separate sync/async systems for extending storage behavior
23
- - **Singleton Support** β€” shared storage instances across components with merge strategies
24
- - **EventBus** β€” decoupled inter-module communication with wildcards and history
25
- - **Cross-tab Sync** β€” BroadcastChannel middleware for multi-tab state synchronization
26
-
27
- ---
28
-
29
- ## Author
30
-
31
- **Vladislav** β€” Senior Frontend Developer (React, TypeScript)
32
-
33
- [GitHub](https://github.com/Vlad92msk/) | [LinkedIn](https://www.linkedin.com/in/vlad-firsov/)
8
+ Framework-agnostic state management toolkit and API client for TypeScript applications.
9
+ Combines reactive storage, memoized selectors, Redux-Observable style effects, and a tag-based HTTP cache β€” all in one library.
34
10
 
35
- ---
36
- *PS: Not recommended for production use yet as I develop this in my free time.
37
- The library works in general, but I can provide guarantees only after full integration into my pet project - Social Network.
38
- This won't happen before changing my current workplace and country of residence*
39
-
40
- ---
41
-
42
- ## Installation
11
+ ## Quick Start
43
12
 
44
13
  ```bash
45
14
  npm install synapse-storage
46
15
  ```
47
16
 
48
- ```bash
49
- # For reactive capabilities
50
- npm install rxjs
51
-
52
- # For React integration
53
- npm install react react-dom
54
- ```
55
-
56
- | Module | Description | Dependencies |
57
- |--------|-------------|--------------|
58
- | `synapse-storage/core` | Storage, selectors, middleware, plugins | β€” |
59
- | `synapse-storage/react` | React hooks and context utilities | React 18+ |
60
- | `synapse-storage/reactive` | Dispatcher, effects, watchers | RxJS 7.8.2+ |
61
- | `synapse-storage/api` | HTTP client with caching | β€” |
62
- | `synapse-storage/utils` | createSynapse, EventBus, awaiter | β€” |
63
-
64
- > Import only the modules you need β€” each works independently.
65
-
66
- ### tsconfig.json
67
-
68
- ```json
69
- {
70
- "compilerOptions": {
71
- "target": "ES2022",
72
- "module": "ES2022",
73
- "moduleResolution": "bundler"
74
- }
75
- }
76
- ```
77
-
78
- ---
79
-
80
- ## Quick Start
81
-
82
17
  ```typescript
83
18
  import { MemoryStorage } from 'synapse-storage/core'
84
-
85
- const storage = new MemoryStorage({
86
- name: 'counter',
87
- initialState: { count: 0, user: { name: 'Anonymous' } },
88
- })
89
- await storage.initialize()
90
-
91
- // Read
92
- storage.getState() // { count: 0, user: { name: 'Anonymous' } }
93
- storage.get('count') // 0
94
-
95
- // Write
96
- storage.set('count', 1)
97
-
98
- // Immer-like update (multiple mutations = one notification)
99
- storage.update((state) => {
100
- state.count += 1
101
- state.user.name = 'Alice'
102
- })
103
-
104
- // Subscribe to nested path
105
- const unsub = storage.subscribe(
106
- (s) => s.user.name,
107
- (name) => console.log('Name changed:', name)
108
- )
109
-
110
- // Reset to initialState
111
- storage.reset()
112
- ```
113
-
114
- ---
115
-
116
- ## Storage Types
117
-
118
- Synapse has two storage categories with **type-safe separation**:
119
-
120
- ### Sync Storage (MemoryStorage, LocalStorage)
121
-
122
- All operations are synchronous β€” `get()`, `set()`, `update()`, `getState()` return values directly.
123
-
124
- ```typescript
125
- import { MemoryStorage, LocalStorage } from 'synapse-storage/core'
126
-
127
- const memory = new MemoryStorage<State>({ name: 'app', initialState })
128
- const local = new LocalStorage<State>({ name: 'app', initialState })
129
-
130
- await memory.initialize()
131
- const value = memory.get('key') // T β€” sync
132
- ```
133
-
134
- ### Async Storage (IndexedDBStorage)
135
-
136
- Operations return Promises β€” persistent browser storage.
137
-
138
- ```typescript
139
- import { IndexedDBStorage } from 'synapse-storage/core'
140
-
141
- const idb = new IndexedDBStorage<State>({
142
- name: 'app',
143
- initialState,
144
- options: { dbName: 'my_app_db' },
145
- })
146
-
147
- await idb.initialize()
148
- const value = await idb.get('key') // Promise<T>
149
- ```
150
-
151
- ### getStateSync()
152
-
153
- Available on **all** storage types β€” returns the cached state synchronously, even for IndexedDB:
154
-
155
- ```typescript
156
- const state = storage.getStateSync() // always sync
157
- ```
158
-
159
- ### Static Factory Methods
160
-
161
- Every storage class has a `.create()` static method:
162
-
163
- ```typescript
164
- const storage = MemoryStorage.create<State>({ name: 'app', initialState })
165
- const storage = LocalStorage.create<State>({ name: 'app', initialState })
166
- const storage = IndexedDBStorage.create<State>({ name: 'app', initialState, options: {} })
167
- ```
168
-
169
- ### StorageFactory
170
-
171
- Universal factory with type-safe overloads:
172
-
173
- ```typescript
174
- import { StorageFactory } from 'synapse-storage/core'
175
-
176
- // Typed factories
177
- const mem = StorageFactory.createMemory<S>({ name: 'x', initialState })
178
- const loc = StorageFactory.createLocal<S>({ name: 'x', initialState })
179
- const idb = StorageFactory.createIndexedDB<S>({ name: 'x', initialState, options: {} })
180
-
181
- // Universal β€” return type depends on `type`
182
- const storage = StorageFactory.create<S>({
183
- type: 'memory', // β†’ ISyncStorage<S>
184
- name: 'x',
185
- initialState,
186
- })
187
- ```
188
-
189
- ---
190
-
191
- ## Reading & Writing Data
192
-
193
- ### Reading
194
-
195
- ```typescript
196
- storage.get('key') // value by key
197
- storage.getState() // full state
198
- storage.getStateSync() // sync cache (all storage types)
199
- storage.has('key') // boolean
200
- storage.keys() // string[]
201
- ```
202
-
203
- ### Writing
204
-
205
- ```typescript
206
- storage.set('key', value) // set single key
207
- storage.update((s) => { s.count++ }) // Immer-like mutations
208
- storage.remove('key') // delete key
209
- storage.reset() // restore initialState
210
- storage.clear() // reset to {}
211
- ```
212
-
213
- > For IndexedDB, all write operations return `Promise`.
214
-
215
- ---
216
-
217
- ## Subscriptions
218
-
219
- ```typescript
220
- // Subscribe by key
221
- const unsub = storage.subscribe('count', (newValue) => {
222
- console.log('count:', newValue)
223
- })
224
-
225
- // Subscribe by selector function (nested paths)
226
- const unsub = storage.subscribe(
227
- (state) => state.user.name,
228
- (name) => console.log('name:', name)
229
- )
230
-
231
- // Subscribe to all changes
232
- const unsub = storage.subscribeToAll((event) => {
233
- // event.type: 'set' | 'update' | 'remove' | 'clear' | 'reset'
234
- // event.key, event.changedPaths
235
- })
236
- ```
237
-
238
- ---
239
-
240
- ## Selector System
241
-
242
- Memoized computed values with dependency tracking:
243
-
244
- ```typescript
245
- import { SelectorModule } from 'synapse-storage/core'
246
-
247
- const sm = new SelectorModule(storage)
248
-
249
- // Simple selector
250
- const count = sm.createSelector((state) => state.count)
251
-
252
- // With custom equality
253
- const items = sm.createSelector(
254
- (state) => state.items,
255
- { equals: (a, b) => JSON.stringify(a) === JSON.stringify(b) }
256
- )
257
-
258
- // Dependent selector (recalculates only when deps change)
259
- const filtered = sm.createSelector(
260
- [items, filter],
261
- (itemsVal, filterVal) => itemsVal.filter(i => i.type === filterVal)
262
- )
263
-
264
- // Usage
265
- const value = filtered.select()
266
- const unsub = filtered.subscribe({ notify: (value) => console.log(value) })
267
- ```
268
-
269
- ---
270
-
271
- ## Middleware System
272
-
273
- Separate sync and async middleware for each storage type:
274
-
275
- ```typescript
276
- const storage = new MemoryStorage<State>({
277
- name: 'store',
278
- initialState,
279
- middlewares: (getDefault) => [
280
- // Batch rapid writes
281
- getDefault().batching({ batchSize: 5, batchDelay: 100 }),
282
-
283
- // Skip updates if value unchanged
284
- getDefault().shallowCompare(),
285
- ],
286
- })
287
- ```
288
-
289
- ### Cross-tab Synchronization
290
-
291
- ```typescript
292
- import { syncBroadcastMiddleware } from 'synapse-storage/core'
293
-
294
- const storage = new MemoryStorage<State>({
295
- name: 'store',
296
- initialState,
297
- middlewares: () => [
298
- syncBroadcastMiddleware({ storageName: 'store', storageType: 'memory' }),
299
- ],
300
- })
301
- ```
302
-
303
- ---
304
-
305
- ## Plugin System
306
-
307
- Lifecycle hooks for intercepting storage operations:
308
-
309
- ```typescript
310
- import { ISyncStoragePlugin, SyncStoragePluginModule } from 'synapse-storage/core'
311
-
312
- class TimestampPlugin implements ISyncStoragePlugin {
313
- name = 'timestamp'
314
-
315
- async initialize() {}
316
- async destroy() {}
317
-
318
- onBeforeSet<T>(value: T, context): T { return value }
319
- onAfterSet<T>(key, value: T, ctx): T { return value }
320
- onBeforeGet(key, ctx) { return key }
321
- onAfterGet<T>(key, value: T | undefined, ctx) { return value }
322
- onBeforeDelete(key, ctx): boolean { return true } // false = block
323
- onAfterDelete(key, ctx) {}
324
- onClear(ctx) {}
325
- }
326
-
327
- const plugins = new SyncStoragePluginModule(undefined, undefined, 'store')
328
- await plugins.add(new TimestampPlugin())
329
-
330
- const storage = new MemoryStorage<State>(
331
- { name: 'store', initialState },
332
- plugins
333
- )
334
- ```
335
-
336
- > For IndexedDB, use `IAsyncStoragePlugin` and `AsyncStoragePluginModule`.
337
-
338
- ---
339
-
340
- ## React Integration
341
-
342
- Hooks are built on `useSyncExternalStore` β€” safe in Concurrent Mode, no tearing.
343
-
344
- ### useCreateStorage
345
-
346
- Returns a **discriminated union**: when `isReady: true`, `storage` is guaranteed non-null.
347
-
348
- ```tsx
349
- import { useCreateStorage } from 'synapse-storage/react'
350
-
351
- function App() {
352
- const { storage, isReady } = useCreateStorage<State>({
353
- type: 'memory', // 'memory' | 'localStorage' β†’ ISyncStorage
354
- name: 'app', // 'indexedDB' β†’ IAsyncStorage
355
- initialState: { count: 0 },
356
- })
357
-
358
- if (!isReady) return <div>Loading...</div>
359
- // storage is ISyncStorage<State> here (not null)
360
- }
361
- ```
362
-
363
- ### useStorageSubscribe
364
-
365
- ```tsx
366
- import { useStorageSubscribe } from 'synapse-storage/react'
367
-
368
- function Counter() {
369
- const count = useStorageSubscribe(storage, (s) => s.count)
370
- const summary = useStorageSubscribe(storage, (s) => `Total: ${s.count}`)
371
- return <div>{count} β€” {summary}</div>
372
- }
373
- ```
374
-
375
- ### useSelector
376
-
377
- ```tsx
378
- import { useSelector } from 'synapse-storage/react'
379
-
380
- function ItemList() {
381
- const items = useSelector(filteredItemsSelector)
382
- return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>
383
- }
384
- ```
385
-
386
- ### createSynapseCtx
387
-
388
- Context-based pattern for sharing synapse across component tree:
389
-
390
- ```tsx
391
- import { createSynapseCtx, useSelector } from 'synapse-storage/react'
392
-
393
- const {
394
- contextSynapse,
395
- useSynapseStorage,
396
- useSynapseSelectors,
397
- useSynapseActions,
398
- cleanupSynapse,
399
- } = createSynapseCtx(storePromise, {
400
- loadingComponent: <div>Loading...</div>,
401
- })
402
-
403
- const Page = contextSynapse(() => {
404
- const selectors = useSynapseSelectors()
405
- const actions = useSynapseActions()
406
- const count = useSelector(selectors.count)
407
-
408
- return <button onClick={() => actions.increment()}>Count: {count}</button>
409
- })
410
- ```
411
-
412
- ### awaitSynapse
413
-
414
- HOC and hook for waiting on synapse initialization:
415
-
416
- ```tsx
417
- import { awaitSynapse } from 'synapse-storage/react'
418
-
419
- const awaiter = awaitSynapse(storePromise, {
420
- loadingComponent: <div>Loading...</div>,
421
- errorComponent: (error) => <div>Error: {error.message}</div>,
422
- })
423
-
424
- // HOC
425
- const ReadyComponent = awaiter.withSynapseReady(MyComponent)
426
-
427
- // Hook
428
- function Status() {
429
- const { isReady, isPending, isError, store } = awaiter.useSynapseReady()
430
- if (isPending) return <div>Loading...</div>
431
- if (isError) return <div>Error</div>
432
- return <div>Ready</div>
433
- }
434
-
435
- // Programmatic (also works outside React)
436
- awaiter.isReady() // boolean
437
- awaiter.getStatus() // 'pending' | 'ready' | 'error'
438
- await awaiter.waitForReady() // Promise<Store>
439
- awaiter.onReady((store) => { /* ... */ })
440
- ```
441
-
442
- ---
443
-
444
- ## Reactive Features (RxJS)
445
-
446
- ### Dispatcher β€” Actions & Watchers
447
-
448
- ```typescript
449
- import { createDispatcher, createAction, createWatcher } from 'synapse-storage/reactive'
450
-
451
- const dispatcher = createDispatcher(
452
- { storage },
453
- (_storage, { createAction, createWatcher }) => {
454
- const increment = createAction({
455
- type: 'increment',
456
- action: () => storage.update((s) => { s.count += 1 }),
457
- })
458
-
459
- const setName = createAction({
460
- type: 'setName',
461
- action: (name: string) => {
462
- storage.set('name', name)
463
- return name
464
- },
465
- })
466
-
467
- const watchCount = createWatcher({
468
- type: 'watchCount',
469
- selector: (state) => state.count,
470
- shouldTrigger: (prev, curr) => prev !== curr,
471
- notifyAfterSubscribe: true,
472
- })
473
-
474
- return { increment, setName, watchCount }
475
- }
476
- )
477
-
478
- // Dispatch
479
- dispatcher.dispatch.increment()
480
- dispatcher.dispatch.setName('Alice')
481
-
482
- // Watch (RxJS Observable)
483
- dispatcher.watchers.watchCount().subscribe((action) => {
484
- console.log('count:', action.payload)
485
- })
486
-
487
- // Action stream
488
- dispatcher.actions.subscribe((action) => {
489
- console.log(action.type, action.payload)
490
- })
491
-
492
- dispatcher.destroy()
493
- ```
494
-
495
- ### Effects
496
-
497
- ```typescript
498
- import { createEffect, ofType } from 'synapse-storage/reactive'
499
- import { debounceTime, switchMap, tap } from 'rxjs/operators'
500
-
501
- createEffect((action$, state$, { dispatcher }) =>
502
- action$.pipe(
503
- ofType(dispatcher.dispatch.search),
504
- debounceTime(400),
505
- switchMap((action) =>
506
- fetchResults(action.payload).pipe(
507
- tap((results) => dispatcher.dispatch.searchSuccess(results))
508
- )
509
- )
510
- )
511
- )
512
- ```
513
-
514
- ---
515
-
516
- ## createSynapse
517
-
518
- High-level utility that wires storage + selectors + dispatcher + effects together:
519
-
520
- ```typescript
521
19
  import { createSynapse } from 'synapse-storage/utils'
20
+ import { useSelector } from 'synapse-storage/react'
522
21
 
523
- const storePromise = createSynapse({
524
- storage: new MemoryStorage<State>({ name: 'app', initialState }),
525
-
526
- createSelectorsFn: (sm) => ({
527
- count: sm.createSelector((s) => s.count),
528
- doubled: sm.createSelector(
529
- [count],
530
- (c) => c * 2
531
- ),
22
+ const synapse = createSynapse({
23
+ storage: new MemoryStorage({
24
+ name: 'counter',
25
+ initialState: { count: 0 },
532
26
  }),
533
-
534
- createDispatcherFn: (storage) =>
535
- createDispatcher({ storage }, (_s, { createAction, createWatcher }) => ({
536
- increment: createAction({
537
- type: 'increment',
538
- action: () => storage.update((s) => { s.count += 1 }),
539
- }),
540
- watchCount: createWatcher({
541
- type: 'watchCount',
542
- selector: (s) => s.count,
543
- }),
544
- })),
545
-
546
- effects: [
547
- createEffect((action$, state$, { dispatcher }) =>
548
- action$.pipe(/* ... */)
549
- ),
550
- ],
551
- })
552
-
553
- const store = await storePromise
554
-
555
- store.storage // ISyncStorage<State>
556
- store.selectors // { count, doubled }
557
- store.actions // { increment, ... }
558
- store.dispatcher // Dispatcher
559
- store.state$ // Observable<State> (when effects are used)
560
- store.destroy() // cleanup everything
561
- ```
562
-
563
- ### Dependencies
564
-
565
- ```typescript
566
- const authStore = createSynapse({ /* ... */ })
567
-
568
- const settingsStore = createSynapse({
569
- dependencies: [authStore],
570
- dependencyTimeout: 5000,
571
-
572
- createStorageFn: async () => {
573
- const auth = await authStore
574
- const userId = auth.storage.getStateSync().userId
575
- const storage = new MemoryStorage({ name: 'settings', initialState: { userId } })
576
- await storage.initialize()
577
- return storage
578
- },
579
- })
580
- ```
581
-
582
- ---
583
-
584
- ## API Client
585
-
586
- HTTP client with typed endpoints, caching, and tag-based invalidation:
587
-
588
- ```typescript
589
- import { ApiClient } from 'synapse-storage/api'
590
- import { MemoryStorage } from 'synapse-storage/core'
591
-
592
- const cacheStorage = new MemoryStorage<Record<string, any>>({
593
- name: 'api-cache',
594
- initialState: {},
595
- })
596
-
597
- const api = new ApiClient({
598
- storage: cacheStorage,
599
- baseQuery: {
600
- baseUrl: 'https://api.example.com',
601
- timeout: 10000,
602
- prepareHeaders: async (headers, context) => {
603
- headers.set('Authorization', `Bearer ${token}`)
604
- return headers
605
- },
606
- },
607
- cache: {
608
- ttl: 60000,
609
- cleanup: { enabled: true, interval: 120000 },
610
- invalidateOnError: true,
611
- },
612
- endpoints: async (create) => ({
613
- getUsers: create<{ limit?: number }, UsersResponse>({
614
- request: (params) => ({
615
- path: '/users',
616
- method: 'GET',
617
- query: params,
618
- }),
619
- cache: { ttl: 120000 },
620
- tags: ['users'],
621
- }),
622
- createUser: create<CreateUserInput, User>({
623
- request: (params) => ({
624
- path: '/users',
625
- method: 'POST',
626
- body: params,
627
- }),
628
- invalidatesTags: ['users'],
629
- cache: false,
630
- }),
27
+ createSelectorsFn: (s) => ({
28
+ count: s.createSelector((state) => state.count),
631
29
  }),
632
30
  })
633
-
634
- await cacheStorage.initialize()
635
- await api.init()
636
-
637
- // Simple request
638
- const result = await api.request('getUsers', { limit: 10 })
639
- if (result.ok) console.log(result.data, result.fromCache)
640
-
641
- // Endpoint-level subscription
642
- const endpoints = api.getEndpoints()
643
- const req = endpoints.getUsers.request({ limit: 10 })
644
-
645
- req.subscribe((state) => {
646
- // state.status: 'idle' | 'loading' | 'success' | 'error'
647
- // state.data, state.error, state.fromCache
648
- })
649
-
650
- const result = await req.wait()
651
- req.abort()
652
- ```
653
-
654
- ---
655
-
656
- ## EventBus
657
-
658
- Decoupled communication between modules:
659
-
660
- ```typescript
661
- import { createEventBus } from 'synapse-storage/utils'
662
-
663
- const eventBus = await createEventBus({
664
- name: 'app-events',
665
- autoCleanup: true,
666
- maxEvents: 1000,
667
- })
668
-
669
- // Publish
670
- await eventBus.actions.publish({
671
- event: 'USER_UPDATED',
672
- data: { userId: 123 },
673
- metadata: { priority: 'high', ttl: 60000 },
674
- })
675
-
676
- // Subscribe (supports wildcards)
677
- const { unsubscribe } = await eventBus.actions.subscribe({
678
- eventPattern: 'USER_*',
679
- handler: (data, event) => console.log(event.event, data),
680
- })
681
-
682
- // History
683
- const history = await eventBus.actions.getEventHistory({
684
- eventType: 'USER_UPDATED',
685
- limit: 10,
686
- })
687
-
688
- await eventBus.destroy()
689
- ```
690
-
691
- ---
692
-
693
- ## Singleton Pattern
694
-
695
- Share storage instances across components:
696
-
697
- ```typescript
698
- import { MemoryStorage, ConfigMergeStrategy } from 'synapse-storage/core'
699
-
700
- // Component A
701
- const storage1 = new MemoryStorage({
702
- name: 'shared',
703
- singleton: {
704
- enabled: true,
705
- mergeStrategy: ConfigMergeStrategy.FIRST_WINS,
706
- },
707
- initialState: { count: 0 },
708
- })
709
-
710
- // Component B β€” gets the same instance
711
- const storage2 = new MemoryStorage({
712
- name: 'shared',
713
- singleton: { enabled: true },
714
- initialState: { count: 99 }, // ignored (FIRST_WINS)
715
- })
716
-
717
- storage1 === storage2 // true
718
- ```
719
-
720
- Merge strategies: `FIRST_WINS`, `DEEP_MERGE`, `OVERRIDE`, `WARN_AND_USE_FIRST`, `STRICT` (throws).
721
-
722
- ---
723
-
724
- ## Storage Lifecycle
725
-
726
- ```typescript
727
- await storage.initialize()
728
- await storage.waitForReady()
729
-
730
- storage.initStatus // { status: 'ready' | 'loading' | 'error' | 'idle' }
731
-
732
- const unsub = storage.onStatusChange((status) => console.log(status))
733
-
734
- await storage.destroy()
735
31
  ```
736
32
 
737
- ---
33
+ ## Key Features
738
34
 
739
- ## Examples
35
+ - **Sync & Async Storage** β€” MemoryStorage, LocalStorage (synchronous), IndexedDB (async) with unified API
36
+ - **Selectors** β€” memoized computed values with dependency tracking
37
+ - **Immer-like Updates** β€” mutate state directly inside `update()` callbacks
38
+ - **API Client** β€” HTTP client with tag-based caching and invalidation
39
+ - **React Integration** β€” hooks on `useSyncExternalStore` (Concurrent Mode safe)
40
+ - **RxJS Effects** β€” dispatchers, effects, and watchers (Redux-Observable style)
41
+ - **Middleware & Plugins** β€” extensible sync/async pipelines
42
+ - **EventBus** β€” decoupled inter-module communication with wildcards
43
+ - **Cross-tab Sync** β€” BroadcastChannel middleware for multi-tab state
740
44
 
741
- - [GitHub Examples](https://github.com/Vlad92msk/synapse-examples)
742
- - [YouTube](https://www.youtube.com/channel/UCGENI_i4qmBkPp93P2HvvGw)
45
+ ## Documentation
743
46
 
744
- ---
47
+ Full documentation, API reference, and examples available on [GitHub](https://github.com/Vlad92msk/synapse).
745
48
 
746
49
  ## License
747
50