@lomray/react-mobx-manager 4.2.1 → 4.3.0-beta.2

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.
Files changed (37) hide show
  1. package/README.md +15 -515
  2. package/index.d.ts +1 -0
  3. package/index.js +1 -1
  4. package/make-fetching.d.ts +11 -7
  5. package/make-fetching.js +1 -1
  6. package/make-fetching.js.map +1 -1
  7. package/manager.d.ts +14 -0
  8. package/manager.js +1 -1
  9. package/manager.js.map +1 -1
  10. package/package.json +23 -21
  11. package/parent-store.d.ts +9 -0
  12. package/parent-store.js +2 -0
  13. package/parent-store.js.map +1 -0
  14. package/plugins/dev-extension/hmr/adapters.d.ts +9 -0
  15. package/plugins/dev-extension/hmr/adapters.js +2 -0
  16. package/plugins/dev-extension/hmr/adapters.js.map +1 -0
  17. package/plugins/dev-extension/hmr/index.d.ts +3 -0
  18. package/plugins/dev-extension/hmr/index.js +2 -0
  19. package/plugins/dev-extension/hmr/index.js.map +1 -0
  20. package/plugins/dev-extension/hmr/service.d.ts +71 -0
  21. package/plugins/dev-extension/hmr/service.js +2 -0
  22. package/plugins/dev-extension/hmr/service.js.map +1 -0
  23. package/plugins/dev-extension/hmr/types.d.ts +14 -0
  24. package/plugins/dev-extension/hmr/types.js +2 -0
  25. package/plugins/dev-extension/hmr/types.js.map +1 -0
  26. package/storages/combined-storage.d.ts +1 -1
  27. package/storages/combined-storage.js +1 -1
  28. package/storages/combined-storage.js.map +1 -1
  29. package/storages/cookie-storage.js +1 -1
  30. package/storages/cookie-storage.js.map +1 -1
  31. package/storages/local-storage.js +1 -1
  32. package/storages/local-storage.js.map +1 -1
  33. package/suspense-query.js +1 -1
  34. package/suspense-query.js.map +1 -1
  35. package/types.d.ts +8 -3
  36. package/with-stores.js +1 -1
  37. package/with-stores.js.map +1 -1
package/README.md CHANGED
@@ -1,21 +1,29 @@
1
- <h1 align='center'>Mobx stores manager for React</h1>
1
+ # React MobX Manager
2
2
 
3
3
  <p align="center">
4
4
  <img src="./logo.png" alt="Mobx stores manager logo" width="250" height="253">
5
5
  </p>
6
6
 
7
- ### Key features:
7
+ Clean React components. Encapsulated business logic. No state-tree pain.
8
+
9
+ It is built for apps that want explicit store ownership, SSR support, persistence, lifecycle cleanup and development tooling without forcing everything into one global state tree.
10
+
11
+ ## Why use it
8
12
 
9
13
  - One way to escape state tree 🌲🌳🌴.
10
14
  - Ready to use with Suspense.
11
15
  - Support SSR.
12
16
  - Support render to stream.
13
- - Manage your Mobx stores like a boss - debug like a hacker.
14
- - Simple idea - simple implementation.
17
+ - Relative stores for component-owned state
18
+ - Parent stores for subtree reuse
19
+ - Global stores for app-wide state
15
20
  - Small package size.
16
21
  - Support code splitting out of the box.
17
22
  - Access stores from other stores.
18
23
  - Can be a replacement for react context.
24
+ - Persistence support
25
+ - Vite plugin support
26
+ - Best-effort HMR
19
27
  - And many other nice things 😎
20
28
 
21
29
  <p align="center">
@@ -31,523 +39,15 @@
31
39
  <img src="https://img.shields.io/npm/v/@lomray/react-mobx-manager?label=semantic%20release&logo=semantic-release" alt="semantic version">
32
40
  </p>
33
41
 
