@asaidimu/react-store 1.2.1 → 1.2.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/README.md CHANGED
@@ -5,6 +5,11 @@
5
5
 
6
6
  A performant, type-safe state management solution for React with built-in observability and middleware support.
7
7
 
8
+ ⚠️ **Beta Warning**
9
+ This package is currently in **beta**. Please note that the API is subject to rapid changes and should not be considered stable. While we are actively iterating and improving, breaking changes may occur frequently without notice.
10
+
11
+ We will update this warning once the package reaches a stable release. Use at your own risk and feel free to share feedback or report issues to help us improve!
12
+
8
13
  ## Features
9
14
 
10
15
  - **Reactive State Management**: Automatically track dependencies and optimize renders
@@ -18,9 +23,7 @@ A performant, type-safe state management solution for React with built-in observ
18
23
  ## Installation
19
24
 
20
25
  ```bash
21
- npm install @asaidimu/react-store
22
- # or
23
- yarn add @asaidimu/react-store
26
+ bun install @asaidimu/react-store
24
27
  ```
25
28
 
26
29
  ## Basic Usage
package/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("react"),t=require("@asaidimu/events");function r(e,t){const r=Symbol.for("delete");if("object"!=typeof e||null===e)return t;if("object"!=typeof t||null===t)return e;const s=structuredClone(e),n=[{target:s,source:t}];for(;n.length>0;){const{target:e,source:t}=n.pop();Object.keys(t).forEach((s=>{const a=t[s],i=e[s];a===r?delete e[s]:Array.isArray(a)?e[s]=a:"object"==typeof a&&null!==a&&"object"==typeof i&&null!==i?(e.hasOwnProperty(s)||(e[s]=structuredClone(a)),n.push({target:e[s],source:a})):e[s]=a}))}return s}function s(e,t){const r=new Set;function s(e,t){const r=e.split(".");for(let e=0;e<r.length;e++){const s=r.slice(0,e+1).join(".");t.add(s)}}return function e(t,n,a){const i=Symbol.for("delete");if(Array.isArray(n)&&Array.isArray(a))JSON.stringify(n)!==JSON.stringify(a)&&s(t,r);else for(const o of Object.keys(a)){const c=t?`${t}.${o}`:o;if(o in n){const t=n[o],d=a[o];d===i?(s(c,r),e(c,{},a[o])):"object"==typeof t&&null!==t&&"object"==typeof d&&null!==d?e(c,t,d):t!==d&&s(c,r)}else s(c,r),e(c,{},a[o])}}("",e||{},t||{}),Array.from(r)}var n=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;bus=t.createEventBus();eventBus=t.createEventBus();constructor(e){this.middleware=[],this.blockingMiddleware=[],this.pendingUpdates=[],this.isUpdating=!1,this.updateTimes=[],this.metrics={updateCount:0,listenerExecutions:0,averageUpdateTime:0,largestUpdateSize:0,mostActiveListenerPaths:[]},this.cache=e}get(){return this.cache}async set(e){if(this.isUpdating)return void this.pendingUpdates.push(e);this.isUpdating=!0;const t=performance.now();this.eventBus.emit({name:"update:start",payload:{timestamp:t}});let n=e;if("function"==typeof e){const t=e(structuredClone(this.get()));n=t instanceof Promise?await t:t}try{const e=this.applyBlockingMiddleware(n);if(e.blocked)return void this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e.error,timestamp:Date.now()}})}catch(e){throw this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}const a=r(this.get(),n),i=this.applyMiddleware(a,n);try{const e=s(this.get(),i);this.metrics.updateCount++,this.metrics.largestUpdateSize=Math.max(this.metrics.largestUpdateSize,e.length),e.length>0&&(this.cache=i,this.notifyListeners(e));const r=performance.now();this.updateTimes.push(r-t),this.updateTimes.length>100&&this.updateTimes.shift(),this.metrics.averageUpdateTime=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length,this.eventBus.emit({name:"update:complete",payload:{changedPaths:e,duration:r-t,timestamp:Date.now()}})}finally{if(this.isUpdating=!1,this.pendingUpdates.length>0){const e=this.pendingUpdates.shift();e&&this.set(e)}}}applyBlockingMiddleware(e){for(const{fn:t,name:r,id:s}of this.blockingMiddleware){const n={id:s,name:r,startTime:performance.now()};this.eventBus.emit({name:"middleware:start",payload:{id:s,name:r,type:"blocking",timestamp:Date.now()}});try{const a=t(this.get(),e);if(n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,!1===a)return n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:n.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:n.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...n,blocked:!1})}catch(e){return n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:n.error,duration:n.duration,timestamp:Date.now()}}),{blocked:!0,error:n.error}}}return{blocked:!1}}applyMiddleware(e,t){return this.middleware.reduce(((e,{fn:s,name:n,id:a})=>{const i={id:a,name:n,startTime:performance.now()};this.eventBus.emit({name:"middleware:start",payload:{id:a,name:n,type:"transform",timestamp:Date.now()}});try{const o=s(e,t);i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.blocked=!1;let c=e;return!1===o?console.warn(`Middleware ${n} returned false but isn't registered as blocking`):o&&"object"==typeof o&&(c=r(e,o)),this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:complete",payload:{id:a,name:n,type:"transform",duration:i.duration,timestamp:Date.now()}}),c}catch(t){return i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.error=t instanceof Error?t:new Error(String(t)),i.blocked=!1,this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:error",payload:{id:a,name:n,error:i.error,duration:i.duration,timestamp:Date.now()}}),console.error(`Middleware ${n} error:`,t),e}}),e)}subscribe(e,t){const r=Array.isArray(e)?e:[e];return this.bus.subscribe("update",(s=>{(r.includes(s)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}transaction(e){const t=structuredClone(this.get());this.eventBus.emit({name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=e();return this.eventBus.emit({name:"transaction:complete",payload:{timestamp:Date.now()}}),t}catch(e){throw this.cache=t,this.eventBus.emit({name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),e}}use(e,t="unnamed-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:r}),r}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:r}),r}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.middleware.length+this.blockingMiddleware.length<t}getPerformanceMetrics(){return structuredClone(this.metrics)}getMiddlewareExecutions(){return structuredClone(this.middlewareExecutions)}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}notifyListeners(e){e.forEach((e=>this.bus.emit({name:"update",payload:e})))}trackMiddlewareExecution(e){this.middlewareExecutions.unshift(e),this.middlewareExecutions.length>this.maxExecutionHistory&&this.middlewareExecutions.pop()}},a=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;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"];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,(r=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),r.batchId&&(t.endsWith("start")?this.activeBatches.add(r.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(r.batchId)),this.recordEvent(t,r),this.enableConsoleLogging&&e&&this.logEventToConsole(t,r),this.checkPerformance(t,r)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===s(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const r={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(r),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const r=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${r}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${r}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];console.log(`%c✅ Update Complete [${r}] - ${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 [${r}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${r}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${r}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${r}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${r}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${r}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${r}]:`,"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.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:r=!0}=e;return(e,s)=>{if(r){(console[t]||console.log)("State Update:",s)}return s}}createValidationMiddleware(e){return(t,r)=>{const s=e(t,r);return"boolean"==typeof s?s:(!s.valid&&s.reason&&console.warn("Validation failed:",s.reason),s.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],r=Math.min(e,this.stateHistory.length-1);for(let e=0;e<r;e++){const r=this.stateHistory[e],n=this.stateHistory[e+1],a=s(n,r),i={},o={};for(const e of a){const t=e.split("."),s=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),a=(e,t,r)=>{const s=t.length-1,n=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[n]=r},c=s(n,t),d=s(r,t);a(i,t,c),a(o,t,d)}let c=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){c=e.timestamp;break}0!==a.length&&t.push({timestamp:c,changedPaths:a,from:i,to:o})}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 r=e+1,s=this.stateHistory[r];t.unshift(this.stateHistory[e]),e=r,await this.store.set(s)}},redo:async()=>{if(t.length>0){const r=t.shift();e=Math.max(0,e-1),await this.store.set(r)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},i=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);r||(r=new WeakMap,this.selectorCache.set(e,r));const s=r.get(t);if(s)return s.result;const n=e(t);return r.set(t,{result:n,deps:[]}),n}}};function o(e){return{id:"opentelemetry",name:"OpenTelemetry Collector",config:e,testConnection:async()=>{try{return(await fetch(`${e.endpoint}/v1/metrics`,{method:"OPTIONS",headers:{...e.apiKey?{"api-key":e.apiKey}:{}}})).ok}catch(e){return!1}},send:async t=>{const r={resourceMetrics:[{resource:{attributes:{"service.name":t.source,...e.resource}},scopeMetrics:[{metrics:t.metrics.map((e=>"counter"===e.type?{name:e.name,unit:e.unit||"1",sum:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asInt:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:"histogram"===e.type?{name:e.name,unit:e.unit||"ms",histogram:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),count:1,sum:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:{name:e.name,unit:e.unit||"1",gauge:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asDouble:"number"==typeof e.value?e.value:1,attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}))}]}]};await fetch(`${e.endpoint}/v1/metrics`,{method:"POST",headers:{"Content-Type":"application/json",...e.apiKey?{"api-key":e.apiKey}:{}},body:JSON.stringify(r)})}}}function c(e){return{id:"prometheus",name:"Prometheus Pushgateway",config:e,testConnection:async()=>{try{const t=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;return(await fetch(e.pushgatewayUrl,{method:"HEAD",headers:{...t?{Authorization:t}:{}}})).ok}catch(e){return!1}},send:async t=>{let r="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const s=Object.entries({...e.labels,source:t.source,instance:t.metrics.find((e=>e.labels?.instanceId))?.labels?.instanceId||"unknown"}).map((([e,t])=>`${e}="${t.replace(/"/g,'\\"')}"`)).join(","),n=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${n} counter\n`:r+=`# TYPE ${n} gauge\n`,r+=`${n}{${s}} ${e.value}\n`}const s=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;await fetch(`${e.pushgatewayUrl}/metrics/job/${e.jobName}`,{method:"POST",headers:{"Content-Type":"text/plain",...s?{Authorization:s}:{}},body:r})}}}function d(e){return{id:"grafana-cloud",name:"Grafana Cloud",config:e,testConnection:async()=>{try{return(await fetch(`${e.url}/api/v1/query?query=up`,{method:"GET",headers:{Authorization:`Bearer ${e.apiKey}`}})).ok}catch(e){return!1}},send:async t=>{const r={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const r=1e6*t.timestamp;let s="";if("object"==typeof e.value)s=JSON.stringify(e.value);else if(s=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,r]of Object.entries(e.labels))s+=` ${t}=${r}`;return[String(r),s]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}}}function l(e){return{id:"http-endpoint",name:"HTTP Metrics Endpoint",config:e,testConnection:async()=>{try{return(await fetch(e.url,{method:"OPTIONS",headers:e.headers})).ok}catch(e){return!1}},send:async t=>{const r=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r)})}}}var m=class extends a{destinations=new Map;metricsBatch=[];reportingInterval;batchSize;serviceName;environment;instanceId;immediateReporting;collectCategories;compressPayloads;reportingTimer=null;traceIdCounter=0;activeTraces=new Map;constructor(e,t){super(e,t),this.serviceName=t.serviceName,this.environment=t.environment,this.instanceId=t.instanceId||this.generateInstanceId(),this.reportingInterval=t.reportingInterval||3e4,this.batchSize=t.batchSize||100,this.immediateReporting=t.immediateReporting||!1,this.collectCategories=t.collectCategories||{performance:!0,errors:!0,stateChanges:!0,middleware:!0},this.compressPayloads=t.compressPayloads||!1,this.immediateReporting||this.startReportingCycle(),this.setupRemoteEventListeners()}generateInstanceId(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}setupRemoteEventListeners(){this.store.onStoreEvent("update:complete",(e=>{this.collectCategories?.stateChanges&&(this.trackMetric({name:"store.update.duration",type:"histogram",value:e.duration||0,unit:"ms",labels:{pathCount:String(e.changedPaths?.length||0)}}),this.trackMetric({name:"store.update.paths_changed",type:"counter",value:e.changedPaths?.length||0,labels:{blocked:String(!!e.blocked)}}))})),this.store.onStoreEvent("middleware:error",(e=>{this.collectCategories?.errors&&this.trackMetric({name:"store.middleware.error",type:"log",value:{middleware:e.name,error:e.error?.message||"Unknown error",stack:e.error?.stack},labels:{middlewareName:e.name}})})),this.store.onStoreEvent("transaction:start",(e=>{if(!this.collectCategories?.performance)return;const t=this.beginTrace("transaction");e.traceId=t,this.beginSpan(t,"transaction.execution",{transactionId:e.id||String(Date.now())})})),this.store.onStoreEvent("transaction:complete",(e=>{this.collectCategories?.performance&&e.traceId&&(this.endSpan(e.traceId,"transaction.execution"),this.endTrace(e.traceId))}))}beginTrace(e){const t=`trace-${++this.traceIdCounter}-${Date.now()}`;return this.activeTraces.set(t,{startTime:performance.now(),name:e,spans:{}}),t}beginSpan(e,t,r={}){const s=this.activeTraces.get(e);if(!s)return"";const n=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[n]={startTime:performance.now(),name:t,labels:r},n}endSpan(e,t){const r=this.activeTraces.get(e);if(!r)return;const s=Object.entries(r.spans).filter((([e,r])=>r.name===t&&!r.endTime)).map((([e])=>e));if(s.length>0){const e=s[s.length-1];r.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const r=performance.now(),s=r-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:s,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,s])=>{s.endTime||(s.endTime=r);const n=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:n,unit:"ms",labels:{...s.labels,spanName:s.name},traceId:e,parentId:t})})),this.activeTraces.delete(e)}addDestination(e){return this.destinations.has(e.id)?(console.warn(`Destination with ID ${e.id} already exists`),!1):(this.destinations.set(e.id,e),!0)}removeDestination(e){return this.destinations.delete(e)}getDestinations(){return Array.from(this.destinations.values()).map((e=>({id:e.id,name:e.name})))}async testAllConnections(){const e={};for(const[t,r]of this.destinations.entries())if(r.testConnection)try{e[t]=await r.testConnection()}catch(r){e[t]=!1}else e[t]=!0;return e}trackMetric(e){e.labels={...e.labels,environment:this.environment,service:this.serviceName,instanceId:this.instanceId},this.metricsBatch.push(e),(this.immediateReporting||this.metricsBatch.length>=this.batchSize)&&this.flushMetrics()}async flushMetrics(){if(0===this.metricsBatch.length)return;const e={timestamp:Date.now(),source:this.serviceName,metrics:[...this.metricsBatch]};this.metricsBatch=[];const t=Array.from(this.destinations.values()).map((async t=>{try{let r=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),s=(new TextEncoder).encode(t),n=new CompressionStream("gzip"),a=new Blob([s]).stream().pipeThrough(n);await new Response(a).blob();r._compressed=!0,r._originalSize=t.length}await t.send(r)}catch(r){if(console.error(`Failed to send metrics to destination ${t.name}:`,r),e.metrics.some((e=>"log"===e.type&&e.name.includes("error")))){const t=e.metrics.filter((e=>"log"===e.type&&e.name.includes("error")));this.metricsBatch.push(...t)}}}));await Promise.all(t)}startReportingCycle(){this.reportingTimer&&clearInterval(this.reportingTimer),this.reportingTimer=setInterval((()=>{this.flushMetrics()}),this.reportingInterval)}setReportingInterval(e){this.reportingInterval=e,this.startReportingCycle()}reportCurrentMetrics(){const e=this.store.getPerformanceMetrics();this.trackMetric({name:"store.performance.update_count",type:"counter",value:e.updateCount,labels:{}}),this.trackMetric({name:"store.performance.listener_executions",type:"counter",value:e.listenerExecutions,labels:{}}),this.trackMetric({name:"store.performance.average_update_time",type:"gauge",value:e.averageUpdateTime,unit:"ms",labels:{}}),this.trackMetric({name:"store.performance.largest_update_size",type:"gauge",value:e.largestUpdateSize,unit:"paths",labels:{}})}disconnect(){this.reportingTimer&&(clearInterval(this.reportingTimer),this.reportingTimer=null),this.flushMetrics().catch((e=>console.error("Error flushing metrics during disconnect:",e))),super.disconnect()}};exports.ReactiveDataStore=n,exports.RemoteObservability=m,exports.StoreObservability=a,exports.createGrafanaCloudDestination=d,exports.createHttpDestination=l,exports.createOpenTelemetryDestination=o,exports.createPrometheusDestination=c,exports.createStore=function(t,{enableMetrics:r,...s}={}){const o=new n(t.state);let c;t.middleware&&t.middleware.forEach((e=>o.use(e))),t.blockingMiddleware&&t.blockingMiddleware.forEach((e=>o.use(e))),r&&(c=new a(o,s));const d=new i,l=Object.entries(t.actions).reduce(((e,[t,r])=>(e[t]=async(...e)=>{try{await o.set((async t=>await r(t,...e)))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e)),{});return function(){const t=e.useCallback((t=>{const r=d.create(t);return e.useSyncExternalStore((e=>function(e,t){const r=[];return e(function e(t){return new Proxy({},{get:(s,n)=>{const a=`${t?`${t}/`:""}${n}`;return r.push(a),e(a)}})}()),o.subscribe(r,t)}(r,e)),(()=>r(o.get())),(()=>r(o.get())))}),[]);return{store:o,observer:c,select:t,actions:l,get state(){return e.useSyncExternalStore((e=>o.subscribe("",e)),(()=>o.get()),(()=>o.get()))}}}},exports.useRemoteObservability=function(e,t){const r=new m(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(o(e)),addPrometheusDestination:e=>r.addDestination(c(e)),addGrafanaCloudDestination:e=>r.addDestination(d(e)),addHttpDestination:e=>r.addDestination(l(e))}};
1
+ "use strict";var e=require("react"),t=require("@asaidimu/events");function r(e,t){const r=Symbol.for("delete");if("object"!=typeof e||null===e)return t;if("object"!=typeof t||null===t)return e;const s=structuredClone(e),n=[{target:s,source:t}];for(;n.length>0;){const{target:e,source:t}=n.pop();Object.keys(t).forEach((s=>{const a=t[s],i=e[s];a===r?delete e[s]:Array.isArray(a)?e[s]=a:"object"==typeof a&&null!==a&&"object"==typeof i&&null!==i?(e.hasOwnProperty(s)||(e[s]=structuredClone(a)),n.push({target:e[s],source:a})):e[s]=a}))}return s}function s(e,t){const r=new Set;function s(e,t){const r=e.split(".");for(let e=0;e<r.length;e++){const s=r.slice(0,e+1).join(".");t.add(s)}}return function e(t,n,a){const i=Symbol.for("delete");if(Array.isArray(n)&&Array.isArray(a))JSON.stringify(n)!==JSON.stringify(a)&&s(t,r);else for(const o of Object.keys(a)){const c=t?`${t}.${o}`:o;if(o in n){const t=n[o],d=a[o];d===i?(s(c,r),e(c,{},a[o])):"object"==typeof t&&null!==t&&"object"==typeof d&&null!==d?e(c,t,d):t!==d&&s(c,r)}else s(c,r),e(c,{},a[o])}}("",e||{},t||{}),Array.from(r)}var n=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;updateBus=t.createEventBus();eventBus=t.createEventBus();executionState;persistence;constructor(e,t){this.middleware=[],this.blockingMiddleware=[],this.pendingUpdates=[],this.isUpdating=!1,this.updateTimes=[],this.metrics={updateCount:0,listenerExecutions:0,averageUpdateTime:0,largestUpdateSize:0,mostActiveListenerPaths:[]},this.executionState={executing:!1,changes:null,pendingChanges:[],middlewares:[],runningMiddleware:null,transactionActive:!1},this.cache=e,t&&this.setPersistence(t)}async setPersistence(e){this.persistence=e;const t=await Promise.resolve(this.persistence.get());t&&(this.cache=t),this.persistence.subscribe((async()=>{const e=await Promise.resolve(this.persistence?.get());if(e){this.isUpdating=!0;try{const t=s(this.get(),e);t.length>0&&(this.cache=e,this.notifyListeners(t),this.eventBus.emit({name:"update:complete",payload:{changedPaths:t,source:"external",timestamp:Date.now()}}))}finally{this.isUpdating=!1}}}))}getExecutionState(){return this.executionState}get(){return this.cache}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.eventBus.emit({name:"update:start",payload:{timestamp:t}});let n=e;if("function"==typeof e){const t=e(structuredClone(this.get()));n=t instanceof Promise?await t:t}try{const e=await this.applyBlockingMiddleware(n);if(e.blocked)return void this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e.error,timestamp:Date.now()}})}catch(e){throw this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}const a=r(this.get(),n),i=await this.applyMiddleware(a,n);try{const e=s(this.get(),i);if(this.metrics.updateCount++,this.metrics.largestUpdateSize=Math.max(this.metrics.largestUpdateSize,e.length),e.length>0){this.cache=i;try{await(this.persistence?.set(this.get()))||this.eventBus.emit({name:"update:complete",payload:{persistence:!1,timestamp:Date.now()}})}catch(e){this.eventBus.emit({name:"update:complete",payload:{persistence:!1,error:e,timestamp:Date.now()}})}this.notifyListeners(e)}const r=performance.now();this.updateTimes.push(r-t),this.updateTimes.length>100&&this.updateTimes.shift(),this.metrics.averageUpdateTime=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length,this.eventBus.emit({name:"update:complete",payload:{changedPaths:e,duration:r-t,timestamp:Date.now()}})}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)}}}async applyBlockingMiddleware(e){for(const{fn:t,name:r,id:s}of this.blockingMiddleware){const n={id:s,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:s,name:r,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:s,name:r,type:"blocking",timestamp:Date.now()}});try{const a=await Promise.resolve(t(this.get(),e));if(n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,!1===a)return n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:n.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:n.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...n,blocked:!1})}catch(e){return n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:n.error,duration:n.duration,timestamp:Date.now()}}),{blocked:!0,error:n.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async applyMiddleware(e,t){let s=e;for(const{fn:e,name:n,id:a}of this.middleware){const i={id:a,name:n,startTime:performance.now()};this.executionState.runningMiddleware={id:a,name:n,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:a,name:n,type:"transform",timestamp:Date.now()}});try{const o=await Promise.resolve(e(s,t));i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.blocked=!1,o&&"object"==typeof o&&(s=r(s,o)),this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:complete",payload:{id:a,name:n,type:"transform",duration:i.duration,timestamp:Date.now()}})}catch(e){i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.error=e instanceof Error?e:new Error(String(e)),i.blocked=!1,this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:error",payload:{id:a,name:n,error:i.error,duration:i.duration,timestamp:Date.now()}}),console.error(`Middleware ${n} error:`,e)}finally{this.executionState.runningMiddleware=null}}return s}subscribe(e,t){const r=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(s=>{(r.includes(s)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}async transaction(e){const t=structuredClone(this.get());this.executionState.transactionActive=!0,this.eventBus.emit({name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=await Promise.resolve(e());return this.eventBus.emit({name:"transaction:complete",payload:{timestamp:Date.now()}}),this.executionState.transactionActive=!1,t}catch(e){throw this.cache=t,this.eventBus.emit({name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),this.executionState.transactionActive=!1,e}}use(e,t="unnamed-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}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.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],this.middleware.length+this.blockingMiddleware.length<t}getPerformanceMetrics(){return structuredClone(this.metrics)}getMiddlewareExecutions(){return structuredClone(this.middlewareExecutions)}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}notifyListeners(e){e.forEach((e=>this.updateBus.emit({name:"update",payload:e})))}trackMiddlewareExecution(e){this.middlewareExecutions.unshift(e),this.middlewareExecutions.length>this.maxExecutionHistory&&this.middlewareExecutions.pop()}},a=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;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"];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,(r=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),r.batchId&&(t.endsWith("start")?this.activeBatches.add(r.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(r.batchId)),this.recordEvent(t,r),this.enableConsoleLogging&&e&&this.logEventToConsole(t,r),this.checkPerformance(t,r)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===s(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const r={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(r),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const r=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${r}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${r}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%c✅ Update Complete [${r}] - ${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 [${r}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${r}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${r}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${r}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${r}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${r}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${r}]:`,"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.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:r=!0}=e;return(e,s)=>{if(r){(console[t]||console.log)("State Update:",s)}return s}}createValidationMiddleware(e){return(t,r)=>{const s=e(t,r);return"boolean"==typeof s?s:(!s.valid&&s.reason&&console.warn("Validation failed:",s.reason),s.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],r=Math.min(e,this.stateHistory.length-1);for(let e=0;e<r;e++){const r=this.stateHistory[e],n=this.stateHistory[e+1],a=s(n,r),i={},o={};for(const e of a){const t=e.split("."),s=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),a=(e,t,r)=>{const s=t.length-1,n=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[n]=r},c=s(n,t),d=s(r,t);a(i,t,c),a(o,t,d)}let c=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){c=e.timestamp;break}0!==a.length&&t.push({timestamp:c,changedPaths:a,from:i,to:o})}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 r=e+1,s=this.stateHistory[r];t.unshift(this.stateHistory[e]),e=r,await this.store.set(s)}},redo:async()=>{if(t.length>0){const r=t.shift();e=Math.max(0,e-1),await this.store.set(r)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},i=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);r||(r=new WeakMap,this.selectorCache.set(e,r));const s=r.get(t);if(s)return s.result;const n=e(t);return r.set(t,{result:n,deps:[]}),n}}};function o(e){return{id:"opentelemetry",name:"OpenTelemetry Collector",config:e,testConnection:async()=>{try{return(await fetch(`${e.endpoint}/v1/metrics`,{method:"OPTIONS",headers:{...e.apiKey?{"api-key":e.apiKey}:{}}})).ok}catch(e){return!1}},send:async t=>{const r={resourceMetrics:[{resource:{attributes:{"service.name":t.source,...e.resource}},scopeMetrics:[{metrics:t.metrics.map((e=>"counter"===e.type?{name:e.name,unit:e.unit||"1",sum:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asInt:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:"histogram"===e.type?{name:e.name,unit:e.unit||"ms",histogram:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),count:1,sum:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:{name:e.name,unit:e.unit||"1",gauge:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asDouble:"number"==typeof e.value?e.value:1,attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}))}]}]};await fetch(`${e.endpoint}/v1/metrics`,{method:"POST",headers:{"Content-Type":"application/json",...e.apiKey?{"api-key":e.apiKey}:{}},body:JSON.stringify(r)})}}}function c(e){return{id:"prometheus",name:"Prometheus Pushgateway",config:e,testConnection:async()=>{try{const t=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;return(await fetch(e.pushgatewayUrl,{method:"HEAD",headers:{...t?{Authorization:t}:{}}})).ok}catch(e){return!1}},send:async t=>{let r="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const s=Object.entries({...e.labels,source:t.source,instance:t.metrics.find((e=>e.labels?.instanceId))?.labels?.instanceId||"unknown"}).map((([e,t])=>`${e}="${t.replace(/"/g,'\\"')}"`)).join(","),n=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${n} counter\n`:r+=`# TYPE ${n} gauge\n`,r+=`${n}{${s}} ${e.value}\n`}const s=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;await fetch(`${e.pushgatewayUrl}/metrics/job/${e.jobName}`,{method:"POST",headers:{"Content-Type":"text/plain",...s?{Authorization:s}:{}},body:r})}}}function d(e){return{id:"grafana-cloud",name:"Grafana Cloud",config:e,testConnection:async()=>{try{return(await fetch(`${e.url}/api/v1/query?query=up`,{method:"GET",headers:{Authorization:`Bearer ${e.apiKey}`}})).ok}catch(e){return!1}},send:async t=>{const r={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const r=1e6*t.timestamp;let s="";if("object"==typeof e.value)s=JSON.stringify(e.value);else if(s=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,r]of Object.entries(e.labels))s+=` ${t}=${r}`;return[String(r),s]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}}}function l(e){return{id:"http-endpoint",name:"HTTP Metrics Endpoint",config:e,testConnection:async()=>{try{return(await fetch(e.url,{method:"OPTIONS",headers:e.headers})).ok}catch(e){return!1}},send:async t=>{const r=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r)})}}}var m=class extends a{destinations=new Map;metricsBatch=[];reportingInterval;batchSize;serviceName;environment;instanceId;immediateReporting;collectCategories;compressPayloads;reportingTimer=null;traceIdCounter=0;activeTraces=new Map;constructor(e,t){super(e,t),this.serviceName=t.serviceName,this.environment=t.environment,this.instanceId=t.instanceId||this.generateInstanceId(),this.reportingInterval=t.reportingInterval||3e4,this.batchSize=t.batchSize||100,this.immediateReporting=t.immediateReporting||!1,this.collectCategories=t.collectCategories||{performance:!0,errors:!0,stateChanges:!0,middleware:!0},this.compressPayloads=t.compressPayloads||!1,this.immediateReporting||this.startReportingCycle(),this.setupRemoteEventListeners()}generateInstanceId(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}setupRemoteEventListeners(){this.store.onStoreEvent("update:complete",(e=>{this.collectCategories?.stateChanges&&(this.trackMetric({name:"store.update.duration",type:"histogram",value:e.duration||0,unit:"ms",labels:{pathCount:String(e.changedPaths?.length||0)}}),this.trackMetric({name:"store.update.paths_changed",type:"counter",value:e.changedPaths?.length||0,labels:{blocked:String(!!e.blocked)}}))})),this.store.onStoreEvent("middleware:error",(e=>{this.collectCategories?.errors&&this.trackMetric({name:"store.middleware.error",type:"log",value:{middleware:e.name,error:e.error?.message||"Unknown error",stack:e.error?.stack},labels:{middlewareName:e.name}})})),this.store.onStoreEvent("transaction:start",(e=>{if(!this.collectCategories?.performance)return;const t=this.beginTrace("transaction");e.traceId=t,this.beginSpan(t,"transaction.execution",{transactionId:e.id||String(Date.now())})})),this.store.onStoreEvent("transaction:complete",(e=>{this.collectCategories?.performance&&e.traceId&&(this.endSpan(e.traceId,"transaction.execution"),this.endTrace(e.traceId))}))}beginTrace(e){const t=`trace-${++this.traceIdCounter}-${Date.now()}`;return this.activeTraces.set(t,{startTime:performance.now(),name:e,spans:{}}),t}beginSpan(e,t,r={}){const s=this.activeTraces.get(e);if(!s)return"";const n=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[n]={startTime:performance.now(),name:t,labels:r},n}endSpan(e,t){const r=this.activeTraces.get(e);if(!r)return;const s=Object.entries(r.spans).filter((([e,r])=>r.name===t&&!r.endTime)).map((([e])=>e));if(s.length>0){const e=s[s.length-1];r.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const r=performance.now(),s=r-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:s,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,s])=>{s.endTime||(s.endTime=r);const n=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:n,unit:"ms",labels:{...s.labels,spanName:s.name},traceId:e,parentId:t})})),this.activeTraces.delete(e)}addDestination(e){return this.destinations.has(e.id)?(console.warn(`Destination with ID ${e.id} already exists`),!1):(this.destinations.set(e.id,e),!0)}removeDestination(e){return this.destinations.delete(e)}getDestinations(){return Array.from(this.destinations.values()).map((e=>({id:e.id,name:e.name})))}async testAllConnections(){const e={};for(const[t,r]of this.destinations.entries())if(r.testConnection)try{e[t]=await r.testConnection()}catch(r){e[t]=!1}else e[t]=!0;return e}trackMetric(e){e.labels={...e.labels,environment:this.environment,service:this.serviceName,instanceId:this.instanceId},this.metricsBatch.push(e),(this.immediateReporting||this.metricsBatch.length>=this.batchSize)&&this.flushMetrics()}async flushMetrics(){if(0===this.metricsBatch.length)return;const e={timestamp:Date.now(),source:this.serviceName,metrics:[...this.metricsBatch]};this.metricsBatch=[];const t=Array.from(this.destinations.values()).map((async t=>{try{let r=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),s=(new TextEncoder).encode(t),n=new CompressionStream("gzip"),a=new Blob([s]).stream().pipeThrough(n);await new Response(a).blob();r._compressed=!0,r._originalSize=t.length}await t.send(r)}catch(r){if(console.error(`Failed to send metrics to destination ${t.name}:`,r),e.metrics.some((e=>"log"===e.type&&e.name.includes("error")))){const t=e.metrics.filter((e=>"log"===e.type&&e.name.includes("error")));this.metricsBatch.push(...t)}}}));await Promise.all(t)}startReportingCycle(){this.reportingTimer&&clearInterval(this.reportingTimer),this.reportingTimer=setInterval((()=>{this.flushMetrics()}),this.reportingInterval)}setReportingInterval(e){this.reportingInterval=e,this.startReportingCycle()}reportCurrentMetrics(){const e=this.store.getPerformanceMetrics();this.trackMetric({name:"store.performance.update_count",type:"counter",value:e.updateCount,labels:{}}),this.trackMetric({name:"store.performance.listener_executions",type:"counter",value:e.listenerExecutions,labels:{}}),this.trackMetric({name:"store.performance.average_update_time",type:"gauge",value:e.averageUpdateTime,unit:"ms",labels:{}}),this.trackMetric({name:"store.performance.largest_update_size",type:"gauge",value:e.largestUpdateSize,unit:"paths",labels:{}})}disconnect(){this.reportingTimer&&(clearInterval(this.reportingTimer),this.reportingTimer=null),this.flushMetrics().catch((e=>console.error("Error flushing metrics during disconnect:",e))),super.disconnect()}};exports.LocalStoragePersistence=class{storageKey;bus=t.createEventBus();constructor(e){this.storageKey=e,window.addEventListener("storage",(e=>{e.key===this.storageKey&&this.bus.emit({name:"storage",payload:null})}))}set(e){try{return localStorage.setItem(this.storageKey,JSON.stringify(e)),!0}catch(e){return console.error("Failed to persist state to localStorage:",e),!1}}get(){try{const e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):null}catch(e){return console.error("Failed to retrieve state from localStorage:",e),null}}subscribe(e){return this.bus.subscribe("storage",e)}clear(){try{return localStorage.removeItem(this.storageKey),!0}catch(e){return console.error("Failed to clear persisted state:",e),!1}}},exports.ReactiveDataStore=n,exports.RemoteObservability=m,exports.StoreObservability=a,exports.createGrafanaCloudDestination=d,exports.createHttpDestination=l,exports.createOpenTelemetryDestination=o,exports.createPrometheusDestination=c,exports.createStore=function(r,{enableMetrics:s,...o}={}){const c=new n(r.state,o.persistence);let d;r.middleware&&Object.entries(r.middleware).forEach((([e,t])=>c.use(t,e))),r.blockingMiddleware&&Object.entries(r.blockingMiddleware).forEach((([e,t])=>c.useBlockingMiddleware(t,e))),s&&(d=new a(c,o));const l=t.createEventBus(),m=[],h={info:{executions:m,...c.getExecutionState()}};l.subscribe("stats",(()=>{h.info={executions:m,...c.getExecutionState()}}));const u=new i,p=Object.entries(r.actions).reduce(((e,[t,r])=>{const s=Date.now();return m.push({id:s,action:t,executing:!1}),e[t]=async(...e)=>{try{await c.set((async t=>{const n=m.findIndex((e=>e.id===s));-1!==n&&(m[n].executing=!0,l.emit({name:"stats",payload:null}));const a=await r(t,...e);return-1!==n&&(m[n].executing=!1,l.emit({name:"stats",payload:null})),a}))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e}),{});return function(){const t=e.useCallback((t=>{const r=u.create(t);return e.useSyncExternalStore((e=>function(e,t){const r=[];return e(function e(t){return new Proxy({},{get:(s,n)=>{const a=`${t?`${t}/`:""}${n}`;return r.push(a),e(a)}})}()),c.subscribe(r,t)}(r,e)),(()=>r(c.get())),(()=>r(c.get())))}),[]);return{store:c,observer:d,select:t,actions:p,get stats(){return e.useSyncExternalStore((e=>l.subscribe("stats",e)),(()=>h.info),(()=>h.info))},get state(){return e.useSyncExternalStore((e=>c.subscribe("",e)),(()=>c.get()),(()=>c.get()))}}}},exports.useRemoteObservability=function(e,t){const r=new m(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(o(e)),addPrometheusDestination:e=>r.addDestination(c(e)),addGrafanaCloudDestination:e=>r.addDestination(d(e)),addHttpDestination:e=>r.addDestination(l(e))}};
package/index.d.cts CHANGED
@@ -1,8 +1,37 @@
1
+ /**
2
+ * Interface for persistence adapters for the ReactiveDataStore
3
+ */
4
+ interface DataStorePersistence<T> {
5
+ /**
6
+ * Persists data to storage
7
+ * @param state The state to persist
8
+ * @returns boolean indicating success or Promise resolving to success status
9
+ */
10
+ set(state: T): boolean | Promise<boolean>;
11
+ /**
12
+ * Retrieves persisted data from storage
13
+ * @returns The retrieved state or null if not found
14
+ */
15
+ get(): T | null | Promise<T | null>;
16
+ /**
17
+ * Subscribes to changes in persisted data (e.g., from other tabs)
18
+ * @param callback Function to call when persisted data changes
19
+ * @returns Function to unsubscribe
20
+ */
21
+ subscribe(callback: () => void): () => void;
22
+ /**
23
+ * Clears the persisted data
24
+ * @returns true if successful
25
+ */
26
+ clear(): boolean | Promise<boolean>;
27
+ }
28
+
1
29
  /**
2
30
  * Reactive Data Store Implementation
3
31
  * A type-safe reactive data state for managing complex application state.
4
32
  * Arrays are treated as primitive values (stored and updated as whole units).
5
33
  */
34
+
6
35
  /**
7
36
  * Utility type for representing partial updates to the state.
8
37
  */
@@ -20,25 +49,32 @@ interface StoreMetrics {
20
49
  mostActiveListenerPaths: string[];
21
50
  }
22
51
  /**
23
- * Event types emitted by the state for observability
52
+ * Extended store state for monitoring execution status
24
53
  */
25
- type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
54
+ interface StoreExecutionState<T> {
55
+ executing: boolean;
56
+ changes: DeepPartial<T> | null;
57
+ pendingChanges: Array<StateUpdater<T>>;
58
+ middlewares: string[];
59
+ runningMiddleware: {
60
+ id: string;
61
+ name: string;
62
+ startTime: number;
63
+ } | null;
64
+ transactionActive: boolean;
65
+ }
26
66
  /**
27
- * Result of a middleware execution
67
+ * Event types emitted by the state for observability
28
68
  */
29
- interface MiddlewareResult<T> {
30
- update: DeepPartial<T>;
31
- blocked: boolean;
32
- error?: Error;
33
- }
69
+ type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
34
70
  /**
35
71
  * Type for middleware functions. Return false to block the update.
36
72
  */
37
- type Middleware<T> = (state: T, update: DeepPartial<T>) => DeepPartial<T> | boolean | void;
73
+ type Middleware<T> = (state: T, update: DeepPartial<T>) => Promise<DeepPartial<T>> | Promise<void> | DeepPartial<T> | void;
38
74
  /**
39
75
  * Type for blocking middleware functions that can prevent updates.
40
76
  */
41
- type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | void;
77
+ type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | Promise<boolean>;
42
78
  /**
43
79
  * Middleware execution information for tracking
44
80
  */
@@ -67,7 +103,7 @@ interface DataStore<T extends object> {
67
103
  get(): T;
68
104
  set(update: StateUpdater<T>): Promise<void>;
69
105
  subscribe(path: string, listener: (state: T) => void): () => void;
70
- transaction<R>(operation: () => R): R;
106
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
71
107
  getPerformanceMetrics(): StoreMetrics;
72
108
  onStoreEvent(event: StoreEvent, listener: (data: any) => void): () => void;
73
109
  getMiddlewareExecutions(): MiddlewareExecution[];
@@ -85,13 +121,21 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
85
121
  private isUpdating;
86
122
  private updateTimes;
87
123
  private cache;
88
- private bus;
124
+ private updateBus;
89
125
  private eventBus;
126
+ private executionState;
127
+ private persistence?;
90
128
  /**
91
129
  * Creates a new instance of ReactiveDataStore.
92
130
  * @param initialData The initial state data.
93
131
  */
94
- constructor(initialData: T);
132
+ constructor(initialData: T, persistence?: DataStorePersistence<T>);
133
+ private setPersistence;
134
+ /**
135
+ * Gets current execution state for monitoring
136
+ * @returns The current execution state of the store
137
+ */
138
+ getExecutionState(): Readonly<StoreExecutionState<T>>;
95
139
  /**
96
140
  * Gets a specific part of the state at the given path.
97
141
  * @returns The current state
@@ -131,7 +175,12 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
131
175
  * @param operation The operation to execute.
132
176
  * @returns The result of the operation.
133
177
  */
134
- transaction<R>(operation: () => R): R;
178
+ /**
179
+ * Executes an operation within a transaction that can be rolled back if an error occurs.
180
+ * @param operation The operation to execute.
181
+ * @returns The result of the operation.
182
+ */
183
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
135
184
  /**
136
185
  * Adds a transformation middleware to the state.
137
186
  * These middleware functions can modify updates but cannot block them.
@@ -300,7 +349,7 @@ declare class StoreObservability<T extends object> {
300
349
  * @returns A middleware function
301
350
  */
302
351
  createLoggingMiddleware(options?: {
303
- logLevel?: 'debug' | 'info' | 'warn';
352
+ logLevel?: "debug" | "info" | "warn";
304
353
  logUpdates?: boolean;
305
354
  }): (state: T, update: DeepPartial<T>) => DeepPartial<T>;
306
355
  /**
@@ -345,28 +394,46 @@ declare class StoreObservability<T extends object> {
345
394
  disconnect(): void;
346
395
  }
347
396
 
348
- type StoreOptions = ObservabilityOptions & {
397
+ type StoreOptions<T> = ObservabilityOptions & {
349
398
  enableMetrics?: boolean;
399
+ persistence?: DataStorePersistence<T>;
350
400
  };
351
401
  type Actions<T> = {
352
- [K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
402
+ [K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
353
403
  };
354
404
  interface StoreDefinition<T, R extends Actions<T>> {
355
405
  state: T;
356
406
  actions: R;
357
407
  sync?: (args: T) => void;
358
- blockingMiddleware?: Middleware<T>[];
359
- middleware?: Middleware<T>[];
408
+ blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
409
+ middleware?: Record<string, Middleware<T>>;
360
410
  }
361
411
 
362
412
  /**
363
413
  * Production-ready state creator without unnecessary memoization
364
414
  */
365
- declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions): () => {
415
+ declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions<T>): () => {
366
416
  store: ReactiveDataStore<T>;
367
417
  observer: StoreObservability<T> | undefined;
368
418
  select: <S>(selector: (state: T) => S) => S;
369
419
  actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
420
+ readonly stats: {
421
+ executing: boolean;
422
+ changes: DeepPartial<T> | null;
423
+ pendingChanges: (DeepPartial<T> | ((state: T) => DeepPartial<T> | Promise<DeepPartial<T>>))[];
424
+ middlewares: string[];
425
+ runningMiddleware: {
426
+ id: string;
427
+ name: string;
428
+ startTime: number;
429
+ } | null;
430
+ transactionActive: boolean;
431
+ executions: {
432
+ executing: boolean;
433
+ id: number;
434
+ action: keyof R;
435
+ }[];
436
+ };
370
437
  readonly state: T;
371
438
  };
372
439
 
@@ -601,4 +668,40 @@ declare function useRemoteObservability<T extends object>(store: DataStore<T>, o
601
668
  addHttpDestination: (config: Parameters<typeof createHttpDestination>[0]) => boolean;
602
669
  };
603
670
 
604
- export { type BlockingMiddleware, type DataStore, type DebugEvent, type DeepPartial, type MetricType, type Middleware, type MiddlewareExecution, type MiddlewareResult, type ObservabilityOptions, ReactiveDataStore, type RemoteDestination, type RemoteMetricsPayload, RemoteObservability, type RemoteObservabilityOptions, type StoreEvent, type StoreMetrics, StoreObservability, createGrafanaCloudDestination, createHttpDestination, createOpenTelemetryDestination, createPrometheusDestination, createStore, useRemoteObservability };
671
+ /**
672
+ * LocalStorage implementation of DataStorePersistence
673
+ */
674
+
675
+ declare class LocalStoragePersistence<T extends object> implements DataStorePersistence<T> {
676
+ private readonly storageKey;
677
+ private bus;
678
+ /**
679
+ * Creates a new LocalStoragePersistence instance
680
+ * @param storageKey The localStorage key to use for persistence
681
+ */
682
+ constructor(storageKey: string);
683
+ /**
684
+ * Persists the state to localStorage
685
+ * @param state The state to persist
686
+ * @returns true if successful, false otherwise
687
+ */
688
+ set(state: T): boolean;
689
+ /**
690
+ * Retrieves the state from localStorage
691
+ * @returns The persisted state or null if not found/invalid
692
+ */
693
+ get(): T | null;
694
+ /**
695
+ * Subscribes to changes in the persisted data
696
+ * @param callback Function to call when persisted data changes
697
+ * @returns Function to unsubscribe
698
+ */
699
+ subscribe(callback: () => void): () => void;
700
+ /**
701
+ * Clears the persisted data
702
+ * @returns true if successful
703
+ */
704
+ clear(): boolean;
705
+ }
706
+
707
+ export { type BlockingMiddleware, type DataStore, type DataStorePersistence, type DebugEvent, type DeepPartial, LocalStoragePersistence, type MetricType, type Middleware, type MiddlewareExecution, type ObservabilityOptions, ReactiveDataStore, type RemoteDestination, type RemoteMetricsPayload, RemoteObservability, type RemoteObservabilityOptions, type StoreEvent, type StoreExecutionState, type StoreMetrics, StoreObservability, createGrafanaCloudDestination, createHttpDestination, createOpenTelemetryDestination, createPrometheusDestination, createStore, useRemoteObservability };
package/index.d.ts CHANGED
@@ -1,8 +1,37 @@
1
+ /**
2
+ * Interface for persistence adapters for the ReactiveDataStore
3
+ */
4
+ interface DataStorePersistence<T> {
5
+ /**
6
+ * Persists data to storage
7
+ * @param state The state to persist
8
+ * @returns boolean indicating success or Promise resolving to success status
9
+ */
10
+ set(state: T): boolean | Promise<boolean>;
11
+ /**
12
+ * Retrieves persisted data from storage
13
+ * @returns The retrieved state or null if not found
14
+ */
15
+ get(): T | null | Promise<T | null>;
16
+ /**
17
+ * Subscribes to changes in persisted data (e.g., from other tabs)
18
+ * @param callback Function to call when persisted data changes
19
+ * @returns Function to unsubscribe
20
+ */
21
+ subscribe(callback: () => void): () => void;
22
+ /**
23
+ * Clears the persisted data
24
+ * @returns true if successful
25
+ */
26
+ clear(): boolean | Promise<boolean>;
27
+ }
28
+
1
29
  /**
2
30
  * Reactive Data Store Implementation
3
31
  * A type-safe reactive data state for managing complex application state.
4
32
  * Arrays are treated as primitive values (stored and updated as whole units).
5
33
  */
34
+
6
35
  /**
7
36
  * Utility type for representing partial updates to the state.
8
37
  */
@@ -20,25 +49,32 @@ interface StoreMetrics {
20
49
  mostActiveListenerPaths: string[];
21
50
  }
22
51
  /**
23
- * Event types emitted by the state for observability
52
+ * Extended store state for monitoring execution status
24
53
  */
25
- type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
54
+ interface StoreExecutionState<T> {
55
+ executing: boolean;
56
+ changes: DeepPartial<T> | null;
57
+ pendingChanges: Array<StateUpdater<T>>;
58
+ middlewares: string[];
59
+ runningMiddleware: {
60
+ id: string;
61
+ name: string;
62
+ startTime: number;
63
+ } | null;
64
+ transactionActive: boolean;
65
+ }
26
66
  /**
27
- * Result of a middleware execution
67
+ * Event types emitted by the state for observability
28
68
  */
29
- interface MiddlewareResult<T> {
30
- update: DeepPartial<T>;
31
- blocked: boolean;
32
- error?: Error;
33
- }
69
+ type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
34
70
  /**
35
71
  * Type for middleware functions. Return false to block the update.
36
72
  */
37
- type Middleware<T> = (state: T, update: DeepPartial<T>) => DeepPartial<T> | boolean | void;
73
+ type Middleware<T> = (state: T, update: DeepPartial<T>) => Promise<DeepPartial<T>> | Promise<void> | DeepPartial<T> | void;
38
74
  /**
39
75
  * Type for blocking middleware functions that can prevent updates.
40
76
  */
41
- type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | void;
77
+ type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | Promise<boolean>;
42
78
  /**
43
79
  * Middleware execution information for tracking
44
80
  */
@@ -67,7 +103,7 @@ interface DataStore<T extends object> {
67
103
  get(): T;
68
104
  set(update: StateUpdater<T>): Promise<void>;
69
105
  subscribe(path: string, listener: (state: T) => void): () => void;
70
- transaction<R>(operation: () => R): R;
106
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
71
107
  getPerformanceMetrics(): StoreMetrics;
72
108
  onStoreEvent(event: StoreEvent, listener: (data: any) => void): () => void;
73
109
  getMiddlewareExecutions(): MiddlewareExecution[];
@@ -85,13 +121,21 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
85
121
  private isUpdating;
86
122
  private updateTimes;
87
123
  private cache;
88
- private bus;
124
+ private updateBus;
89
125
  private eventBus;
126
+ private executionState;
127
+ private persistence?;
90
128
  /**
91
129
  * Creates a new instance of ReactiveDataStore.
92
130
  * @param initialData The initial state data.
93
131
  */
94
- constructor(initialData: T);
132
+ constructor(initialData: T, persistence?: DataStorePersistence<T>);
133
+ private setPersistence;
134
+ /**
135
+ * Gets current execution state for monitoring
136
+ * @returns The current execution state of the store
137
+ */
138
+ getExecutionState(): Readonly<StoreExecutionState<T>>;
95
139
  /**
96
140
  * Gets a specific part of the state at the given path.
97
141
  * @returns The current state
@@ -131,7 +175,12 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
131
175
  * @param operation The operation to execute.
132
176
  * @returns The result of the operation.
133
177
  */
134
- transaction<R>(operation: () => R): R;
178
+ /**
179
+ * Executes an operation within a transaction that can be rolled back if an error occurs.
180
+ * @param operation The operation to execute.
181
+ * @returns The result of the operation.
182
+ */
183
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
135
184
  /**
136
185
  * Adds a transformation middleware to the state.
137
186
  * These middleware functions can modify updates but cannot block them.
@@ -300,7 +349,7 @@ declare class StoreObservability<T extends object> {
300
349
  * @returns A middleware function
301
350
  */
302
351
  createLoggingMiddleware(options?: {
303
- logLevel?: 'debug' | 'info' | 'warn';
352
+ logLevel?: "debug" | "info" | "warn";
304
353
  logUpdates?: boolean;
305
354
  }): (state: T, update: DeepPartial<T>) => DeepPartial<T>;
306
355
  /**
@@ -345,28 +394,46 @@ declare class StoreObservability<T extends object> {
345
394
  disconnect(): void;
346
395
  }
347
396
 
348
- type StoreOptions = ObservabilityOptions & {
397
+ type StoreOptions<T> = ObservabilityOptions & {
349
398
  enableMetrics?: boolean;
399
+ persistence?: DataStorePersistence<T>;
350
400
  };
351
401
  type Actions<T> = {
352
- [K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
402
+ [K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
353
403
  };
354
404
  interface StoreDefinition<T, R extends Actions<T>> {
355
405
  state: T;
356
406
  actions: R;
357
407
  sync?: (args: T) => void;
358
- blockingMiddleware?: Middleware<T>[];
359
- middleware?: Middleware<T>[];
408
+ blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
409
+ middleware?: Record<string, Middleware<T>>;
360
410
  }
361
411
 
362
412
  /**
363
413
  * Production-ready state creator without unnecessary memoization
364
414
  */
365
- declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions): () => {
415
+ declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions<T>): () => {
366
416
  store: ReactiveDataStore<T>;
367
417
  observer: StoreObservability<T> | undefined;
368
418
  select: <S>(selector: (state: T) => S) => S;
369
419
  actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
420
+ readonly stats: {
421
+ executing: boolean;
422
+ changes: DeepPartial<T> | null;
423
+ pendingChanges: (DeepPartial<T> | ((state: T) => DeepPartial<T> | Promise<DeepPartial<T>>))[];
424
+ middlewares: string[];
425
+ runningMiddleware: {
426
+ id: string;
427
+ name: string;
428
+ startTime: number;
429
+ } | null;
430
+ transactionActive: boolean;
431
+ executions: {
432
+ executing: boolean;
433
+ id: number;
434
+ action: keyof R;
435
+ }[];
436
+ };
370
437
  readonly state: T;
371
438
  };
372
439
 
@@ -601,4 +668,40 @@ declare function useRemoteObservability<T extends object>(store: DataStore<T>, o
601
668
  addHttpDestination: (config: Parameters<typeof createHttpDestination>[0]) => boolean;
602
669
  };
603
670
 
604
- export { type BlockingMiddleware, type DataStore, type DebugEvent, type DeepPartial, type MetricType, type Middleware, type MiddlewareExecution, type MiddlewareResult, type ObservabilityOptions, ReactiveDataStore, type RemoteDestination, type RemoteMetricsPayload, RemoteObservability, type RemoteObservabilityOptions, type StoreEvent, type StoreMetrics, StoreObservability, createGrafanaCloudDestination, createHttpDestination, createOpenTelemetryDestination, createPrometheusDestination, createStore, useRemoteObservability };
671
+ /**
672
+ * LocalStorage implementation of DataStorePersistence
673
+ */
674
+
675
+ declare class LocalStoragePersistence<T extends object> implements DataStorePersistence<T> {
676
+ private readonly storageKey;
677
+ private bus;
678
+ /**
679
+ * Creates a new LocalStoragePersistence instance
680
+ * @param storageKey The localStorage key to use for persistence
681
+ */
682
+ constructor(storageKey: string);
683
+ /**
684
+ * Persists the state to localStorage
685
+ * @param state The state to persist
686
+ * @returns true if successful, false otherwise
687
+ */
688
+ set(state: T): boolean;
689
+ /**
690
+ * Retrieves the state from localStorage
691
+ * @returns The persisted state or null if not found/invalid
692
+ */
693
+ get(): T | null;
694
+ /**
695
+ * Subscribes to changes in the persisted data
696
+ * @param callback Function to call when persisted data changes
697
+ * @returns Function to unsubscribe
698
+ */
699
+ subscribe(callback: () => void): () => void;
700
+ /**
701
+ * Clears the persisted data
702
+ * @returns true if successful
703
+ */
704
+ clear(): boolean;
705
+ }
706
+
707
+ export { type BlockingMiddleware, type DataStore, type DataStorePersistence, type DebugEvent, type DeepPartial, LocalStoragePersistence, type MetricType, type Middleware, type MiddlewareExecution, type ObservabilityOptions, ReactiveDataStore, type RemoteDestination, type RemoteMetricsPayload, RemoteObservability, type RemoteObservabilityOptions, type StoreEvent, type StoreExecutionState, type StoreMetrics, StoreObservability, createGrafanaCloudDestination, createHttpDestination, createOpenTelemetryDestination, createPrometheusDestination, createStore, useRemoteObservability };
package/index.js CHANGED
@@ -1 +1 @@
1
- import{useCallback as e,useSyncExternalStore as t}from"react";import{createEventBus as r}from"@asaidimu/events";function s(e,t){const r=Symbol.for("delete");if("object"!=typeof e||null===e)return t;if("object"!=typeof t||null===t)return e;const s=structuredClone(e),n=[{target:s,source:t}];for(;n.length>0;){const{target:e,source:t}=n.pop();Object.keys(t).forEach((s=>{const a=t[s],i=e[s];a===r?delete e[s]:Array.isArray(a)?e[s]=a:"object"==typeof a&&null!==a&&"object"==typeof i&&null!==i?(e.hasOwnProperty(s)||(e[s]=structuredClone(a)),n.push({target:e[s],source:a})):e[s]=a}))}return s}function n(e,t){const r=new Set;function s(e,t){const r=e.split(".");for(let e=0;e<r.length;e++){const s=r.slice(0,e+1).join(".");t.add(s)}}return function e(t,n,a){const i=Symbol.for("delete");if(Array.isArray(n)&&Array.isArray(a))JSON.stringify(n)!==JSON.stringify(a)&&s(t,r);else for(const o of Object.keys(a)){const c=t?`${t}.${o}`:o;if(o in n){const t=n[o],d=a[o];d===i?(s(c,r),e(c,{},a[o])):"object"==typeof t&&null!==t&&"object"==typeof d&&null!==d?e(c,t,d):t!==d&&s(c,r)}else s(c,r),e(c,{},a[o])}}("",e||{},t||{}),Array.from(r)}var a=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;bus=r();eventBus=r();constructor(e){this.middleware=[],this.blockingMiddleware=[],this.pendingUpdates=[],this.isUpdating=!1,this.updateTimes=[],this.metrics={updateCount:0,listenerExecutions:0,averageUpdateTime:0,largestUpdateSize:0,mostActiveListenerPaths:[]},this.cache=e}get(){return this.cache}async set(e){if(this.isUpdating)return void this.pendingUpdates.push(e);this.isUpdating=!0;const t=performance.now();this.eventBus.emit({name:"update:start",payload:{timestamp:t}});let r=e;if("function"==typeof e){const t=e(structuredClone(this.get()));r=t instanceof Promise?await t:t}try{const e=this.applyBlockingMiddleware(r);if(e.blocked)return void this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e.error,timestamp:Date.now()}})}catch(e){throw this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}const a=s(this.get(),r),i=this.applyMiddleware(a,r);try{const e=n(this.get(),i);this.metrics.updateCount++,this.metrics.largestUpdateSize=Math.max(this.metrics.largestUpdateSize,e.length),e.length>0&&(this.cache=i,this.notifyListeners(e));const r=performance.now();this.updateTimes.push(r-t),this.updateTimes.length>100&&this.updateTimes.shift(),this.metrics.averageUpdateTime=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length,this.eventBus.emit({name:"update:complete",payload:{changedPaths:e,duration:r-t,timestamp:Date.now()}})}finally{if(this.isUpdating=!1,this.pendingUpdates.length>0){const e=this.pendingUpdates.shift();e&&this.set(e)}}}applyBlockingMiddleware(e){for(const{fn:t,name:r,id:s}of this.blockingMiddleware){const n={id:s,name:r,startTime:performance.now()};this.eventBus.emit({name:"middleware:start",payload:{id:s,name:r,type:"blocking",timestamp:Date.now()}});try{const a=t(this.get(),e);if(n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,!1===a)return n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:n.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:n.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...n,blocked:!1})}catch(e){return n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:n.error,duration:n.duration,timestamp:Date.now()}}),{blocked:!0,error:n.error}}}return{blocked:!1}}applyMiddleware(e,t){return this.middleware.reduce(((e,{fn:r,name:n,id:a})=>{const i={id:a,name:n,startTime:performance.now()};this.eventBus.emit({name:"middleware:start",payload:{id:a,name:n,type:"transform",timestamp:Date.now()}});try{const o=r(e,t);i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.blocked=!1;let c=e;return!1===o?console.warn(`Middleware ${n} returned false but isn't registered as blocking`):o&&"object"==typeof o&&(c=s(e,o)),this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:complete",payload:{id:a,name:n,type:"transform",duration:i.duration,timestamp:Date.now()}}),c}catch(t){return i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.error=t instanceof Error?t:new Error(String(t)),i.blocked=!1,this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:error",payload:{id:a,name:n,error:i.error,duration:i.duration,timestamp:Date.now()}}),console.error(`Middleware ${n} error:`,t),e}}),e)}subscribe(e,t){const r=Array.isArray(e)?e:[e];return this.bus.subscribe("update",(s=>{(r.includes(s)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}transaction(e){const t=structuredClone(this.get());this.eventBus.emit({name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=e();return this.eventBus.emit({name:"transaction:complete",payload:{timestamp:Date.now()}}),t}catch(e){throw this.cache=t,this.eventBus.emit({name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),e}}use(e,t="unnamed-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:r}),r}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:r}),r}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.middleware.length+this.blockingMiddleware.length<t}getPerformanceMetrics(){return structuredClone(this.metrics)}getMiddlewareExecutions(){return structuredClone(this.middlewareExecutions)}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}notifyListeners(e){e.forEach((e=>this.bus.emit({name:"update",payload:e})))}trackMiddlewareExecution(e){this.middlewareExecutions.unshift(e),this.middlewareExecutions.length>this.maxExecutionHistory&&this.middlewareExecutions.pop()}},i=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;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"];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,(r=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),r.batchId&&(t.endsWith("start")?this.activeBatches.add(r.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(r.batchId)),this.recordEvent(t,r),this.enableConsoleLogging&&e&&this.logEventToConsole(t,r),this.checkPerformance(t,r)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===n(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const r={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(r),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const r=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${r}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${r}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];console.log(`%c✅ Update Complete [${r}] - ${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 [${r}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${r}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${r}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${r}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${r}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${r}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${r}]:`,"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.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:r=!0}=e;return(e,s)=>{if(r){(console[t]||console.log)("State Update:",s)}return s}}createValidationMiddleware(e){return(t,r)=>{const s=e(t,r);return"boolean"==typeof s?s:(!s.valid&&s.reason&&console.warn("Validation failed:",s.reason),s.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],r=Math.min(e,this.stateHistory.length-1);for(let e=0;e<r;e++){const r=this.stateHistory[e],s=this.stateHistory[e+1],a=n(s,r),i={},o={};for(const e of a){const t=e.split("."),n=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),a=(e,t,r)=>{const s=t.length-1,n=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[n]=r},c=n(s,t),d=n(r,t);a(i,t,c),a(o,t,d)}let c=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){c=e.timestamp;break}0!==a.length&&t.push({timestamp:c,changedPaths:a,from:i,to:o})}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 r=e+1,s=this.stateHistory[r];t.unshift(this.stateHistory[e]),e=r,await this.store.set(s)}},redo:async()=>{if(t.length>0){const r=t.shift();e=Math.max(0,e-1),await this.store.set(r)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},o=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);r||(r=new WeakMap,this.selectorCache.set(e,r));const s=r.get(t);if(s)return s.result;const n=e(t);return r.set(t,{result:n,deps:[]}),n}}};function c(r,{enableMetrics:s,...n}={}){const c=new a(r.state);let d;r.middleware&&r.middleware.forEach((e=>c.use(e))),r.blockingMiddleware&&r.blockingMiddleware.forEach((e=>c.use(e))),s&&(d=new i(c,n));const l=new o,m=Object.entries(r.actions).reduce(((e,[t,r])=>(e[t]=async(...e)=>{try{await c.set((async t=>await r(t,...e)))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e)),{});return function(){const r=e((e=>{const r=l.create(e);return t((e=>function(e,t){const r=[];return e(function e(t){return new Proxy({},{get:(s,n)=>{const a=`${t?`${t}/`:""}${n}`;return r.push(a),e(a)}})}()),c.subscribe(r,t)}(r,e)),(()=>r(c.get())),(()=>r(c.get())))}),[]);return{store:c,observer:d,select:r,actions:m,get state(){return t((e=>c.subscribe("",e)),(()=>c.get()),(()=>c.get()))}}}}function d(e){return{id:"opentelemetry",name:"OpenTelemetry Collector",config:e,testConnection:async()=>{try{return(await fetch(`${e.endpoint}/v1/metrics`,{method:"OPTIONS",headers:{...e.apiKey?{"api-key":e.apiKey}:{}}})).ok}catch(e){return!1}},send:async t=>{const r={resourceMetrics:[{resource:{attributes:{"service.name":t.source,...e.resource}},scopeMetrics:[{metrics:t.metrics.map((e=>"counter"===e.type?{name:e.name,unit:e.unit||"1",sum:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asInt:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:"histogram"===e.type?{name:e.name,unit:e.unit||"ms",histogram:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),count:1,sum:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:{name:e.name,unit:e.unit||"1",gauge:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asDouble:"number"==typeof e.value?e.value:1,attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}))}]}]};await fetch(`${e.endpoint}/v1/metrics`,{method:"POST",headers:{"Content-Type":"application/json",...e.apiKey?{"api-key":e.apiKey}:{}},body:JSON.stringify(r)})}}}function l(e){return{id:"prometheus",name:"Prometheus Pushgateway",config:e,testConnection:async()=>{try{const t=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;return(await fetch(e.pushgatewayUrl,{method:"HEAD",headers:{...t?{Authorization:t}:{}}})).ok}catch(e){return!1}},send:async t=>{let r="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const s=Object.entries({...e.labels,source:t.source,instance:t.metrics.find((e=>e.labels?.instanceId))?.labels?.instanceId||"unknown"}).map((([e,t])=>`${e}="${t.replace(/"/g,'\\"')}"`)).join(","),n=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${n} counter\n`:r+=`# TYPE ${n} gauge\n`,r+=`${n}{${s}} ${e.value}\n`}const s=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;await fetch(`${e.pushgatewayUrl}/metrics/job/${e.jobName}`,{method:"POST",headers:{"Content-Type":"text/plain",...s?{Authorization:s}:{}},body:r})}}}function m(e){return{id:"grafana-cloud",name:"Grafana Cloud",config:e,testConnection:async()=>{try{return(await fetch(`${e.url}/api/v1/query?query=up`,{method:"GET",headers:{Authorization:`Bearer ${e.apiKey}`}})).ok}catch(e){return!1}},send:async t=>{const r={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const r=1e6*t.timestamp;let s="";if("object"==typeof e.value)s=JSON.stringify(e.value);else if(s=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,r]of Object.entries(e.labels))s+=` ${t}=${r}`;return[String(r),s]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}}}function h(e){return{id:"http-endpoint",name:"HTTP Metrics Endpoint",config:e,testConnection:async()=>{try{return(await fetch(e.url,{method:"OPTIONS",headers:e.headers})).ok}catch(e){return!1}},send:async t=>{const r=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r)})}}}var u=class extends i{destinations=new Map;metricsBatch=[];reportingInterval;batchSize;serviceName;environment;instanceId;immediateReporting;collectCategories;compressPayloads;reportingTimer=null;traceIdCounter=0;activeTraces=new Map;constructor(e,t){super(e,t),this.serviceName=t.serviceName,this.environment=t.environment,this.instanceId=t.instanceId||this.generateInstanceId(),this.reportingInterval=t.reportingInterval||3e4,this.batchSize=t.batchSize||100,this.immediateReporting=t.immediateReporting||!1,this.collectCategories=t.collectCategories||{performance:!0,errors:!0,stateChanges:!0,middleware:!0},this.compressPayloads=t.compressPayloads||!1,this.immediateReporting||this.startReportingCycle(),this.setupRemoteEventListeners()}generateInstanceId(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}setupRemoteEventListeners(){this.store.onStoreEvent("update:complete",(e=>{this.collectCategories?.stateChanges&&(this.trackMetric({name:"store.update.duration",type:"histogram",value:e.duration||0,unit:"ms",labels:{pathCount:String(e.changedPaths?.length||0)}}),this.trackMetric({name:"store.update.paths_changed",type:"counter",value:e.changedPaths?.length||0,labels:{blocked:String(!!e.blocked)}}))})),this.store.onStoreEvent("middleware:error",(e=>{this.collectCategories?.errors&&this.trackMetric({name:"store.middleware.error",type:"log",value:{middleware:e.name,error:e.error?.message||"Unknown error",stack:e.error?.stack},labels:{middlewareName:e.name}})})),this.store.onStoreEvent("transaction:start",(e=>{if(!this.collectCategories?.performance)return;const t=this.beginTrace("transaction");e.traceId=t,this.beginSpan(t,"transaction.execution",{transactionId:e.id||String(Date.now())})})),this.store.onStoreEvent("transaction:complete",(e=>{this.collectCategories?.performance&&e.traceId&&(this.endSpan(e.traceId,"transaction.execution"),this.endTrace(e.traceId))}))}beginTrace(e){const t=`trace-${++this.traceIdCounter}-${Date.now()}`;return this.activeTraces.set(t,{startTime:performance.now(),name:e,spans:{}}),t}beginSpan(e,t,r={}){const s=this.activeTraces.get(e);if(!s)return"";const n=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[n]={startTime:performance.now(),name:t,labels:r},n}endSpan(e,t){const r=this.activeTraces.get(e);if(!r)return;const s=Object.entries(r.spans).filter((([e,r])=>r.name===t&&!r.endTime)).map((([e])=>e));if(s.length>0){const e=s[s.length-1];r.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const r=performance.now(),s=r-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:s,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,s])=>{s.endTime||(s.endTime=r);const n=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:n,unit:"ms",labels:{...s.labels,spanName:s.name},traceId:e,parentId:t})})),this.activeTraces.delete(e)}addDestination(e){return this.destinations.has(e.id)?(console.warn(`Destination with ID ${e.id} already exists`),!1):(this.destinations.set(e.id,e),!0)}removeDestination(e){return this.destinations.delete(e)}getDestinations(){return Array.from(this.destinations.values()).map((e=>({id:e.id,name:e.name})))}async testAllConnections(){const e={};for(const[t,r]of this.destinations.entries())if(r.testConnection)try{e[t]=await r.testConnection()}catch(r){e[t]=!1}else e[t]=!0;return e}trackMetric(e){e.labels={...e.labels,environment:this.environment,service:this.serviceName,instanceId:this.instanceId},this.metricsBatch.push(e),(this.immediateReporting||this.metricsBatch.length>=this.batchSize)&&this.flushMetrics()}async flushMetrics(){if(0===this.metricsBatch.length)return;const e={timestamp:Date.now(),source:this.serviceName,metrics:[...this.metricsBatch]};this.metricsBatch=[];const t=Array.from(this.destinations.values()).map((async t=>{try{let r=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),s=(new TextEncoder).encode(t),n=new CompressionStream("gzip"),a=new Blob([s]).stream().pipeThrough(n);await new Response(a).blob();r._compressed=!0,r._originalSize=t.length}await t.send(r)}catch(r){if(console.error(`Failed to send metrics to destination ${t.name}:`,r),e.metrics.some((e=>"log"===e.type&&e.name.includes("error")))){const t=e.metrics.filter((e=>"log"===e.type&&e.name.includes("error")));this.metricsBatch.push(...t)}}}));await Promise.all(t)}startReportingCycle(){this.reportingTimer&&clearInterval(this.reportingTimer),this.reportingTimer=setInterval((()=>{this.flushMetrics()}),this.reportingInterval)}setReportingInterval(e){this.reportingInterval=e,this.startReportingCycle()}reportCurrentMetrics(){const e=this.store.getPerformanceMetrics();this.trackMetric({name:"store.performance.update_count",type:"counter",value:e.updateCount,labels:{}}),this.trackMetric({name:"store.performance.listener_executions",type:"counter",value:e.listenerExecutions,labels:{}}),this.trackMetric({name:"store.performance.average_update_time",type:"gauge",value:e.averageUpdateTime,unit:"ms",labels:{}}),this.trackMetric({name:"store.performance.largest_update_size",type:"gauge",value:e.largestUpdateSize,unit:"paths",labels:{}})}disconnect(){this.reportingTimer&&(clearInterval(this.reportingTimer),this.reportingTimer=null),this.flushMetrics().catch((e=>console.error("Error flushing metrics during disconnect:",e))),super.disconnect()}};function p(e,t){const r=new u(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(d(e)),addPrometheusDestination:e=>r.addDestination(l(e)),addGrafanaCloudDestination:e=>r.addDestination(m(e)),addHttpDestination:e=>r.addDestination(h(e))}}export{a as ReactiveDataStore,u as RemoteObservability,i as StoreObservability,m as createGrafanaCloudDestination,h as createHttpDestination,d as createOpenTelemetryDestination,l as createPrometheusDestination,c as createStore,p as useRemoteObservability};
1
+ import{useCallback as e,useSyncExternalStore as t}from"react";import{createEventBus as r}from"@asaidimu/events";function s(e,t){const r=Symbol.for("delete");if("object"!=typeof e||null===e)return t;if("object"!=typeof t||null===t)return e;const s=structuredClone(e),n=[{target:s,source:t}];for(;n.length>0;){const{target:e,source:t}=n.pop();Object.keys(t).forEach((s=>{const i=t[s],a=e[s];i===r?delete e[s]:Array.isArray(i)?e[s]=i:"object"==typeof i&&null!==i&&"object"==typeof a&&null!==a?(e.hasOwnProperty(s)||(e[s]=structuredClone(i)),n.push({target:e[s],source:i})):e[s]=i}))}return s}function n(e,t){const r=new Set;function s(e,t){const r=e.split(".");for(let e=0;e<r.length;e++){const s=r.slice(0,e+1).join(".");t.add(s)}}return function e(t,n,i){const a=Symbol.for("delete");if(Array.isArray(n)&&Array.isArray(i))JSON.stringify(n)!==JSON.stringify(i)&&s(t,r);else for(const o of Object.keys(i)){const c=t?`${t}.${o}`:o;if(o in n){const t=n[o],d=i[o];d===a?(s(c,r),e(c,{},i[o])):"object"==typeof t&&null!==t&&"object"==typeof d&&null!==d?e(c,t,d):t!==d&&s(c,r)}else s(c,r),e(c,{},i[o])}}("",e||{},t||{}),Array.from(r)}var i=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;updateBus=r();eventBus=r();executionState;persistence;constructor(e,t){this.middleware=[],this.blockingMiddleware=[],this.pendingUpdates=[],this.isUpdating=!1,this.updateTimes=[],this.metrics={updateCount:0,listenerExecutions:0,averageUpdateTime:0,largestUpdateSize:0,mostActiveListenerPaths:[]},this.executionState={executing:!1,changes:null,pendingChanges:[],middlewares:[],runningMiddleware:null,transactionActive:!1},this.cache=e,t&&this.setPersistence(t)}async setPersistence(e){this.persistence=e;const t=await Promise.resolve(this.persistence.get());t&&(this.cache=t),this.persistence.subscribe((async()=>{const e=await Promise.resolve(this.persistence?.get());if(e){this.isUpdating=!0;try{const t=n(this.get(),e);t.length>0&&(this.cache=e,this.notifyListeners(t),this.eventBus.emit({name:"update:complete",payload:{changedPaths:t,source:"external",timestamp:Date.now()}}))}finally{this.isUpdating=!1}}}))}getExecutionState(){return this.executionState}get(){return this.cache}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.eventBus.emit({name:"update:start",payload:{timestamp:t}});let r=e;if("function"==typeof e){const t=e(structuredClone(this.get()));r=t instanceof Promise?await t:t}try{const e=await this.applyBlockingMiddleware(r);if(e.blocked)return void this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e.error,timestamp:Date.now()}})}catch(e){throw this.eventBus.emit({name:"update:complete",payload:{blocked:!0,error:e,timestamp:Date.now()}}),e}const i=s(this.get(),r),a=await this.applyMiddleware(i,r);try{const e=n(this.get(),a);if(this.metrics.updateCount++,this.metrics.largestUpdateSize=Math.max(this.metrics.largestUpdateSize,e.length),e.length>0){this.cache=a;try{await(this.persistence?.set(this.get()))||this.eventBus.emit({name:"update:complete",payload:{persistence:!1,timestamp:Date.now()}})}catch(e){this.eventBus.emit({name:"update:complete",payload:{persistence:!1,error:e,timestamp:Date.now()}})}this.notifyListeners(e)}const r=performance.now();this.updateTimes.push(r-t),this.updateTimes.length>100&&this.updateTimes.shift(),this.metrics.averageUpdateTime=this.updateTimes.reduce(((e,t)=>e+t),0)/this.updateTimes.length,this.eventBus.emit({name:"update:complete",payload:{changedPaths:e,duration:r-t,timestamp:Date.now()}})}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)}}}async applyBlockingMiddleware(e){for(const{fn:t,name:r,id:s}of this.blockingMiddleware){const n={id:s,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:s,name:r,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:s,name:r,type:"blocking",timestamp:Date.now()}});try{const i=await Promise.resolve(t(this.get(),e));if(n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,!1===i)return n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:n.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:n.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...n,blocked:!1})}catch(e){return n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,n.error=e instanceof Error?e:new Error(String(e)),n.blocked=!0,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:n.error,duration:n.duration,timestamp:Date.now()}}),{blocked:!0,error:n.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async applyMiddleware(e,t){let r=e;for(const{fn:e,name:n,id:i}of this.middleware){const a={id:i,name:n,startTime:performance.now()};this.executionState.runningMiddleware={id:i,name:n,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:i,name:n,type:"transform",timestamp:Date.now()}});try{const o=await Promise.resolve(e(r,t));a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,a.blocked=!1,o&&"object"==typeof o&&(r=s(r,o)),this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:complete",payload:{id:i,name:n,type:"transform",duration:a.duration,timestamp:Date.now()}})}catch(e){a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,a.error=e instanceof Error?e:new Error(String(e)),a.blocked=!1,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:error",payload:{id:i,name:n,error:a.error,duration:a.duration,timestamp:Date.now()}}),console.error(`Middleware ${n} error:`,e)}finally{this.executionState.runningMiddleware=null}}return r}subscribe(e,t){const r=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(s=>{(r.includes(s)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}async transaction(e){const t=structuredClone(this.get());this.executionState.transactionActive=!0,this.eventBus.emit({name:"transaction:start",payload:{timestamp:Date.now()}});try{const t=await Promise.resolve(e());return this.eventBus.emit({name:"transaction:complete",payload:{timestamp:Date.now()}}),this.executionState.transactionActive=!1,t}catch(e){throw this.cache=t,this.eventBus.emit({name:"transaction:error",payload:{error:e instanceof Error?e:new Error(String(e)),timestamp:Date.now()}}),this.executionState.transactionActive=!1,e}}use(e,t="unnamed-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const r=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:r}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],r}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.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],this.middleware.length+this.blockingMiddleware.length<t}getPerformanceMetrics(){return structuredClone(this.metrics)}getMiddlewareExecutions(){return structuredClone(this.middlewareExecutions)}onStoreEvent(e,t){return this.eventBus.subscribe(e,t)}notifyListeners(e){e.forEach((e=>this.updateBus.emit({name:"update",payload:e})))}trackMiddlewareExecution(e){this.middlewareExecutions.unshift(e),this.middlewareExecutions.length>this.maxExecutionHistory&&this.middlewareExecutions.pop()}},a=class{store;eventHistory=[];maxEvents;enableConsoleLogging;logEvents;performanceThresholds;unsubscribers=[];stateHistory=[];maxStateHistory=20;activeTransactionCount=0;activeBatches=new Set;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"];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,(r=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),r.batchId&&(t.endsWith("start")?this.activeBatches.add(r.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(r.batchId)),this.recordEvent(t,r),this.enableConsoleLogging&&e&&this.logEventToConsole(t,r),this.checkPerformance(t,r)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===n(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const r={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(r),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const r=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${r}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${r}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%c✅ Update Complete [${r}] - ${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 [${r}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${r}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${r}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${r}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${r}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${r}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${r}]:`,"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.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:r=!0}=e;return(e,s)=>{if(r){(console[t]||console.log)("State Update:",s)}return s}}createValidationMiddleware(e){return(t,r)=>{const s=e(t,r);return"boolean"==typeof s?s:(!s.valid&&s.reason&&console.warn("Validation failed:",s.reason),s.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],r=Math.min(e,this.stateHistory.length-1);for(let e=0;e<r;e++){const r=this.stateHistory[e],s=this.stateHistory[e+1],i=n(s,r),a={},o={};for(const e of i){const t=e.split("."),n=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),i=(e,t,r)=>{const s=t.length-1,n=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[n]=r},c=n(s,t),d=n(r,t);i(a,t,c),i(o,t,d)}let c=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){c=e.timestamp;break}0!==i.length&&t.push({timestamp:c,changedPaths:i,from:a,to:o})}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 r=e+1,s=this.stateHistory[r];t.unshift(this.stateHistory[e]),e=r,await this.store.set(s)}},redo:async()=>{if(t.length>0){const r=t.shift();e=Math.max(0,e-1),await this.store.set(r)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},o=class{selectorCache=new WeakMap;create(e){return t=>{let r=this.selectorCache.get(e);r||(r=new WeakMap,this.selectorCache.set(e,r));const s=r.get(t);if(s)return s.result;const n=e(t);return r.set(t,{result:n,deps:[]}),n}}};function c(s,{enableMetrics:n,...c}={}){const d=new i(s.state,c.persistence);let l;s.middleware&&Object.entries(s.middleware).forEach((([e,t])=>d.use(t,e))),s.blockingMiddleware&&Object.entries(s.blockingMiddleware).forEach((([e,t])=>d.useBlockingMiddleware(t,e))),n&&(l=new a(d,c));const m=r(),h=[],u={info:{executions:h,...d.getExecutionState()}};m.subscribe("stats",(()=>{u.info={executions:h,...d.getExecutionState()}}));const p=new o,g=Object.entries(s.actions).reduce(((e,[t,r])=>{const s=Date.now();return h.push({id:s,action:t,executing:!1}),e[t]=async(...e)=>{try{await d.set((async t=>{const n=h.findIndex((e=>e.id===s));-1!==n&&(h[n].executing=!0,m.emit({name:"stats",payload:null}));const i=await r(t,...e);return-1!==n&&(h[n].executing=!1,m.emit({name:"stats",payload:null})),i}))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e}),{});return function(){const r=e((e=>{const r=p.create(e);return t((e=>function(e,t){const r=[];return e(function e(t){return new Proxy({},{get:(s,n)=>{const i=`${t?`${t}/`:""}${n}`;return r.push(i),e(i)}})}()),d.subscribe(r,t)}(r,e)),(()=>r(d.get())),(()=>r(d.get())))}),[]);return{store:d,observer:l,select:r,actions:g,get stats(){return t((e=>m.subscribe("stats",e)),(()=>u.info),(()=>u.info))},get state(){return t((e=>d.subscribe("",e)),(()=>d.get()),(()=>d.get()))}}}}function d(e){return{id:"opentelemetry",name:"OpenTelemetry Collector",config:e,testConnection:async()=>{try{return(await fetch(`${e.endpoint}/v1/metrics`,{method:"OPTIONS",headers:{...e.apiKey?{"api-key":e.apiKey}:{}}})).ok}catch(e){return!1}},send:async t=>{const r={resourceMetrics:[{resource:{attributes:{"service.name":t.source,...e.resource}},scopeMetrics:[{metrics:t.metrics.map((e=>"counter"===e.type?{name:e.name,unit:e.unit||"1",sum:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asInt:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:"histogram"===e.type?{name:e.name,unit:e.unit||"ms",histogram:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),count:1,sum:Number(e.value),attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}:{name:e.name,unit:e.unit||"1",gauge:{dataPoints:[{timeUnixNano:BigInt(1e6*t.timestamp),asDouble:"number"==typeof e.value?e.value:1,attributes:Object.entries(e.labels||{}).map((([e,t])=>({key:e,value:{stringValue:t}})))}]}}))}]}]};await fetch(`${e.endpoint}/v1/metrics`,{method:"POST",headers:{"Content-Type":"application/json",...e.apiKey?{"api-key":e.apiKey}:{}},body:JSON.stringify(r)})}}}function l(e){return{id:"prometheus",name:"Prometheus Pushgateway",config:e,testConnection:async()=>{try{const t=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;return(await fetch(e.pushgatewayUrl,{method:"HEAD",headers:{...t?{Authorization:t}:{}}})).ok}catch(e){return!1}},send:async t=>{let r="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const s=Object.entries({...e.labels,source:t.source,instance:t.metrics.find((e=>e.labels?.instanceId))?.labels?.instanceId||"unknown"}).map((([e,t])=>`${e}="${t.replace(/"/g,'\\"')}"`)).join(","),n=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${n} counter\n`:r+=`# TYPE ${n} gauge\n`,r+=`${n}{${s}} ${e.value}\n`}const s=e.username&&e.password?`Basic ${btoa(`${e.username}:${e.password}`)}`:void 0;await fetch(`${e.pushgatewayUrl}/metrics/job/${e.jobName}`,{method:"POST",headers:{"Content-Type":"text/plain",...s?{Authorization:s}:{}},body:r})}}}function m(e){return{id:"grafana-cloud",name:"Grafana Cloud",config:e,testConnection:async()=>{try{return(await fetch(`${e.url}/api/v1/query?query=up`,{method:"GET",headers:{Authorization:`Bearer ${e.apiKey}`}})).ok}catch(e){return!1}},send:async t=>{const r={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const r=1e6*t.timestamp;let s="";if("object"==typeof e.value)s=JSON.stringify(e.value);else if(s=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,r]of Object.entries(e.labels))s+=` ${t}=${r}`;return[String(r),s]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(r)})}}}function h(e){return{id:"http-endpoint",name:"HTTP Metrics Endpoint",config:e,testConnection:async()=>{try{return(await fetch(e.url,{method:"OPTIONS",headers:e.headers})).ok}catch(e){return!1}},send:async t=>{const r=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(r)})}}}var u=class extends a{destinations=new Map;metricsBatch=[];reportingInterval;batchSize;serviceName;environment;instanceId;immediateReporting;collectCategories;compressPayloads;reportingTimer=null;traceIdCounter=0;activeTraces=new Map;constructor(e,t){super(e,t),this.serviceName=t.serviceName,this.environment=t.environment,this.instanceId=t.instanceId||this.generateInstanceId(),this.reportingInterval=t.reportingInterval||3e4,this.batchSize=t.batchSize||100,this.immediateReporting=t.immediateReporting||!1,this.collectCategories=t.collectCategories||{performance:!0,errors:!0,stateChanges:!0,middleware:!0},this.compressPayloads=t.compressPayloads||!1,this.immediateReporting||this.startReportingCycle(),this.setupRemoteEventListeners()}generateInstanceId(){return`${Date.now()}-${Math.random().toString(36).substring(2,9)}`}setupRemoteEventListeners(){this.store.onStoreEvent("update:complete",(e=>{this.collectCategories?.stateChanges&&(this.trackMetric({name:"store.update.duration",type:"histogram",value:e.duration||0,unit:"ms",labels:{pathCount:String(e.changedPaths?.length||0)}}),this.trackMetric({name:"store.update.paths_changed",type:"counter",value:e.changedPaths?.length||0,labels:{blocked:String(!!e.blocked)}}))})),this.store.onStoreEvent("middleware:error",(e=>{this.collectCategories?.errors&&this.trackMetric({name:"store.middleware.error",type:"log",value:{middleware:e.name,error:e.error?.message||"Unknown error",stack:e.error?.stack},labels:{middlewareName:e.name}})})),this.store.onStoreEvent("transaction:start",(e=>{if(!this.collectCategories?.performance)return;const t=this.beginTrace("transaction");e.traceId=t,this.beginSpan(t,"transaction.execution",{transactionId:e.id||String(Date.now())})})),this.store.onStoreEvent("transaction:complete",(e=>{this.collectCategories?.performance&&e.traceId&&(this.endSpan(e.traceId,"transaction.execution"),this.endTrace(e.traceId))}))}beginTrace(e){const t=`trace-${++this.traceIdCounter}-${Date.now()}`;return this.activeTraces.set(t,{startTime:performance.now(),name:e,spans:{}}),t}beginSpan(e,t,r={}){const s=this.activeTraces.get(e);if(!s)return"";const n=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[n]={startTime:performance.now(),name:t,labels:r},n}endSpan(e,t){const r=this.activeTraces.get(e);if(!r)return;const s=Object.entries(r.spans).filter((([e,r])=>r.name===t&&!r.endTime)).map((([e])=>e));if(s.length>0){const e=s[s.length-1];r.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const r=performance.now(),s=r-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:s,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,s])=>{s.endTime||(s.endTime=r);const n=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:n,unit:"ms",labels:{...s.labels,spanName:s.name},traceId:e,parentId:t})})),this.activeTraces.delete(e)}addDestination(e){return this.destinations.has(e.id)?(console.warn(`Destination with ID ${e.id} already exists`),!1):(this.destinations.set(e.id,e),!0)}removeDestination(e){return this.destinations.delete(e)}getDestinations(){return Array.from(this.destinations.values()).map((e=>({id:e.id,name:e.name})))}async testAllConnections(){const e={};for(const[t,r]of this.destinations.entries())if(r.testConnection)try{e[t]=await r.testConnection()}catch(r){e[t]=!1}else e[t]=!0;return e}trackMetric(e){e.labels={...e.labels,environment:this.environment,service:this.serviceName,instanceId:this.instanceId},this.metricsBatch.push(e),(this.immediateReporting||this.metricsBatch.length>=this.batchSize)&&this.flushMetrics()}async flushMetrics(){if(0===this.metricsBatch.length)return;const e={timestamp:Date.now(),source:this.serviceName,metrics:[...this.metricsBatch]};this.metricsBatch=[];const t=Array.from(this.destinations.values()).map((async t=>{try{let r=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),s=(new TextEncoder).encode(t),n=new CompressionStream("gzip"),i=new Blob([s]).stream().pipeThrough(n);await new Response(i).blob();r._compressed=!0,r._originalSize=t.length}await t.send(r)}catch(r){if(console.error(`Failed to send metrics to destination ${t.name}:`,r),e.metrics.some((e=>"log"===e.type&&e.name.includes("error")))){const t=e.metrics.filter((e=>"log"===e.type&&e.name.includes("error")));this.metricsBatch.push(...t)}}}));await Promise.all(t)}startReportingCycle(){this.reportingTimer&&clearInterval(this.reportingTimer),this.reportingTimer=setInterval((()=>{this.flushMetrics()}),this.reportingInterval)}setReportingInterval(e){this.reportingInterval=e,this.startReportingCycle()}reportCurrentMetrics(){const e=this.store.getPerformanceMetrics();this.trackMetric({name:"store.performance.update_count",type:"counter",value:e.updateCount,labels:{}}),this.trackMetric({name:"store.performance.listener_executions",type:"counter",value:e.listenerExecutions,labels:{}}),this.trackMetric({name:"store.performance.average_update_time",type:"gauge",value:e.averageUpdateTime,unit:"ms",labels:{}}),this.trackMetric({name:"store.performance.largest_update_size",type:"gauge",value:e.largestUpdateSize,unit:"paths",labels:{}})}disconnect(){this.reportingTimer&&(clearInterval(this.reportingTimer),this.reportingTimer=null),this.flushMetrics().catch((e=>console.error("Error flushing metrics during disconnect:",e))),super.disconnect()}};function p(e,t){const r=new u(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(d(e)),addPrometheusDestination:e=>r.addDestination(l(e)),addGrafanaCloudDestination:e=>r.addDestination(m(e)),addHttpDestination:e=>r.addDestination(h(e))}}var g=class{storageKey;bus=r();constructor(e){this.storageKey=e,window.addEventListener("storage",(e=>{e.key===this.storageKey&&this.bus.emit({name:"storage",payload:null})}))}set(e){try{return localStorage.setItem(this.storageKey,JSON.stringify(e)),!0}catch(e){return console.error("Failed to persist state to localStorage:",e),!1}}get(){try{const e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):null}catch(e){return console.error("Failed to retrieve state from localStorage:",e),null}}subscribe(e){return this.bus.subscribe("storage",e)}clear(){try{return localStorage.removeItem(this.storageKey),!0}catch(e){return console.error("Failed to clear persisted state:",e),!1}}};export{g as LocalStoragePersistence,i as ReactiveDataStore,u as RemoteObservability,a as StoreObservability,m as createGrafanaCloudDestination,h as createHttpDestination,d as createOpenTelemetryDestination,l as createPrometheusDestination,c as createStore,p as useRemoteObservability};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/react-store",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Efficient react state manager.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",