@asaidimu/utils-store 2.3.1 → 2.3.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.
- package/index.d.mts +48 -1
- package/index.d.ts +48 -1
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/package.json +2 -2
package/index.d.mts
CHANGED
|
@@ -1,4 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
interface SimplePersistence<T> {
|
|
2
|
+
/**
|
|
3
|
+
* Persists data to storage.
|
|
4
|
+
*
|
|
5
|
+
* @param id The **unique identifier of the *consumer instance*** making the change. This is NOT the ID of the data (`T`) itself.
|
|
6
|
+
* Think of it as the ID of the specific browser tab, component, or module that's currently interacting with the persistence layer.
|
|
7
|
+
* It should typically be a **UUID** generated once at the consumer instance's instantiation.
|
|
8
|
+
* This `id` is crucial for the `subscribe` method, helping to differentiate updates originating from the current instance versus other instances/tabs, thereby preventing self-triggered notification loops.
|
|
9
|
+
* @param state The state (of type T) to persist. This state is generally considered the **global or shared state** that all instances interact with.
|
|
10
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations (like `IndexedDBPersistence`), this returns a `Promise<boolean>`.
|
|
11
|
+
*/
|
|
12
|
+
set(id: string, state: T): boolean | Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves the global persisted data from storage.
|
|
15
|
+
*
|
|
16
|
+
* @returns The retrieved state of type `T`, or `null` if no data is found or if an error occurs during retrieval/parsing.
|
|
17
|
+
* For asynchronous implementations, this returns a `Promise<T | null>`.
|
|
18
|
+
*/
|
|
19
|
+
get(): (T | null) | (Promise<T | null>);
|
|
20
|
+
/**
|
|
21
|
+
* Subscribes to changes in the global persisted data that originate from *other* instances of your application (e.g., other tabs or independent components using the same persistence layer).
|
|
22
|
+
*
|
|
23
|
+
* @param id The **unique identifier of the *consumer instance* subscribing**. This allows the persistence implementation to filter out notifications that were initiated by the subscribing instance itself.
|
|
24
|
+
* @param callback The function to call when the global persisted data changes from *another* source. The new state (`T`) is passed as an argument to this callback.
|
|
25
|
+
* @returns A function that, when called, will unsubscribe the provided callback from future updates. Call this when your component or instance is no longer active to prevent memory leaks.
|
|
26
|
+
*/
|
|
27
|
+
subscribe(id: string, callback: (state: T) => void): () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Clears (removes) the entire global persisted data from storage.
|
|
30
|
+
*
|
|
31
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations, this returns a `Promise<boolean>`.
|
|
32
|
+
*/
|
|
33
|
+
clear(): boolean | Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns metadata about the persistence layer.
|
|
36
|
+
*
|
|
37
|
+
* This is useful for distinguishing between multiple apps running on the same host
|
|
38
|
+
* (e.g., several apps served at `localhost:3000` that share the same storage key).
|
|
39
|
+
*
|
|
40
|
+
* @returns An object containing:
|
|
41
|
+
* - `version`: The semantic version string of the persistence schema or application.
|
|
42
|
+
* - `id`: A unique identifier for the application using this persistence instance.
|
|
43
|
+
*/
|
|
44
|
+
stats(): {
|
|
45
|
+
version: string;
|
|
46
|
+
id: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
2
49
|
|
|
3
50
|
/**
|
|
4
51
|
* Utility type for representing partial updates to the state.
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
interface SimplePersistence<T> {
|
|
2
|
+
/**
|
|
3
|
+
* Persists data to storage.
|
|
4
|
+
*
|
|
5
|
+
* @param id The **unique identifier of the *consumer instance*** making the change. This is NOT the ID of the data (`T`) itself.
|
|
6
|
+
* Think of it as the ID of the specific browser tab, component, or module that's currently interacting with the persistence layer.
|
|
7
|
+
* It should typically be a **UUID** generated once at the consumer instance's instantiation.
|
|
8
|
+
* This `id` is crucial for the `subscribe` method, helping to differentiate updates originating from the current instance versus other instances/tabs, thereby preventing self-triggered notification loops.
|
|
9
|
+
* @param state The state (of type T) to persist. This state is generally considered the **global or shared state** that all instances interact with.
|
|
10
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations (like `IndexedDBPersistence`), this returns a `Promise<boolean>`.
|
|
11
|
+
*/
|
|
12
|
+
set(id: string, state: T): boolean | Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves the global persisted data from storage.
|
|
15
|
+
*
|
|
16
|
+
* @returns The retrieved state of type `T`, or `null` if no data is found or if an error occurs during retrieval/parsing.
|
|
17
|
+
* For asynchronous implementations, this returns a `Promise<T | null>`.
|
|
18
|
+
*/
|
|
19
|
+
get(): (T | null) | (Promise<T | null>);
|
|
20
|
+
/**
|
|
21
|
+
* Subscribes to changes in the global persisted data that originate from *other* instances of your application (e.g., other tabs or independent components using the same persistence layer).
|
|
22
|
+
*
|
|
23
|
+
* @param id The **unique identifier of the *consumer instance* subscribing**. This allows the persistence implementation to filter out notifications that were initiated by the subscribing instance itself.
|
|
24
|
+
* @param callback The function to call when the global persisted data changes from *another* source. The new state (`T`) is passed as an argument to this callback.
|
|
25
|
+
* @returns A function that, when called, will unsubscribe the provided callback from future updates. Call this when your component or instance is no longer active to prevent memory leaks.
|
|
26
|
+
*/
|
|
27
|
+
subscribe(id: string, callback: (state: T) => void): () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Clears (removes) the entire global persisted data from storage.
|
|
30
|
+
*
|
|
31
|
+
* @returns `true` if the operation was successful, `false` if an error occurred. For asynchronous implementations, this returns a `Promise<boolean>`.
|
|
32
|
+
*/
|
|
33
|
+
clear(): boolean | Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Returns metadata about the persistence layer.
|
|
36
|
+
*
|
|
37
|
+
* This is useful for distinguishing between multiple apps running on the same host
|
|
38
|
+
* (e.g., several apps served at `localhost:3000` that share the same storage key).
|
|
39
|
+
*
|
|
40
|
+
* @returns An object containing:
|
|
41
|
+
* - `version`: The semantic version string of the persistence schema or application.
|
|
42
|
+
* - `id`: A unique identifier for the application using this persistence instance.
|
|
43
|
+
*/
|
|
44
|
+
stats(): {
|
|
45
|
+
version: string;
|
|
46
|
+
id: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
2
49
|
|
|
3
50
|
/**
|
|
4
51
|
* Utility type for representing partial updates to the state.
|
package/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t,s=require("uuid"),r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,d=(e,t,s)=>(s=null!=e?r(o(e)):{},((e,t,s,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let o of n(t))c.call(e,o)||o===s||i(e,o,{get:()=>t[o],enumerable:!(r=a(t,o))||r.enumerable});return e})(e&&e.__esModule?s:i(s,"default",{value:e,enumerable:!0}),e)),h=(e={"node_modules/@asaidimu/events/index.js"(e,t){var s,r=Object.defineProperty,i=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var s in t)r(e,s,{get:t[s],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(s=o,((e,t,s,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of a(t))n.call(e,c)||c===s||r(e,c,{get:()=>t[c],enumerable:!(o=i(t,c))||o.enumerable});return e})(r({},"__esModule",{value:!0}),s));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],r=0,i=0;const a=new Map,n=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{r++,i+=t,a.set(e,(a.get(e)||0)+1)},d=()=>{const t=s;s=[],t.forEach((({name:t,payload:s})=>{const r=performance.now();try{(n.get(t)||[]).forEach((e=>e(s)))}catch(r){e.errorHandler({...r,eventName:t,payload:s})}c(t,performance.now()-r)}))},h=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(d,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?n.set(e,Array.from(s)):n.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:s}=e.data;(n.get(t)||[]).forEach((e=>e(s)))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const r=t.get(e);return r.add(s),l(e),()=>{r.delete(s),0===r.size?(t.delete(e),n.delete(e)):l(e)}},emit:({name:t,payload:r})=>{if(e.async)return s.push({name:t,payload:r}),s.length>=e.batchSize?d():h(),void(o&&o.postMessage({name:t,payload:r}));const i=performance.now();try{(n.get(t)||[]).forEach((e=>e(r))),o&&o.postMessage({name:t,payload:r})}catch(s){e.errorHandler({...s,eventName:t,payload:r})}c(t,performance.now()-i)},getMetrics:()=>({totalEvents:r,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:a,averageEmitDuration:r>0?i/r:0}),clear:()=>{t.clear(),n.clear(),s=[],r=0,i=0,a.clear(),o&&(o.close(),o=null)}}}}},function(){return t||(0,e[n(e)[0]])((t={exports:{}}).exports,t),t.exports}),l=d(h()),u=Symbol.for("delete"),p=e=>Array.isArray(e)?[...e]:{...e};function m(e){const t=e?.deleteMarker||u;return function(e,s){if("object"!=typeof e||null===e)return"object"==typeof s&&null!==s?a(s):s===t?{}:s;if("object"!=typeof s||null===s)return e;const r=p(e),i=[{target:r,source:s}];for(;i.length>0;){const{target:e,source:s}=i.pop();for(const r of Object.keys(s)){if(!Object.prototype.hasOwnProperty.call(s,r))continue;const n=s[r];n!==t?Array.isArray(n)?e[r]=n.map((e=>"object"!=typeof e||null===e||Array.isArray(e)?e===t?void 0:e:a(e))):"object"==typeof n&&null!==n?(e[r]=p(r in e&&"object"==typeof e[r]&&null!==e[r]?e[r]:{}),i.push({target:e[r],source:n})):e[r]=n:delete e[r]}}return r;function a(e){if(null==e)return e;if(Array.isArray(e))return e.filter((e=>e!==t)).map((e=>"object"!=typeof e||null===e||Array.isArray(e)?e===t?void 0:e:a(e)));if("object"==typeof e){const s={};for(const[r,i]of Object.entries(e))if(i!==t)if("object"==typeof i&&null!==i){const e=a(i);void 0!==e&&(s[r]=e)}else s[r]=i;return s}return e===t?void 0:e}}}function g(e,t){if(e===t)return!0;if(e&&t&&"object"==typeof e&&"object"==typeof t){if(e.constructor!==t.constructor)return!1;let s,r;if(Array.isArray(e)){if(s=e.length,s!=t.length)return!1;for(r=s;r-- >0;)if(!g(e[r],t[r]))return!1;return!0}const i=Object.keys(e);if(s=i.length,s!==Object.keys(t).length)return!1;for(r=s;r-- >0;){const s=i[r];if(!Object.prototype.hasOwnProperty.call(t,s)||!g(e[s],t[s]))return!1}return!0}return e!=e&&t!=t}function f(e){const t=e?.deleteMarker||u;return function(e,s){const r=new Set,i=[{pathArray:[],pathStr:"",orig:e||{},part:s||{}}];for(;i.length>0;){const{pathArray:e,pathStr:s,orig:a,part:n}=i.pop();if(null!=n&&!g(a,n))if("object"!=typeof n||Array.isArray(n))s&&r.add(s);else for(const o of Object.keys(n)){const c=[...e,o],d=s?s+"."+o:o,h=n[o],l=a&&"object"==typeof a?a[o]:void 0;h!==t?"object"==typeof h&&null!==h?i.push({pathArray:c,pathStr:d,orig:l,part:h}):g(l,h)||r.add(d):void 0===l&&a&&"object"==typeof a||r.add(d)}}const a=new Set(r);for(const e of r){let t=e.lastIndexOf(".");for(;t>0;){const s=e.slice(0,t);if(a.has(s))break;a.add(s),t=e.lastIndexOf(".",t-1)}}return Array.from(a)}}m(),f();var y=class{constructor(e,t,s){this.updateBus=t,this.diff=s,this.cache=structuredClone(e)}cache;get(e){return e?structuredClone(this.cache):this.cache}applyChanges(e){const t=this.diff(this.get(!0),e);return t.length>0&&(this.cache=structuredClone(e),this.notifyListeners(t)),t}notifyListeners(e){e.forEach((e=>this.updateBus.emit({name:"update",payload:e})))}};d(h());var w=class{constructor(e,t,s){this.eventBus=e,this.executionState=t,this.merge=s}middleware=[];blockingMiddleware=[];async executeBlocking(e,t){for(const{fn:s,name:r,id:i}of this.blockingMiddleware){const a={id:i,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:i,name:r,startTime:performance.now()},this.emitMiddlewareLifecycle("start",{id:i,name:r,type:"blocking"});try{const n=await Promise.resolve(s(e,t));if(a.endTime=performance.now(),a.duration=a.endTime-a.startTime,!1===n)return a.blocked=!0,this.emitMiddlewareLifecycle("blocked",{id:i,name:r,duration:a.duration}),this.emit(this.eventBus,{name:"middleware:executed",payload:a}),{blocked:!0};this.emitMiddlewareLifecycle("complete",{id:i,name:r,type:"blocking",duration:a.duration}),this.emit(this.eventBus,{name:"middleware:executed",payload:{...a,blocked:!1}})}catch(e){return a.endTime=performance.now(),a.duration=a.endTime-a.startTime,a.error=e instanceof Error?e:new Error(String(e)),a.blocked=!0,this.emitMiddlewareError(i,r,a.error,a.duration),this.emit(this.eventBus,{name:"middleware:executed",payload:a}),{blocked:!0,error:a.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async executeTransform(e,t){let s=e,r=t;for(const{fn:e,name:i,id:a}of this.middleware){const n={id:a,name:i,startTime:performance.now()};this.executionState.runningMiddleware={id:a,name:i,startTime:performance.now()},this.emitMiddlewareLifecycle("start",{id:a,name:i,type:"transform"});try{const o=await Promise.resolve(e(s,t));n.endTime=performance.now(),n.duration=n.endTime-n.startTime,n.blocked=!1,o&&"object"==typeof o&&(s=this.merge(s,o),r=this.merge(r,o)),this.emit(this.eventBus,{name:"middleware:executed",payload:n}),this.emitMiddlewareLifecycle("complete",{id:a,name:i,type:"transform",duration:n.duration})}catch(e){n.endTime=performance.now(),n.duration=n.endTime-n.startTime,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!1,this.emit(this.eventBus,{name:"middleware:executed",payload:n}),this.emitMiddlewareError(a,i,n.error,n.duration),console.error(`Middleware ${i} error:`,e)}finally{this.executionState.runningMiddleware=null}}return r}addMiddleware(e,t="unnamed-middleware"){const s=this.generateId();return this.middleware.push({fn:e,name:t,id:s}),this.updateExecutionState(),s}addBlockingMiddleware(e,t="unnamed-blocking-middleware"){const s=this.generateId();return this.blockingMiddleware.push({fn:e,name:t,id:s}),this.updateExecutionState(),s}removeMiddleware(e){const t=this.middleware.length+this.blockingMiddleware.length;return this.middleware=this.middleware.filter((t=>t.id!==e)),this.blockingMiddleware=this.blockingMiddleware.filter((t=>t.id!==e)),this.updateExecutionState(),this.middleware.length+this.blockingMiddleware.length<t}updateExecutionState(){this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))]}emitMiddlewareLifecycle(e,t){this.emit(this.eventBus,{name:`middleware:${e}`,payload:{...t,timestamp:Date.now()}})}emitMiddlewareError(e,t,s,r){this.emit(this.eventBus,{name:"middleware:error",payload:{id:e,name:t,error:s,duration:r,timestamp:Date.now()}})}generateId(){return crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var b=class{constructor(e,t,s,r){this.eventBus=e,this.coreState=t,this.instanceID=s,this.maxRetries=r?.maxRetries??3,this.retryDelay=r?.retryDelay??1e3}persistence;instanceID;persistenceReady=!1;backgroundQueue=[];isProcessingQueue=!1;maxRetries=3;retryDelay=1e3;queueProcessor;async initialize(e){e?await this.setPersistence(e):this.setPersistenceReady()}isReady(){return this.persistenceReady}handleStateChange(e,t){if(!this.persistence||0===e.length)return;const s={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,state:structuredClone(t),changedPaths:[...e],timestamp:Date.now(),retries:0};this.backgroundQueue.push(s),this.scheduleQueueProcessing(),this.emit(this.eventBus,{name:"persistence:queued",payload:{taskId:s.id,changedPaths:e,queueSize:this.backgroundQueue.length,timestamp:s.timestamp}})}getQueueStatus(){return{queueSize:this.backgroundQueue.length,isProcessing:this.isProcessingQueue,oldestTask:this.backgroundQueue[0]?.timestamp}}async flush(){this.isProcessingQueue||await this.processQueue()}clearQueue(){const e=this.backgroundQueue.length;this.backgroundQueue=[],this.queueProcessor&&(clearTimeout(this.queueProcessor),this.queueProcessor=void 0),this.emit(this.eventBus,{name:"persistence:queue_cleared",payload:{clearedTasks:e,timestamp:Date.now()}})}scheduleQueueProcessing(){this.queueProcessor||this.isProcessingQueue||(this.queueProcessor=setTimeout((()=>{this.processQueue().catch((e=>{console.error("Queue processing failed:",e)}))}),10))}async processQueue(){if(!this.isProcessingQueue&&0!==this.backgroundQueue.length){this.isProcessingQueue=!0,this.queueProcessor=void 0;try{for(;this.backgroundQueue.length>0;){const e=this.backgroundQueue.shift();await this.processTask(e)}}finally{this.isProcessingQueue=!1}}}async processTask(e){try{await this.persistence.set(this.instanceID,e.state)?this.emit(this.eventBus,{name:"persistence:success",payload:{taskId:e.id,changedPaths:e.changedPaths,duration:Date.now()-e.timestamp,timestamp:Date.now()}}):await this.handleTaskFailure(e,new Error("Persistence returned false"))}catch(t){await this.handleTaskFailure(e,t)}}async handleTaskFailure(e,t){if(e.retries++,e.retries<=this.maxRetries){const s=this.retryDelay*Math.pow(2,e.retries-1);this.emit(this.eventBus,{name:"persistence:retry",payload:{taskId:e.id,attempt:e.retries,maxRetries:this.maxRetries,nextRetryIn:s,error:t,timestamp:Date.now()}}),setTimeout((()=>{this.backgroundQueue.unshift(e),this.scheduleQueueProcessing()}),s)}else this.emit(this.eventBus,{name:"persistence:failed",payload:{taskId:e.id,changedPaths:e.changedPaths,attempts:e.retries,error:t,timestamp:Date.now()}})}setPersistenceReady(){this.persistenceReady=!0,this.emit(this.eventBus,{name:"persistence:ready",payload:{timestamp:Date.now()}})}async setPersistence(e){this.persistence=e;try{const e=await this.persistence.get();e&&this.coreState.applyChanges(e)}catch(e){console.error("Failed to initialize persistence:",e),this.emit(this.eventBus,{name:"persistence:init_error",payload:{error:e,timestamp:Date.now()}})}finally{this.setPersistenceReady()}this.persistence.subscribe&&this.persistence.subscribe(this.instanceID,(async e=>{const t=this.coreState.applyChanges(e);t.length>0&&this.emit(this.eventBus,{name:"update:complete",payload:{changedPaths:t,source:"external",timestamp:Date.now()}})}))}dispose(){this.clearQueue(),this.isProcessingQueue=!1,this.persistenceReady=!1}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var v=class{constructor(e,t,s){this.eventBus=e,this.coreState=t,this.executionState=s}async execute(e){const t=this.coreState.get(!0);this.executionState.transactionActive=!0,this.emit(this.eventBus,{name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=await Promise.resolve(e());return this.emit(this.eventBus,{name:"transaction:complete",payload:{timestamp:Date.now()}}),this.executionState.transactionActive=!1,t}catch(e){throw this.coreState.applyChanges(t),this.emit(this.eventBus,{name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),this.executionState.transactionActive=!1,e}}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var x=class{updateCount=0;listenerExecutions=0;averageUpdateTime=0;largestUpdateSize=0;mostActiveListenerPaths=[];totalUpdates=0;blockedUpdates=0;averageUpdateDuration=0;middlewareExecutions=0;transactionCount=0;totalEventsFired=0;updateTimes=[];pathExecutionCounts=new Map;constructor(e){this.setupEventListeners(e)}getMetrics(){return{updateCount:this.updateCount,listenerExecutions:this.listenerExecutions,averageUpdateTime:this.averageUpdateTime,largestUpdateSize:this.largestUpdateSize,mostActiveListenerPaths:[...this.mostActiveListenerPaths],totalUpdates:this.totalUpdates,blockedUpdates:this.blockedUpdates,averageUpdateDuration:this.averageUpdateDuration,middlewareExecutions:this.middlewareExecutions,transactionCount:this.transactionCount,totalEventsFired:this.totalEventsFired}}setupEventListeners(e){const t=e.emit;e.emit=s=>(this.totalEventsFired++,t.call(e,s)),e.subscribe("update:complete",(e=>{if(this.totalUpdates++,e.blocked)this.blockedUpdates++;else{if(e.duration){this.updateTimes.push(e.duration),this.updateTimes.length>100&&this.updateTimes.shift();const t=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length;this.averageUpdateTime=t,this.averageUpdateDuration=t}e.changedPaths?.length&&(this.updateCount++,this.largestUpdateSize=Math.max(this.largestUpdateSize,e.changedPaths.length),e.changedPaths.forEach((e=>{const t=this.pathExecutionCounts.get(e)||0;this.pathExecutionCounts.set(e,t+1)})),this.mostActiveListenerPaths=Array.from(this.pathExecutionCounts.entries()).sort((([,e],[,t])=>t-e)).slice(0,5).map((([e])=>e)))}})),e.subscribe("middleware:start",(()=>{this.middlewareExecutions++})),e.subscribe("transaction:start",(()=>{this.transactionCount++}))}reset(){this.updateCount=0,this.listenerExecutions=0,this.averageUpdateTime=0,this.largestUpdateSize=0,this.mostActiveListenerPaths=[],this.totalUpdates=0,this.blockedUpdates=0,this.averageUpdateDuration=0,this.middlewareExecutions=0,this.transactionCount=0,this.totalEventsFired=0,this.updateTimes=[],this.pathExecutionCounts.clear()}getDetailedMetrics(){return{pathExecutionCounts:new Map(this.pathExecutionCounts),recentUpdateTimes:[...this.updateTimes],successRate:this.totalUpdates>0?(this.totalUpdates-this.blockedUpdates)/this.totalUpdates:1,averagePathsPerUpdate:this.updateCount>0?Array.from(this.pathExecutionCounts.values()).reduce(((e,t)=>e+t),0)/this.updateCount:0}}dispose(){this.reset()}},E=f();exports.DELETE_SYMBOL=u,exports.ReactiveDataStore=class{coreState;middlewareEngine;persistenceHandler;transactionManager;metricsCollector;pendingUpdates=[];isUpdating=!1;updateBus=(0,l.createEventBus)();eventBus=(0,l.createEventBus)();executionState;instanceID=s.v4();merge;diff;constructor(e,t,s=u,r){this.executionState={executing:!1,changes:null,pendingChanges:[],middlewares:[],runningMiddleware:null,transactionActive:!1},this.merge=m({deleteMarker:s}),this.diff=f({deleteMarker:s}),this.coreState=new y(e,this.updateBus,this.diff),this.middlewareEngine=new w(this.eventBus,this.executionState,this.merge),this.persistenceHandler=new b(this.eventBus,this.coreState,this.instanceID,{maxRetries:r?.persistenceMaxRetries,retryDelay:r?.persistenceRetryDelay}),this.transactionManager=new v(this.eventBus,this.coreState,this.executionState),this.metricsCollector=new x(this.eventBus),this.persistenceHandler.initialize(t)}isReady(){return this.persistenceHandler.isReady()}state(){return this.executionState}get(e){return this.coreState.get(e)}async set(e){if(this.isUpdating)return this.pendingUpdates.push(e),void(this.executionState.pendingChanges=[...this.pendingUpdates]);this.isUpdating=!0,this.executionState.executing=!0;const t=performance.now();this.emit(this.eventBus,{name:"update:start",payload:{timestamp:t}});try{let s=e;if("function"==typeof e){const t=e(this.get(!0));s=t instanceof Promise?await t:t}const r=await this.middlewareEngine.executeBlocking(this.get(),s);if(r.blocked)return void this.emit(this.eventBus,{name:"update:complete",payload:{blocked:!0,error:r.error,timestamp:Date.now()}});const i=this.merge(this.get(!0),s),a=await this.middlewareEngine.executeTransform(i,s),n=this.merge(this.get(!0),a),o=this.coreState.applyChanges(n),c=performance.now();this.emit(this.eventBus,{name:"update:complete",payload:{changedPaths:o,duration:c-t,timestamp:Date.now()}}),o.length>0&&this.persistenceHandler.handleStateChange(o,this.get())}catch(e){throw this.emit(this.eventBus,{name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}finally{if(this.isUpdating=!1,this.executionState.executing=!1,this.executionState.changes=null,this.executionState.runningMiddleware=null,this.pendingUpdates.length>0){const e=this.pendingUpdates.shift();if(this.executionState.pendingChanges=[...this.pendingUpdates],e)return this.set(e)}}}subscribe(e,t){const s=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(r=>{(s.includes(r)||""===e)&&(t(this.get(!0)),this.metricsCollector.listenerExecutions++)}))}id(){return this.instanceID}async transaction(e){return this.transactionManager.execute(e)}use(e){return e.block?this.middlewareEngine.addBlockingMiddleware(e.action,e.name):this.middlewareEngine.addMiddleware(e.action,e.name)}unuse(e){return this.middlewareEngine.removeMiddleware(e)}metrics(){return this.metricsCollector.getMetrics()}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}getPersistenceStatus(){return this.persistenceHandler.getQueueStatus()}async flushPersistence(){return this.persistenceHandler.flush()}clearPersistenceQueue(){this.persistenceHandler.clearQueue()}dispose(){this.persistenceHandler.dispose(),this.metricsCollector.dispose?.()}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}},exports.StoreObserver=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;middlewareExecutions=[];constructor(e,t={}){this.store=e,this.maxEvents=t.maxEvents??500,this.maxStateHistory=t.maxStateHistory??20,this.enableConsoleLogging=t.enableConsoleLogging??!1,this.logEvents={updates:t.logEvents?.updates??!0,middleware:t.logEvents?.middleware??!0,transactions:t.logEvents?.transactions??!0},this.performanceThresholds={updateTime:t.performanceThresholds?.updateTime??50,middlewareTime:t.performanceThresholds?.middlewareTime??20},this.recordStateSnapshot(),this.setupEventListeners()}setupEventListeners(){const e=["update:start","update:complete","middleware:start","middleware:complete","middleware:error","middleware:blocked","transaction:start","transaction:complete","transaction:error","middleware:executed"];this.unsubscribers.push(this.store.subscribe("",(()=>{this.recordStateSnapshot()})));for(const t of e){const e=t.startsWith("update")&&this.logEvents.updates||t.startsWith("middleware")&&this.logEvents.middleware||t.startsWith("transaction")&&this.logEvents.transactions;this.unsubscribers.push(this.store.onStoreEvent(t,(s=>{"middleware:executed"===t?this.middlewareExecutions.push(s):"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),s.batchId&&(t.endsWith("start")?this.activeBatches.add(s.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(s.batchId)),this.recordEvent(t,s),this.enableConsoleLogging&&e&&this.logEventToConsole(t,s),this.checkPerformance(t,s)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===E(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const s={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(s),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const s=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${s}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${s}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%c✅ Update Complete [${s}] - ${e.length} paths changed in ${t.duration?.toFixed(2)}ms`,"color: #2a9d8f",e)}console.groupEnd()}else"middleware:start"===e?console.debug(`%c◀ Middleware "${t.name}" started [${s}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${s}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${s}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${s}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${s}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${s}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${s}]:`,"color: #e56b6f",t.error),console.groupEnd())}checkPerformance(e,t){this.enableConsoleLogging&&("update:complete"===e&&!t.blocked&&t.duration>this.performanceThresholds.updateTime&&console.warn(`%c⚠️ Slow update detected [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{changedPaths:t.changedPaths,threshold:this.performanceThresholds.updateTime}),"middleware:complete"===e&&t.duration>this.performanceThresholds.middlewareTime&&console.warn(`%c⚠️ Slow middleware "${t.name}" [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{threshold:this.performanceThresholds.middlewareTime}))}getEventHistory(){return structuredClone(this.eventHistory)}getStateHistory(){return structuredClone(this.stateHistory)}getMiddlewareExecutions(){return this.middlewareExecutions}getPerformanceMetrics(){return this.store.metrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:s=!0}=e;return(e,r)=>{if(s){(console[t]||console.log)("State Update:",r)}return r}}createValidationMiddleware(e){return(t,s)=>{const r=e(t,s);return"boolean"==typeof r?r:(!r.valid&&r.reason&&console.warn("Validation failed:",r.reason),r.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],s=Math.min(e,this.stateHistory.length-1);for(let e=0;e<s;e++){const s=this.stateHistory[e],r=this.stateHistory[e+1],i=E(r,s),a={},n={};for(const e of i){const t=e.split("."),i=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),o=(e,t,s)=>{const r=t.length-1,i=t[r];t.slice(0,r).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[i]=s},c=i(r,t),d=i(s,t);o(a,t,c),o(n,t,d)}let o=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){o=e.timestamp;break}0!==i.length&&t.push({timestamp:o,changedPaths:i,from:a,to:n})}return t}createTimeTravel(){let e=0,t=[];return{canUndo:()=>e<this.stateHistory.length-1,canRedo:()=>t.length>0,undo:async()=>{if(e<this.stateHistory.length-1){const s=e+1,r=this.stateHistory[s];t.unshift(this.stateHistory[e]),e=s,await this.store.set(r)}},redo:async()=>{if(t.length>0){const s=t.shift();e=Math.max(0,e-1),await this.store.set(s)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},exports.author="https://github.com/asaidimu";
|
|
1
|
+
"use strict";var e=require("./store"),t=require("./observability"),r=require("./types");Object.keys(e).forEach((function(t){"default"===t||Object.prototype.hasOwnProperty.call(exports,t)||Object.defineProperty(exports,t,{enumerable:!0,get:function(){return e[t]}})})),Object.keys(t).forEach((function(e){"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:function(){return t[e]}})})),Object.keys(r).forEach((function(e){"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:function(){return r[e]}})}));
|
package/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{v4 as e}from"uuid";var t,s,i=Object.create,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,d=(e,t,s)=>(s=null!=e?i(o(e)):{},((e,t,s,i)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let o of n(t))c.call(e,o)||o===s||r(e,o,{get:()=>t[o],enumerable:!(i=a(t,o))||i.enumerable});return e})(e&&e.__esModule?s:r(s,"default",{value:e,enumerable:!0}),e)),h=(t={"node_modules/@asaidimu/events/index.js"(e,t){var s,i=Object.defineProperty,r=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,n=Object.prototype.hasOwnProperty,o={};((e,t)=>{for(var s in t)i(e,s,{get:t[s],enumerable:!0})})(o,{createEventBus:()=>c}),t.exports=(s=o,((e,t,s,o)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of a(t))n.call(e,c)||c===s||i(e,c,{get:()=>t[c],enumerable:!(o=r(t,c))||o.enumerable});return e})(i({},"__esModule",{value:!0}),s));var c=(e={async:!1,batchSize:1e3,batchDelay:16,errorHandler:e=>console.error("EventBus Error:",e),crossTab:!1,channelName:"event-bus-channel"})=>{const t=new Map;let s=[],i=0,r=0;const a=new Map,n=new Map;let o=null;e.crossTab&&"undefined"!=typeof BroadcastChannel?o=new BroadcastChannel(e.channelName):e.crossTab&&console.warn("BroadcastChannel is not supported in this browser. Cross-tab notifications are disabled.");const c=(e,t)=>{i++,r+=t,a.set(e,(a.get(e)||0)+1)},d=()=>{const t=s;s=[],t.forEach((({name:t,payload:s})=>{const i=performance.now();try{(n.get(t)||[]).forEach((e=>e(s)))}catch(i){e.errorHandler({...i,eventName:t,payload:s})}c(t,performance.now()-i)}))},h=(()=>{let t;return()=>{clearTimeout(t),t=setTimeout(d,e.batchDelay)}})(),l=e=>{const s=t.get(e);s?n.set(e,Array.from(s)):n.delete(e)};return o&&(o.onmessage=e=>{const{name:t,payload:s}=e.data;(n.get(t)||[]).forEach((e=>e(s)))}),{subscribe:(e,s)=>{t.has(e)||t.set(e,new Set);const i=t.get(e);return i.add(s),l(e),()=>{i.delete(s),0===i.size?(t.delete(e),n.delete(e)):l(e)}},emit:({name:t,payload:i})=>{if(e.async)return s.push({name:t,payload:i}),s.length>=e.batchSize?d():h(),void(o&&o.postMessage({name:t,payload:i}));const r=performance.now();try{(n.get(t)||[]).forEach((e=>e(i))),o&&o.postMessage({name:t,payload:i})}catch(s){e.errorHandler({...s,eventName:t,payload:i})}c(t,performance.now()-r)},getMetrics:()=>({totalEvents:i,activeSubscriptions:Array.from(t.values()).reduce(((e,t)=>e+t.size),0),eventCounts:a,averageEmitDuration:i>0?r/i:0}),clear:()=>{t.clear(),n.clear(),s=[],i=0,r=0,a.clear(),o&&(o.close(),o=null)}}}}},function(){return s||(0,t[n(t)[0]])((s={exports:{}}).exports,s),s.exports}),l=d(h()),u=Symbol.for("delete"),p="https://github.com/asaidimu",m=e=>Array.isArray(e)?[...e]:{...e};function g(e){const t=e?.deleteMarker||u;return function(e,s){if("object"!=typeof e||null===e)return"object"==typeof s&&null!==s?a(s):s===t?{}:s;if("object"!=typeof s||null===s)return e;const i=m(e),r=[{target:i,source:s}];for(;r.length>0;){const{target:e,source:s}=r.pop();for(const i of Object.keys(s)){if(!Object.prototype.hasOwnProperty.call(s,i))continue;const n=s[i];n!==t?Array.isArray(n)?e[i]=n.map((e=>"object"!=typeof e||null===e||Array.isArray(e)?e===t?void 0:e:a(e))):"object"==typeof n&&null!==n?(e[i]=m(i in e&&"object"==typeof e[i]&&null!==e[i]?e[i]:{}),r.push({target:e[i],source:n})):e[i]=n:delete e[i]}}return i;function a(e){if(null==e)return e;if(Array.isArray(e))return e.filter((e=>e!==t)).map((e=>"object"!=typeof e||null===e||Array.isArray(e)?e===t?void 0:e:a(e)));if("object"==typeof e){const s={};for(const[i,r]of Object.entries(e))if(r!==t)if("object"==typeof r&&null!==r){const e=a(r);void 0!==e&&(s[i]=e)}else s[i]=r;return s}return e===t?void 0:e}}}function f(e,t){if(e===t)return!0;if(e&&t&&"object"==typeof e&&"object"==typeof t){if(e.constructor!==t.constructor)return!1;let s,i;if(Array.isArray(e)){if(s=e.length,s!=t.length)return!1;for(i=s;i-- >0;)if(!f(e[i],t[i]))return!1;return!0}const r=Object.keys(e);if(s=r.length,s!==Object.keys(t).length)return!1;for(i=s;i-- >0;){const s=r[i];if(!Object.prototype.hasOwnProperty.call(t,s)||!f(e[s],t[s]))return!1}return!0}return e!=e&&t!=t}function y(e){const t=e?.deleteMarker||u;return function(e,s){const i=new Set,r=[{pathArray:[],pathStr:"",orig:e||{},part:s||{}}];for(;r.length>0;){const{pathArray:e,pathStr:s,orig:a,part:n}=r.pop();if(null!=n&&!f(a,n))if("object"!=typeof n||Array.isArray(n))s&&i.add(s);else for(const o of Object.keys(n)){const c=[...e,o],d=s?s+"."+o:o,h=n[o],l=a&&"object"==typeof a?a[o]:void 0;h!==t?"object"==typeof h&&null!==h?r.push({pathArray:c,pathStr:d,orig:l,part:h}):f(l,h)||i.add(d):void 0===l&&a&&"object"==typeof a||i.add(d)}}const a=new Set(i);for(const e of i){let t=e.lastIndexOf(".");for(;t>0;){const s=e.slice(0,t);if(a.has(s))break;a.add(s),t=e.lastIndexOf(".",t-1)}}return Array.from(a)}}g(),y();var w=class{constructor(e,t,s){this.updateBus=t,this.diff=s,this.cache=structuredClone(e)}cache;get(e){return e?structuredClone(this.cache):this.cache}applyChanges(e){const t=this.diff(this.get(!0),e);return t.length>0&&(this.cache=structuredClone(e),this.notifyListeners(t)),t}notifyListeners(e){e.forEach((e=>this.updateBus.emit({name:"update",payload:e})))}};d(h());var b=class{constructor(e,t,s){this.eventBus=e,this.executionState=t,this.merge=s}middleware=[];blockingMiddleware=[];async executeBlocking(e,t){for(const{fn:s,name:i,id:r}of this.blockingMiddleware){const a={id:r,name:i,startTime:performance.now()};this.executionState.runningMiddleware={id:r,name:i,startTime:performance.now()},this.emitMiddlewareLifecycle("start",{id:r,name:i,type:"blocking"});try{const n=await Promise.resolve(s(e,t));if(a.endTime=performance.now(),a.duration=a.endTime-a.startTime,!1===n)return a.blocked=!0,this.emitMiddlewareLifecycle("blocked",{id:r,name:i,duration:a.duration}),this.emit(this.eventBus,{name:"middleware:executed",payload:a}),{blocked:!0};this.emitMiddlewareLifecycle("complete",{id:r,name:i,type:"blocking",duration:a.duration}),this.emit(this.eventBus,{name:"middleware:executed",payload:{...a,blocked:!1}})}catch(e){return a.endTime=performance.now(),a.duration=a.endTime-a.startTime,a.error=e instanceof Error?e:new Error(String(e)),a.blocked=!0,this.emitMiddlewareError(r,i,a.error,a.duration),this.emit(this.eventBus,{name:"middleware:executed",payload:a}),{blocked:!0,error:a.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async executeTransform(e,t){let s=e,i=t;for(const{fn:e,name:r,id:a}of this.middleware){const n={id:a,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:a,name:r,startTime:performance.now()},this.emitMiddlewareLifecycle("start",{id:a,name:r,type:"transform"});try{const o=await Promise.resolve(e(s,t));n.endTime=performance.now(),n.duration=n.endTime-n.startTime,n.blocked=!1,o&&"object"==typeof o&&(s=this.merge(s,o),i=this.merge(i,o)),this.emit(this.eventBus,{name:"middleware:executed",payload:n}),this.emitMiddlewareLifecycle("complete",{id:a,name:r,type:"transform",duration:n.duration})}catch(e){n.endTime=performance.now(),n.duration=n.endTime-n.startTime,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!1,this.emit(this.eventBus,{name:"middleware:executed",payload:n}),this.emitMiddlewareError(a,r,n.error,n.duration),console.error(`Middleware ${r} error:`,e)}finally{this.executionState.runningMiddleware=null}}return i}addMiddleware(e,t="unnamed-middleware"){const s=this.generateId();return this.middleware.push({fn:e,name:t,id:s}),this.updateExecutionState(),s}addBlockingMiddleware(e,t="unnamed-blocking-middleware"){const s=this.generateId();return this.blockingMiddleware.push({fn:e,name:t,id:s}),this.updateExecutionState(),s}removeMiddleware(e){const t=this.middleware.length+this.blockingMiddleware.length;return this.middleware=this.middleware.filter((t=>t.id!==e)),this.blockingMiddleware=this.blockingMiddleware.filter((t=>t.id!==e)),this.updateExecutionState(),this.middleware.length+this.blockingMiddleware.length<t}updateExecutionState(){this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))]}emitMiddlewareLifecycle(e,t){this.emit(this.eventBus,{name:`middleware:${e}`,payload:{...t,timestamp:Date.now()}})}emitMiddlewareError(e,t,s,i){this.emit(this.eventBus,{name:"middleware:error",payload:{id:e,name:t,error:s,duration:i,timestamp:Date.now()}})}generateId(){return crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var v=class{constructor(e,t,s,i){this.eventBus=e,this.coreState=t,this.instanceID=s,this.maxRetries=i?.maxRetries??3,this.retryDelay=i?.retryDelay??1e3}persistence;instanceID;persistenceReady=!1;backgroundQueue=[];isProcessingQueue=!1;maxRetries=3;retryDelay=1e3;queueProcessor;async initialize(e){e?await this.setPersistence(e):this.setPersistenceReady()}isReady(){return this.persistenceReady}handleStateChange(e,t){if(!this.persistence||0===e.length)return;const s={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,state:structuredClone(t),changedPaths:[...e],timestamp:Date.now(),retries:0};this.backgroundQueue.push(s),this.scheduleQueueProcessing(),this.emit(this.eventBus,{name:"persistence:queued",payload:{taskId:s.id,changedPaths:e,queueSize:this.backgroundQueue.length,timestamp:s.timestamp}})}getQueueStatus(){return{queueSize:this.backgroundQueue.length,isProcessing:this.isProcessingQueue,oldestTask:this.backgroundQueue[0]?.timestamp}}async flush(){this.isProcessingQueue||await this.processQueue()}clearQueue(){const e=this.backgroundQueue.length;this.backgroundQueue=[],this.queueProcessor&&(clearTimeout(this.queueProcessor),this.queueProcessor=void 0),this.emit(this.eventBus,{name:"persistence:queue_cleared",payload:{clearedTasks:e,timestamp:Date.now()}})}scheduleQueueProcessing(){this.queueProcessor||this.isProcessingQueue||(this.queueProcessor=setTimeout((()=>{this.processQueue().catch((e=>{console.error("Queue processing failed:",e)}))}),10))}async processQueue(){if(!this.isProcessingQueue&&0!==this.backgroundQueue.length){this.isProcessingQueue=!0,this.queueProcessor=void 0;try{for(;this.backgroundQueue.length>0;){const e=this.backgroundQueue.shift();await this.processTask(e)}}finally{this.isProcessingQueue=!1}}}async processTask(e){try{await this.persistence.set(this.instanceID,e.state)?this.emit(this.eventBus,{name:"persistence:success",payload:{taskId:e.id,changedPaths:e.changedPaths,duration:Date.now()-e.timestamp,timestamp:Date.now()}}):await this.handleTaskFailure(e,new Error("Persistence returned false"))}catch(t){await this.handleTaskFailure(e,t)}}async handleTaskFailure(e,t){if(e.retries++,e.retries<=this.maxRetries){const s=this.retryDelay*Math.pow(2,e.retries-1);this.emit(this.eventBus,{name:"persistence:retry",payload:{taskId:e.id,attempt:e.retries,maxRetries:this.maxRetries,nextRetryIn:s,error:t,timestamp:Date.now()}}),setTimeout((()=>{this.backgroundQueue.unshift(e),this.scheduleQueueProcessing()}),s)}else this.emit(this.eventBus,{name:"persistence:failed",payload:{taskId:e.id,changedPaths:e.changedPaths,attempts:e.retries,error:t,timestamp:Date.now()}})}setPersistenceReady(){this.persistenceReady=!0,this.emit(this.eventBus,{name:"persistence:ready",payload:{timestamp:Date.now()}})}async setPersistence(e){this.persistence=e;try{const e=await this.persistence.get();e&&this.coreState.applyChanges(e)}catch(e){console.error("Failed to initialize persistence:",e),this.emit(this.eventBus,{name:"persistence:init_error",payload:{error:e,timestamp:Date.now()}})}finally{this.setPersistenceReady()}this.persistence.subscribe&&this.persistence.subscribe(this.instanceID,(async e=>{const t=this.coreState.applyChanges(e);t.length>0&&this.emit(this.eventBus,{name:"update:complete",payload:{changedPaths:t,source:"external",timestamp:Date.now()}})}))}dispose(){this.clearQueue(),this.isProcessingQueue=!1,this.persistenceReady=!1}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var x=class{constructor(e,t,s){this.eventBus=e,this.coreState=t,this.executionState=s}async execute(e){const t=this.coreState.get(!0);this.executionState.transactionActive=!0,this.emit(this.eventBus,{name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=await Promise.resolve(e());return this.emit(this.eventBus,{name:"transaction:complete",payload:{timestamp:Date.now()}}),this.executionState.transactionActive=!1,t}catch(e){throw this.coreState.applyChanges(t),this.emit(this.eventBus,{name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),this.executionState.transactionActive=!1,e}}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}};d(h());var E=class{updateCount=0;listenerExecutions=0;averageUpdateTime=0;largestUpdateSize=0;mostActiveListenerPaths=[];totalUpdates=0;blockedUpdates=0;averageUpdateDuration=0;middlewareExecutions=0;transactionCount=0;totalEventsFired=0;updateTimes=[];pathExecutionCounts=new Map;constructor(e){this.setupEventListeners(e)}getMetrics(){return{updateCount:this.updateCount,listenerExecutions:this.listenerExecutions,averageUpdateTime:this.averageUpdateTime,largestUpdateSize:this.largestUpdateSize,mostActiveListenerPaths:[...this.mostActiveListenerPaths],totalUpdates:this.totalUpdates,blockedUpdates:this.blockedUpdates,averageUpdateDuration:this.averageUpdateDuration,middlewareExecutions:this.middlewareExecutions,transactionCount:this.transactionCount,totalEventsFired:this.totalEventsFired}}setupEventListeners(e){const t=e.emit;e.emit=s=>(this.totalEventsFired++,t.call(e,s)),e.subscribe("update:complete",(e=>{if(this.totalUpdates++,e.blocked)this.blockedUpdates++;else{if(e.duration){this.updateTimes.push(e.duration),this.updateTimes.length>100&&this.updateTimes.shift();const t=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length;this.averageUpdateTime=t,this.averageUpdateDuration=t}e.changedPaths?.length&&(this.updateCount++,this.largestUpdateSize=Math.max(this.largestUpdateSize,e.changedPaths.length),e.changedPaths.forEach((e=>{const t=this.pathExecutionCounts.get(e)||0;this.pathExecutionCounts.set(e,t+1)})),this.mostActiveListenerPaths=Array.from(this.pathExecutionCounts.entries()).sort((([,e],[,t])=>t-e)).slice(0,5).map((([e])=>e)))}})),e.subscribe("middleware:start",(()=>{this.middlewareExecutions++})),e.subscribe("transaction:start",(()=>{this.transactionCount++}))}reset(){this.updateCount=0,this.listenerExecutions=0,this.averageUpdateTime=0,this.largestUpdateSize=0,this.mostActiveListenerPaths=[],this.totalUpdates=0,this.blockedUpdates=0,this.averageUpdateDuration=0,this.middlewareExecutions=0,this.transactionCount=0,this.totalEventsFired=0,this.updateTimes=[],this.pathExecutionCounts.clear()}getDetailedMetrics(){return{pathExecutionCounts:new Map(this.pathExecutionCounts),recentUpdateTimes:[...this.updateTimes],successRate:this.totalUpdates>0?(this.totalUpdates-this.blockedUpdates)/this.totalUpdates:1,averagePathsPerUpdate:this.updateCount>0?Array.from(this.pathExecutionCounts.values()).reduce(((e,t)=>e+t),0)/this.updateCount:0}}dispose(){this.reset()}},S=class{coreState;middlewareEngine;persistenceHandler;transactionManager;metricsCollector;pendingUpdates=[];isUpdating=!1;updateBus=(0,l.createEventBus)();eventBus=(0,l.createEventBus)();executionState;instanceID=e();merge;diff;constructor(e,t,s=u,i){this.executionState={executing:!1,changes:null,pendingChanges:[],middlewares:[],runningMiddleware:null,transactionActive:!1},this.merge=g({deleteMarker:s}),this.diff=y({deleteMarker:s}),this.coreState=new w(e,this.updateBus,this.diff),this.middlewareEngine=new b(this.eventBus,this.executionState,this.merge),this.persistenceHandler=new v(this.eventBus,this.coreState,this.instanceID,{maxRetries:i?.persistenceMaxRetries,retryDelay:i?.persistenceRetryDelay}),this.transactionManager=new x(this.eventBus,this.coreState,this.executionState),this.metricsCollector=new E(this.eventBus),this.persistenceHandler.initialize(t)}isReady(){return this.persistenceHandler.isReady()}state(){return this.executionState}get(e){return this.coreState.get(e)}async set(e){if(this.isUpdating)return this.pendingUpdates.push(e),void(this.executionState.pendingChanges=[...this.pendingUpdates]);this.isUpdating=!0,this.executionState.executing=!0;const t=performance.now();this.emit(this.eventBus,{name:"update:start",payload:{timestamp:t}});try{let s=e;if("function"==typeof e){const t=e(this.get(!0));s=t instanceof Promise?await t:t}const i=await this.middlewareEngine.executeBlocking(this.get(),s);if(i.blocked)return void this.emit(this.eventBus,{name:"update:complete",payload:{blocked:!0,error:i.error,timestamp:Date.now()}});const r=this.merge(this.get(!0),s),a=await this.middlewareEngine.executeTransform(r,s),n=this.merge(this.get(!0),a),o=this.coreState.applyChanges(n),c=performance.now();this.emit(this.eventBus,{name:"update:complete",payload:{changedPaths:o,duration:c-t,timestamp:Date.now()}}),o.length>0&&this.persistenceHandler.handleStateChange(o,this.get())}catch(e){throw this.emit(this.eventBus,{name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}finally{if(this.isUpdating=!1,this.executionState.executing=!1,this.executionState.changes=null,this.executionState.runningMiddleware=null,this.pendingUpdates.length>0){const e=this.pendingUpdates.shift();if(this.executionState.pendingChanges=[...this.pendingUpdates],e)return this.set(e)}}}subscribe(e,t){const s=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(i=>{(s.includes(i)||""===e)&&(t(this.get(!0)),this.metricsCollector.listenerExecutions++)}))}id(){return this.instanceID}async transaction(e){return this.transactionManager.execute(e)}use(e){return e.block?this.middlewareEngine.addBlockingMiddleware(e.action,e.name):this.middlewareEngine.addMiddleware(e.action,e.name)}unuse(e){return this.middlewareEngine.removeMiddleware(e)}metrics(){return this.metricsCollector.getMetrics()}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}getPersistenceStatus(){return this.persistenceHandler.getQueueStatus()}async flushPersistence(){return this.persistenceHandler.flush()}clearPersistenceQueue(){this.persistenceHandler.clearQueue()}dispose(){this.persistenceHandler.dispose(),this.metricsCollector.dispose?.()}emit(e,t){queueMicrotask((()=>{e.emit(t)}))}},k=y(),T=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;middlewareExecutions=[];constructor(e,t={}){this.store=e,this.maxEvents=t.maxEvents??500,this.maxStateHistory=t.maxStateHistory??20,this.enableConsoleLogging=t.enableConsoleLogging??!1,this.logEvents={updates:t.logEvents?.updates??!0,middleware:t.logEvents?.middleware??!0,transactions:t.logEvents?.transactions??!0},this.performanceThresholds={updateTime:t.performanceThresholds?.updateTime??50,middlewareTime:t.performanceThresholds?.middlewareTime??20},this.recordStateSnapshot(),this.setupEventListeners()}setupEventListeners(){const e=["update:start","update:complete","middleware:start","middleware:complete","middleware:error","middleware:blocked","transaction:start","transaction:complete","transaction:error","middleware:executed"];this.unsubscribers.push(this.store.subscribe("",(()=>{this.recordStateSnapshot()})));for(const t of e){const e=t.startsWith("update")&&this.logEvents.updates||t.startsWith("middleware")&&this.logEvents.middleware||t.startsWith("transaction")&&this.logEvents.transactions;this.unsubscribers.push(this.store.onStoreEvent(t,(s=>{"middleware:executed"===t?this.middlewareExecutions.push(s):"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),s.batchId&&(t.endsWith("start")?this.activeBatches.add(s.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(s.batchId)),this.recordEvent(t,s),this.enableConsoleLogging&&e&&this.logEventToConsole(t,s),this.checkPerformance(t,s)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===k(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const s={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(s),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const s=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${s}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${s}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%c✅ Update Complete [${s}] - ${e.length} paths changed in ${t.duration?.toFixed(2)}ms`,"color: #2a9d8f",e)}console.groupEnd()}else"middleware:start"===e?console.debug(`%c◀ Middleware "${t.name}" started [${s}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${s}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${s}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${s}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${s}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${s}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${s}]:`,"color: #e56b6f",t.error),console.groupEnd())}checkPerformance(e,t){this.enableConsoleLogging&&("update:complete"===e&&!t.blocked&&t.duration>this.performanceThresholds.updateTime&&console.warn(`%c⚠️ Slow update detected [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{changedPaths:t.changedPaths,threshold:this.performanceThresholds.updateTime}),"middleware:complete"===e&&t.duration>this.performanceThresholds.middlewareTime&&console.warn(`%c⚠️ Slow middleware "${t.name}" [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{threshold:this.performanceThresholds.middlewareTime}))}getEventHistory(){return structuredClone(this.eventHistory)}getStateHistory(){return structuredClone(this.stateHistory)}getMiddlewareExecutions(){return this.middlewareExecutions}getPerformanceMetrics(){return this.store.metrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:s=!0}=e;return(e,i)=>{if(s){(console[t]||console.log)("State Update:",i)}return i}}createValidationMiddleware(e){return(t,s)=>{const i=e(t,s);return"boolean"==typeof i?i:(!i.valid&&i.reason&&console.warn("Validation failed:",i.reason),i.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],s=Math.min(e,this.stateHistory.length-1);for(let e=0;e<s;e++){const s=this.stateHistory[e],i=this.stateHistory[e+1],r=k(i,s),a={},n={};for(const e of r){const t=e.split("."),r=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),o=(e,t,s)=>{const i=t.length-1,r=t[i];t.slice(0,i).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[r]=s},c=r(i,t),d=r(s,t);o(a,t,c),o(n,t,d)}let o=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){o=e.timestamp;break}0!==r.length&&t.push({timestamp:o,changedPaths:r,from:a,to:n})}return t}createTimeTravel(){let e=0,t=[];return{canUndo:()=>e<this.stateHistory.length-1,canRedo:()=>t.length>0,undo:async()=>{if(e<this.stateHistory.length-1){const s=e+1,i=this.stateHistory[s];t.unshift(this.stateHistory[e]),e=s,await this.store.set(i)}},redo:async()=>{if(t.length>0){const s=t.shift();e=Math.max(0,e-1),await this.store.set(s)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}};export{u as DELETE_SYMBOL,S as ReactiveDataStore,T as StoreObserver,p as author};
|
|
1
|
+
export*from"./store";export*from"./observability";export*from"./types";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asaidimu/utils-store",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "A reactive data store",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@asaidimu/events": "^1.1.1",
|
|
33
|
-
"@asaidimu/utils-persistence": "2.
|
|
33
|
+
"@asaidimu/utils-persistence": "2.2.0",
|
|
34
34
|
"uuid": "^11.1.0"
|
|
35
35
|
},
|
|
36
36
|
"exports": {
|