@asaidimu/utils-remote-store 1.0.0 → 1.1.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/index.d.mts CHANGED
@@ -267,6 +267,8 @@ interface QuerySubscription<T> {
267
267
  operation: string;
268
268
  /** The parameters for the query. */
269
269
  params: any;
270
+ /** A stable reference to the operations results. */
271
+ result: QueryResult<T> | PagedQueryResult<T>;
270
272
  }
271
273
 
272
274
  /**
@@ -571,4 +573,11 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
571
573
  destroy(): void;
572
574
  }
573
575
 
574
- export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord };
576
+ /**
577
+ * Computes the FNV-1a 64-bit hash of the given data.
578
+ * @param {any} data - The data to hash. Can be any type that can be serialized.
579
+ * @returns {string} The FNV-1a 64-bit hash as a hexadecimal string.
580
+ */
581
+ declare function hash(data: any): string;
582
+
583
+ export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, hash };
package/index.d.ts CHANGED
@@ -267,6 +267,8 @@ interface QuerySubscription<T> {
267
267
  operation: string;
268
268
  /** The parameters for the query. */
269
269
  params: any;
270
+ /** A stable reference to the operations results. */
271
+ result: QueryResult<T> | PagedQueryResult<T>;
270
272
  }
271
273
 
272
274
  /**
@@ -571,4 +573,11 @@ declare class ReactiveRemoteStore<T extends StoreRecord, TFindOptions = Record<s
571
573
  destroy(): void;
572
574
  }
573
575
 
574
- export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord };
576
+ /**
577
+ * Computes the FNV-1a 64-bit hash of the given data.
578
+ * @param {any} data - The data to hash. Can be any type that can be serialized.
579
+ * @returns {string} The FNV-1a 64-bit hash as a hexadecimal string.
580
+ */
581
+ declare function hash(data: any): string;
582
+
583
+ export { type ActiveQuery, type BaseStore, type Correlator, type MutationOperation, type Page, type PagedQueryResult, type PaginationInfo, type QueryResult, type QuerySubscription, type ReactivePagedQueryResult, type ReactiveQueryResult, ReactiveRemoteStore, type ResourceName, StoreError, type StoreErrorDetails, type StoreEvent, type StoreEventCorrelator, type StoreRecord, hash };
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}exports.ReactiveRemoteStore=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o=()=>{i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}}))},c=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&o()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&o()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&o()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&o()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&o()}))],n={unsubscribeCallbacks:c,subscribers:i,cachedSubscribe:t=>(i.add(t),()=>{i.delete(t),0===i.size&&(c.forEach((e=>e())),this.querySubscriptions.delete(e))}),cachedSelector:a,operation:t,params:r};return this.querySubscriptions.set(e,n),n}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);return t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)})),{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){return()=>{const s=this.cache.getStats().entries.find((e=>e.key===t)),i=this.cache.peek(t);s?.isStale&&this.cache.get(t,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${t}:`,e)})),void 0!==i&&this.cache.has(t)||this.cache.get(t,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${t}:`,e)}));const o=r.page||1;return{page:i,loading:s?.isLoading??void 0===i,error:s?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:s?.isStale??!0,updated:s?.lastUpdated??0,hasNext:!!i&&o<i.page.pages,hasPrevious:o>1,next:async()=>{if(i&&o<i.page.pages){const e={...r,page:o+1},s=this.buildKey(t.split(":")[0],e);this.querySubscriptions.has(s)||this.cache.registerQuery(s,(()=>a(e))),await this.cache.get(s,{waitForFresh:!0})}},previous:async()=>{if(o>1){const e={...r,page:o-1},s=this.buildKey(t.split(":")[0],e);this.querySubscriptions.has(s)||this.cache.registerQuery(s,(()=>a(e))),await this.cache.get(s,{waitForFresh:!0})}},fetch:async e=>{const s={...r,page:e},i=this.buildKey(t.split(":")[0],s);this.querySubscriptions.has(i)||this.cache.registerQuery(i,(()=>a(s))),await this.cache.get(i,{waitForFresh:!0})}}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),this.cache.remove(this.buildKey("read",{id:e.id})),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}},exports.StoreError=e;
1
+ "use strict";var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}exports.ReactiveRemoteStore=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o={operation:t,params:r,subscribers:i},c=()=>{const e=a();var r,s;("read"===t?(r=o.result,s=e,r&&s?r.data===s.data&&r.loading===s.loading&&r.stale===s.stale&&(r.error===s.error||r.error?.message===s.error?.message):r===s):function(e,t){return e&&t?e.page===t.page&&e.loading===t.loading&&e.stale===t.stale&&(e.error===t.error||e.error?.message===t.error?.message)&&e.hasNext===t.hasNext&&e.hasPrevious===t.hasPrevious:e===t}(o.result,e))||(o.result=e,i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}})))};return o.unsubscribeCallbacks=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&c()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&c()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&c()}))],o.cachedSubscribe=t=>(i.add(t),()=>{i.delete(t),0===i.size&&(o.unsubscribeCallbacks.forEach((e=>e())),this.querySubscriptions.delete(e))}),o.cachedSelector=()=>o.result,o.result=a(),this.querySubscriptions.set(e,o),o}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}));return{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){let s=r;const i=this,o={next:async()=>{const e=s.page||1,r=i.buildKey(t.split(":")[0],s),o=i.cache.peek(r);if(o&&e<o.page.pages){const r={...s,page:e+1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},previous:async()=>{const e=s.page||1;if(e>1){const r={...s,page:e-1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},fetch:async e=>{const r={...s,page:e},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}};return()=>{const r=this.buildKey(t.split(":")[0],s),a=this.cache.getStats().entries.find((e=>e.key===r)),i=this.cache.peek(r);a?.isStale&&this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${r}:`,e)})),void 0!==i&&this.cache.has(r)||this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${r}:`,e)}));const c=s.page||1;return{page:i,loading:a?.isLoading??void 0===i,error:a?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0,hasNext:!!i&&c<i.page.pages,hasPrevious:c>1,...o}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}},exports.StoreError=e,exports.hash=r;
package/index.mjs CHANGED
@@ -1 +1 @@
1
- var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}var a=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o=()=>{i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}}))},c=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&o()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&o()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&o()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&o()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&o()}))],n={unsubscribeCallbacks:c,subscribers:i,cachedSubscribe:t=>(i.add(t),()=>{i.delete(t),0===i.size&&(c.forEach((e=>e())),this.querySubscriptions.delete(e))}),cachedSelector:a,operation:t,params:r};return this.querySubscriptions.set(e,n),n}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);return t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)})),{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){return()=>{const s=this.cache.getStats().entries.find((e=>e.key===t)),i=this.cache.peek(t);s?.isStale&&this.cache.get(t,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${t}:`,e)})),void 0!==i&&this.cache.has(t)||this.cache.get(t,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${t}:`,e)}));const o=r.page||1;return{page:i,loading:s?.isLoading??void 0===i,error:s?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:s?.isStale??!0,updated:s?.lastUpdated??0,hasNext:!!i&&o<i.page.pages,hasPrevious:o>1,next:async()=>{if(i&&o<i.page.pages){const e={...r,page:o+1},s=this.buildKey(t.split(":")[0],e);this.querySubscriptions.has(s)||this.cache.registerQuery(s,(()=>a(e))),await this.cache.get(s,{waitForFresh:!0})}},previous:async()=>{if(o>1){const e={...r,page:o-1},s=this.buildKey(t.split(":")[0],e);this.querySubscriptions.has(s)||this.cache.registerQuery(s,(()=>a(e))),await this.cache.get(s,{waitForFresh:!0})}},fetch:async e=>{const s={...r,page:e},i=this.buildKey(t.split(":")[0],s);this.querySubscriptions.has(i)||this.cache.registerQuery(i,(()=>a(s))),await this.cache.get(i,{waitForFresh:!0})}}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),this.cache.remove(this.buildKey("read",{id:e.id})),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}};export{a as ReactiveRemoteStore,e as StoreError};
1
+ var e=class e extends Error{code;status;data;isRetryable;originalError;constructor(e,t){super(e.message),this.name="StoreError",this.code=e.code,this.status=e.status,this.data=e.data,this.isRetryable=e.isRetryable,this.originalError=t}static fromError(t,r){if(t.isAbort||"AbortError"===t.name)return new e({code:"ABORTED",message:"Request was cancelled",isRetryable:!1},t);if("NetworkError"===t.name||!navigator.onLine)return new e({code:"NETWORK_ERROR",message:"Network connection failed. Please check your internet connection.",isRetryable:!0},t);if(t.status)switch(t.status){case 400:return new e({code:"VALIDATION_ERROR",message:t.message||"Invalid request data",status:400,data:t.data,isRetryable:!1},t);case 401:return new e({code:"UNAUTHORIZED",message:"Authentication required or invalid credentials",status:401,isRetryable:!1},t);case 403:return new e({code:"FORBIDDEN",message:"Access denied for this resource",status:403,isRetryable:!1},t);case 404:return new e({code:"NOT_FOUND",message:"Resource not found",status:404,isRetryable:!1},t);case 409:return new e({code:"CONFLICT",message:t.message||"Resource conflict occurred",status:409,isRetryable:!1},t);case 429:return new e({code:"RATE_LIMITED",message:"Too many requests. Please try again later.",status:429,isRetryable:!0},t);case 500:case 502:case 503:case 504:return new e({code:"SERVER_ERROR",message:"Server error occurred. Please try again later.",status:t.status,isRetryable:!0},t);default:return new e({code:"HTTP_ERROR",message:t.message||`HTTP ${t.status} error occurred`,status:t.status,isRetryable:t.status>=500},t)}return"TimeoutError"===t.name||"TIMEOUT"===t.code?new e({code:"TIMEOUT",message:"Request timed out. Please try again.",isRetryable:!0},t):new e({code:"UNKNOWN_ERROR",message:t.message||`Unknown error occurred during ${r}`,isRetryable:!0},t)}};function t(e,r=new WeakMap){return void 0===e?"undefined":"function"==typeof e||"symbol"==typeof e?"":"string"==typeof e?`"${e}"`:"number"==typeof e||"boolean"==typeof e||null===e?String(e):Array.isArray(e)?`[${e.map((e=>t(e,r))).join(",")}]`:"object"==typeof e?r.has(e)?'"__circular__"':(r.set(e,!0),`{${Object.keys(e).sort().map((a=>`"${a}":${t(e[a],r)}`)).join(",")}}`):""}function r(e){let r=3421674724,a=2216829733;const s=t(e);for(let e=0;e<s.length;e++){a^=s.charCodeAt(e);const t=Math.imul(a,435),i=Math.imul(a,435)+Math.imul(r,435);a=t>>>0,r=i>>>0}return(r>>>0).toString(16)+(a>>>0).toString(16)}var a=class{constructor(e,t,r,a){if(this.cache=e,this.baseStore=t,this.correlator=r,this.storeEventCorrelator=a,this.storeEventCorrelator){queueMicrotask((async()=>{this.unsubscribeFromBaseStore=await this.baseStore.subscribe("*",this.handleStoreEvent.bind(this))}))}}querySubscriptions=new Map;keyCache=new WeakMap;unsubscribeFromBaseStore;createSubscription(e,t,r,a,s){const i=new Set,o={operation:t,params:r,subscribers:i},c=()=>{const e=a();var r,s;("read"===t?(r=o.result,s=e,r&&s?r.data===s.data&&r.loading===s.loading&&r.stale===s.stale&&(r.error===s.error||r.error?.message===s.error?.message):r===s):function(e,t){return e&&t?e.page===t.page&&e.loading===t.loading&&e.stale===t.stale&&(e.error===t.error||e.error?.message===t.error?.message)&&e.hasNext===t.hasNext&&e.hasPrevious===t.hasPrevious:e===t}(o.result,e))||(o.result=e,i.forEach((e=>{try{e()}catch(e){console.error("ReactiveRemoteStore: Subscriber callback error:",e)}})))};return o.unsubscribeCallbacks=[this.cache.on("cache:fetch:success",(e=>{s(e.key)&&c()})),this.cache.on("cache:fetch:error",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:set",(e=>{s(e.key)&&c()})),this.cache.on("cache:data:invalidate",(e=>{s(e.key)&&c()})),this.cache.on("cache:read:hit",(e=>{s(e.key)&&e.isStale&&c()}))],o.cachedSubscribe=t=>(i.add(t),()=>{i.delete(t),0===i.size&&(o.unsubscribeCallbacks.forEach((e=>e())),this.querySubscriptions.delete(e))}),o.cachedSelector=()=>o.result,o.result=a(),this.querySubscriptions.set(e,o),o}getOrCreateSubscription(e,t,r,a,s){let i=this.querySubscriptions.get(e);return i||(i=this.createSubscription(e,t,r,a,s)),i}buildKey(e,t){if("object"==typeof t&&null!==t&&this.keyCache.has(t))return this.keyCache.get(t);const a=`${e}:${r(t)}`;return"object"==typeof t&&null!==t&&this.keyCache.set(t,a),a}createSelector(e){return()=>{const t=this.cache.getStats().entries.find((t=>t.key===e)),r=this.cache.peek(e);t?.isStale&&this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${e}:`,t)})),void 0!==r&&this.cache.has(e)||this.cache.get(e,{waitForFresh:!1}).catch((t=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${e}:`,t)}));return{data:r,loading:t?.isLoading??void 0===r,error:t?.error?new Error("Query failed"):void 0,stale:t?.isStale??!0,updated:t?.lastUpdated??0}}}read(e){const t=this.buildKey("read",e);this.querySubscriptions.has(t)||this.cache.registerQuery(t,(async()=>await this.baseStore.read(e)));const r=this.createSelector(t),a=this.getOrCreateSubscription(t,"read",e,r,(e=>e===t));return{value:a.cachedSelector,onValueChange:a.cachedSubscribe}}createPagedSelector(t,r,a){let s=r;const i=this,o={next:async()=>{const e=s.page||1,r=i.buildKey(t.split(":")[0],s),o=i.cache.peek(r);if(o&&e<o.page.pages){const r={...s,page:e+1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},previous:async()=>{const e=s.page||1;if(e>1){const r={...s,page:e-1},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}},fetch:async e=>{const r={...s,page:e},o=i.buildKey(t.split(":")[0],r);s=r,i.querySubscriptions.has(o)||i.cache.registerQuery(o,(()=>a(r))),await i.cache.get(o,{waitForFresh:!0})}};return()=>{const r=this.buildKey(t.split(":")[0],s),a=this.cache.getStats().entries.find((e=>e.key===r)),i=this.cache.peek(r);a?.isStale&&this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background refetch failed for ${r}:`,e)})),void 0!==i&&this.cache.has(r)||this.cache.get(r,{waitForFresh:!1}).catch((e=>{console.warn(`ReactiveRemoteStore: Background fetch failed for ${r}:`,e)}));const c=s.page||1;return{page:i,loading:a?.isLoading??void 0===i,error:a?.error?e.fromError(new Error("Query failed"),"query"):void 0,stale:a?.isStale??!0,updated:a?.lastUpdated??0,hasNext:!!i&&c<i.page.pages,hasPrevious:c>1,...o}}}setupPagedQuery(e,t,r){const a=this.buildKey(e,t);this.querySubscriptions.has(a)||this.cache.registerQuery(a,(async()=>r(t)));const s=this.createPagedSelector(a,t,r),i=this.getOrCreateSubscription(a,e,t,s,(t=>t.startsWith(`${e}:`)));return{value:i.cachedSelector,onValueChange:i.cachedSubscribe}}list(e){return this.setupPagedQuery("list",e,this.baseStore.list.bind(this.baseStore))}find(e){return this.setupPagedQuery("find",e,this.baseStore.find.bind(this.baseStore))}async invalidateQueries(e){if(this.correlator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params}))),r=this.correlator(e,t).map((e=>this.cache.invalidate(e)));await Promise.all(r)}else await this.cache.invalidatePattern(/^list:/),await this.cache.invalidatePattern(/^find:/)}handleStoreEvent(e){if(console.log("ReactiveRemoteStore: Received store event:",e),this.storeEventCorrelator){const t=Array.from(this.querySubscriptions.entries()).map((([e,t])=>({queryKey:e,operation:t.operation,params:t.params})));console.log("ReactiveRemoteStore: Active queries:",t);const r=this.storeEventCorrelator(e,t);console.log("ReactiveRemoteStore: Queries to invalidate:",r),r.forEach((e=>this.cache.invalidate(e)))}else console.warn("ReactiveRemoteStore: handleStoreEvent called without _storeEventCorrelator.")}async create(e){try{const t=await this.baseStore.create(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"create",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Create operation failed:",e),e}}async update(e){try{const t=await this.baseStore.update(e);return t&&await this.invalidateQueries({operation:"update",params:e}),t}catch(e){throw console.error("ReactiveRemoteStore: Update operation failed:",e),e}}async delete(e){try{await this.baseStore.delete(e),await this.invalidateQueries({operation:"delete",params:e})}catch(e){throw console.error("ReactiveRemoteStore: Delete operation failed:",e),e}}async notify(e){return this.baseStore.notify(e)}async stream(e,t){return this.baseStore.stream(e,t)}async upload(e){try{const t=await this.baseStore.upload(e);return t&&(this.cache.setData(this.buildKey("read",{id:t.id}),t),await this.invalidateQueries({operation:"upload",params:e})),t}catch(e){throw console.error("ReactiveRemoteStore: Upload operation failed:",e),e}}async refresh(e,t){const r=this.buildKey(e,t);return this.cache.refresh(r)}prefetch(e,t){const r=this.buildKey(e,t);this.cache.prefetch(r).catch((e=>{console.warn(`ReactiveRemoteStore: Prefetch failed for ${r}:`,e)}))}async invalidate(e,t){const r=this.buildKey(e,t);await this.cache.invalidate(r)}async invalidateAll(){const e=Array.from(this.querySubscriptions.keys()).map((e=>this.cache.invalidate(e)));await Promise.all(e)}getStats(){return{...this.cache.getStats(),activeSubscriptions:this.querySubscriptions.size}}destroy(){this.querySubscriptions.forEach((e=>{e.unsubscribeCallbacks.forEach((e=>e()))})),this.querySubscriptions.clear(),this.keyCache=new WeakMap,this.unsubscribeFromBaseStore&&this.unsubscribeFromBaseStore()}};export{a as ReactiveRemoteStore,e as StoreError,r as hash};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-remote-store",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "A reactive store for remote data, built on top of @asaidimu/utils-cache",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -9,7 +9,7 @@
9
9
  "./*"
10
10
  ],
11
11
  "dependencies": {
12
- "@asaidimu/utils-cache": "2.0.5",
12
+ "@asaidimu/utils-cache": "2.1.0",
13
13
  "eventsource": "^4.0.0"
14
14
  },
15
15
  "exports": {