@hamak/ui-store 0.5.9 → 0.7.1

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 (30) hide show
  1. package/dist/api/tokens/service-tokens.d.ts +8 -0
  2. package/dist/api/tokens/service-tokens.d.ts.map +1 -1
  3. package/dist/api/tokens/service-tokens.js +11 -0
  4. package/dist/api/types/store-types.d.ts +20 -12
  5. package/dist/api/types/store-types.d.ts.map +1 -1
  6. package/dist/impl/core/store-manager.d.ts +13 -1
  7. package/dist/impl/core/store-manager.d.ts.map +1 -1
  8. package/dist/impl/core/store-manager.js +17 -0
  9. package/dist/impl/index.d.ts +1 -0
  10. package/dist/impl/index.d.ts.map +1 -1
  11. package/dist/impl/index.js +1 -0
  12. package/dist/impl/persistence/index.d.ts +9 -0
  13. package/dist/impl/persistence/index.d.ts.map +1 -0
  14. package/dist/impl/persistence/index.js +8 -0
  15. package/dist/impl/persistence/persistence-filter.d.ts +11 -0
  16. package/dist/impl/persistence/persistence-filter.d.ts.map +1 -0
  17. package/dist/impl/persistence/persistence-filter.js +24 -0
  18. package/dist/impl/persistence/persistence-resolver.d.ts +9 -0
  19. package/dist/impl/persistence/persistence-resolver.d.ts.map +1 -0
  20. package/dist/impl/persistence/persistence-resolver.js +22 -0
  21. package/dist/impl/persistence/persistence-wiring.d.ts +29 -0
  22. package/dist/impl/persistence/persistence-wiring.d.ts.map +1 -0
  23. package/dist/impl/persistence/persistence-wiring.js +69 -0
  24. package/dist/impl/persistence/web-storage-persistence-provider.d.ts +46 -0
  25. package/dist/impl/persistence/web-storage-persistence-provider.d.ts.map +1 -0
  26. package/dist/impl/persistence/web-storage-persistence-provider.js +155 -0
  27. package/dist/impl/plugin/store-plugin-factory.d.ts +13 -1
  28. package/dist/impl/plugin/store-plugin-factory.d.ts.map +1 -1
  29. package/dist/impl/plugin/store-plugin-factory.js +56 -1
  30. package/package.json +1 -1
@@ -7,4 +7,12 @@ export declare const MIDDLEWARE_REGISTRY_TOKEN: unique symbol;
7
7
  export declare const REDUCER_REGISTRY_TOKEN: unique symbol;
8
8
  export declare const STORE_EXTENSIONS_TOKEN: unique symbol;
9
9
  export declare const AUTOSAVE_REGISTRY_TOKEN: unique symbol;
10
+ export declare const PERSISTENCE_PROVIDER_TOKEN: unique symbol;
11
+ /**
12
+ * Global-registry alias for {@link PERSISTENCE_PROVIDER_TOKEN}. The unique
13
+ * symbol above only resolves for consumers that import it from this package;
14
+ * decoupled sibling plugins look the provider up via `Symbol.for(...)` instead
15
+ * (same convention as `Symbol.for('StoreManager')`, see #28 and #5).
16
+ */
17
+ export declare const PERSISTENCE_PROVIDER_GLOBAL_TOKEN: unique symbol;
10
18
  //# sourceMappingURL=service-tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-tokens.d.ts","sourceRoot":"","sources":["../../../src/api/tokens/service-tokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,mBAAmB,eAAyB,CAAC;AAC1D,eAAO,MAAM,yBAAyB,eAA+B,CAAC;AACtE,eAAO,MAAM,sBAAsB,eAA4B,CAAC;AAChE,eAAO,MAAM,sBAAsB,eAA4B,CAAC;AAGhE,eAAO,MAAM,uBAAuB,eAA6B,CAAC"}