34
- ## Table of contents
35
-
36
- - [Getting started](#getting-started)
37
- - [Usage](#usage)
38
- - [Support SSR](#support-ssr)
39
- - [Important Tips](#important-tips)
40
- - [Documentation](#documentation)
41
- - [Manager](#manager)
42
- - [withStores](#withstores)
43
- - [StoreManagerProvider](#storemanagerprovider)
44
- - [useStoreManagerContext](#usestoremanager)
45
- - [useStoreManagerParentContext](#usestoremanagerparent)
46
- - [Store](#store)
47
- - [Example](#demo)
48
- - [React Native Debug Plugin](#react-native-debug-plugin)
49
- - [Bugs and feature requests](#bugs-and-feature-requests)
50
- - [License](#license)
51
-
52
- ## Getting started
53
-
54
- The React-mobx-manager package is distributed using [npm](https://www.npmjs.com/), the node package manager.
55
-
56
- ```
57
- npm i --save @lomray/react-mobx-manager @lomray/consistent-suspense
58
- ```
59
-
60
- __NOTE:__ this package use [@lomray/consistent-suspense](https://github.com/Lomray-Software/consistent-suspense) for generate stable id's inside Suspense.
42
+ ## Install
61
43
 
62
- __Choose one of store id generating strategy (1 or 2 or 3)__:
63
-
64
- 1. Configure your bundler to keep classnames and function names. Store id will be generated from class names (chose unique class names).
65
- - **React:** (craco or webpack config, terser options)
66
44
  ```bash
67
- terserOptions.keep_classnames = true;
68
- terserOptions.keep_fnames = true;
69
- ```
70
-
71
- - **React Native:** (metro bundler config: metro.config.js)
72
- ```js
73
- module.exports = {
74
- transformer: {
75
- minifierConfig: {
76
- keep_classnames: true,
77
- keep_fnames: true,
78
- },
79
- }
80
- }
45
+ npm i @lomray/react-mobx-manager @lomray/consistent-suspense
81
46
  ```
82
- 2. Define `id` for each store.
83
-
84
- ```typescript
85
- import { makeObservable } from "mobx";
86
-
87
- class MyStore {
88
- /**
89
- * Define unique store id
90
- */
91
- static id = 'Unique-store-id';
92
-
93
- constructor() {
94
- makeObservable(this, {})
95
- }
96
- }
97
- ```
98
- 3. Use `Vite plugins`.
99
-
100
- ```typescript
101
- import { defineConfig } from 'vite';
102
- import react from '@vitejs/plugin-react';
103
- import MobxManager from '@lomray/react-mobx-manager/plugins/vite/index';
104
-
105
- // https://vitejs.dev/config/
106
- export default defineConfig({
107
- /**
108
- * Store id's will be generated automatically, just chill
109
- */
110
- plugins: [react(), MobxManager()]
111
- });
112
-
113
- /**
114
- * Detect mobx store:
115
- - by makeObservable or makeAutoObservable
116
- - by @mobx-store jsdoc before class
117
- */
118
- ```
119
-
120
- ## Usage
121
-
122
- Import `Manager, StoreManagerProvider` from `@lomray/react-mobx-manager` into your index file and wrap `<App/>` with `<StoreManagerProvider/>`
123
-
124
- ```typescript jsx
125
- import React from 'react';
126
- import ReactDOM from 'react-dom/client';
127
- import { ConsistentSuspenseProvider } from '@lomray/consistent-suspense';
128
- import { Manager, StoreManagerProvider, MobxLocalStorage } from '@lomray/react-mobx-manager';
129
- import App from './app';
130
- import MyApiClient from './services/my-api-client';
131
- import './index.css';
132
-
133
- const apiClient = new MyApiClient();
134
- const storeManager = new Manager({
135
- storage: new MobxLocalStorage(), // optional: needs for persisting stores
136
- storesParams: { apiClient }, // optional: we can provide our api client for access from the each store
137
- });
138
-
139
- const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
140
-
141
- root.render(
142
- <React.StrictMode>
143
- <ConsistentSuspenseProvider> {/** required **/}
144
- <StoreManagerProvider storeManager={storeManager} shouldInit>
145
- <App />
146
- </StoreManagerProvider>
147
- </ConsistentSuspenseProvider>
148
- </React.StrictMode>,
149
- );
150
- ```
151
-
152
- Connect mobx store to the manager, and you're good to go!
153
-
154
- ```typescript
155
- import { withStores, Manager } from '@lomray/react-mobx-manager';
156
- import { makeObservable, observable, action } from 'mobx';
157
- import type { IConstructorParams, ClassReturnType } from '@lomray/react-mobx-manager';
158
-
159
- /**
160
- * Mobx user store
161
- *
162
- * Usually store like that are related to the global store,
163
- * because they store information about the current user,
164
- * which may be needed in different places of the application.
165
- *
166
- * You may also want to save the state of the store, for example,
167
- * to local storage, so that it can be restored after page reload,
168
- * in this case, just export wrap export with 'persist':
169
- * export default Manager.persistStore(UserStore, 'user');
170
- */
171
- class UserStore {
172
- /**
173
- * Required only if we don't configure our bundler to keep classnames and function names
174
- * Default: current class name
175
- */
176
- static id = 'user';
177
-
178
- /**
179
- * You can also enable behavior for global application stores
180
- * Default: false
181
- */
182
- static isGlobal = true;
183
-
184
- /**
185
- * Our state
186
- */
187
- public name = 'Matthew'
188
-
189
- /**
190
- * Our API client
191
- */
192
- private apiClient: MyApiClient;
193
-
194
- /**
195
- * @constructor
196
- */
197
- constructor({ getStore, apiClient }: IConstructorParams) {
198
- this.apiClient = apiClient;
199
- // if we need, we can get a global store or store from the parent context
200
- // this.otherStore = getStore(SomeOtherStore);
201
-
202
- makeObservable(this, {
203
- name: observable,
204
- setName: action.bound,
205
- });
206
- }
207
-
208
- /**
209
- * Set user name
210
- */
211
- public setName(name: string): void {
212
- this.name = name;
213
- }
214
-
215
- /**
216
- * Example async
217
- * Call this func from component
218
- */
219
- public getNameFromApi = async (userId: number) => {
220
- const name = await this.apiClient.fetchName(userId);
221
-
222
- this.setName(name);
223
- }
224
- }
225
-
226
- /**
227
- * Define stores for component
228
- */
229
- const stores = {
230
- userStore: UserStore
231
- };
232
-
233
- // support typescript
234
- type TProps = StoresType <typeof stores>;
235
-
236
- /**
237
- * User component
238
- */
239
- const User: FC<TProps> = ({ userStore: { name } }) => {
240
- return (
241
- <div>{name}</div>
242
- )
243
- }
244
-
245
- /**
246
- * Connect stores to component
247
- */
248
- export default withStores(User, stores);
249
- ```
250
-
251
- [See app example](https://github.com/Lomray-Software/vite-template) for a better understanding.
252
-
253
- ## Support SSR
254
- Does this library support SSR? Short answer - yes, but we need some steps to prepare our framework.
255
- - Look at [Vite demo app](https://github.com/Lomray-Software/vite-template) for a better understanding.
256
- - Look at [After.js (razzle) based project](https://github.com/Lomray-Software/microservices-dashboard/blob/staging/src/pages/user/index.tsx#L82) for a better understanding.
257
- - Look at [NextJS example](https://github.com/Lomray-Software/nextjs-mobx-store-manager-example) for a better understanding (needs writing a wrapper).
258
-
259
- ## Important Tips
260
- - Create **global** store only for e.g: application settings, logged user, theme, etc.
261
- - To get started, stick to the concept: Store for Component. Don't connect (through withStores) not global store to several components.
262
47
 
263
48
  ## Documentation
264
49
 
265
- ### Manager
266
- ```typescript
267
- import { Manager, MobxLocalStorage, MobxAsyncStorage } from '@lomray/react-mobx-manager';
268
- // import AsyncStorage from '@react-native-async-storage/async-storage';
269
-
270
- // Params
271
- const storeManager = new Manager({
272
- /**
273
- * Optional: needs for persisting stores when you use Manager.persistStore
274
- * Available: MobxLocalStorage and MobxAsyncStorage
275
- * Default: none
276
- */
277
- storage: new MobxLocalStorage(), // React
278
- // storage: new MobxAsyncStorage(AsyncStorage), // React Native
279
- // storage: new CombinedStorage({ local: MobxAsyncStorage, cookie: CookieStorage }), // Define multiple storages
280
- /**
281
- * Optional: provide some params for access from store constructor
282
- * E.g. we can provide our api client for access from the store
283
- * Default: {}
284
- */
285
- storesParams: { apiClient },
286
- /**
287
- * Initial stores state.
288
- * E.g. in SSR case, restore client state from a server
289
- * Default: {}
290
- */
291
- initState: { storeId: { param: 'test' } },
292
- /**
293
- * Additional manager options
294
- */
295
- options: {
296
- /**
297
- * Disable persisting stores
298
- * E.g., it should be 'true' on a server-side (SSR)
299
- * Default: false
300
- */
301
- shouldDisablePersist: false,
302
- /**
303
- * Remove the initial store state after initialization
304
- * Default: true
305
- */
306
- shouldRemoveInitState: true,
307
- /**
308
- * Configure store destroy timers
309
- */
310
- destroyTimers: {
311
- init: 500,
312
- touched: 10000, // NOTE: set to max request timeout
313
- unused: 1000,
314
- },
315
- }
316
- });
317
-
318
- // Methods
319
-
320
- /**
321
- * Optional: Call this method to load persisting data from persist storage
322
- * E.g., you may want manually to do this before the app render
323
- * Default: StoreManagerProvider does this automatically with 'shoudInit' prop
324
- */
325
- await storeManager.init();
326
-
327
- /**
328
- * Get all-created stores
329
- */
330
- const managerStores = storeManager.getStores();
331
-
332
- /**
333
- * Get specific store
334
- */
335
- const store = storeManager.getStore(SomeGlobalStore);
336
- const store2 = storeManager.getStore(SomeStore, { contextId: 'necessary-context-id' });
337
-
338
- /**
339
- * Get stores context's and relations
340
- */
341
- const relations = storeManager.getStoresRelations();
342
-
343
- /**
344
- * Manually create stores for component
345
- * NOTE: 'withStores' wrapper use this method, probably you won't need it
346
- * WARNING: Avoid using this method directly, it may cause unexpected behavior
347
- */
348
- const stores = storeManager.createStores(['someStore', MyStore], 'parent-id', 'context-id', 'suspense-id', 'HomePage', { componentProp: 'test' });
349
-
350
- /**
351
- * Mount/Unmount simple stores to component
352
- * WARNING: Avoid using this method directly, it may cause unexpected behavior
353
- */
354
- const unmount = storeManager.mountStores(stores);
355
-
356
- /**
357
- * Get all-stores state
358
- */
359
- const storesState = storeManager.toJSON();
360
-
361
- /**
362
- * Get all persisted store's state
363
- */
364
- const persistedStoresState = storeManager.toPersistedJSON();
365
-
366
- /**
367
- * Get only persisted stores id's
368
- */
369
- const persistedIds = Manager.getPersistedStoresIds();
370
-
371
- /**
372
- * Get store observable props
373
- */
374
- const observableProps = Manager.getObservableProps(store);
375
-
376
- /**
377
- * Static method for access to manager from anywhere
378
- * NOTE: Be careful with this, especially with SSR on server-side
379
- */
380
- const manager = Manager.get();
381
-
382
- /**
383
- * Enable persisting state for store
384
- */
385
- const storeClass = Manager.persistStore(class MyStore {}, 'my-store');
386
-
387
- /**
388
- * Choose storage and attributes
389
- */
390
- const storeClass2 = Manager.persistStore(class MyStore {}, 'my-store', {
391
- attributes: {
392
- local: ['someProp'], // thees attributes will be stored in local storage
393
- cookie: ['specificProp'], // thees attributes will be stored in cookie storage
394
- }
395
- });
396
- ```
397
-
398
- ### withStores
399
- ```typescript
400
- import { withStores } from '@lomray/react-mobx-manager';
401
-
402
- const stores = {
403
- myStore: MyStore,
404
- anotherStore: AnotherStore,
405
- // assign static id to future store
406
- demoStore: { store: SomeStore, id: 'my-id' },
407
- // get parent store, do this only inside children components
408
- parentStore: { store: SomeParentStore, isParent: true },
409
- };
410
-
411
- /**
412
- * Create and connect 'stores' to component with custom context id
413
- * NOTE: In most cases, you don't need to pass a third argument (contextId).
414
- */
415
- withStores(Component, stores, { customContextId: 'optional-context-id' });
416
- ```
417
-
418
- ### StoreManagerProvider
419
- ```typescript jsx
420
- import { StoreManagerProvider } from '@lomray/react-mobx-manager';
421
-
422
- /**
423
- * Wrap your application for a pass-down store manager, context id, and init persisted state
424
- *
425
- * shouldInit - default: false, enable initialize peristed state
426
- * fallback - show loader while the manager has initialized
427
- */
428
- <StoreManagerProvider storeManager={storeManager} shouldInit fallback={<Loader />}>
429
- {/* your components */}
430
- </StoreManagerProvider>
431
- ```
432
-
433
- ### useStoreManager
434
- ```typescript jsx
435
- import { useStoreManager } from '@lomray/react-mobx-manager';
436
-
437
- const MyComponent: FC = () => {
438
- /**
439
- * Get store manager inside your function component
440
- */
441
- const storeManager = useStoreManager();
442
- }
443
- ```
444
-
445
- ### useStoreManagerParent
446
- ```typescript jsx
447
- import { useStoreManagerParent } from '@lomray/react-mobx-manager';
448
-
449
- const MyComponent: FC = () => {
450
- /**
451
- * Get parent context id
452
- */
453
- const { parentId } = useStoreManagerParent();
454
- }
455
- ```
456
-
457
- ### Store
458
-
459
- ```typescript
460
- import { makeObservable, observable, action } from 'mobx';
461
-
462
- class MyStore {
463
- /**
464
- * Required only if we don't configure our bundler to keep classnames and function names
465
- * Default: current class name
466
- */
467
- static id = 'user';
468
-
469
- /**
470
- * You can also enable behavior for global application stores
471
- * Default: false
472
- */
473
- static isGlobal = true;
474
-
475
- /**
476
- * Store observable state
477
- */
478
- public state = {
479
- name: 'Matthew',
480
- username: 'meow',
481
- }
482
-
483
- /**
484
- * @private
485
- */
486
- private readonly someParentStore: ClassReturnType<typeof SomeParentStore>;
487
-
488
- /**
489
- * @constructor
490
- *
491
- * getStore - get parent store or global store
492
- * storeManager - access to store manager
493
- * apiClient - your custom param, see 'storesParams' in Manager
494
- */
495
- constructor({ getStore, storeManager, apiClient, componentProps }: IConstructorParams) {
496
- this.apiClient = apiClient;
497
- this.someParentStore = getStore(SomeParentStore);
498
-
499
- // In case when store is't global you can get access to component props
500
- console.log(componentProps);
501
-
502
- makeObservable(this, {
503
- state: observable,
504
- });
505
- }
506
-
507
- /**
508
- * Define this method if you want to do something after initialize the store
509
- * State restored, store ready for usage
510
- * Optional.
511
- * @private
512
- */
513
- private init(): void {
514
- // do something
515
- }
516
-
517
- /**
518
- * Define this method if you want to do something when a component with this store is unmount
519
- * @private
520
- */
521
- private onDestroy(): void {
522
- // do something
523
- }
524
-
525
- /**
526
- * Custom method for return store state
527
- * Optional.
528
- * Default: @see Manager.toJSON
529
- */
530
- public toJSON(): Record<string, any> {
531
- return { state: { username: this.state.username } };
532
- }
533
- }
534
- ```
535
-
536
- Lifecycles:
537
- - constructor
538
- - wakeup (restore state from persisted store)
539
- - init
540
- - onDestroy
541
-
542
- ## Demo
543
- Explore [demo app](https://github.com/Lomray-Software/vite-template) to more understand.
544
-
545
- ## React Native debug plugin
546
- For debug state, you can use [Reactotron debug plugin](https://github.com/Lomray-Software/reactotron-mobx-store-manager)
547
-
548
- ## Bugs and feature requests
549
-
550
- Bug or a feature request, [please open a new issue](https://github.com/Lomray-Software/react-mobx-manager/issues/new).
50
+ Full documentation lives in [here](https://lomray-software.github.io/react-mobx-manager/)
551
51
 
552
52
  ## License
553
53
  Made with 💚
package/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { default as Manager } from "./manager.js";
4
4
  export { default as onChangeListener } from "./on-change-listener.js";
5
5
  export { default as wakeup } from "./wakeup.js";
6
6
  export { default as withStores } from "./with-stores.js";
7
+ export { default as parentStore } from "./parent-store.js";
7
8
  export * from "./make-exported.js";
8
9
  export * from "./make-fetching.js";
9
10
  export { default as Events } from "./events.js";
package/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export{StoreManagerContext,StoreManagerParentContext,StoreManagerParentProvider,StoreManagerProvider,useStoreManager,useStoreManagerParent}from"./context.js";export{default as Manager}from"./manager.js";export{default as onChangeListener}from"./on-change-listener.js";export{default as wakeup}from"./wakeup.js";export{default as withStores}from"./with-stores.js";export{isPropExcludedFromExport,isPropObservableExported,isPropSimpleExported,makeExported}from"./make-exported.js";import"mobx";export{default as Events}from"./events.js";export{default as Logger}from"./logger.js";
1
+ export{StoreManagerContext,StoreManagerParentContext,StoreManagerParentProvider,StoreManagerProvider,useStoreManager,useStoreManagerParent}from"./context.js";export{default as Manager}from"./manager.js";export{default as onChangeListener}from"./on-change-listener.js";export{default as wakeup}from"./wakeup.js";export{default as withStores}from"./with-stores.js";export{default as parentStore}from"./parent-store.js";export{isPropExcludedFromExport,isPropObservableExported,isPropSimpleExported,makeExported}from"./make-exported.js";import"mobx";export{default as Events}from"./events.js";export{default as Logger}from"./logger.js";
2
2
  //# sourceMappingURL=index.js.map
@@ -1,8 +1,12 @@
1
- type TParams<T> = {
2
- [K in keyof T]?: keyof T;
3
- };
4
- /**
5
- * Wrap functions to manage their fetching indicator
6
- */
7
- declare const makeFetching: <T extends Record<any, any>>(instance: T, params?: TParams<T>, hasLock?: boolean) => void;
1
+ type AnyFn = (...args: any[]) => any;
2
+ type MethodKeys<T> = {
3
+ [K in keyof T]-?: T[K] extends AnyFn ? K : never;
4
+ }[keyof T];
5
+ type BooleanKeys<T> = {
6
+ [K in keyof T]-?: T[K] extends boolean ? K : never;
7
+ }[keyof T];
8
+ type StrictParams<T> = Partial<Record<MethodKeys<T>, BooleanKeys<T>>>;
9
+ type UnsafeParams = Record<string, string>;
10
+ declare function makeFetching<T extends Record<any, any>>(instance: T, params: StrictParams<T>, hasLock?: boolean): void;
11
+ declare function makeFetching<T extends Record<any, any>>(instance: T, params: UnsafeParams, hasLock?: boolean): void;
8
12
  export { makeFetching as default };
package/make-fetching.js CHANGED
@@ -1,2 +1,2 @@
1
- import{runInAction as t}from"mobx";const o=(o,e={},r=!1)=>{Object.entries(e).forEach(([e,n])=>{const f=o[e],c=e=>{t(()=>{o[n]=e})};o[e]=(...t)=>{if(r&&o[n])return;c(!0);const e=f(...t);return"object"==typeof e&&e.finally?e.finally(()=>{c(!1)}):c(!1),e}})};export{o as default};
1
+ import{runInAction as t}from"mobx";function o(o,e={},n=!1){Object.entries(e).forEach(([e,r])=>{const f=o[e];let a=0;const c=e=>{t(()=>{o[r]=e})},i=()=>{a=Math.max(a-1,0),0===a&&c(!1)};o[e]=(...t)=>{if(n&&o[r])return;a+=1,1===a&&c(!0);const e=f(...t);return"object"==typeof e&&e.finally?e.finally(()=>{i()}):i(),e}})}export{o as default};
2
2
  //# sourceMappingURL=make-fetching.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"make-fetching.js","sources":["../src/make-fetching.ts"],"sourcesContent":["import { runInAction } from 'mobx';\n\ntype TParams<T> = {\n [K in keyof T]?: keyof T;\n};\n\n/**\n * Wrap functions to manage their fetching indicator\n */\nconst makeFetching = <T extends Record<any, any>>(\n instance: T,\n params: TParams<T> = {},\n hasLock = false,\n): void => {\n Object.entries(params).forEach(([funcName, propName]) => {\n const callback = instance[funcName] as (...arg: any[]) => any;\n const setValue = (value: boolean) => {\n runInAction(() => {\n // @ts-expect-error not necessary\n instance[propName] = value;\n });\n };\n\n // @ts-expect-error not necessary\n instance[funcName] = (...args: any[]) => {\n if (hasLock && instance[propName]) {\n return;\n }\n\n setValue(true);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = callback(...args);\n\n if (typeof result === 'object' && result.finally) {\n result.finally(() => {\n setValue(false);\n });\n } else {\n setValue(false);\n }\n\n return result;\n };\n });\n};\n\nexport default makeFetching;\n"],"names":["makeFetching","instance","params","hasLock","Object","entries","forEach","funcName","propName","callback","setValue","value","runInAction","args","result","finally"],"mappings":"mCASA,MAAMA,EAAe,CACnBC,EACAC,EAAqB,CAAA,EACrBC,GAAU,KAEVC,OAAOC,QAAQH,GAAQI,QAAQ,EAAEC,EAAUC,MACzC,MAAMC,EAAWR,EAASM,GACpBG,EAAYC,IAChBC,EAAY,KAEVX,EAASO,GAAYG,KAKzBV,EAASM,GAAY,IAAIM,KACvB,GAAIV,GAAWF,EAASO,GACtB,OAGFE,GAAS,GAGT,MAAMI,EAASL,KAAYI,GAU3B,MARsB,iBAAXC,GAAuBA,EAAOC,QACvCD,EAAOC,QAAQ,KACbL,GAAS,KAGXA,GAAS,GAGJI"}
1
+ {"version":3,"file":"make-fetching.js","sources":["../src/make-fetching.ts"],"sourcesContent":["import { runInAction } from 'mobx';\n\ntype AnyFn = (...args: any[]) => any;\n\ntype MethodKeys<T> = {\n [K in keyof T]-?: T[K] extends AnyFn ? K : never;\n}[keyof T];\n\ntype BooleanKeys<T> = {\n [K in keyof T]-?: T[K] extends boolean ? K : never;\n}[keyof T];\n\ntype StrictParams<T> = Partial<Record<MethodKeys<T>, BooleanKeys<T>>>;\ntype UnsafeParams = Record<string, string>;\n\nfunction makeFetching<T extends Record<any, any>>(\n instance: T,\n params: StrictParams<T>,\n hasLock?: boolean,\n): void;\nfunction makeFetching<T extends Record<any, any>>(\n instance: T,\n params: UnsafeParams,\n hasLock?: boolean,\n): void;\n\nfunction makeFetching<T extends Record<any, any>>(\n instance: T,\n params: StrictParams<T> | UnsafeParams = {},\n hasLock = false,\n): void {\n Object.entries(params).forEach(([funcName, propName]) => {\n const callback = instance[funcName] as (...arg: any[]) => any;\n let inFlight = 0;\n const setValue = (value: boolean): void => {\n runInAction(() => {\n // @ts-expect-error not necessary\n instance[propName] = value;\n });\n };\n const increment = (): void => {\n inFlight += 1;\n\n if (inFlight === 1) {\n setValue(true);\n }\n };\n const decrement = (): void => {\n inFlight = Math.max(inFlight - 1, 0);\n\n if (inFlight === 0) {\n setValue(false);\n }\n };\n\n // @ts-expect-error not necessary\n instance[funcName] = (...args: any[]) => {\n if (hasLock && instance[propName]) {\n return;\n }\n\n increment();\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n const result = callback(...args);\n\n if (typeof result === 'object' && result.finally) {\n result.finally(() => {\n decrement();\n });\n } else {\n decrement();\n }\n\n return result;\n };\n });\n}\n\nexport default makeFetching;\n"],"names":["makeFetching","instance","params","hasLock","Object","entries","forEach","funcName","propName","callback","inFlight","setValue","value","runInAction","decrement","Math","max","args","result","finally"],"mappings":"mCA0BA,SAASA,EACPC,EACAC,EAAyC,CAAA,EACzCC,GAAU,GAEVC,OAAOC,QAAQH,GAAQI,QAAQ,EAAEC,EAAUC,MACzC,MAAMC,EAAWR,EAASM,GAC1B,IAAIG,EAAW,EACf,MAAMC,EAAYC,IAChBC,EAAY,KAEVZ,EAASO,GAAYI,KAUnBE,EAAY,KAChBJ,EAAWK,KAAKC,IAAIN,EAAW,EAAG,GAEjB,IAAbA,GACFC,GAAS,IAKbV,EAASM,GAAY,IAAIU,KACvB,GAAId,GAAWF,EAASO,GACtB,OAjBFE,GAAY,EAEK,IAAbA,GACFC,GAAS,GAoBX,MAAMO,EAAST,KAAYQ,GAU3B,MARsB,iBAAXC,GAAuBA,EAAOC,QACvCD,EAAOC,QAAQ,KACbL,MAGFA,IAGKI,IAGb"}
package/manager.d.ts CHANGED
@@ -165,6 +165,13 @@ declare class Manager {
165
165
  * Delete relation context id
166
166
  */
167
167
  protected removeRelationContext(contextId: string): void;
168
+ /**
169
+ * Append callback to store destroy lifecycle
170
+ */
171
+ /**
172
+ * Append callback to store destroy lifecycle
173
+ */
174
+ protected appendDestroyCallback(store: TStores[string], callback?: () => void): void;
168
175
  /**
169
176
  * Prepare store before usage
170
177
  */
@@ -190,6 +197,13 @@ declare class Manager {
190
197
  * NOTE: use only inside withStores wrapper
191
198
  */
192
199
  mountStores(contextId: string, { globalStores, relativeStores }: Partial<IGroupedStores>): () => void;
200
+ /**
201
+ * Destroy manager stores and detach internal relations
202
+ */
203
+ /**
204
+ * Destroy manager stores and detach internal relations
205
+ */
206
+ destroy(): void;
193
207
  /**
194
208
  * Change the stores status to touched
195
209
  */
package/manager.js CHANGED
@@ -1,2 +1,2 @@
1
- import t from"@lomray/event-manager";import{toJS as e,isObservableProp as s}from"mobx";import{ROOT_CONTEXT_ID as o}from"./constants.js";import r from"./deep-merge.js";import i from"./events.js";import n from"./logger.js";import{isPropObservableExported as a,isPropExcludedFromExport as l,isPropSimpleExported as h}from"./make-exported.js";import d from"./on-change-listener.js";import p from"./storages/combined-storage.js";import S from"./store-status.js";import c from"./wakeup.js";class u{static instance;stores=new Map;storesRelations=new Map;static persistedStores=new Set;initState;storage;storesParams;options={shouldDisablePersist:!1,shouldRemoveInitState:!0,failedCreationStrategy:"empty"};suspenseRelations=new Map;logger;constructor({initState:t,storesParams:e,storage:s,options:o,logger:r}={}){if(this.initState=t||{},this.storesParams=e||{},this.logger=r&&"log"in r?r:new n({level:3,...r??{},manager:this}),this.storage=s instanceof p?s:s?new p({default:s}):void 0,Object.assign(this.options,o||{}),u.instance=this,"undefined"!=typeof window){const t=window.mbxM;window.mbxM={push:this.pushInitState},(Array.isArray(t)?t:[]).forEach(this.pushInitState)}}async init(){try{this.storage&&await this.storage.get()}catch(t){this.logger.err("Failed initialized store manager: ",t)}return this}static get(){if(!u.instance)throw new Error("Store manager is not initialized.");return u.instance}getStores(){return this.stores}getStoresRelations(){return this.storesRelations}getSuspenseRelations(){return this.suspenseRelations}static getPersistedStoresIds(){return u.persistedStores}pushInitState=(t={})=>{for(const[e,s]of Object.entries(t))this.initState[e]=s};getStoreId(t,e={}){const{id:s,contextId:o,key:r}=e;if(s)return s;if(t.libStoreId)return t.libStoreId;let i=t.id||t.name||t.constructor.name;return t.isGlobal?i:(i=`${i}--${o}`,r?`${i}--${r}`:i)}getStore(t,e={}){const s=this.getStoreId(t,e);return this.stores.has(s)?this.stores.get(s):t.isGlobal?this.createStore(t,{id:s,contextId:"global",parentId:o,suspenseId:"",componentName:"root-app",componentProps:{}}):this.lookupStore(s,e)}lookupStore(t,e){const{contextId:s,parentId:r}=e,i=t.split("--")?.[0],{ids:n,parentId:a}=this.storesRelations.get(s)??{ids:new Set,parentId:r},l=[...n].filter(t=>t.startsWith(`${i}--`));if(1===l.length)return this.stores.get(l[0]);if(l.length>1)this.logger.err("Parent context has multiple stores with the same id, please pass key to getStore function.");else if(a&&a!==o)return this.lookupStore(t,{contextId:this.getBiggerContext(a,r)})}getBiggerContext(t,e){if(!t)return e;if(!e)return t;const s=/[^a-zA-Z]/g;return t.replace(s,"")>e.replace(s,"")?t:e}createStore(e,s){const{id:r,contextId:n,parentId:a,suspenseId:l,componentName:h,componentProps:d}=s;if(this.stores.has(r))return this.stores.get(r);const p=new e({...this.storesParams,storeManager:this,getStore:(t,e={contextId:n,parentId:a})=>this.getStore(t,e),componentProps:d,initState:this.initState[r]});return p.libStoreId=r,p.isGlobal=e.isGlobal,p.libStoreContextId=e.isGlobal?"global":n,p.libStoreParentId=e.isGlobal||!a||a===n?o:a,p.libStoreSuspenseId=l,p.libStoreComponentName=h,this.setStoreStatus(p,e.isGlobal?S.inUse:S.init),this.prepareStore(p),t.publish(i.CREATE_STORE,{store:e}),p}createStores(t,e,s,o,r,i={}){const{failedCreationStrategy:n}=this.options,a=t.reduce((t,[a,l])=>{const{id:h,store:d,isParent:p=!1}="store"in l?l:{store:l,id:void 0,isParent:!1};let S=h||(p?this.getStore(d,{contextId:s,parentId:e})?.libStoreId:this.getStoreId(d,{key:a,contextId:s}));if(!S){const i=`Cannot find or create store '${a}': '${this.getStoreId(d)}'`;if(this.logger.warn(i),this.logger.debug(i,{contextId:s,parentId:e,suspenseId:o,componentName:r,isParent:p},!0),"dummy"!==n)return"empty"===n&&(t.hasCreationFailure=!0),t;S=this.getStoreId(d,{key:a,contextId:s})}const c=this.createStore(d,{id:S,contextId:s,parentId:e,suspenseId:o,componentName:r,componentProps:i});return p?t.parentStores[a]=c:c.isGlobal?t.globalStores[a]=c:t.relativeStores[a]=c,t},{relativeStores:{},parentStores:{},globalStores:{},hasCreationFailure:!1});return this.createRelationContext(s,e,r),a}createRelationContext(t,e,s){this.storesRelations.has(t)||this.storesRelations.set(t,{ids:new Set,parentId:e&&e!==t?e:o,componentName:s})}removeRelationContext(t){const e=this.storesRelations.get(t);!e||t===o||e.ids.size>0||this.storesRelations.delete(t)}prepareStore(t){const e=t.libStoreId,s=t.libStoreContextId,o=t.libStoreSuspenseId;if(this.stores.has(e))return;const i=this.initState[e];if(i&&r(t,i),"wakeup"in t&&u.persistedStores.has(e)&&t.wakeup?.({initState:i,persistedState:this.storage?.getStoreData(t),manager:this}),u.persistedStores.has(e)&&"addOnChangeListener"in t){const e=t.onDestroy?.bind(t),s=t.addOnChangeListener(t,this);t.onDestroy=()=>{s?.(),e?.()}}t.init?.(),this.createRelationContext(s,t.libStoreParentId,t.libStoreComponentName),this.suspenseRelations.has(o)||this.suspenseRelations.set(o,new Set);const{ids:n}=this.storesRelations.get(s);this.stores.set(e,t),n.add(e),this.suspenseRelations.get(o).add(e)}removeStore(e){const s=e.libStoreId,o=e.libStoreSuspenseId,{ids:r}=this.storesRelations.get(e.libStoreContextId)??{ids:new Set};this.stores.has(s)&&(this.stores.delete(s),r.delete(s),o&&this.suspenseRelations.get(o)?.has(s)&&this.suspenseRelations.get(o).delete(s),this.removeRelationContext(e.libStoreContextId),"onDestroy"in e&&e.onDestroy?.(),t.publish(i.DELETE_STORE,{store:e}))}mountStores(e,{globalStores:s={},relativeStores:o={}}){const{shouldRemoveInitState:r}=this.options,n={...s,...o};return Object.values(n).forEach(e=>{const s=e.libStoreId;r&&this.initState[s]&&delete this.initState[s],this.setStoreStatus(e,S.inUse),t.publish(i.MOUNT_STORE,{store:e})}),()=>{Object.values(n).forEach(e=>{e.isGlobal||(this.setStoreStatus(e,S.unused),t.publish(i.UNMOUNT_STORE,{store:e}))}),this.removeRelationContext(e)}}touchedStores(t){Object.values(t).forEach(t=>{t.libStoreStatus!==S.init||t.isGlobal||this.setStoreStatus(t,S.touched)})}setStoreStatus(t,e){const{destroyTimers:{init:s=500,touched:o=1e4,unused:r=1e3}={}}=this.options;t.libStoreStatus=e,clearTimeout(t.libDestroyTimer);let i=0;switch(e){case S.init:i=s;break;case S.touched:i=o;break;case S.unused:i=r}i&&(t.libDestroyTimer=setTimeout(()=>this.removeStore(t),i))}getStoreState(t,e=!1){return t.toJSON?.()??u.getObservableProps(t,e)}toJSON(t,e=!1){const s={},o=Array.isArray(t)?t.reduce((t,e)=>(this.stores.has(e)&&t.set(e,this.stores.get(e)),t),new Map):this.stores;for(const[t,r]of o.entries())s[t]=this.getStoreState(r,e);return s}async savePersistedStore(t){if(this.options.shouldDisablePersist||!this.storage)return!1;try{return await this.storage.saveStoreData(t,this.getStoreState(t,!0)),!0}catch(t){this.logger.err("Failed to persist stores: ",t)}return!1}static getObservableProps(t,o=!1){const r=e(t);return Object.entries(r).reduce((e,[r,i])=>({...e,...s(t,r)&&!l(t,r,o)||h(t,r)?{[r]:i}:{},...a(t,r)?{[r]:u.getObservableProps(t[r])}:{}}),{})}static persistStore(t,e,s={}){return u.persistedStores.add(e),t.libStoreId=e,"libStorageOptions"in t.prototype||(t.prototype.libStorageOptions=s),"wakeup"in t.prototype||(t.prototype.wakeup=c),"addOnChangeListener"in t.prototype||(t.prototype.addOnChangeListener=d),t}}export{u as default};
1
+ import t from"@lomray/event-manager";import{toJS as e,isObservableProp as s}from"mobx";import{ROOT_CONTEXT_ID as o}from"./constants.js";import r from"./deep-merge.js";import i from"./events.js";import n from"./logger.js";import{isPropObservableExported as a,isPropExcludedFromExport as l,isPropSimpleExported as h}from"./make-exported.js";import d from"./on-change-listener.js";import p from"./storages/combined-storage.js";import S from"./store-status.js";import c from"./wakeup.js";class u{static instance;stores=new Map;storesRelations=new Map;static persistedStores=new Set;initState;storage;storesParams;options={shouldDisablePersist:!1,shouldRemoveInitState:!0,failedCreationStrategy:"empty"};suspenseRelations=new Map;logger;constructor({initState:t,storesParams:e,storage:s,options:o,logger:r}={}){if(this.initState=t||{},this.storesParams=e||{},this.logger=r&&"log"in r?r:new n({level:3,...r??{},manager:this}),this.storage=s instanceof p?s:s?new p({default:s}):void 0,Object.assign(this.options,o||{}),u.instance=this,"undefined"!=typeof window){const t=window.mbxM;window.mbxM={push:this.pushInitState},(Array.isArray(t)?t:[]).forEach(this.pushInitState)}}async init(){try{this.storage&&await this.storage.get()}catch(t){this.logger.err("Failed initialized store manager: ",t)}return this}static get(){if(!u.instance)throw new Error("Store manager is not initialized.");return u.instance}getStores(){return this.stores}getStoresRelations(){return this.storesRelations}getSuspenseRelations(){return this.suspenseRelations}static getPersistedStoresIds(){return u.persistedStores}pushInitState=(t={})=>{for(const[e,s]of Object.entries(t))this.initState[e]=s};getStoreId(t,e={}){const{id:s,contextId:o,key:r}=e;if(s)return s;if(t.libStoreId)return t.libStoreId;let i=t.id||t.name||t.constructor.name;return t.isGlobal?i:(i=`${i}--${o}`,r?`${i}--${r}`:i)}getStore(t,e={}){const s=this.getStoreId(t,e);return this.stores.has(s)?this.stores.get(s):t.isGlobal?this.createStore(t,{id:s,contextId:"global",parentId:o,suspenseId:"",componentName:"root-app",componentProps:{}}):this.lookupStore(s,e)}lookupStore(t,e){const{contextId:s,parentId:r}=e,i=t.split("--")?.[0],{ids:n,parentId:a}=this.storesRelations.get(s)??{ids:new Set,parentId:r},l=[...n].filter(t=>t.startsWith(`${i}--`));if(1===l.length)return this.stores.get(l[0]);if(l.length>1)this.logger.err("Parent context has multiple stores with the same id, please pass key to getStore function.");else if(a&&a!==o)return this.lookupStore(t,{contextId:this.getBiggerContext(a,r)})}getBiggerContext(t,e){if(!t)return e;if(!e)return t;const s=/[^a-zA-Z]/g;return t.replace(s,"")>e.replace(s,"")?t:e}createStore(e,s){const{id:r,contextId:n,parentId:a,suspenseId:l,componentName:h,componentProps:d}=s;if(this.stores.has(r))return this.stores.get(r);const p=new e({...this.storesParams,storeManager:this,getStore:(t,e={contextId:n,parentId:a})=>this.getStore(t,e),componentProps:d,initState:this.initState[r]});return p.libStoreId=r,p.isGlobal=e.isGlobal,p.libStoreContextId=e.isGlobal?"global":n,p.libStoreParentId=e.isGlobal||!a||a===n?o:a,p.libStoreSuspenseId=l,p.libStoreComponentName=h,this.setStoreStatus(p,e.isGlobal?S.inUse:S.init),this.prepareStore(p),t.publish(i.CREATE_STORE,{store:e}),p}createStores(t,e,s,o,r,i={}){const{failedCreationStrategy:n}=this.options,a=t.reduce((t,[a,l])=>{const{id:h,store:d,isParent:p=!1}="store"in l?l:{store:l,id:void 0,isParent:!1};let S=h||(p?this.getStore(d,{contextId:s,parentId:e})?.libStoreId:this.getStoreId(d,{key:a,contextId:s}));if(!S){const i=`Cannot find or create store '${a}': '${this.getStoreId(d)}'`;if(this.logger.warn(i),this.logger.debug(i,{contextId:s,parentId:e,suspenseId:o,componentName:r,isParent:p},!0),"dummy"!==n)return"empty"===n&&(t.hasCreationFailure=!0),t;S=this.getStoreId(d,{key:a,contextId:s})}const c=this.createStore(d,{id:S,contextId:s,parentId:e,suspenseId:o,componentName:r,componentProps:d.isGlobal||p?{}:i});return p?t.parentStores[a]=c:c.isGlobal?t.globalStores[a]=c:t.relativeStores[a]=c,t},{relativeStores:{},parentStores:{},globalStores:{},hasCreationFailure:!1});return this.createRelationContext(s,e,r),a}createRelationContext(t,e,s){this.storesRelations.has(t)||this.storesRelations.set(t,{ids:new Set,parentId:e&&e!==t?e:o,componentName:s})}removeRelationContext(t){const e=this.storesRelations.get(t);!e||t===o||e.ids.size>0||this.storesRelations.delete(t)}appendDestroyCallback(t,e){if(!e)return;const s=t.onDestroy?.bind(t);t.onDestroy=()=>{e(),s?.()}}prepareStore(t){const e=t.libStoreId,s=t.libStoreContextId,o=t.libStoreSuspenseId;if(this.stores.has(e))return;const i=this.initState[e];if(i&&r(t,i),"wakeup"in t&&u.persistedStores.has(e)&&t.wakeup?.({initState:i,persistedState:this.storage?.getStoreData(t),manager:this}),u.persistedStores.has(e)&&"addOnChangeListener"in t){const e=t.addOnChangeListener(t,this);this.appendDestroyCallback(t,e)}const n=t.init?.();"function"==typeof n&&this.appendDestroyCallback(t,n),this.createRelationContext(s,t.libStoreParentId,t.libStoreComponentName),this.suspenseRelations.has(o)||this.suspenseRelations.set(o,new Set);const{ids:a}=this.storesRelations.get(s);this.stores.set(e,t),a.add(e),this.suspenseRelations.get(o).add(e)}removeStore(e){const s=e.libStoreId,o=e.libStoreSuspenseId,{ids:r}=this.storesRelations.get(e.libStoreContextId)??{ids:new Set};this.stores.has(s)&&(this.stores.delete(s),r.delete(s),o&&this.suspenseRelations.get(o)?.has(s)&&this.suspenseRelations.get(o).delete(s),this.removeRelationContext(e.libStoreContextId),"onDestroy"in e&&e.onDestroy?.(),t.publish(i.DELETE_STORE,{store:e}))}mountStores(e,{globalStores:s={},relativeStores:o={}}){const{shouldRemoveInitState:r}=this.options,n={...s,...o};return Object.values(n).forEach(e=>{const s=e.libStoreId;r&&this.initState[s]&&delete this.initState[s],this.setStoreStatus(e,S.inUse),t.publish(i.MOUNT_STORE,{store:e})}),()=>{Object.values(n).forEach(e=>{e.isGlobal||(this.setStoreStatus(e,S.unused),t.publish(i.UNMOUNT_STORE,{store:e}))}),this.removeRelationContext(e)}}destroy(){for(const t of Array.from(this.stores.values()))clearTimeout(t.libDestroyTimer),this.removeStore(t);this.storesRelations.clear(),this.suspenseRelations.clear()}touchedStores(t){Object.values(t).forEach(t=>{t.libStoreStatus!==S.init||t.isGlobal||this.setStoreStatus(t,S.touched)})}setStoreStatus(t,e){const{destroyTimers:{init:s=500,touched:o=1e4,unused:r=1e3}={}}=this.options;t.libStoreStatus=e,clearTimeout(t.libDestroyTimer);let i=0;switch(e){case S.init:i=s;break;case S.touched:i=o;break;case S.unused:i=r}i&&(t.libDestroyTimer=setTimeout(()=>this.removeStore(t),i))}getStoreState(t,e=!1){return t.toJSON?.()??u.getObservableProps(t,e)}toJSON(t,e=!1){const s={},o=Array.isArray(t)?t.reduce((t,e)=>(this.stores.has(e)&&t.set(e,this.stores.get(e)),t),new Map):this.stores;for(const[t,r]of o.entries())s[t]=this.getStoreState(r,e);return s}async savePersistedStore(t){if(this.options.shouldDisablePersist||!this.storage)return!1;try{return await this.storage.saveStoreData(t,this.getStoreState(t,!0)),!0}catch(t){this.logger.err("Failed to persist stores: ",t)}return!1}static getObservableProps(t,o=!1){const r=e(t);return Object.entries(r).reduce((e,[r,i])=>({...e,...s(t,r)&&!l(t,r,o)||h(t,r)?{[r]:i}:{},...a(t,r)?{[r]:u.getObservableProps(t[r])}:{}}),{})}static persistStore(t,e,s={}){return u.persistedStores.add(e),t.libStoreId=e,"libStorageOptions"in t.prototype||(t.prototype.libStorageOptions=s),"wakeup"in t.prototype||(t.prototype.wakeup=c),"addOnChangeListener"in t.prototype||(t.prototype.addOnChangeListener=d),t}}export{u as default};
2
2
  //# sourceMappingURL=manager.js.map