@asaidimu/react-store 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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"),r=require("uuid"),s=require("@asaidimu/indexed"),a=require("@asaidimu/query");function n(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),a=[{target:s,source:t}];for(;a.length>0;){const{target:e,source:t}=a.pop();Object.keys(t).forEach((s=>{const n=t[s],i=e[s];n===r?delete e[s]:Array.isArray(n)?e[s]=n:"object"==typeof n&&null!==n&&"object"==typeof i&&null!==i?(e.hasOwnProperty(s)||(e[s]=structuredClone(n)),a.push({target:e[s],source:n})):e[s]=n}))}return s}function i(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,a,n){const i=Symbol.for("delete");if(Array.isArray(a)&&Array.isArray(n))JSON.stringify(a)!==JSON.stringify(n)&&s(t,r);else for(const o of Object.keys(n)){const c=t?`${t}.${o}`:o;if(o in a){const t=a[o],d=n[o];d===i?(s(c,r),e(c,{},n[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,{},n[o])}}("",e||{},t||{}),Array.from(r)}var o=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;updateBus=t.createEventBus();eventBus=t.createEventBus();executionState;persistence;instanceID=r.v4();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(this.instanceID,(async e=>{this.isUpdating=!0;try{const t=i(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 s=n(this.get(),r),a=await this.applyMiddleware(s,r);try{const e=i(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.instanceID,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 a={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 n=await Promise.resolve(t(this.get(),e));if(a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,!1===n)return a.blocked=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:blocked",payload:{id:s,name:r,duration:a.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:s,name:r,type:"blocking",duration:a.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...a,blocked:!1})}catch(e){return 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=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:error",payload:{id:s,name:r,error:a.error,duration:a.duration,timestamp:Date.now()}}),{blocked:!0,error:a.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async applyMiddleware(e,t){let r=e;for(const{fn:e,name:s,id:a}of this.middleware){const i={id:a,name:s,startTime:performance.now()};this.executionState.runningMiddleware={id:a,name:s,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:a,name:s,type:"transform",timestamp:Date.now()}});try{const o=await Promise.resolve(e(r,t));i.endTime=performance.now(),i.duration=void 0!==i.startTime?i.endTime-i.startTime:0,i.blocked=!1,o&&"object"==typeof o&&(r=n(r,o)),this.trackMiddlewareExecution(i),this.eventBus.emit({name:"middleware:complete",payload:{id:a,name:s,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:s,error:i.error,duration:i.duration,timestamp:Date.now()}}),console.error(`Middleware ${s} 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++)}))}id(){return this.instanceID}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()}},c=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===i(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],a=i(s,r),n={},o={};for(const e of a){const t=e.split("."),a=(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,a=t[s];t.slice(0,s).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[a]=r},c=a(s,t),d=a(r,t);i(n,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!==a.length&&t.push({timestamp:c,changedPaths:a,from:n,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()}},d=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 a=e(t);return r.set(t,{result:a,deps:[]}),a}}};function l(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 u(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(","),a=e.name.replace(/\./g,"_");"counter"===e.type?r+=`# TYPE ${a} counter\n`:r+=`# TYPE ${a} gauge\n`,r+=`${a}{${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 h(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 m(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 p=class extends c{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 a=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return s.spans[a]={startTime:performance.now(),name:t,labels:r},a}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 a=s.endTime-s.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:a,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),a=new CompressionStream("gzip"),n=new Blob([s]).stream().pipeThrough(a);await new Response(n).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()}};var g=class e{static dbInstance=null;static eventBusMap=new Map;static dbName="ReactiveDataStore";static modelName="stores";static getDatabase(){return e.dbInstance||(e.dbInstance=s.DatabaseConnection({name:e.dbName,enableTelemetry:!1}).then((async t=>{const r={name:e.modelName,version:"1.0.0",fields:{storeId:{type:"string",required:!0},data:{type:"object",required:!0}}};try{await t.createModel(r)}catch(e){if(e instanceof s.DatabaseError&&"SCHEMA_ALREADY_EXISTS"!==e.name)throw e}return t}))),e.dbInstance}static getEventBus(r){const s=`store_${r}`;return e.eventBusMap.has(s)||e.eventBusMap.set(s,t.createEventBus({batchSize:5,async:!0,batchDelay:16,errorHandler:e=>console.error(`Event bus error for ${r}:`,e),crossTab:!0,channelName:s})),e.eventBusMap.get(s)}static async getCursor(){return(await e.getDatabase()).cursor(e.modelName)}static async close(){const t=await e.dbInstance;t&&t.close(),e.dbInstance=null,e.eventBusMap.forEach((e=>e.clear())),e.eventBusMap.clear()}};exports.IndexedDBPersistence=class{cursorPromise;storeId;eventBus;constructor(e){this.storeId=e,this.cursorPromise=g.getCursor(),this.eventBus=g.getEventBus(e)}async set(e,t){try{const r=await this.cursorPromise,s=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),n=await r.find(s.filters),i={storeId:this.storeId,data:t};let o;return n?o=await n.update(i):(await r.create(i),o=!0),o&&this.eventBus.emit({name:"store:updated",payload:{storeId:this.storeId,instanceId:e,state:t}}),o}catch(t){return console.error(`Failed to set state for store ${this.storeId} by instance ${e}:`,t),!1}}async get(){try{const e=await this.cursorPromise,t=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),r=await e.find(t.filters);return r?r.read().then((()=>r.data)):null}catch(e){return console.error(`Failed to get state for store ${this.storeId}:`,e),null}}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storeId:r,instanceId:s,state:a})=>{r===this.storeId&&s!==e&&t(a)}))}async clear(){try{const e=await this.cursorPromise,t=(new a.QueryBuilder).where({field:"storeId",operator:"eq",value:this.storeId}).build(),r=await e.find(t.filters);return!r||await r.delete()}catch(e){return console.error(`Failed to clear state for store ${this.storeId}:`,e),!1}}static async closeAll(){await g.close()}},exports.LocalStoragePersistence=class{storageKey;bus;constructor(e){this.storageKey=e,this.bus=t.createEventBus({async:!0,batchSize:5,batchDelay:16,errorHandler:t=>console.error(`Event bus error for ${e}:`,t),crossTab:!0,channelName:`storage_${e}`}),window.addEventListener("storage",(e=>{if(e.key===this.storageKey&&e.newValue)try{const t=JSON.parse(e.newValue);this.bus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:"external",state:t}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const r=JSON.stringify(t);return localStorage.setItem(this.storageKey,r),this.bus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:e,state:t}}),!0}catch(e){return console.error(`Failed to persist state to localStorage for ${this.storageKey}:`,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 for ${this.storageKey}:`,e),null}}subscribe(e,t){return this.bus.subscribe("storage:updated",(({storageKey:r,instanceId:s,state:a})=>{r===this.storageKey&&s!==e&&t(a)}))}clear(){try{return localStorage.removeItem(this.storageKey),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.storageKey}:`,e),!1}}},exports.ReactiveDataStore=o,exports.RemoteObservability=p,exports.StoreObservability=c,exports.createGrafanaCloudDestination=h,exports.createHttpDestination=m,exports.createOpenTelemetryDestination=l,exports.createPrometheusDestination=u,exports.createStore=function(r,{enableMetrics:s,...a}={}){const n=new o(r.state,a.persistence);let i;r.middleware&&Object.entries(r.middleware).forEach((([e,t])=>n.use(t,e))),r.blockingMiddleware&&Object.entries(r.blockingMiddleware).forEach((([e,t])=>n.useBlockingMiddleware(t,e))),s&&(i=new c(n,a));const l=t.createEventBus(),u=[],h={info:{executions:u,...n.getExecutionState()}};l.subscribe("stats",(()=>{h.info={executions:u,...n.getExecutionState()}}));const m=new d,p=Object.entries(r.actions).reduce(((e,[t,r])=>{const s=Date.now();return u.push({id:s,action:t,executing:!1}),e[t]=async(...e)=>{try{await n.set((async t=>{const a=u.findIndex((e=>e.id===s));-1!==a&&(u[a].executing=!0,l.emit({name:"stats",payload:null}));const n=await r(t,...e);return-1!==a&&(u[a].executing=!1,l.emit({name:"stats",payload:null})),n}))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e}),{});return function(){const t=e.useCallback((t=>{const r=m.create(t);return e.useSyncExternalStore((e=>function(e,t){const r=[];return e(function e(t){return new Proxy({},{get:(s,a)=>{const n=`${t?`${t}/`:""}${a}`;return r.push(n),e(n)}})}()),n.subscribe(r,t)}(r,e)),(()=>r(n.get())),(()=>r(n.get())))}),[]);return{store:n,observer:i,select:t,actions:p,get stats(){return e.useSyncExternalStore((e=>l.subscribe("stats",e)),(()=>h.info),(()=>h.info))},get state(){return e.useSyncExternalStore((e=>n.subscribe("",e)),(()=>n.get()),(()=>n.get()))}}}},exports.useRemoteObservability=function(e,t){const r=new p(e,t);return{remote:r,addOpenTelemetryDestination:e=>r.addDestination(l(e)),addPrometheusDestination:e=>r.addDestination(u(e)),addGrafanaCloudDestination:e=>r.addDestination(h(e)),addHttpDestination:e=>r.addDestination(m(e))}};
package/index.d.cts CHANGED
@@ -1,8 +1,39 @@
1
+ /**
2
+ * Interface for persistence adapters for the ReactiveDataStore
3
+ */
4
+ interface DataStorePersistence<T> {
5
+ /**
6
+ * Persists data to storage
7
+ * @param id The id of the ReactiveDataStore instance
8
+ * @param state The state to persist
9
+ * @returns boolean indicating success or Promise resolving to success status
10
+ */
11
+ set(id: string, state: T): boolean | Promise<boolean>;
12
+ /**
13
+ * Retrieves persisted data from storage
14
+ * @returns The retrieved state or null if not found
15
+ */
16
+ get(): T | null | Promise<T | null>;
17
+ /**
18
+ * Subscribes to changes in persisted data (e.g., from other tabs)
19
+ * @param id The id of the ReactiveDataStore instance
20
+ * @param callback Function to call when persisted data changes
21
+ * @returns Function to unsubscribe
22
+ */
23
+ subscribe(id: string, callback: (state: T) => void): () => void;
24
+ /**
25
+ * Clears the persisted data
26
+ * @returns true if successful
27
+ */
28
+ clear(): boolean | Promise<boolean>;
29
+ }
30
+
1
31
  /**
2
32
  * Reactive Data Store Implementation
3
33
  * A type-safe reactive data state for managing complex application state.
4
34
  * Arrays are treated as primitive values (stored and updated as whole units).
5
35
  */
36
+
6
37
  /**
7
38
  * Utility type for representing partial updates to the state.
8
39
  */
@@ -20,25 +51,32 @@ interface StoreMetrics {
20
51
  mostActiveListenerPaths: string[];
21
52
  }
22
53
  /**
23
- * Event types emitted by the state for observability
54
+ * Extended store state for monitoring execution status
24
55
  */
25
- type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
56
+ interface StoreExecutionState<T> {
57
+ executing: boolean;
58
+ changes: DeepPartial<T> | null;
59
+ pendingChanges: Array<StateUpdater<T>>;
60
+ middlewares: string[];
61
+ runningMiddleware: {
62
+ id: string;
63
+ name: string;
64
+ startTime: number;
65
+ } | null;
66
+ transactionActive: boolean;
67
+ }
26
68
  /**
27
- * Result of a middleware execution
69
+ * Event types emitted by the state for observability
28
70
  */
29
- interface MiddlewareResult<T> {
30
- update: DeepPartial<T>;
31
- blocked: boolean;
32
- error?: Error;
33
- }
71
+ type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
34
72
  /**
35
73
  * Type for middleware functions. Return false to block the update.
36
74
  */
37
- type Middleware<T> = (state: T, update: DeepPartial<T>) => DeepPartial<T> | boolean | void;
75
+ type Middleware<T> = (state: T, update: DeepPartial<T>) => Promise<DeepPartial<T>> | Promise<void> | DeepPartial<T> | void;
38
76
  /**
39
77
  * Type for blocking middleware functions that can prevent updates.
40
78
  */
41
- type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | void;
79
+ type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | Promise<boolean>;
42
80
  /**
43
81
  * Middleware execution information for tracking
44
82
  */
@@ -67,7 +105,7 @@ interface DataStore<T extends object> {
67
105
  get(): T;
68
106
  set(update: StateUpdater<T>): Promise<void>;
69
107
  subscribe(path: string, listener: (state: T) => void): () => void;
70
- transaction<R>(operation: () => R): R;
108
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
71
109
  getPerformanceMetrics(): StoreMetrics;
72
110
  onStoreEvent(event: StoreEvent, listener: (data: any) => void): () => void;
73
111
  getMiddlewareExecutions(): MiddlewareExecution[];
@@ -85,13 +123,22 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
85
123
  private isUpdating;
86
124
  private updateTimes;
87
125
  private cache;
88
- private bus;
126
+ private updateBus;
89
127
  private eventBus;
128
+ private executionState;
129
+ private persistence?;
130
+ private instanceID;
90
131
  /**
91
132
  * Creates a new instance of ReactiveDataStore.
92
133
  * @param initialData The initial state data.
93
134
  */
94
- constructor(initialData: T);
135
+ constructor(initialData: T, persistence?: DataStorePersistence<T>);
136
+ private setPersistence;
137
+ /**
138
+ * Gets current execution state for monitoring
139
+ * @returns The current execution state of the store
140
+ */
141
+ getExecutionState(): Readonly<StoreExecutionState<T>>;
95
142
  /**
96
143
  * Gets a specific part of the state at the given path.
97
144
  * @returns The current state
@@ -126,12 +173,24 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
126
173
  * @returns Function to unsubscribe the listener.
127
174
  */
128
175
  subscribe(path: string | Array<string>, callback: (state: T) => void): () => void;
176
+ /**
177
+ * returns instance id
178
+ * @param path The path to subscribe to.
179
+ * @param callback The callback function to call when the path changes.
180
+ * @returns Function to unsubscribe the listener.
181
+ */
182
+ id(): string;
183
+ /**
184
+ * Executes an operation within a transaction that can be rolled back if an error occurs.
185
+ * @param operation The operation to execute.
186
+ * @returns The result of the operation.
187
+ */
129
188
  /**
130
189
  * Executes an operation within a transaction that can be rolled back if an error occurs.
131
190
  * @param operation The operation to execute.
132
191
  * @returns The result of the operation.
133
192
  */
134
- transaction<R>(operation: () => R): R;
193
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
135
194
  /**
136
195
  * Adds a transformation middleware to the state.
137
196
  * These middleware functions can modify updates but cannot block them.
@@ -300,7 +359,7 @@ declare class StoreObservability<T extends object> {
300
359
  * @returns A middleware function
301
360
  */
302
361
  createLoggingMiddleware(options?: {
303
- logLevel?: 'debug' | 'info' | 'warn';
362
+ logLevel?: "debug" | "info" | "warn";
304
363
  logUpdates?: boolean;
305
364
  }): (state: T, update: DeepPartial<T>) => DeepPartial<T>;
306
365
  /**
@@ -345,28 +404,46 @@ declare class StoreObservability<T extends object> {
345
404
  disconnect(): void;
346
405
  }
347
406
 
348
- type StoreOptions = ObservabilityOptions & {
407
+ type StoreOptions<T> = ObservabilityOptions & {
349
408
  enableMetrics?: boolean;
409
+ persistence?: DataStorePersistence<T>;
350
410
  };
351
411
  type Actions<T> = {
352
- [K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
412
+ [K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
353
413
  };
354
414
  interface StoreDefinition<T, R extends Actions<T>> {
355
415
  state: T;
356
416
  actions: R;
357
417
  sync?: (args: T) => void;
358
- blockingMiddleware?: Middleware<T>[];
359
- middleware?: Middleware<T>[];
418
+ blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
419
+ middleware?: Record<string, Middleware<T>>;
360
420
  }
361
421
 
362
422
  /**
363
423
  * Production-ready state creator without unnecessary memoization
364
424
  */
365
- declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions): () => {
425
+ declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions<T>): () => {
366
426
  store: ReactiveDataStore<T>;
367
427
  observer: StoreObservability<T> | undefined;
368
428
  select: <S>(selector: (state: T) => S) => S;
369
429
  actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
430
+ readonly stats: {
431
+ executing: boolean;
432
+ changes: DeepPartial<T> | null;
433
+ pendingChanges: (DeepPartial<T> | ((state: T) => DeepPartial<T> | Promise<DeepPartial<T>>))[];
434
+ middlewares: string[];
435
+ runningMiddleware: {
436
+ id: string;
437
+ name: string;
438
+ startTime: number;
439
+ } | null;
440
+ transactionActive: boolean;
441
+ executions: {
442
+ executing: boolean;
443
+ id: number;
444
+ action: keyof R;
445
+ }[];
446
+ };
370
447
  readonly state: T;
371
448
  };
372
449
 
@@ -601,4 +678,56 @@ declare function useRemoteObservability<T extends object>(store: DataStore<T>, o
601
678
  addHttpDestination: (config: Parameters<typeof createHttpDestination>[0]) => boolean;
602
679
  };
603
680
 
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 };
681
+ /**
682
+ * LocalStorage implementation of DataStorePersistence
683
+ */
684
+ declare class LocalStoragePersistence<T extends object> implements DataStorePersistence<T> {
685
+ private readonly storageKey;
686
+ private bus;
687
+ /**
688
+ * Creates a new LocalStoragePersistence instance
689
+ * @param storageKey The localStorage key to use for persistence (e.g., 'user-profile')
690
+ */
691
+ constructor(storageKey: string);
692
+ /**
693
+ * Persists the state to localStorage
694
+ * @param id The instance ID of the ReactiveDataStore (e.g., UUID)
695
+ * @param state The state to persist
696
+ * @returns true if successful, false otherwise
697
+ */
698
+ set(id: string, state: T): boolean;
699
+ /**
700
+ * Retrieves the state from localStorage
701
+ * @returns The persisted state or null if not found/invalid
702
+ */
703
+ get(): T | null;
704
+ /**
705
+ * Subscribes to changes in the persisted data
706
+ * @param id The instance ID of the ReactiveDataStore (e.g., UUID)
707
+ * @param callback Function to call with the new state when data changes
708
+ * @returns Function to unsubscribe
709
+ */
710
+ subscribe(id: string, callback: (state: T) => void): () => void;
711
+ /**
712
+ * Clears the persisted data
713
+ * @returns true if successful, false otherwise
714
+ */
715
+ clear(): boolean;
716
+ }
717
+
718
+ /**
719
+ * IndexedDB persistence adapter with storeId and ephemeral instance IDs
720
+ */
721
+ declare class IndexedDBPersistence<T> implements DataStorePersistence<T> {
722
+ private cursorPromise;
723
+ private storeId;
724
+ private eventBus;
725
+ constructor(storeId: string);
726
+ set(id: string, state: T): Promise<boolean>;
727
+ get(): Promise<T | null>;
728
+ subscribe(id: string, callback: (state: T) => void): () => void;
729
+ clear(): Promise<boolean>;
730
+ static closeAll(): Promise<void>;
731
+ }
732
+
733
+ export { type BlockingMiddleware, type DataStore, type DataStorePersistence, type DebugEvent, type DeepPartial, IndexedDBPersistence, 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,39 @@
1
+ /**
2
+ * Interface for persistence adapters for the ReactiveDataStore
3
+ */
4
+ interface DataStorePersistence<T> {
5
+ /**
6
+ * Persists data to storage
7
+ * @param id The id of the ReactiveDataStore instance
8
+ * @param state The state to persist
9
+ * @returns boolean indicating success or Promise resolving to success status
10
+ */
11
+ set(id: string, state: T): boolean | Promise<boolean>;
12
+ /**
13
+ * Retrieves persisted data from storage
14
+ * @returns The retrieved state or null if not found
15
+ */
16
+ get(): T | null | Promise<T | null>;
17
+ /**
18
+ * Subscribes to changes in persisted data (e.g., from other tabs)
19
+ * @param id The id of the ReactiveDataStore instance
20
+ * @param callback Function to call when persisted data changes
21
+ * @returns Function to unsubscribe
22
+ */
23
+ subscribe(id: string, callback: (state: T) => void): () => void;
24
+ /**
25
+ * Clears the persisted data
26
+ * @returns true if successful
27
+ */
28
+ clear(): boolean | Promise<boolean>;
29
+ }
30
+
1
31
  /**
2
32
  * Reactive Data Store Implementation
3
33
  * A type-safe reactive data state for managing complex application state.
4
34
  * Arrays are treated as primitive values (stored and updated as whole units).
5
35
  */
36
+
6
37
  /**
7
38
  * Utility type for representing partial updates to the state.
8
39
  */
@@ -20,25 +51,32 @@ interface StoreMetrics {
20
51
  mostActiveListenerPaths: string[];
21
52
  }
22
53
  /**
23
- * Event types emitted by the state for observability
54
+ * Extended store state for monitoring execution status
24
55
  */
25
- type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
56
+ interface StoreExecutionState<T> {
57
+ executing: boolean;
58
+ changes: DeepPartial<T> | null;
59
+ pendingChanges: Array<StateUpdater<T>>;
60
+ middlewares: string[];
61
+ runningMiddleware: {
62
+ id: string;
63
+ name: string;
64
+ startTime: number;
65
+ } | null;
66
+ transactionActive: boolean;
67
+ }
26
68
  /**
27
- * Result of a middleware execution
69
+ * Event types emitted by the state for observability
28
70
  */
29
- interface MiddlewareResult<T> {
30
- update: DeepPartial<T>;
31
- blocked: boolean;
32
- error?: Error;
33
- }
71
+ type StoreEvent = "update:start" | "update:complete" | "middleware:start" | "middleware:complete" | "middleware:error" | "middleware:blocked" | "transaction:start" | "transaction:complete" | "transaction:error";
34
72
  /**
35
73
  * Type for middleware functions. Return false to block the update.
36
74
  */
37
- type Middleware<T> = (state: T, update: DeepPartial<T>) => DeepPartial<T> | boolean | void;
75
+ type Middleware<T> = (state: T, update: DeepPartial<T>) => Promise<DeepPartial<T>> | Promise<void> | DeepPartial<T> | void;
38
76
  /**
39
77
  * Type for blocking middleware functions that can prevent updates.
40
78
  */
41
- type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | void;
79
+ type BlockingMiddleware<T> = (state: T, update: DeepPartial<T>) => boolean | Promise<boolean>;
42
80
  /**
43
81
  * Middleware execution information for tracking
44
82
  */
@@ -67,7 +105,7 @@ interface DataStore<T extends object> {
67
105
  get(): T;
68
106
  set(update: StateUpdater<T>): Promise<void>;
69
107
  subscribe(path: string, listener: (state: T) => void): () => void;
70
- transaction<R>(operation: () => R): R;
108
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
71
109
  getPerformanceMetrics(): StoreMetrics;
72
110
  onStoreEvent(event: StoreEvent, listener: (data: any) => void): () => void;
73
111
  getMiddlewareExecutions(): MiddlewareExecution[];
@@ -85,13 +123,22 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
85
123
  private isUpdating;
86
124
  private updateTimes;
87
125
  private cache;
88
- private bus;
126
+ private updateBus;
89
127
  private eventBus;
128
+ private executionState;
129
+ private persistence?;
130
+ private instanceID;
90
131
  /**
91
132
  * Creates a new instance of ReactiveDataStore.
92
133
  * @param initialData The initial state data.
93
134
  */
94
- constructor(initialData: T);
135
+ constructor(initialData: T, persistence?: DataStorePersistence<T>);
136
+ private setPersistence;
137
+ /**
138
+ * Gets current execution state for monitoring
139
+ * @returns The current execution state of the store
140
+ */
141
+ getExecutionState(): Readonly<StoreExecutionState<T>>;
95
142
  /**
96
143
  * Gets a specific part of the state at the given path.
97
144
  * @returns The current state
@@ -126,12 +173,24 @@ declare class ReactiveDataStore<T extends object> implements DataStore<T> {
126
173
  * @returns Function to unsubscribe the listener.
127
174
  */
128
175
  subscribe(path: string | Array<string>, callback: (state: T) => void): () => void;
176
+ /**
177
+ * returns instance id
178
+ * @param path The path to subscribe to.
179
+ * @param callback The callback function to call when the path changes.
180
+ * @returns Function to unsubscribe the listener.
181
+ */
182
+ id(): string;
183
+ /**
184
+ * Executes an operation within a transaction that can be rolled back if an error occurs.
185
+ * @param operation The operation to execute.
186
+ * @returns The result of the operation.
187
+ */
129
188
  /**
130
189
  * Executes an operation within a transaction that can be rolled back if an error occurs.
131
190
  * @param operation The operation to execute.
132
191
  * @returns The result of the operation.
133
192
  */
134
- transaction<R>(operation: () => R): R;
193
+ transaction<R>(operation: () => R | Promise<R>): Promise<R>;
135
194
  /**
136
195
  * Adds a transformation middleware to the state.
137
196
  * These middleware functions can modify updates but cannot block them.
@@ -300,7 +359,7 @@ declare class StoreObservability<T extends object> {
300
359
  * @returns A middleware function
301
360
  */
302
361
  createLoggingMiddleware(options?: {
303
- logLevel?: 'debug' | 'info' | 'warn';
362
+ logLevel?: "debug" | "info" | "warn";
304
363
  logUpdates?: boolean;
305
364
  }): (state: T, update: DeepPartial<T>) => DeepPartial<T>;
306
365
  /**
@@ -345,28 +404,46 @@ declare class StoreObservability<T extends object> {
345
404
  disconnect(): void;
346
405
  }
347
406
 
348
- type StoreOptions = ObservabilityOptions & {
407
+ type StoreOptions<T> = ObservabilityOptions & {
349
408
  enableMetrics?: boolean;
409
+ persistence?: DataStorePersistence<T>;
350
410
  };
351
411
  type Actions<T> = {
352
- [K: string]: (state: T, ...args: any[]) => Partial<T> | Promise<Partial<T>>;
412
+ [K: string]: (state: T, ...args: any[]) => DeepPartial<T> | Promise<DeepPartial<T>>;
353
413
  };
354
414
  interface StoreDefinition<T, R extends Actions<T>> {
355
415
  state: T;
356
416
  actions: R;
357
417
  sync?: (args: T) => void;
358
- blockingMiddleware?: Middleware<T>[];
359
- middleware?: Middleware<T>[];
418
+ blockingMiddleware?: Record<string, BlockingMiddleware<T>>;
419
+ middleware?: Record<string, Middleware<T>>;
360
420
  }
361
421
 
362
422
  /**
363
423
  * Production-ready state creator without unnecessary memoization
364
424
  */
365
- declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions): () => {
425
+ declare function createStore<T extends Record<string, unknown>, R extends Actions<T>>(definition: StoreDefinition<T, R>, { enableMetrics, ...options }?: StoreOptions<T>): () => {
366
426
  store: ReactiveDataStore<T>;
367
427
  observer: StoreObservability<T> | undefined;
368
428
  select: <S>(selector: (state: T) => S) => S;
369
429
  actions: { [K in keyof R]: (...args: Parameters<R[K]> extends [T, ...infer P] ? P : never) => void; };
430
+ readonly stats: {
431
+ executing: boolean;
432
+ changes: DeepPartial<T> | null;
433
+ pendingChanges: (DeepPartial<T> | ((state: T) => DeepPartial<T> | Promise<DeepPartial<T>>))[];
434
+ middlewares: string[];
435
+ runningMiddleware: {
436
+ id: string;
437
+ name: string;
438
+ startTime: number;
439
+ } | null;
440
+ transactionActive: boolean;
441
+ executions: {
442
+ executing: boolean;
443
+ id: number;
444
+ action: keyof R;
445
+ }[];
446
+ };
370
447
  readonly state: T;
371
448
  };
372
449
 
@@ -601,4 +678,56 @@ declare function useRemoteObservability<T extends object>(store: DataStore<T>, o
601
678
  addHttpDestination: (config: Parameters<typeof createHttpDestination>[0]) => boolean;
602
679
  };
603
680
 
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 };
681
+ /**
682
+ * LocalStorage implementation of DataStorePersistence
683
+ */
684
+ declare class LocalStoragePersistence<T extends object> implements DataStorePersistence<T> {
685
+ private readonly storageKey;
686
+ private bus;
687
+ /**
688
+ * Creates a new LocalStoragePersistence instance
689
+ * @param storageKey The localStorage key to use for persistence (e.g., 'user-profile')
690
+ */
691
+ constructor(storageKey: string);
692
+ /**
693
+ * Persists the state to localStorage
694
+ * @param id The instance ID of the ReactiveDataStore (e.g., UUID)
695
+ * @param state The state to persist
696
+ * @returns true if successful, false otherwise
697
+ */
698
+ set(id: string, state: T): boolean;
699
+ /**
700
+ * Retrieves the state from localStorage
701
+ * @returns The persisted state or null if not found/invalid
702
+ */
703
+ get(): T | null;
704
+ /**
705
+ * Subscribes to changes in the persisted data
706
+ * @param id The instance ID of the ReactiveDataStore (e.g., UUID)
707
+ * @param callback Function to call with the new state when data changes
708
+ * @returns Function to unsubscribe
709
+ */
710
+ subscribe(id: string, callback: (state: T) => void): () => void;
711
+ /**
712
+ * Clears the persisted data
713
+ * @returns true if successful, false otherwise
714
+ */
715
+ clear(): boolean;
716
+ }
717
+
718
+ /**
719
+ * IndexedDB persistence adapter with storeId and ephemeral instance IDs
720
+ */
721
+ declare class IndexedDBPersistence<T> implements DataStorePersistence<T> {
722
+ private cursorPromise;
723
+ private storeId;
724
+ private eventBus;
725
+ constructor(storeId: string);
726
+ set(id: string, state: T): Promise<boolean>;
727
+ get(): Promise<T | null>;
728
+ subscribe(id: string, callback: (state: T) => void): () => void;
729
+ clear(): Promise<boolean>;
730
+ static closeAll(): Promise<void>;
731
+ }
732
+
733
+ export { type BlockingMiddleware, type DataStore, type DataStorePersistence, type DebugEvent, type DeepPartial, IndexedDBPersistence, 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 s}from"@asaidimu/events";import{v4 as r}from"uuid";import{DatabaseConnection as a,DatabaseError as n}from"@asaidimu/indexed";import{QueryBuilder as i}from"@asaidimu/query";function o(e,t){const s=Symbol.for("delete");if("object"!=typeof e||null===e)return t;if("object"!=typeof t||null===t)return e;const r=structuredClone(e),a=[{target:r,source:t}];for(;a.length>0;){const{target:e,source:t}=a.pop();Object.keys(t).forEach((r=>{const n=t[r],i=e[r];n===s?delete e[r]:Array.isArray(n)?e[r]=n:"object"==typeof n&&null!==n&&"object"==typeof i&&null!==i?(e.hasOwnProperty(r)||(e[r]=structuredClone(n)),a.push({target:e[r],source:n})):e[r]=n}))}return r}function c(e,t){const s=new Set;function r(e,t){const s=e.split(".");for(let e=0;e<s.length;e++){const r=s.slice(0,e+1).join(".");t.add(r)}}return function e(t,a,n){const i=Symbol.for("delete");if(Array.isArray(a)&&Array.isArray(n))JSON.stringify(a)!==JSON.stringify(n)&&r(t,s);else for(const o of Object.keys(n)){const c=t?`${t}.${o}`:o;if(o in a){const t=a[o],d=n[o];d===i?(r(c,s),e(c,{},n[o])):"object"==typeof t&&null!==t&&"object"==typeof d&&null!==d?e(c,t,d):t!==d&&r(c,s)}else r(c,s),e(c,{},n[o])}}("",e||{},t||{}),Array.from(s)}var d=class{metrics;middleware;blockingMiddleware;middlewareExecutions=[];maxExecutionHistory=100;pendingUpdates;isUpdating;updateTimes;cache=null;updateBus=s();eventBus=s();executionState;persistence;instanceID=r();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(this.instanceID,(async e=>{this.isUpdating=!0;try{const t=c(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 s=e;if("function"==typeof e){const t=e(structuredClone(this.get()));s=t instanceof Promise?await t:t}try{const e=await this.applyBlockingMiddleware(s);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 r=o(this.get(),s),a=await this.applyMiddleware(r,s);try{const e=c(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.instanceID,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 s=performance.now();this.updateTimes.push(s-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:s-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:s,id:r}of this.blockingMiddleware){const a={id:r,name:s,startTime:performance.now()};this.executionState.runningMiddleware={id:r,name:s,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:r,name:s,type:"blocking",timestamp:Date.now()}});try{const n=await Promise.resolve(t(this.get(),e));if(a.endTime=performance.now(),a.duration=void 0!==a.startTime?a.endTime-a.startTime:0,!1===n)return a.blocked=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:blocked",payload:{id:r,name:s,duration:a.duration,timestamp:Date.now()}}),{blocked:!0};this.eventBus.emit({name:"middleware:complete",payload:{id:r,name:s,type:"blocking",duration:a.duration,timestamp:Date.now()}}),this.trackMiddlewareExecution({...a,blocked:!1})}catch(e){return 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=!0,this.trackMiddlewareExecution(a),this.eventBus.emit({name:"middleware:error",payload:{id:r,name:s,error:a.error,duration:a.duration,timestamp:Date.now()}}),{blocked:!0,error:a.error}}finally{this.executionState.runningMiddleware=null}}return{blocked:!1}}async applyMiddleware(e,t){let s=e;for(const{fn:e,name:r,id:a}of this.middleware){const n={id:a,name:r,startTime:performance.now()};this.executionState.runningMiddleware={id:a,name:r,startTime:performance.now()},this.eventBus.emit({name:"middleware:start",payload:{id:a,name:r,type:"transform",timestamp:Date.now()}});try{const i=await Promise.resolve(e(s,t));n.endTime=performance.now(),n.duration=void 0!==n.startTime?n.endTime-n.startTime:0,n.blocked=!1,i&&"object"==typeof i&&(s=o(s,i)),this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:complete",payload:{id:a,name:r,type:"transform",duration:n.duration,timestamp:Date.now()}})}catch(e){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=!1,this.trackMiddlewareExecution(n),this.eventBus.emit({name:"middleware:error",payload:{id:a,name:r,error:n.error,duration:n.duration,timestamp:Date.now()}}),console.error(`Middleware ${r} error:`,e)}finally{this.executionState.runningMiddleware=null}}return s}subscribe(e,t){const s=Array.isArray(e)?e:[e];return this.updateBus.subscribe("update",(r=>{(s.includes(r)||""===e)&&(t(this.get()),this.metrics.listenerExecutions++)}))}id(){return this.instanceID}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 s=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.middleware.push({fn:e,name:t,id:s}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],s}useBlockingMiddleware(e,t="unnamed-blocking-middleware"){const s=crypto.randomUUID?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).substring(2,15)}`;return this.blockingMiddleware.push({fn:e,name:t,id:s}),this.executionState.middlewares=[...this.middleware.map((e=>e.name)),...this.blockingMiddleware.map((e=>e.name))],s}removeMiddleware(e){const t=this.middleware.length+this.blockingMiddleware.length;return this.middleware=this.middleware.filter((t=>t.id!==e)),this.blockingMiddleware=this.blockingMiddleware.filter((t=>t.id!==e)),this.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()}},l=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,(s=>{"transaction:start"===t?this.activeTransactionCount++:"transaction:complete"!==t&&"transaction:error"!==t||(this.activeTransactionCount=Math.max(0,this.activeTransactionCount-1)),s.batchId&&(t.endsWith("start")?this.activeBatches.add(s.batchId):(t.endsWith("complete")||t.endsWith("error"))&&this.activeBatches.delete(s.batchId)),this.recordEvent(t,s),this.enableConsoleLogging&&e&&this.logEventToConsole(t,s),this.checkPerformance(t,s)})))}}recordStateSnapshot(){const e=structuredClone(this.store.get());this.stateHistory.length>0&&0===c(e,this.stateHistory[0]).length||(this.stateHistory.unshift(e),this.stateHistory.length>this.maxStateHistory&&this.stateHistory.pop())}recordEvent(e,t){const s={type:e,timestamp:Date.now(),data:structuredClone(t)};this.eventHistory.unshift(s),this.eventHistory.length>this.maxEvents&&this.eventHistory.pop()}logEventToConsole(e,t){const s=new Date(t.timestamp||Date.now()).toISOString().split("T")[1].replace("Z","");if("update:start"===e)console.group(`%c⚡ Store Update Started [${s}]`,"color: #4a6da7");else if("update:complete"===e){if(t.blocked)console.warn(`%c✋ Update Blocked [${s}]`,"color: #bf8c0a",t.error);else{const e=t.changedPaths||[];e.length>0&&console.log(`%c✅ Update Complete [${s}] - ${e.length} paths changed in ${t.duration?.toFixed(2)}ms`,"color: #2a9d8f",e)}console.groupEnd()}else"middleware:start"===e?console.debug(`%c◀ Middleware "${t.name}" started [${s}] (${t.type})`,"color: #8c8c8c"):"middleware:complete"===e?console.debug(`%c▶ Middleware "${t.name}" completed [${s}] in ${t.duration?.toFixed(2)}ms`,"color: #7c9c7c"):"middleware:error"===e?console.error(`%c❌ Middleware "${t.name}" error [${s}]:`,"color: #e63946",t.error):"middleware:blocked"===e?console.warn(`%c🛑 Middleware "${t.name}" blocked update [${s}]`,"color: #e76f51"):"transaction:start"===e?console.group(`%c📦 Transaction Started [${s}]`,"color: #6d597a"):"transaction:complete"===e?(console.log(`%c📦 Transaction Complete [${s}]`,"color: #355070"),console.groupEnd()):"transaction:error"===e&&(console.error(`%c📦 Transaction Error [${s}]:`,"color: #e56b6f",t.error),console.groupEnd())}checkPerformance(e,t){this.enableConsoleLogging&&("update:complete"===e&&!t.blocked&&t.duration>this.performanceThresholds.updateTime&&console.warn(`%c⚠️ Slow update detected [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{changedPaths:t.changedPaths,threshold:this.performanceThresholds.updateTime}),"middleware:complete"===e&&t.duration>this.performanceThresholds.middlewareTime&&console.warn(`%c⚠️ Slow middleware "${t.name}" [${t.duration.toFixed(2)}ms]`,"color: #ff9f1c",{threshold:this.performanceThresholds.middlewareTime}))}getEventHistory(){return structuredClone(this.eventHistory)}getStateHistory(){return structuredClone(this.stateHistory)}getMiddlewareExecutions(){return this.store.getMiddlewareExecutions()}getPerformanceMetrics(){return this.store.getPerformanceMetrics()}getTransactionStatus(){return{activeTransactions:this.activeTransactionCount,activeBatches:Array.from(this.activeBatches)}}createLoggingMiddleware(e={}){const{logLevel:t="debug",logUpdates:s=!0}=e;return(e,r)=>{if(s){(console[t]||console.log)("State Update:",r)}return r}}createValidationMiddleware(e){return(t,s)=>{const r=e(t,s);return"boolean"==typeof r?r:(!r.valid&&r.reason&&console.warn("Validation failed:",r.reason),r.valid)}}clearHistory(){if(this.eventHistory=[],this.stateHistory.length>0){const e=this.stateHistory[0];this.stateHistory=[e]}}getRecentChanges(e=5){const t=[],s=Math.min(e,this.stateHistory.length-1);for(let e=0;e<s;e++){const s=this.stateHistory[e],r=this.stateHistory[e+1],a=c(r,s),n={},i={};for(const e of a){const t=e.split("."),a=(e,t)=>t.reduce(((e,t)=>e&&void 0!==e[t]?e[t]:void 0),e),o=(e,t,s)=>{const r=t.length-1,a=t[r];t.slice(0,r).reduce(((e,t)=>(e[t]=e[t]??{},e[t])),e)[a]=s},c=a(r,t),d=a(s,t);o(n,t,c),o(i,t,d)}let o=Date.now();for(const e of this.eventHistory)if("update:complete"===e.type&&e.data.changedPaths?.length>0){o=e.timestamp;break}0!==a.length&&t.push({timestamp:o,changedPaths:a,from:n,to:i})}return t}createTimeTravel(){let e=0,t=[];return{canUndo:()=>e<this.stateHistory.length-1,canRedo:()=>t.length>0,undo:async()=>{if(e<this.stateHistory.length-1){const s=e+1,r=this.stateHistory[s];t.unshift(this.stateHistory[e]),e=s,await this.store.set(r)}},redo:async()=>{if(t.length>0){const s=t.shift();e=Math.max(0,e-1),await this.store.set(s)}},getHistoryLength:()=>this.stateHistory.length,clear:()=>{t=[],e=0}}}disconnect(){this.unsubscribers.forEach((e=>e())),this.unsubscribers=[],this.clearHistory()}},h=class{selectorCache=new WeakMap;create(e){return t=>{let s=this.selectorCache.get(e);s||(s=new WeakMap,this.selectorCache.set(e,s));const r=s.get(t);if(r)return r.result;const a=e(t);return s.set(t,{result:a,deps:[]}),a}}};function u(r,{enableMetrics:a,...n}={}){const i=new d(r.state,n.persistence);let o;r.middleware&&Object.entries(r.middleware).forEach((([e,t])=>i.use(t,e))),r.blockingMiddleware&&Object.entries(r.blockingMiddleware).forEach((([e,t])=>i.useBlockingMiddleware(t,e))),a&&(o=new l(i,n));const c=s(),u=[],m={info:{executions:u,...i.getExecutionState()}};c.subscribe("stats",(()=>{m.info={executions:u,...i.getExecutionState()}}));const p=new h,g=Object.entries(r.actions).reduce(((e,[t,s])=>{const r=Date.now();return u.push({id:r,action:t,executing:!1}),e[t]=async(...e)=>{try{await i.set((async t=>{const a=u.findIndex((e=>e.id===r));-1!==a&&(u[a].executing=!0,c.emit({name:"stats",payload:null}));const n=await s(t,...e);return-1!==a&&(u[a].executing=!1,c.emit({name:"stats",payload:null})),n}))}catch(e){throw console.error(`Error in action ${t}:`,e),e}},e}),{});return function(){const s=e((e=>{const s=p.create(e);return t((e=>function(e,t){const s=[];return e(function e(t){return new Proxy({},{get:(r,a)=>{const n=`${t?`${t}/`:""}${a}`;return s.push(n),e(n)}})}()),i.subscribe(s,t)}(s,e)),(()=>s(i.get())),(()=>s(i.get())))}),[]);return{store:i,observer:o,select:s,actions:g,get stats(){return t((e=>c.subscribe("stats",e)),(()=>m.info),(()=>m.info))},get state(){return t((e=>i.subscribe("",e)),(()=>i.get()),(()=>i.get()))}}}}function m(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 s={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(s)})}}}function p(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 s="";for(const e of t.metrics){if("log"===e.type||"trace"===e.type)continue;const r=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(","),a=e.name.replace(/\./g,"_");"counter"===e.type?s+=`# TYPE ${a} counter\n`:s+=`# TYPE ${a} gauge\n`,s+=`${a}{${r}} ${e.value}\n`}const r=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",...r?{Authorization:r}:{}},body:s})}}}function g(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 s={streams:[{stream:{service:t.source,job:"store-metrics"},values:t.metrics.map((e=>{const s=1e6*t.timestamp;let r="";if("object"==typeof e.value)r=JSON.stringify(e.value);else if(r=`level=info metric=${e.name} value=${e.value} type=${e.type}`,e.labels)for(const[t,s]of Object.entries(e.labels))r+=` ${t}=${s}`;return[String(s),r]}))}]};await fetch(`${e.url}/loki/api/v1/push`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e.apiKey}`},body:JSON.stringify(s)})}}}function y(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 s=e.transformPayload?e.transformPayload(t):t;await fetch(e.url,{method:e.method||"POST",headers:{"Content-Type":"application/json",...e.headers},body:JSON.stringify(s)})}}}var f=class extends l{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,s={}){const r=this.activeTraces.get(e);if(!r)return"";const a=`span-${Date.now()}-${Math.random().toString(36).substring(2,9)}`;return r.spans[a]={startTime:performance.now(),name:t,labels:s},a}endSpan(e,t){const s=this.activeTraces.get(e);if(!s)return;const r=Object.entries(s.spans).filter((([e,s])=>s.name===t&&!s.endTime)).map((([e])=>e));if(r.length>0){const e=r[r.length-1];s.spans[e].endTime=performance.now()}}endTrace(e){const t=this.activeTraces.get(e);if(!t)return;const s=performance.now(),r=s-t.startTime;this.trackMetric({name:"store.trace.duration",type:"histogram",value:r,unit:"ms",labels:{traceName:t.name,traceId:e}}),Object.entries(t.spans).forEach((([t,r])=>{r.endTime||(r.endTime=s);const a=r.endTime-r.startTime;this.trackMetric({name:"store.trace.span.duration",type:"trace",value:a,unit:"ms",labels:{...r.labels,spanName:r.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,s]of this.destinations.entries())if(s.testConnection)try{e[t]=await s.testConnection()}catch(s){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 s=e;if(this.compressPayloads&&"undefined"!=typeof window&&window.CompressionStream){const t=JSON.stringify(e),r=(new TextEncoder).encode(t),a=new CompressionStream("gzip"),n=new Blob([r]).stream().pipeThrough(a);await new Response(n).blob();s._compressed=!0,s._originalSize=t.length}await t.send(s)}catch(s){if(console.error(`Failed to send metrics to destination ${t.name}:`,s),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 w(e,t){const s=new f(e,t);return{remote:s,addOpenTelemetryDestination:e=>s.addDestination(m(e)),addPrometheusDestination:e=>s.addDestination(p(e)),addGrafanaCloudDestination:e=>s.addDestination(g(e)),addHttpDestination:e=>s.addDestination(y(e))}}var b=class{storageKey;bus;constructor(e){this.storageKey=e,this.bus=s({async:!0,batchSize:5,batchDelay:16,errorHandler:t=>console.error(`Event bus error for ${e}:`,t),crossTab:!0,channelName:`storage_${e}`}),window.addEventListener("storage",(e=>{if(e.key===this.storageKey&&e.newValue)try{const t=JSON.parse(e.newValue);this.bus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:"external",state:t}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const s=JSON.stringify(t);return localStorage.setItem(this.storageKey,s),this.bus.emit({name:"storage:updated",payload:{storageKey:this.storageKey,instanceId:e,state:t}}),!0}catch(e){return console.error(`Failed to persist state to localStorage for ${this.storageKey}:`,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 for ${this.storageKey}:`,e),null}}subscribe(e,t){return this.bus.subscribe("storage:updated",(({storageKey:s,instanceId:r,state:a})=>{s===this.storageKey&&r!==e&&t(a)}))}clear(){try{return localStorage.removeItem(this.storageKey),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.storageKey}:`,e),!1}}},v=class e{static dbInstance=null;static eventBusMap=new Map;static dbName="ReactiveDataStore";static modelName="stores";static getDatabase(){return e.dbInstance||(e.dbInstance=a({name:e.dbName,enableTelemetry:!1}).then((async t=>{const s={name:e.modelName,version:"1.0.0",fields:{storeId:{type:"string",required:!0},data:{type:"object",required:!0}}};try{await t.createModel(s)}catch(e){if(e instanceof n&&"SCHEMA_ALREADY_EXISTS"!==e.name)throw e}return t}))),e.dbInstance}static getEventBus(t){const r=`store_${t}`;return e.eventBusMap.has(r)||e.eventBusMap.set(r,s({batchSize:5,async:!0,batchDelay:16,errorHandler:e=>console.error(`Event bus error for ${t}:`,e),crossTab:!0,channelName:r})),e.eventBusMap.get(r)}static async getCursor(){return(await e.getDatabase()).cursor(e.modelName)}static async close(){const t=await e.dbInstance;t&&t.close(),e.dbInstance=null,e.eventBusMap.forEach((e=>e.clear())),e.eventBusMap.clear()}},T=class{cursorPromise;storeId;eventBus;constructor(e){this.storeId=e,this.cursorPromise=v.getCursor(),this.eventBus=v.getEventBus(e)}async set(e,t){try{const s=await this.cursorPromise,r=(new i).where({field:"storeId",operator:"eq",value:this.storeId}).build(),a=await s.find(r.filters),n={storeId:this.storeId,data:t};let o;return a?o=await a.update(n):(await s.create(n),o=!0),o&&this.eventBus.emit({name:"store:updated",payload:{storeId:this.storeId,instanceId:e,state:t}}),o}catch(t){return console.error(`Failed to set state for store ${this.storeId} by instance ${e}:`,t),!1}}async get(){try{const e=await this.cursorPromise,t=(new i).where({field:"storeId",operator:"eq",value:this.storeId}).build(),s=await e.find(t.filters);return s?s.read().then((()=>s.data)):null}catch(e){return console.error(`Failed to get state for store ${this.storeId}:`,e),null}}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storeId:s,instanceId:r,state:a})=>{s===this.storeId&&r!==e&&t(a)}))}async clear(){try{const e=await this.cursorPromise,t=(new i).where({field:"storeId",operator:"eq",value:this.storeId}).build(),s=await e.find(t.filters);return!s||await s.delete()}catch(e){return console.error(`Failed to clear state for store ${this.storeId}:`,e),!1}}static async closeAll(){await v.close()}};export{T as IndexedDBPersistence,b as LocalStoragePersistence,d as ReactiveDataStore,f as RemoteObservability,l as StoreObservability,g as createGrafanaCloudDestination,y as createHttpDestination,m as createOpenTelemetryDestination,p as createPrometheusDestination,u as createStore,w 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.3.0",
4
4
  "description": "Efficient react state manager.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -26,8 +26,11 @@
26
26
  "access": "public"
27
27
  },
28
28
  "dependencies": {
29
- "@asaidimu/events": "^1.0.0",
29
+ "@asaidimu/events": "^1.1.1",
30
+ "@asaidimu/indexed": "^1.1.2",
30
31
  "@asaidimu/node": "^1.0.6",
31
- "react": "^19.0.0"
32
+ "@asaidimu/query": "^1.1.2",
33
+ "react": "^19.0.0",
34
+ "uuid": "^11.1.0"
32
35
  }
33
36
  }