1
+ {"version":3,"file":"service-tokens.d.ts","sourceRoot":"","sources":["../../../src/api/tokens/service-tokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,mBAAmB,eAAyB,CAAC;AAC1D,eAAO,MAAM,yBAAyB,eAA+B,CAAC;AACtE,eAAO,MAAM,sBAAsB,eAA4B,CAAC;AAChE,eAAO,MAAM,sBAAsB,eAA4B,CAAC;AAGhE,eAAO,MAAM,uBAAuB,eAA6B,CAAC;AAKlE,eAAO,MAAM,0BAA0B,eAAgC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,iCAAiC,eAE7C,CAAC"}
@@ -8,3 +8,14 @@ export const REDUCER_REGISTRY_TOKEN = Symbol('ReducerRegistry');
8
8
  export const STORE_EXTENSIONS_TOKEN = Symbol('StoreExtensions');
9
9
  // Autosave tokens
10
10
  export const AUTOSAVE_REGISTRY_TOKEN = Symbol('AutosaveRegistry');
11
+ // Persistence token — resolves the active IPersistenceProvider so consumers
12
+ // (or sibling plugins) can mount a custom provider instead of the default
13
+ // web-storage one. See amah/app-framework#30.
14
+ export const PERSISTENCE_PROVIDER_TOKEN = Symbol('PersistenceProvider');
15
+ /**
16
+ * Global-registry alias for {@link PERSISTENCE_PROVIDER_TOKEN}. The unique
17
+ * symbol above only resolves for consumers that import it from this package;
18
+ * decoupled sibling plugins look the provider up via `Symbol.for(...)` instead
19
+ * (same convention as `Symbol.for('StoreManager')`, see #28 and #5).
20
+ */
21
+ export const PERSISTENCE_PROVIDER_GLOBAL_TOKEN = Symbol.for('@hamak/ui-store:PersistenceProvider');
@@ -2,6 +2,25 @@
2
2
  * Store Type Definitions
3
3
  */
4
4
  import type { Store, Action, StoreEnhancer } from 'redux';
5
+ /**
6
+ * State persistence configuration. Persistence is applied at the slice
7
+ * (top-level reducer key) level: a whitelisted slice is serialized to the
8
+ * configured web storage and rehydrated on boot. See amah/app-framework#30.
9
+ */
10
+ export interface StorePersistenceConfig {
11
+ /** Enable state persistence */
12
+ enabled: boolean;
13
+ /** Storage key the serialized slice is written under */
14
+ key?: string;
15
+ /** Storage type (indexedDB is not yet implemented and falls back to localStorage) */
16
+ storage?: 'localStorage' | 'sessionStorage' | 'indexedDB';
17
+ /** Slices to persist (top-level reducer keys); when set, only these are written */
18
+ whitelist?: string[];
19
+ /** Slices to never persist (top-level reducer keys); applied after the whitelist */
20
+ blacklist?: string[];
21
+ /** Debounce window (ms) for save-on-change; defaults to 250ms */
22
+ debounceMs?: number;
23
+ }
5
24
  /**
6
25
  * Store configuration
7
26
  */
@@ -13,18 +32,7 @@ export interface StoreConfig {
13
32
  /** Additional store enhancers */
14
33
  enhancers?: StoreEnhancer[];
15
34
  /** Persistence configuration */
16
- persistence?: {
17
- /** Enable state persistence */
18
- enabled: boolean;
19
- /** Storage key prefix */
20
- key?: string;
21
- /** Storage type */
22
- storage?: 'localStorage' | 'sessionStorage' | 'indexedDB';
23
- /** Keys to persist (whitelist) */
24
- whitelist?: string[];
25
- /** Keys to ignore (blacklist) */
26
- blacklist?: string[];
27
- };
35
+ persistence?: StorePersistenceConfig;
28
36
  }
29
37
  /**
30
38
  * Root state shape (can be extended by applications)
@@ -1 +1 @@
1
- {"version":3,"file":"store-types.d.ts","sourceRoot":"","sources":["../../../src/api/types/store-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,GAAG,CAAC;IAErB,iCAAiC;IACjC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAE5B,gCAAgC;IAChC,WAAW,CAAC,EAAE;QACZ,+BAA+B;QAC/B,OAAO,EAAE,OAAO,CAAC;QACjB,yBAAyB;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,mBAAmB;QACnB,OAAO,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,WAAW,CAAC;QAC1D,kCAAkC;QAClC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QACrB,iCAAiC;QACjC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,MAAM,CAAC,MAAM,CAAC;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC"}
1
+ {"version":3,"file":"store-types.d.ts","sourceRoot":"","sources":["../../../src/api/types/store-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,+BAA+B;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,wDAAwD;IACxD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,OAAO,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,WAAW,CAAC;IAC1D,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,oFAAoF;IACpF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,GAAG,CAAC;IAErB,iCAAiC;IACjC,SAAS,CAAC,EAAE,aAAa,EAAE,CAAC;IAE5B,gCAAgC;IAChC,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,GAAG,CAAE,SAAQ,MAAM,CAAC,MAAM,CAAC;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC"}
@@ -3,21 +3,33 @@
3
3
  */
4
4
  import { type Store, type Reducer } from '@reduxjs/toolkit';
5
5
  import type { IStoreManager, StoreConfig, RootState, AppAction } from '../../api/index.js';
6
+ import type { IPersistenceProvider } from '../../spi/index.js';
6
7
  import { MiddlewareRegistry } from './middleware-registry.js';
7
8
  import { ReducerRegistry } from './reducer-registry.js';
9
+ /**
10
+ * Internal store config. Extends the public {@link StoreConfig} with the
11
+ * resolved persistence provider, which the plugin factory injects after
12
+ * resolving it via DI (see amah/app-framework#30). Kept out of the public
13
+ * type so consumers configure persistence declaratively, not by passing
14
+ * provider instances.
15
+ */
16
+ export interface StoreManagerConfig extends StoreConfig {
17
+ persistenceProvider?: IPersistenceProvider;
18
+ }
8
19
  export declare class StoreManager implements IStoreManager {
9
20
  private store;
10
21
  private middlewareRegistry;
11
22
  private reducerRegistry;
12
23
  private initialized;
13
24
  private fileSystemAdapter;
25
+ private persistenceUnsubscribe;
14
26
  constructor();
15
27
  getMiddlewareRegistry(): MiddlewareRegistry;
16
28
  getReducerRegistry(): ReducerRegistry;
17
29
  setFileSystemAdapter(adapter: any): void;
18
30
  getFileSystemAdapter(): any;
19
31
  isInitialized(): boolean;
20
- initialize(config?: StoreConfig): Store<RootState, AppAction>;
32
+ initialize(config?: StoreManagerConfig): Store<RootState, AppAction>;
21
33
  getStore(): Store<RootState, AppAction>;
22
34
  dispatch<A extends AppAction>(action: A): A;
23
35
  getState<S = RootState>(): S;
@@ -1 +1 @@
1
- {"version":3,"file":"store-manager.d.ts","sourceRoot":"","sources":["../../../src/impl/core/store-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAoB;;IAY7C,qBAAqB;IAIrB,kBAAkB;IAIlB,oBAAoB,CAAC,OAAO,EAAE,GAAG;IAIjC,oBAAoB;IAIpB,aAAa,IAAI,OAAO;IAIxB,UAAU,CAAC,MAAM,GAAE,WAAgB,GAAG,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC;IAsDjE,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC;IAOvC,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC;IAI3C,QAAQ,CAAC,CAAC,GAAG,SAAS,KAAK,CAAC;IAI5B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAI3C,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,IAAI;IAIhE,OAAO,IAAI,IAAI;CAKhB"}
1
+ {"version":3,"file":"store-manager.d.ts","sourceRoot":"","sources":["../../../src/impl/core/store-manager.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAkB,KAAK,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAClF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;CAC5C;AAED,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,KAAK,CAA4C;IACzD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,sBAAsB,CAA6B;;IAY3D,qBAAqB;IAIrB,kBAAkB;IAIlB,oBAAoB,CAAC,OAAO,EAAE,GAAG;IAIjC,oBAAoB;IAIpB,aAAa,IAAI,OAAO;IAIxB,UAAU,CAAC,MAAM,GAAE,kBAAuB,GAAG,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC;IAiExE,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC;IAOvC,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC;IAI3C,QAAQ,CAAC,CAAC,GAAG,SAAS,KAAK,CAAC;IAI5B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI;IAI3C,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,GAAG,IAAI;IAIhE,OAAO,IAAI,IAAI;CAShB"}
@@ -2,6 +2,7 @@
2
2
  * Store Manager Implementation
3
3
  */
4
4
  import { configureStore } from '@reduxjs/toolkit';
5
+ import { attachPersistenceSave } from '../persistence/index.js';
5
6
  import { MiddlewareRegistry } from './middleware-registry.js';
6
7
  import { ReducerRegistry } from './reducer-registry.js';
7
8
  export class StoreManager {
@@ -36,6 +37,12 @@ export class StoreManager {
36
37
  writable: true,
37
38
  value: null
38
39
  });
40
+ Object.defineProperty(this, "persistenceUnsubscribe", {
41
+ enumerable: true,
42
+ configurable: true,
43
+ writable: true,
44
+ value: null
45
+ });
39
46
  this.middlewareRegistry = new MiddlewareRegistry();
40
47
  this.reducerRegistry = new ReducerRegistry((rootReducer) => {
41
48
  // Hot replacement callback
@@ -92,6 +99,12 @@ export class StoreManager {
92
99
  } : false,
93
100
  });
94
101
  this.initialized = true;
102
+ // Persist the whitelisted slice on change when persistence is enabled and a
103
+ // provider was injected (rehydration is handled upstream by the plugin via
104
+ // preloadedState, since load() is async). See amah/app-framework#30.
105
+ if (config.persistence?.enabled && config.persistenceProvider) {
106
+ this.persistenceUnsubscribe = attachPersistenceSave(this.store, config.persistenceProvider, config.persistence);
107
+ }
95
108
  console.log('[StoreManager] Store initialized with:', {
96
109
  reducers: this.reducerRegistry.getAllRegistrations().map(r => r.key),
97
110
  middleware: this.middlewareRegistry.getAllRegistrations().map(m => ({
@@ -122,6 +135,10 @@ export class StoreManager {
122
135
  this.getStore().replaceReducer(nextReducer);
123
136
  }
124
137
  destroy() {
138
+ if (this.persistenceUnsubscribe) {
139
+ this.persistenceUnsubscribe();
140
+ this.persistenceUnsubscribe = null;
141
+ }
125
142
  this.store = null;
126
143
  this.initialized = false;
127
144
  console.log('[StoreManager] Store destroyed');
@@ -9,4 +9,5 @@ export * from './middleware/index.js';
9
9
  export * from './plugin/index.js';
10
10
  export * from './fs/index.js';
11
11
  export * from './autosave/index.js';
12
+ export * from './persistence/index.js';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/impl/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,MAAM,CAAC;AACrB,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/impl/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,MAAM,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC"}
@@ -10,3 +10,4 @@ export * from './middleware/index.js';
10
10
  export * from './plugin/index.js';
11
11
  export * from './fs/index.js';
12
12
  export * from './autosave/index.js';
13
+ export * from './persistence/index.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * State persistence implementations and wiring.
3
+ * See amah/app-framework#30.
4
+ */
5
+ export * from './web-storage-persistence-provider.js';
6
+ export * from './persistence-filter.js';
7
+ export * from './persistence-resolver.js';
8
+ export * from './persistence-wiring.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/impl/persistence/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,oCAAoC,CAAC;AACnD,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * State persistence implementations and wiring.
3
+ * See amah/app-framework#30.
4
+ */
5
+ export * from './web-storage-persistence-provider.js';
6
+ export * from './persistence-filter.js';
7
+ export * from './persistence-resolver.js';
8
+ export * from './persistence-wiring.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Slice-level whitelist/blacklist filtering for persisted state.
3
+ *
4
+ * Persistence operates at the top-level reducer key ("slice") granularity:
5
+ * a whitelist keeps only the named slices; a blacklist drops named slices.
6
+ * The whitelist is applied first, then the blacklist. See amah/app-framework#30.
7
+ */
8
+ import type { StorePersistenceConfig } from '../../api/index.js';
9
+ export type SliceSelection = Pick<StorePersistenceConfig, 'whitelist' | 'blacklist'>;
10
+ export declare function pickPersistableState(state: Record<string, unknown> | null | undefined, selection: SliceSelection): Record<string, unknown>;
11
+ //# sourceMappingURL=persistence-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-filter.d.ts","sourceRoot":"","sources":["../../../src/impl/persistence/persistence-filter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,IAAI,CAC/B,sBAAsB,EACtB,WAAW,GAAG,WAAW,CAC1B,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,EACjD,SAAS,EAAE,cAAc,GACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgBzB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Slice-level whitelist/blacklist filtering for persisted state.
3
+ *
4
+ * Persistence operates at the top-level reducer key ("slice") granularity:
5
+ * a whitelist keeps only the named slices; a blacklist drops named slices.
6
+ * The whitelist is applied first, then the blacklist. See amah/app-framework#30.
7
+ */
8
+ export function pickPersistableState(state, selection) {
9
+ if (!state || typeof state !== 'object')
10
+ return {};
11
+ const { whitelist, blacklist } = selection;
12
+ const result = {};
13
+ for (const key of Object.keys(state)) {
14
+ // A set whitelist constrains to its members (an empty whitelist persists
15
+ // nothing); an unset whitelist imposes no constraint. The blacklist is
16
+ // then applied on top.
17
+ if (whitelist && !whitelist.includes(key))
18
+ continue;
19
+ if (blacklist && blacklist.includes(key))
20
+ continue;
21
+ result[key] = state[key];
22
+ }
23
+ return result;
24
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolves a concrete `IPersistenceProvider` from a `StorePersistenceConfig`.
3
+ * Used to build the default provider when a consumer enables persistence but
4
+ * does not mount a custom one. See amah/app-framework#30.
5
+ */
6
+ import type { StorePersistenceConfig } from '../../api/index.js';
7
+ import type { IPersistenceProvider } from '../../spi/index.js';
8
+ export declare function resolvePersistenceProvider(persistence: StorePersistenceConfig): IPersistenceProvider;
9
+ //# sourceMappingURL=persistence-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-resolver.d.ts","sourceRoot":"","sources":["../../../src/impl/persistence/persistence-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMtD,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,sBAAsB,GAClC,oBAAoB,CAiBtB"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Resolves a concrete `IPersistenceProvider` from a `StorePersistenceConfig`.
3
+ * Used to build the default provider when a consumer enables persistence but
4
+ * does not mount a custom one. See amah/app-framework#30.
5
+ */
6
+ import { createLocalStoragePersistenceProvider, createSessionStoragePersistenceProvider, } from './web-storage-persistence-provider.js';
7
+ export function resolvePersistenceProvider(persistence) {
8
+ const storage = persistence.storage ?? 'localStorage';
9
+ switch (storage) {
10
+ case 'sessionStorage':
11
+ return createSessionStoragePersistenceProvider();
12
+ case 'indexedDB':
13
+ // Not yet implemented — fall back to localStorage so enabling it is not a
14
+ // silent no-op. Tracked as a follow-up in #30.
15
+ console.warn("[persistence] storage 'indexedDB' is not implemented yet; " +
16
+ 'falling back to localStorage.');
17
+ return createLocalStoragePersistenceProvider();
18
+ case 'localStorage':
19
+ default:
20
+ return createLocalStoragePersistenceProvider();
21
+ }
22
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Load/save wiring that connects a Redux store to an `IPersistenceProvider`.
3
+ *
4
+ * - `loadPersistedState` rehydrates the whitelisted slice on boot (async,
5
+ * called before store creation so the result can seed `preloadedState`).
6
+ * - `attachPersistenceSave` subscribes to the store and writes the whitelisted
7
+ * slice back, debounced, on every change. See amah/app-framework#30.
8
+ */
9
+ import type { Store } from 'redux';
10
+ import type { StorePersistenceConfig } from '../../api/index.js';
11
+ import type { IPersistenceProvider } from '../../spi/index.js';
12
+ /** Default storage key when `persistence.key` is not set. */
13
+ export declare const DEFAULT_PERSISTENCE_KEY = "state";
14
+ /** Default debounce window for save-on-change. */
15
+ export declare const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 250;
16
+ export declare function persistenceKey(persistence: StorePersistenceConfig): string;
17
+ /**
18
+ * Load the persisted slice and re-apply the whitelist/blacklist defensively
19
+ * (in case the config tightened since the data was written). Returns
20
+ * `undefined` when nothing is persisted, so callers can spread it into
21
+ * `preloadedState` without overwriting reducer defaults.
22
+ */
23
+ export declare function loadPersistedState(provider: IPersistenceProvider, persistence: StorePersistenceConfig): Promise<Record<string, unknown> | undefined>;
24
+ /**
25
+ * Subscribe to the store and persist the whitelisted slice on change,
26
+ * debounced. Returns an unsubscribe function that also cancels a pending save.
27
+ */
28
+ export declare function attachPersistenceSave(store: Pick<Store, 'getState' | 'subscribe'>, provider: IPersistenceProvider, persistence: StorePersistenceConfig): () => void;
29
+ //# sourceMappingURL=persistence-wiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-wiring.d.ts","sourceRoot":"","sources":["../../../src/impl/persistence/persistence-wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAGtD,6DAA6D;AAC7D,eAAO,MAAM,uBAAuB,UAAU,CAAC;AAE/C,kDAAkD;AAClD,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,wBAAgB,cAAc,CAAC,WAAW,EAAE,sBAAsB,GAAG,MAAM,CAE1E;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,oBAAoB,EAC9B,WAAW,EAAE,sBAAsB,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAQ9C;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAC,EAC5C,QAAQ,EAAE,oBAAoB,EAC9B,WAAW,EAAE,sBAAsB,GAClC,MAAM,IAAI,CAyCZ"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Load/save wiring that connects a Redux store to an `IPersistenceProvider`.
3
+ *
4
+ * - `loadPersistedState` rehydrates the whitelisted slice on boot (async,
5
+ * called before store creation so the result can seed `preloadedState`).
6
+ * - `attachPersistenceSave` subscribes to the store and writes the whitelisted
7
+ * slice back, debounced, on every change. See amah/app-framework#30.
8
+ */
9
+ import { pickPersistableState } from './persistence-filter.js';
10
+ /** Default storage key when `persistence.key` is not set. */
11
+ export const DEFAULT_PERSISTENCE_KEY = 'state';
12
+ /** Default debounce window for save-on-change. */
13
+ export const DEFAULT_PERSISTENCE_DEBOUNCE_MS = 250;
14
+ export function persistenceKey(persistence) {
15
+ return persistence.key ?? DEFAULT_PERSISTENCE_KEY;
16
+ }
17
+ /**
18
+ * Load the persisted slice and re-apply the whitelist/blacklist defensively
19
+ * (in case the config tightened since the data was written). Returns
20
+ * `undefined` when nothing is persisted, so callers can spread it into
21
+ * `preloadedState` without overwriting reducer defaults.
22
+ */
23
+ export async function loadPersistedState(provider, persistence) {
24
+ const loaded = await provider.load(persistenceKey(persistence));
25
+ if (loaded == null || typeof loaded !== 'object')
26
+ return undefined;
27
+ const filtered = pickPersistableState(loaded, persistence);
28
+ return Object.keys(filtered).length > 0 ? filtered : undefined;
29
+ }
30
+ /**
31
+ * Subscribe to the store and persist the whitelisted slice on change,
32
+ * debounced. Returns an unsubscribe function that also cancels a pending save.
33
+ */
34
+ export function attachPersistenceSave(store, provider, persistence) {
35
+ const key = persistenceKey(persistence);
36
+ const debounceMs = persistence.debounceMs ?? DEFAULT_PERSISTENCE_DEBOUNCE_MS;
37
+ let timer = null;
38
+ // Seed the baseline with the current slice so a change that leaves the
39
+ // persisted slice untouched (e.g. a non-whitelisted slice updated) never
40
+ // triggers a redundant write — including right after rehydration.
41
+ let lastSerialized = JSON.stringify(pickPersistableState(store.getState(), persistence));
42
+ const flush = () => {
43
+ timer = null;
44
+ const slice = pickPersistableState(store.getState(), persistence);
45
+ const serialized = JSON.stringify(slice);
46
+ // Skip redundant writes when the persisted slice did not actually change.
47
+ if (serialized === lastSerialized)
48
+ return;
49
+ lastSerialized = serialized;
50
+ void provider.save(key, slice);
51
+ };
52
+ const unsubscribe = store.subscribe(() => {
53
+ if (timer !== null)
54
+ clearTimeout(timer);
55
+ if (debounceMs <= 0) {
56
+ flush();
57
+ }
58
+ else {
59
+ timer = setTimeout(flush, debounceMs);
60
+ }
61
+ });
62
+ return () => {
63
+ if (timer !== null) {
64
+ clearTimeout(timer);
65
+ timer = null;
66
+ }
67
+ unsubscribe();
68
+ };
69
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Web Storage Persistence Provider
3
+ *
4
+ * Concrete `IPersistenceProvider` backed by a Web Storage area
5
+ * (`localStorage` / `sessionStorage`). When the backing storage is
6
+ * unavailable — SSR, private-mode quota errors, disabled storage — it
7
+ * degrades gracefully to an in-memory map so callers never throw. This mirrors
8
+ * the `createLocalStorageActiveProjectStorage` fallback pattern used
9
+ * downstream. See amah/app-framework#30.
10
+ */
11
+ import type { IPersistenceProvider } from '../../spi/index.js';
12
+ /** Minimal structural subset of the Web Storage API we depend on. */
13
+ export interface WebStorageLike {
14
+ getItem(key: string): string | null;
15
+ setItem(key: string, value: string): void;
16
+ removeItem(key: string): void;
17
+ key(index: number): string | null;
18
+ readonly length: number;
19
+ }
20
+ /** Default namespace every persisted key is prefixed with. */
21
+ export declare const DEFAULT_PERSISTENCE_NAMESPACE = "@hamak/ui-store";
22
+ export declare class WebStoragePersistenceProvider implements IPersistenceProvider {
23
+ private readonly storage;
24
+ private readonly namespace;
25
+ private readonly memory;
26
+ private readonly available;
27
+ /**
28
+ * @param storage Backing web storage, or `null` to run in-memory only.
29
+ * @param namespace Key prefix; `clear()` only removes keys under this prefix
30
+ * so it never wipes unrelated app state.
31
+ */
32
+ constructor(storage: WebStorageLike | null, namespace?: string);
33
+ isAvailable(): boolean;
34
+ save(key: string, state: unknown): Promise<void>;
35
+ load(key: string): Promise<any | null>;
36
+ remove(key: string): Promise<void>;
37
+ clear(): Promise<void>;
38
+ private fullKey;
39
+ /** Verify the storage area is usable with a write/remove round-trip. */
40
+ private static probe;
41
+ }
42
+ /** Provider backed by `window.localStorage` (in-memory fallback when absent). */
43
+ export declare function createLocalStoragePersistenceProvider(namespace?: string): WebStoragePersistenceProvider;
44
+ /** Provider backed by `window.sessionStorage` (in-memory fallback when absent). */
45
+ export declare function createSessionStoragePersistenceProvider(namespace?: string): WebStoragePersistenceProvider;
46
+ //# sourceMappingURL=web-storage-persistence-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-storage-persistence-provider.d.ts","sourceRoot":"","sources":["../../../src/impl/persistence/web-storage-persistence-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEtD,qEAAqE;AACrE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,oBAAoB,CAAC;AAI/D,qBAAa,6BAA8B,YAAW,oBAAoB;IAUtE,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAV5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6B;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IAEpC;;;;OAIG;gBAEgB,OAAO,EAAE,cAAc,GAAG,IAAI,EAC9B,SAAS,GAAE,MAAsC;IAKpE,WAAW,IAAI,OAAO;IAIhB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAsBtC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYlC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5B,OAAO,CAAC,OAAO;IAIf,wEAAwE;IACxE,OAAO,CAAC,MAAM,CAAC,KAAK;CAUrB;AAcD,iFAAiF;AACjF,wBAAgB,qCAAqC,CACnD,SAAS,CAAC,EAAE,MAAM,GACjB,6BAA6B,CAK/B;AAED,mFAAmF;AACnF,wBAAgB,uCAAuC,CACrD,SAAS,CAAC,EAAE,MAAM,GACjB,6BAA6B,CAK/B"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Web Storage Persistence Provider
3
+ *
4
+ * Concrete `IPersistenceProvider` backed by a Web Storage area
5
+ * (`localStorage` / `sessionStorage`). When the backing storage is
6
+ * unavailable — SSR, private-mode quota errors, disabled storage — it
7
+ * degrades gracefully to an in-memory map so callers never throw. This mirrors
8
+ * the `createLocalStorageActiveProjectStorage` fallback pattern used
9
+ * downstream. See amah/app-framework#30.
10
+ */
11
+ /** Default namespace every persisted key is prefixed with. */
12
+ export const DEFAULT_PERSISTENCE_NAMESPACE = '@hamak/ui-store';
13
+ const PROBE_KEY = '@hamak/ui-store:__persist_probe__';
14
+ export class WebStoragePersistenceProvider {
15
+ /**
16
+ * @param storage Backing web storage, or `null` to run in-memory only.
17
+ * @param namespace Key prefix; `clear()` only removes keys under this prefix
18
+ * so it never wipes unrelated app state.
19
+ */
20
+ constructor(storage, namespace = DEFAULT_PERSISTENCE_NAMESPACE) {
21
+ Object.defineProperty(this, "storage", {
22
+ enumerable: true,
23
+ configurable: true,
24
+ writable: true,
25
+ value: storage
26
+ });
27
+ Object.defineProperty(this, "namespace", {
28
+ enumerable: true,
29
+ configurable: true,
30
+ writable: true,
31
+ value: namespace
32
+ });
33
+ Object.defineProperty(this, "memory", {
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true,
37
+ value: new Map()
38
+ });
39
+ Object.defineProperty(this, "available", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: void 0
44
+ });
45
+ this.available = WebStoragePersistenceProvider.probe(storage);
46
+ }
47
+ isAvailable() {
48
+ return this.available;
49
+ }
50
+ async save(key, state) {
51
+ const serialized = JSON.stringify(state);
52
+ const fullKey = this.fullKey(key);
53
+ if (this.available && this.storage) {
54
+ try {
55
+ this.storage.setItem(fullKey, serialized);
56
+ return;
57
+ }
58
+ catch (e) {
59
+ console.warn('[persistence] save failed, falling back to memory:', e);
60
+ }
61
+ }
62
+ this.memory.set(fullKey, serialized);
63
+ }
64
+ async load(key) {
65
+ const fullKey = this.fullKey(key);
66
+ let raw = null;
67
+ if (this.available && this.storage) {
68
+ try {
69
+ raw = this.storage.getItem(fullKey);
70
+ }
71
+ catch (e) {
72
+ console.warn('[persistence] load failed:', e);
73
+ }
74
+ }
75
+ if (raw == null) {
76
+ raw = this.memory.get(fullKey) ?? null;
77
+ }
78
+ if (raw == null)
79
+ return null;
80
+ try {
81
+ return JSON.parse(raw);
82
+ }
83
+ catch (e) {
84
+ console.warn('[persistence] could not parse persisted state:', e);
85
+ return null;
86
+ }
87
+ }
88
+ async remove(key) {
89
+ const fullKey = this.fullKey(key);
90
+ this.memory.delete(fullKey);
91
+ if (this.available && this.storage) {
92
+ try {
93
+ this.storage.removeItem(fullKey);
94
+ }
95
+ catch (e) {
96
+ console.warn('[persistence] remove failed:', e);
97
+ }
98
+ }
99
+ }
100
+ async clear() {
101
+ // Only remove keys under our namespace — never the whole storage area.
102
+ for (const memKey of [...this.memory.keys()]) {
103
+ if (memKey.startsWith(this.namespace))
104
+ this.memory.delete(memKey);
105
+ }
106
+ if (this.available && this.storage) {
107
+ try {
108
+ const toRemove = [];
109
+ for (let i = 0; i < this.storage.length; i++) {
110
+ const k = this.storage.key(i);
111
+ if (k && k.startsWith(this.namespace))
112
+ toRemove.push(k);
113
+ }
114
+ toRemove.forEach((k) => this.storage.removeItem(k));
115
+ }
116
+ catch (e) {
117
+ console.warn('[persistence] clear failed:', e);
118
+ }
119
+ }
120
+ }
121
+ fullKey(key) {
122
+ return `${this.namespace}:${key}`;
123
+ }
124
+ /** Verify the storage area is usable with a write/remove round-trip. */
125
+ static probe(storage) {
126
+ if (!storage)
127
+ return false;
128
+ try {
129
+ storage.setItem(PROBE_KEY, '1');
130
+ storage.removeItem(PROBE_KEY);
131
+ return true;
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ }
138
+ function getWindowStorage(kind) {
139
+ try {
140
+ const g = globalThis;
141
+ return g[kind] ?? null;
142
+ }
143
+ catch {
144
+ // Accessing storage can throw in some sandboxed environments.
145
+ return null;
146
+ }
147
+ }
148
+ /** Provider backed by `window.localStorage` (in-memory fallback when absent). */
149
+ export function createLocalStoragePersistenceProvider(namespace) {
150
+ return new WebStoragePersistenceProvider(getWindowStorage('localStorage'), namespace);
151
+ }
152
+ /** Provider backed by `window.sessionStorage` (in-memory fallback when absent). */
153
+ export function createSessionStoragePersistenceProvider(namespace) {
154
+ return new WebStoragePersistenceProvider(getWindowStorage('sessionStorage'), namespace);
155
+ }
@@ -3,13 +3,25 @@
3
3
  * Creates a microkernel plugin for Redux store management
4
4
  */
5
5
  import type { PluginModule } from '@hamak/microkernel-spi';
6
- import { type StorePluginExtensions } from '../../api/index.js';
6
+ import { type StorePluginExtensions, type StorePersistenceConfig } from '../../api/index.js';
7
+ import type { IPersistenceProvider } from '../../spi/index.js';
7
8
  export declare const FILESYSTEM_ADAPTER_TOKEN = "FILESYSTEM_ADAPTER";
8
9
  export interface StorePluginConfig extends StorePluginExtensions {
9
10
  /** Enable Redux DevTools integration */
10
11
  devTools?: boolean;
11
12
  /** Enable logger middleware in development */
12
13
  logger?: boolean;
14
+ /**
15
+ * State persistence configuration. When `enabled`, the whitelisted slice is
16
+ * rehydrated on boot and saved (debounced) on change. See #30.
17
+ */
18
+ persistence?: StorePersistenceConfig;
19
+ /**
20
+ * Custom persistence provider to mount instead of the default web-storage
21
+ * one. Takes precedence over any DI-registered provider and over the
22
+ * `persistence.storage` default. See #30.
23
+ */
24
+ persistenceProvider?: IPersistenceProvider;
13
25
  }
14
26
  export declare function createStorePlugin(config?: StorePluginConfig): PluginModule;
15
27
  //# sourceMappingURL=store-plugin-factory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-plugin-factory.d.ts","sourceRoot":"","sources":["../../../src/impl/plugin/store-plugin-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAC;AAanB,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAE7D,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC9D,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,iBAAsB,GAC7B,YAAY,CAqId"}
1
+ {"version":3,"file":"store-plugin-factory.d.ts","sourceRoot":"","sources":["../../../src/impl/plugin/store-plugin-factory.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAQL,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AActD,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAE7D,MAAM,WAAW,iBAAkB,SAAQ,qBAAqB;IAC9D,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,sBAAsB,CAAC;IAErC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,oBAAoB,CAAC;CAC5C;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,iBAAsB,GAC7B,YAAY,CAmMd"}
@@ -2,11 +2,12 @@
2
2
  * Store Plugin Factory
3
3
  * Creates a microkernel plugin for Redux store management
4
4
  */
5
- import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN, } from '../../api/index.js';
5
+ import { STORE_MANAGER_TOKEN, MIDDLEWARE_REGISTRY_TOKEN, REDUCER_REGISTRY_TOKEN, STORE_EXTENSIONS_TOKEN, PERSISTENCE_PROVIDER_TOKEN, PERSISTENCE_PROVIDER_GLOBAL_TOKEN, } from '../../api/index.js';
6
6
  import { StoreManager } from '../core/store-manager.js';
7
7
  import { createEventBridgeMiddleware, createLoggerMiddleware, } from '../middleware/index.js';
8
8
  import { applyStoreExtensions, createStoreExtensionsCollector, } from '../extensions/store-extensions.js';
9
9
  import { createFileSystemAdapter } from '../fs/core/fs-adapter.js';
10
+ import { resolvePersistenceProvider, loadPersistedState } from '../persistence/index.js';
10
11
  // DI token for filesystem adapter
11
12
  export const FILESYSTEM_ADAPTER_TOKEN = 'FILESYSTEM_ADAPTER';
12
13
  export function createStorePlugin(config = {}) {
@@ -14,6 +15,8 @@ export function createStorePlugin(config = {}) {
14
15
  const middlewareRegistry = storeManager.getMiddlewareRegistry();
15
16
  const reducerRegistry = storeManager.getReducerRegistry();
16
17
  const extensionsCollector = createStoreExtensionsCollector();
18
+ // Resolved once in initialize(), reused in activate() for rehydrate + save.
19
+ let persistenceProvider;
17
20
  // Create filesystem adapter with 'fs' slice name
18
21
  const fileSystemAdapter = createFileSystemAdapter('fs');
19
22
  const registerDefaultMiddleware = (hooks) => {
@@ -95,15 +98,67 @@ export function createStorePlugin(config = {}) {
95
98
  provide: FILESYSTEM_ADAPTER_TOKEN,
96
99
  useValue: fileSystemAdapter,
97
100
  });
101
+ // Back-compat aliases (global symbol registry). The unique-symbol tokens
102
+ // above only resolve for consumers that import them from
103
+ // `@hamak/ui-store-api`. Several sibling plugins (ui-navigation, auth,
104
+ // notification, event-channel) deliberately stay decoupled and look the
105
+ // store services up via `Symbol.for(...)` keys instead. Register the same
106
+ // instances under those global keys so those lookups succeed instead of
107
+ // silently no-opping. See amah/app-framework#28 and #5.
108
+ ctx.provide({
109
+ provide: Symbol.for('StoreManager'),
110
+ useValue: storeManager,
111
+ });
112
+ ctx.provide({
113
+ provide: Symbol.for('@hamak/ui-store:StoreExtensionsRegistry'),
114
+ useValue: extensionsCollector,
115
+ });
116
+ // Resolve the persistence provider (Option B, #30). Precedence:
117
+ // 1. explicit mount-time provider (config.persistenceProvider)
118
+ // 2. a provider another plugin already registered under the token
119
+ // 3. a default built from `persistence.storage`
120
+ // The resolved instance is re-provided under both the unique and global
121
+ // tokens so sibling plugins can look it up the same way they do the store.
122
+ persistenceProvider = config.persistenceProvider;
123
+ if (!persistenceProvider) {
124
+ try {
125
+ persistenceProvider = ctx.resolve(PERSISTENCE_PROVIDER_TOKEN);
126
+ }
127
+ catch {
128
+ // No provider registered yet — fall through to the default.
129
+ }
130
+ }
131
+ if (!persistenceProvider && config.persistence?.enabled) {
132
+ persistenceProvider = resolvePersistenceProvider(config.persistence);
133
+ }
134
+ if (persistenceProvider) {
135
+ ctx.provide({
136
+ provide: PERSISTENCE_PROVIDER_TOKEN,
137
+ useValue: persistenceProvider,
138
+ });
139
+ ctx.provide({
140
+ provide: PERSISTENCE_PROVIDER_GLOBAL_TOKEN,
141
+ useValue: persistenceProvider,
142
+ });
143
+ }
98
144
  registerDefaultMiddleware(ctx.hooks);
99
145
  registerConfigExtensions();
100
146
  console.log('[ui-store] Plugin initialized with filesystem support');
101
147
  },
102
148
  async activate(ctx) {
103
149
  applyStoreExtensions(extensionsCollector, middlewareRegistry, reducerRegistry);
150
+ // Rehydrate the persisted slice before store creation (load() is async,
151
+ // but preloadedState must be supplied synchronously). See #30.
152
+ let preloadedState;
153
+ if (persistenceProvider && config.persistence?.enabled) {
154
+ preloadedState = await loadPersistedState(persistenceProvider, config.persistence);
155
+ }
104
156
  // Initialize the store after all plugins have registered middleware/reducers
105
157
  const store = storeManager.initialize({
106
158
  devTools: config.devTools,
159
+ preloadedState,
160
+ persistence: config.persistence,
161
+ persistenceProvider,
107
162
  });
108
163
  // Bridge Redux state changes to microkernel events
109
164
  store.subscribe(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamak/ui-store",
3
- "version": "0.5.9",
3
+ "version": "0.7.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Redux store management for microkernel-based applications",