@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 +6 -3
- package/index.cjs +1 -1
- package/index.d.cts +150 -21
- package/index.d.ts +150 -21
- package/index.js +1 -1
- package/package.json +6 -3
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
|
-
|
|
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
|
-
*
|
|
54
|
+
* Extended store state for monitoring execution status
|
|
24
55
|
*/
|
|
25
|
-
|
|
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
|
-
*
|
|
69
|
+
* Event types emitted by the state for observability
|
|
28
70
|
*/
|
|
29
|
-
|
|
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> |
|
|
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 |
|
|
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
|
|
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?:
|
|
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[]) =>
|
|
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?:
|
|
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
|
-
|
|
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
|
-
*
|
|
54
|
+
* Extended store state for monitoring execution status
|
|
24
55
|
*/
|
|
25
|
-
|
|
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
|
-
*
|
|
69
|
+
* Event types emitted by the state for observability
|
|
28
70
|
*/
|
|
29
|
-
|
|
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> |
|
|
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 |
|
|
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
|
|
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?:
|
|
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[]) =>
|
|
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?:
|
|
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
|
-
|
|
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.
|
|
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.
|
|
29
|
+
"@asaidimu/events": "^1.1.1",
|
|
30
|
+
"@asaidimu/indexed": "^1.1.2",
|
|
30
31
|
"@asaidimu/node": "^1.0.6",
|
|
31
|
-
"
|
|
32
|
+
"@asaidimu/query": "^1.1.2",
|
|
33
|
+
"react": "^19.0.0",
|
|
34
|
+
"uuid": "^11.1.0"
|
|
32
35
|
}
|
|
33
36
|
}
|