@asaidimu/utils-persistence 6.1.4 → 6.1.6

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
@@ -220,4 +220,81 @@ declare class IndexedDBPersistence<T> implements SimplePersistence<T> {
220
220
  close(): Promise<void>;
221
221
  }
222
222
 
223
- export { IndexedDBPersistence, type IndexedDBPersistenceConfig, type SimplePersistence, type StorageEvents, type StoreConfig, WebStoragePersistence, type WebStoragePersistenceConfig };
223
+ /**
224
+ * Implements SimplePersistence using an in-memory store.
225
+ * Data is NOT persisted across page loads or application restarts.
226
+ *
227
+ * Synchronization: This class fully synchronizes its in-memory state across
228
+ * different browser tabs using an event bus with a Last Write Wins (LWW) strategy.
229
+ * When a state is set or cleared in one tab, the latest version (based on timestamp)
230
+ * will propagate to and update the in-memory state of all other tabs.
231
+ *
232
+ * IMPORTANT: While the in-memory state is synchronized *across tabs*, it remains
233
+ * ephemeral within each tab. Closing all tabs will result in data loss.
234
+ *
235
+ * Note: The best use case for this class is in tests where intergration with a
236
+ * SimplePersistence is required.
237
+ */
238
+ declare class EphemeralPersistence<T> implements SimplePersistence<T> {
239
+ private readonly eventBus;
240
+ private inMemoryState;
241
+ private readonly config;
242
+ /**
243
+ * Initializes a new instance of EphemeralPersistence.
244
+ * @param config Configuration object for the persistence adapter.
245
+ */
246
+ constructor(config: StoreConfig<T> & {
247
+ storageKey: string;
248
+ });
249
+ /**
250
+ * Initializes the event bus with configured settings for cross-tab communication.
251
+ * @returns Configured event bus instance.
252
+ */
253
+ private initializeEventBus;
254
+ /**
255
+ * Sets up an internal listener to synchronize the in-memory state
256
+ * using the Last Write Wins (LWW) strategy.
257
+ */
258
+ private setupLwwSynchronizationListener;
259
+ /**
260
+ * Persists the provided state to in-memory storage and notifies all subscribers
261
+ * across tabs. The state is synchronized using LWW.
262
+ * @param instanceId Unique identifier of the instance making the update.
263
+ * @param state The state to persist.
264
+ * @returns True if successful, false if an error occurs.
265
+ */
266
+ set(instanceId: string, state: T): boolean;
267
+ /**
268
+ * Retrieves the current synchronized state from in-memory storage.
269
+ * @returns The synchronized state, or null if not set or if cleared.
270
+ */
271
+ get(): T | null;
272
+ /**
273
+ * Subscribes to updates in the in-memory state.
274
+ * The callback is invoked when the state is updated by any instance
275
+ * (including other tabs) *excluding the subscribing instance's own 'set' or 'clear' calls*.
276
+ * @param instanceId Unique identifier of the subscribing instance.
277
+ * @param onStateChange Callback to invoke with the updated state.
278
+ * @returns Function to unsubscribe from updates.
279
+ */
280
+ subscribe(instanceId: string, onStateChange: (state: T) => void): () => void;
281
+ /**
282
+ * Clears the in-memory state of this instance and synchronizes this clear
283
+ * across all tabs using the LWW strategy.
284
+ * @returns True if successful, false if an error occurs.
285
+ */
286
+ clear(): boolean;
287
+ /**
288
+ * Returns metadata about the persistence layer.
289
+ *
290
+ * @returns An object containing:
291
+ * - `version`: The semantic version string of the persistence schema or application.
292
+ * - `id`: A unique identifier for the application using this persistence instance.
293
+ */
294
+ stats(): {
295
+ version: string;
296
+ id: string;
297
+ };
298
+ }
299
+
300
+ export { EphemeralPersistence, IndexedDBPersistence, type IndexedDBPersistenceConfig, type SimplePersistence, type StorageEvents, type StoreConfig, WebStoragePersistence, type WebStoragePersistenceConfig };
package/index.d.ts CHANGED
@@ -220,4 +220,81 @@ declare class IndexedDBPersistence<T> implements SimplePersistence<T> {
220
220
  close(): Promise<void>;
221
221
  }
222
222
 
223
- export { IndexedDBPersistence, type IndexedDBPersistenceConfig, type SimplePersistence, type StorageEvents, type StoreConfig, WebStoragePersistence, type WebStoragePersistenceConfig };
223
+ /**
224
+ * Implements SimplePersistence using an in-memory store.
225
+ * Data is NOT persisted across page loads or application restarts.
226
+ *
227
+ * Synchronization: This class fully synchronizes its in-memory state across
228
+ * different browser tabs using an event bus with a Last Write Wins (LWW) strategy.
229
+ * When a state is set or cleared in one tab, the latest version (based on timestamp)
230
+ * will propagate to and update the in-memory state of all other tabs.
231
+ *
232
+ * IMPORTANT: While the in-memory state is synchronized *across tabs*, it remains
233
+ * ephemeral within each tab. Closing all tabs will result in data loss.
234
+ *
235
+ * Note: The best use case for this class is in tests where intergration with a
236
+ * SimplePersistence is required.
237
+ */
238
+ declare class EphemeralPersistence<T> implements SimplePersistence<T> {
239
+ private readonly eventBus;
240
+ private inMemoryState;
241
+ private readonly config;
242
+ /**
243
+ * Initializes a new instance of EphemeralPersistence.
244
+ * @param config Configuration object for the persistence adapter.
245
+ */
246
+ constructor(config: StoreConfig<T> & {
247
+ storageKey: string;
248
+ });
249
+ /**
250
+ * Initializes the event bus with configured settings for cross-tab communication.
251
+ * @returns Configured event bus instance.
252
+ */
253
+ private initializeEventBus;
254
+ /**
255
+ * Sets up an internal listener to synchronize the in-memory state
256
+ * using the Last Write Wins (LWW) strategy.
257
+ */
258
+ private setupLwwSynchronizationListener;
259
+ /**
260
+ * Persists the provided state to in-memory storage and notifies all subscribers
261
+ * across tabs. The state is synchronized using LWW.
262
+ * @param instanceId Unique identifier of the instance making the update.
263
+ * @param state The state to persist.
264
+ * @returns True if successful, false if an error occurs.
265
+ */
266
+ set(instanceId: string, state: T): boolean;
267
+ /**
268
+ * Retrieves the current synchronized state from in-memory storage.
269
+ * @returns The synchronized state, or null if not set or if cleared.
270
+ */
271
+ get(): T | null;
272
+ /**
273
+ * Subscribes to updates in the in-memory state.
274
+ * The callback is invoked when the state is updated by any instance
275
+ * (including other tabs) *excluding the subscribing instance's own 'set' or 'clear' calls*.
276
+ * @param instanceId Unique identifier of the subscribing instance.
277
+ * @param onStateChange Callback to invoke with the updated state.
278
+ * @returns Function to unsubscribe from updates.
279
+ */
280
+ subscribe(instanceId: string, onStateChange: (state: T) => void): () => void;
281
+ /**
282
+ * Clears the in-memory state of this instance and synchronizes this clear
283
+ * across all tabs using the LWW strategy.
284
+ * @returns True if successful, false if an error occurs.
285
+ */
286
+ clear(): boolean;
287
+ /**
288
+ * Returns metadata about the persistence layer.
289
+ *
290
+ * @returns An object containing:
291
+ * - `version`: The semantic version string of the persistence schema or application.
292
+ * - `id`: A unique identifier for the application using this persistence instance.
293
+ */
294
+ stats(): {
295
+ version: string;
296
+ id: string;
297
+ };
298
+ }
299
+
300
+ export { EphemeralPersistence, IndexedDBPersistence, type IndexedDBPersistenceConfig, type SimplePersistence, type StorageEvents, type StoreConfig, WebStoragePersistence, type WebStoragePersistenceConfig };
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var e,t,i=require("@asaidimu/utils-events"),r=require("@asaidimu/utils-database"),s=Object.create,n=Object.defineProperty,o=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,c=Object.getPrototypeOf,u=Object.prototype.hasOwnProperty,l=(e={"node_modules/@asaidimu/query/index.js"(e,t){var i,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var i in t)r(e,i,{get:t[i],enumerable:!0})})(a,{QueryBuilder:()=>c,createJoiner:()=>g,createMatcher:()=>l,createPaginator:()=>O,createProjector:()=>q,createSorter:()=>b}),t.exports=(i=a,((e,t,i,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))!o.call(e,c)&&c!==i&&r(e,c,{get:()=>t[c],enumerable:!(a=s(t,c))||a.enumerable});return e})(r({},"__esModule",{value:!0}),i));var c=class{query;constructor(){this.query={}}where(e){return this.query.filters=e,this}orderBy(e,t){return this.query.sort||(this.query.sort=[]),this.query.sort.push({field:e,direction:t}),this}offset(e,t){return this.query.pagination={type:"offset",offset:e,limit:t},this}cursor(e,t,i){return this.query.pagination={type:"cursor",cursor:e,limit:t,direction:i},this}include(e){return this.query.projection||(this.query.projection={}),this.query.projection.include=e,this}exclude(e){return this.query.projection||(this.query.projection={}),this.query.projection.exclude=e,this}computed(e,t){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"computed",expression:e,alias:t}),this}case(e,t,i){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"case",conditions:e,else:t,alias:i}),this}join(e,t,i){return this.query.joins||(this.query.joins=[]),this.query.joins.push({relation:e,alias:i,query:t}),this}aggregate(e,t){return this.query.aggregations={groupBy:e,metrics:t},this}window(e){return this.query.window||(this.query.window=[]),this.query.window.push(e),this}hint(e){return this.query.hints||(this.query.hints=[]),this.query.hints.push(e),this}build(){return this.query}};function u(e){function t(e,t){return function(e){return"field"===e?.type}(t)?e[t.field]:function(e){return"value"===e?.type}(t)?t.value:function(e){return"function"===e?.type}(t)?r(t,e):function(e){return"computed"===e?.type}(t)?r(t.expression,e):function(e){return"case"===e?.type}(t)?function(e,t){for(let i of t.conditions)if(s(e,i.when))return i.then;return t.else}(e,t):t}let i=new Map([["and",(e,t)=>t.every((t=>s(e,t)))],["or",(e,t)=>t.some((t=>s(e,t)))],["not",(e,t)=>!t.every((t=>s(e,t)))],["nor",(e,t)=>!t.some((t=>s(e,t)))],["xor",(e,t)=>1===t.filter((t=>s(e,t))).length]]);function r(i,r){let s=i.arguments.map((e=>t(r,e)));if(e[i.function])return e[i.function](...s);throw new Error(`Function ${i.function} not found!`)}function s(r,s){if(function(e){return!!e&&void 0!==e.conditions}(s))return function(e,t){let{operator:r,conditions:s}=t,n=i.get(r);if(n)return n(e,s);throw new Error(`Unsupported logical operator: ${r}`)}(r,s);if(!s||!s.field)return!1;let{field:n,operator:o,value:a}=s,c=r[n],u=t(r,a),l=new Map([["eq",(e,t)=>e===t],["neq",(e,t)=>e!==t],["lt",(e,t)=>e<t],["lte",(e,t)=>e<=t],["gt",(e,t)=>e>t],["gte",(e,t)=>e>=t],["in",(e,t)=>Array.isArray(t)&&t.includes(e)],["nin",(e,t)=>Array.isArray(t)&&!t.includes(e)],["contains",(e,t)=>"string"==typeof e?e.includes(t):!!Array.isArray(e)&&e.includes(a)],["ncontains",(e,t)=>"string"==typeof e&&!e.includes(t)],["startswith",(e,t)=>"string"==typeof e&&e.startsWith(t)],["endswith",(e,t)=>"string"==typeof e&&e.endsWith(t)],["exists",e=>null!=e],["nexists",e=>null==e]]),h=e[o]||l.get(o);if(h)return h(c,u);throw new Error(`Unsupported comparison operator: ${o}`)}return{resolve:t,evaluate:s}}function l(e){let{evaluate:t}=u(e),i=new WeakMap;function r(e,r){let s=i.get(e);s||(s=new Map,i.set(e,s));let n=JSON.stringify(r);if(s.has(n))return s.get(n);let o=t(e,r);return s.set(n,o),o}return{matcher:r,match:r}}var h=class extends Error{constructor(e,t){super(e),this.code=t,this.name="JoinError"}},p=e=>e&&"field"in e&&"operator"in e&&"value"in e,d=(e,t)=>{if(e){if(p(e)&&e){let i=(e=>"object"==typeof e&&null!==e&&"type"in e&&"field"===e.type)(e.value)?((e,t)=>t.split(".").reduce(((e,t)=>e?.[t]),e))(t,e.value.field):e.value;return{...e,value:i}}if((e=>"operator"in e&&"conditions"in e)(e)){let i={...e};return e.conditions&&(i.conditions=e.conditions.map((e=>d(e,t)))),i}return e}},f=async(e,t,i,r)=>{try{if(((e,t)=>{if(!e.relation)throw new h("Join configuration must specify a relation","INVALID_CONFIG");if(!t[e.relation])throw new h(`Collection "${e.relation}" not found in database`,"COLLECTION_NOT_FOUND");if(e.alias&&"string"!=typeof e.alias)throw new h("Join alias must be a string","INVALID_ALIAS")})(i,e),!Array.isArray(t))throw new h("Source data must be an array","INVALID_SOURCE_DATA");let s,n=e[i.relation];return i.query?.filters&&p(i.query.filters)&&(s=((e,t)=>{let i=new Map;return e.forEach((e=>{let r=e[t];i.has(r)||i.set(r,[]),i.get(r).push(e)})),i})(n,i.query.filters.field)),(await Promise.all(t.map((async e=>{let t,o=((e,t)=>{if(!e)return{};let i=d(e.filters,t);return{...e,filters:i}})(i.query,e);return t=o.filters&&p(o.filters)&&s?.has(o.filters.value)?s.get(o.filters.value)||[]:n.filter((e=>r.matcher(e,o.filters))),((e,t,i)=>{let r=i.alias||i.relation;return[{...e,[r]:t}]})(e,await[e=>o.sort?r.sorter(e,o.sort):e,e=>o.projection?r.projector(e,o.projection):e,async e=>o.pagination?await r.paginator(e,o.pagination):e].reduce((async(e,t)=>t(await e)),Promise.resolve(t)),i)})))).flat()}catch(e){throw e instanceof h?e:new h(`Join operation failed: ${e.message}`,"JOIN_EXECUTION_ERROR")}},y=async(e,t,i,r)=>i.reduce((async(t,i)=>f(e,await t,i,r)),Promise.resolve(t));function g(){return{join:y}}var m=class extends Error{constructor(e,t){super(`Unsupported comparison between ${typeof e} and ${typeof t}`),this.name="UnsupportedComparisonError"}},w=class extends Error{constructor(e){super(`Unsupported sort direction: ${e}`),this.name="InvalidSortDirectionError"}};function v(e,t,i){if(e===t)return 0;if(null==e||null==t)return null==e?"asc"===i?-1:1:"asc"===i?1:-1;if("string"==typeof e&&"string"==typeof t)return"asc"===i?e.localeCompare(t):t.localeCompare(e);if("number"==typeof e&&"number"==typeof t)return"asc"===i?e-t:t-e;throw new m(e,t)}function _(e,t){let i=Array.from(e);return 0===t.length?i:i.sort(((e,i)=>{for(let r of t){let{field:t,direction:s}=r,n=e[t],o=i[t];try{if("asc"!==s&&"desc"!==s)throw new w(s);let e=v(n,o,s);if(0!==e)return e}catch(e){throw e instanceof m||e instanceof w?e:new Error(`Error comparing field '${t}': ${e.message}`)}}return 0}))}function b(){return{sort:_}}function q(e){let{resolve:t}=u(e);function i(e,r){let s={};return r.include?.length&&function(e,t,r){for(let s of t)if("string"!=typeof s){for(let[t,n]of Object.entries(s))if(Object.prototype.hasOwnProperty.call(e,t)){let s=i(e[t],n);r[t]=s}}else{let t=s;r[t]=e[t]}}(e,r.include,s),r.exclude?.length&&function(e,t,r){0===Object.keys(r).length&&Object.assign(r,e);for(let e of t)if("string"!=typeof e)for(let[t,s]of Object.entries(e)){if(!Object.prototype.hasOwnProperty.call(r,t))continue;let e=r[t];e&&"object"==typeof e?r[t]=i(e,s):delete r[t]}else delete r[e]}(e,r.exclude,s),r.computed?.length&&function(e,i,r){for(let s of i)r[s.alias]=t(e,s)}(e,r.computed,s),s}return{project:i}}async function*k(e,t,i=e=>String(e)){"offset"===t.type?yield*async function*(e,t){let{offset:i,limit:r}=t,s=0,n=r,o=[];for await(let t of e)n<=0&&(yield o,o=[],n=r),s<i?s++:(o.push(t),n--);o.length>0&&(yield o)}(e,t):yield*async function*(e,t,i){let{cursor:r,limit:s,direction:n}=t,o=s,a=void 0===r,c=[];if("forward"===n)for await(let t of e)o<=0&&(yield c,c=[],o=s),a?(c.push(t),o--):a=i(t)===r;else{let t=[];for await(let i of e)t.push(i);for(let e=t.length-1;e>=0;e--){let n=t[e];a?(c.push(n),o--,o<=0&&(yield c,c=[],o=s)):a=i(n)===r}}c.length>0&&(yield c)}(e,t,i)}function O(){return{paginate:k}}}},function(){return t||(0,e[a(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=((e,t,i)=>(i=null!=e?s(c(e)):{},((e,t,i,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of a(t))u.call(e,s)||s===i||n(e,s,{get:()=>t[s],enumerable:!(r=o(t,s))||r.enumerable});return e})(e&&e.__esModule?i:n(i,"default",{value:e,enumerable:!0}),e)))(l()),p=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},d=class extends p{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},f=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const i=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await i;let r;await Promise.race([i.then((()=>clearTimeout(r))),new Promise(((i,s)=>{r=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new d("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},y=class{mutex=new f({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,t,i="Operation timed out"){if(null==t)return e;let r;return Promise.race([e.then((e=>(clearTimeout(r),e))),new Promise(((e,s)=>{r=setTimeout((()=>s(new d(i))),t)}))])}},g=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new y({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}},m=class e{static dbResources=new Map;static eventBusMap=new Map;static createDatabaseFactory(e){return async()=>{const{database:t,enableTelemetry:i=!1,collection:s="stores"}=e,n=await r.DatabaseConnection({database:t,enableTelemetry:i},r.createIndexedDbStore);try{await n.createCollection({name:s,version:"1.0.0",nestedSchemas:{},fields:{store:{name:"store",type:"string",required:!0},data:{name:"data",type:"object",required:!0},version:{name:"version",type:"string",required:!0},app:{name:"app",type:"string",required:!0}}})}catch(e){if(e instanceof r.DatabaseError&&"SCHEMA_ALREADY_EXISTS"!==e.type)throw e}return n}}static createDatabaseCleanup(t){return i=>{i?.close();const r=[];e.eventBusMap.forEach(((e,i)=>{i.startsWith(`${t}_store_`)&&(e.clear(),r.push(i))})),r.forEach((t=>e.eventBusMap.delete(t)))}}static async acquireDatabase(t){const i=t.database;if(!e.dbResources.has(i)){const r=new g(e.createDatabaseFactory(t),e.createDatabaseCleanup(i),{gracePeriod:"microtask"});e.dbResources.set(i,r)}return e.dbResources.get(i).acquire()}static releaseDatabase(t){const i=e.dbResources.get(t);i&&i.release()}static async getCollection(t){const i=await e.acquireDatabase(t),r=t.collection??"stores";return i.collection(r)}static getEventBus(t,r){const s=`${t}_store_${r}`;return e.eventBusMap.has(s)||e.eventBusMap.set(s,i.createEventBus({errorHandler:e=>console.error(`Event bus error for ${t}:${r}:`,e),broadcast:{channel:s}})),e.eventBusMap.get(s)}};exports.IndexedDBPersistence=class{collection=null;collectionPromise;config;eventBus;initialized=!1;_initializing=!1;initializationCallbacks=[];doc=null;getStoreName(){return`_${this.config.app}_${this.config.store}_`}constructor(e){this.config=e,this.collectionPromise=m.getCollection(this.config),this.collectionPromise.then((e=>{this.collection=e,this.initialize()})).catch((e=>{console.error(`Failed to initialize collection for store ${this.getStoreName()}:`,e)})),this.eventBus=m.getEventBus(this.config.database,this.getStoreName()),this.eventBus.subscribe("store:updated",(({storageKey:e})=>{e===this.getStoreName()&&(this.doc=null)}))}async initialize(){this._initializing=!0;try{const e=await this._get();if(e&&e.version!==this.config.version&&this.config.onUpgrade){const{state:t}=await this.config.onUpgrade({data:e.data,version:e.version,app:e.app});await this.set("migration",t)}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize and upgrade store ${this.getStoreName()}:`,e)}finally{this._initializing=!1}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}async getCollection(){return this.collection??this.collectionPromise}async set(e,t){this.initialized||this._initializing||await new Promise((e=>this._onInitialized(e)));try{const i=await this.getCollection(),r=await this._read(),s={store:this.getStoreName(),data:t,version:this.config.version,app:this.config.app};let n;return r?n=await r.update(s):(this.doc=await i.create(s),n=!0),n&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:e,state:t,version:this.config.version,app:this.config.app}}),n}catch(t){return console.error(`Failed to set state for store ${this.getStoreName()} in database ${this.config.database} by instance ${e}:`,t),!1}}async _read(){if(this.doc)return this.doc;const e=await this.getCollection(),t=(new h.QueryBuilder).where({field:"store",operator:"eq",value:this.getStoreName()}).build();return this.doc=await e.find(t.filters),this.doc}async _get(){try{const e=await this._read();return e?e.read().then((()=>e)):null}catch(e){return console.error(`Failed to get state for store ${this.getStoreName()} in database ${this.config.database}:`,e),null}}async get(){this.initialized||await new Promise((e=>this._onInitialized(e)));const e=await this._get();return e?e.data:null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.getStoreName()&&r!==e&&t(s)}))}async clear(){this.initialized||await new Promise((e=>this._onInitialized(e)));try{const e=await this._read();return!e||!await e.delete()||(this.doc=null,this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:"",state:null,version:this.config.version,app:this.config.app}}),!0)}catch(e){return console.error(`Failed to clear state for store ${this.getStoreName()} in database ${this.config.database}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}async close(){m.releaseDatabase(this.config.database)}},exports.WebStoragePersistence=class{eventBus;storage;config;initialized=!1;initializationCallbacks=[];constructor(e){this.config=e,this.storage=e.session?sessionStorage:localStorage,this.eventBus=this.initializeEventBus(),this.initialize(),e.session||this.setupStorageEventListener()}async initialize(){try{const e=this.storage.getItem(this.getStoreName());if(e){const t=JSON.parse(e);if(t.version!==this.config.version&&this.config.onUpgrade){const{state:e}=await this.config.onUpgrade({data:t.state,version:t.version,app:t.app});this.set("migration",e)}}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize WebStoragePersistence for ${this.config.storageKey}:`,e)}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}initializeEventBus(){const e={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey}:`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(e.broadcast={channel:`storage_${this.getStoreName()}`}),i.createEventBus(e)}getStoreName(){return`_${this.config.app}_${this.config.storageKey}_`}setupStorageEventListener(){window.addEventListener("storage",(e=>{if(e.key===this.getStoreName()&&e.newValue)try{const t=JSON.parse(e.newValue);t&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"external",state:t.state,version:t.version,app:t.app}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const i={state:structuredClone(t),version:this.config.version,app:this.config.app},r=JSON.stringify(i);return this.storage.setItem(this.getStoreName(),r),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to persist state to web storage for ${this.config.storageKey}:`,e),!1}}_get(){try{const e=this.storage.getItem(this.getStoreName());if(!e)return null;return JSON.parse(e).state}catch(e){return console.error(`Failed to retrieve state from web storage for ${this.config.storageKey}:`,e),null}}async get(){return this.initialized||await new Promise((e=>{this._onInitialized(e)})),this._get()}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{return this.storage.removeItem(this.getStoreName()),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"clear-initiator",state:null,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}};
1
+ "use strict";var e,t,i=require("@asaidimu/utils-events"),r=require("@asaidimu/utils-database"),s=Object.create,n=Object.defineProperty,o=Object.getOwnPropertyDescriptor,a=Object.getOwnPropertyNames,c=Object.getPrototypeOf,u=Object.prototype.hasOwnProperty,l=(e={"node_modules/@asaidimu/query/index.js"(e,t){var i,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var i in t)r(e,i,{get:t[i],enumerable:!0})})(a,{QueryBuilder:()=>c,createJoiner:()=>g,createMatcher:()=>l,createPaginator:()=>q,createProjector:()=>E,createSorter:()=>b}),t.exports=(i=a,((e,t,i,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))!o.call(e,c)&&c!==i&&r(e,c,{get:()=>t[c],enumerable:!(a=s(t,c))||a.enumerable});return e})(r({},"__esModule",{value:!0}),i));var c=class{query;constructor(){this.query={}}where(e){return this.query.filters=e,this}orderBy(e,t){return this.query.sort||(this.query.sort=[]),this.query.sort.push({field:e,direction:t}),this}offset(e,t){return this.query.pagination={type:"offset",offset:e,limit:t},this}cursor(e,t,i){return this.query.pagination={type:"cursor",cursor:e,limit:t,direction:i},this}include(e){return this.query.projection||(this.query.projection={}),this.query.projection.include=e,this}exclude(e){return this.query.projection||(this.query.projection={}),this.query.projection.exclude=e,this}computed(e,t){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"computed",expression:e,alias:t}),this}case(e,t,i){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"case",conditions:e,else:t,alias:i}),this}join(e,t,i){return this.query.joins||(this.query.joins=[]),this.query.joins.push({relation:e,alias:i,query:t}),this}aggregate(e,t){return this.query.aggregations={groupBy:e,metrics:t},this}window(e){return this.query.window||(this.query.window=[]),this.query.window.push(e),this}hint(e){return this.query.hints||(this.query.hints=[]),this.query.hints.push(e),this}build(){return this.query}};function u(e){function t(e,t){return function(e){return"field"===e?.type}(t)?e[t.field]:function(e){return"value"===e?.type}(t)?t.value:function(e){return"function"===e?.type}(t)?r(t,e):function(e){return"computed"===e?.type}(t)?r(t.expression,e):function(e){return"case"===e?.type}(t)?function(e,t){for(let i of t.conditions)if(s(e,i.when))return i.then;return t.else}(e,t):t}let i=new Map([["and",(e,t)=>t.every((t=>s(e,t)))],["or",(e,t)=>t.some((t=>s(e,t)))],["not",(e,t)=>!t.every((t=>s(e,t)))],["nor",(e,t)=>!t.some((t=>s(e,t)))],["xor",(e,t)=>1===t.filter((t=>s(e,t))).length]]);function r(i,r){let s=i.arguments.map((e=>t(r,e)));if(e[i.function])return e[i.function](...s);throw new Error(`Function ${i.function} not found!`)}function s(r,s){if(function(e){return!!e&&void 0!==e.conditions}(s))return function(e,t){let{operator:r,conditions:s}=t,n=i.get(r);if(n)return n(e,s);throw new Error(`Unsupported logical operator: ${r}`)}(r,s);if(!s||!s.field)return!1;let{field:n,operator:o,value:a}=s,c=r[n],u=t(r,a),l=new Map([["eq",(e,t)=>e===t],["neq",(e,t)=>e!==t],["lt",(e,t)=>e<t],["lte",(e,t)=>e<=t],["gt",(e,t)=>e>t],["gte",(e,t)=>e>=t],["in",(e,t)=>Array.isArray(t)&&t.includes(e)],["nin",(e,t)=>Array.isArray(t)&&!t.includes(e)],["contains",(e,t)=>"string"==typeof e?e.includes(t):!!Array.isArray(e)&&e.includes(a)],["ncontains",(e,t)=>"string"==typeof e&&!e.includes(t)],["startswith",(e,t)=>"string"==typeof e&&e.startsWith(t)],["endswith",(e,t)=>"string"==typeof e&&e.endsWith(t)],["exists",e=>null!=e],["nexists",e=>null==e]]),h=e[o]||l.get(o);if(h)return h(c,u);throw new Error(`Unsupported comparison operator: ${o}`)}return{resolve:t,evaluate:s}}function l(e){let{evaluate:t}=u(e),i=new WeakMap;function r(e,r){let s=i.get(e);s||(s=new Map,i.set(e,s));let n=JSON.stringify(r);if(s.has(n))return s.get(n);let o=t(e,r);return s.set(n,o),o}return{matcher:r,match:r}}var h=class extends Error{constructor(e,t){super(e),this.code=t,this.name="JoinError"}},p=e=>e&&"field"in e&&"operator"in e&&"value"in e,d=(e,t)=>{if(e){if(p(e)&&e){let i=(e=>"object"==typeof e&&null!==e&&"type"in e&&"field"===e.type)(e.value)?((e,t)=>t.split(".").reduce(((e,t)=>e?.[t]),e))(t,e.value.field):e.value;return{...e,value:i}}if((e=>"operator"in e&&"conditions"in e)(e)){let i={...e};return e.conditions&&(i.conditions=e.conditions.map((e=>d(e,t)))),i}return e}},f=async(e,t,i,r)=>{try{if(((e,t)=>{if(!e.relation)throw new h("Join configuration must specify a relation","INVALID_CONFIG");if(!t[e.relation])throw new h(`Collection "${e.relation}" not found in database`,"COLLECTION_NOT_FOUND");if(e.alias&&"string"!=typeof e.alias)throw new h("Join alias must be a string","INVALID_ALIAS")})(i,e),!Array.isArray(t))throw new h("Source data must be an array","INVALID_SOURCE_DATA");let s,n=e[i.relation];return i.query?.filters&&p(i.query.filters)&&(s=((e,t)=>{let i=new Map;return e.forEach((e=>{let r=e[t];i.has(r)||i.set(r,[]),i.get(r).push(e)})),i})(n,i.query.filters.field)),(await Promise.all(t.map((async e=>{let t,o=((e,t)=>{if(!e)return{};let i=d(e.filters,t);return{...e,filters:i}})(i.query,e);return t=o.filters&&p(o.filters)&&s?.has(o.filters.value)?s.get(o.filters.value)||[]:n.filter((e=>r.matcher(e,o.filters))),((e,t,i)=>{let r=i.alias||i.relation;return[{...e,[r]:t}]})(e,await[e=>o.sort?r.sorter(e,o.sort):e,e=>o.projection?r.projector(e,o.projection):e,async e=>o.pagination?await r.paginator(e,o.pagination):e].reduce((async(e,t)=>t(await e)),Promise.resolve(t)),i)})))).flat()}catch(e){throw e instanceof h?e:new h(`Join operation failed: ${e.message}`,"JOIN_EXECUTION_ERROR")}},y=async(e,t,i,r)=>i.reduce((async(t,i)=>f(e,await t,i,r)),Promise.resolve(t));function g(){return{join:y}}var m=class extends Error{constructor(e,t){super(`Unsupported comparison between ${typeof e} and ${typeof t}`),this.name="UnsupportedComparisonError"}},w=class extends Error{constructor(e){super(`Unsupported sort direction: ${e}`),this.name="InvalidSortDirectionError"}};function v(e,t,i){if(e===t)return 0;if(null==e||null==t)return null==e?"asc"===i?-1:1:"asc"===i?1:-1;if("string"==typeof e&&"string"==typeof t)return"asc"===i?e.localeCompare(t):t.localeCompare(e);if("number"==typeof e&&"number"==typeof t)return"asc"===i?e-t:t-e;throw new m(e,t)}function _(e,t){let i=Array.from(e);return 0===t.length?i:i.sort(((e,i)=>{for(let r of t){let{field:t,direction:s}=r,n=e[t],o=i[t];try{if("asc"!==s&&"desc"!==s)throw new w(s);let e=v(n,o,s);if(0!==e)return e}catch(e){throw e instanceof m||e instanceof w?e:new Error(`Error comparing field '${t}': ${e.message}`)}}return 0}))}function b(){return{sort:_}}function E(e){let{resolve:t}=u(e);function i(e,r){let s={};return r.include?.length&&function(e,t,r){for(let s of t)if("string"!=typeof s){for(let[t,n]of Object.entries(s))if(Object.prototype.hasOwnProperty.call(e,t)){let s=i(e[t],n);r[t]=s}}else{let t=s;r[t]=e[t]}}(e,r.include,s),r.exclude?.length&&function(e,t,r){0===Object.keys(r).length&&Object.assign(r,e);for(let e of t)if("string"!=typeof e)for(let[t,s]of Object.entries(e)){if(!Object.prototype.hasOwnProperty.call(r,t))continue;let e=r[t];e&&"object"==typeof e?r[t]=i(e,s):delete r[t]}else delete r[e]}(e,r.exclude,s),r.computed?.length&&function(e,i,r){for(let s of i)r[s.alias]=t(e,s)}(e,r.computed,s),s}return{project:i}}async function*S(e,t,i=e=>String(e)){"offset"===t.type?yield*async function*(e,t){let{offset:i,limit:r}=t,s=0,n=r,o=[];for await(let t of e)n<=0&&(yield o,o=[],n=r),s<i?s++:(o.push(t),n--);o.length>0&&(yield o)}(e,t):yield*async function*(e,t,i){let{cursor:r,limit:s,direction:n}=t,o=s,a=void 0===r,c=[];if("forward"===n)for await(let t of e)o<=0&&(yield c,c=[],o=s),a?(c.push(t),o--):a=i(t)===r;else{let t=[];for await(let i of e)t.push(i);for(let e=t.length-1;e>=0;e--){let n=t[e];a?(c.push(n),o--,o<=0&&(yield c,c=[],o=s)):a=i(n)===r}}c.length>0&&(yield c)}(e,t,i)}function q(){return{paginate:S}}}},function(){return t||(0,e[a(e)[0]])((t={exports:{}}).exports,t),t.exports}),h=((e,t,i)=>(i=null!=e?s(c(e)):{},((e,t,i,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of a(t))u.call(e,s)||s===i||n(e,s,{get:()=>t[s],enumerable:!(r=o(t,s))||r.enumerable});return e})(e&&e.__esModule?i:n(i,"default",{value:e,enumerable:!0}),e)))(l()),p=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},d=class extends p{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},f=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const i=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await i;let r;await Promise.race([i.then((()=>clearTimeout(r))),new Promise(((i,s)=>{r=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new d("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},y=class{mutex=new f({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,t,i="Operation timed out"){if(null==t)return e;let r;return Promise.race([e.then((e=>(clearTimeout(r),e))),new Promise(((e,s)=>{r=setTimeout((()=>s(new d(i))),t)}))])}},g=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new y({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}},m=class e{static dbResources=new Map;static eventBusMap=new Map;static createDatabaseFactory(e){return async()=>{const{database:t,enableTelemetry:i=!1,collection:s="stores"}=e,n=await r.DatabaseConnection({database:t,enableTelemetry:i},r.createIndexedDbStore);try{await n.createCollection({name:s,version:"1.0.0",nestedSchemas:{},fields:{store:{name:"store",type:"string",required:!0},data:{name:"data",type:"object",required:!0},version:{name:"version",type:"string",required:!0},app:{name:"app",type:"string",required:!0}}})}catch(e){if(e instanceof r.DatabaseError&&"SCHEMA_ALREADY_EXISTS"!==e.type)throw e}return n}}static createDatabaseCleanup(t){return i=>{i?.close();const r=[];e.eventBusMap.forEach(((e,i)=>{i.startsWith(`${t}_store_`)&&(e.clear(),r.push(i))})),r.forEach((t=>e.eventBusMap.delete(t)))}}static async acquireDatabase(t){const i=t.database;if(!e.dbResources.has(i)){const r=new g(e.createDatabaseFactory(t),e.createDatabaseCleanup(i),{gracePeriod:"microtask"});e.dbResources.set(i,r)}return e.dbResources.get(i).acquire()}static releaseDatabase(t){const i=e.dbResources.get(t);i&&i.release()}static async getCollection(t){const i=await e.acquireDatabase(t),r=t.collection??"stores";return i.collection(r)}static getEventBus(t,r){const s=`${t}_store_${r}`;return e.eventBusMap.has(s)||e.eventBusMap.set(s,i.createEventBus({errorHandler:e=>console.error(`Event bus error for ${t}:${r}:`,e),broadcast:{channel:s}})),e.eventBusMap.get(s)}};exports.EphemeralPersistence=class{eventBus;inMemoryState=null;config;constructor(e){this.config=e,this.eventBus=this.initializeEventBus(),this.setupLwwSynchronizationListener()}initializeEventBus(){const e={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey} (Ephemeral LWW):`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(e.broadcast={channel:`ephemeral_lww_${this.config.storageKey}`}),i.createEventBus(e)}setupLwwSynchronizationListener(){this.eventBus.subscribe("store:updated",(({storageKey:e,instanceId:t,state:i,timestamp:r,version:s})=>{e===this.config.storageKey&&(!this.inMemoryState||r&&r>this.inMemoryState.timestamp)&&(this.inMemoryState={data:i,timestamp:r,instanceId:t,version:s})}))}set(e,t){try{const i=Date.now();return this.inMemoryState={data:structuredClone(t),timestamp:i,instanceId:e,version:this.config.version},this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,timestamp:i,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to set state in EphemeralPersistence (LWW) for ${this.config.storageKey}:`,e),!1}}get(){return this.inMemoryState?.data??null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{const e=Date.now(),t="clear-initiator";return this.inMemoryState={data:null,timestamp:e,instanceId:t,version:this.config.version},this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:t,state:null,timestamp:e,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear state in EphemeralPersistence (LWW) for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}},exports.IndexedDBPersistence=class{collection=null;collectionPromise;config;eventBus;initialized=!1;_initializing=!1;initializationCallbacks=[];doc=null;getStoreName(){return`_${this.config.app}_${this.config.store}_`}constructor(e){this.config=e,this.collectionPromise=m.getCollection(this.config),this.collectionPromise.then((e=>{this.collection=e,this.initialize()})).catch((e=>{console.error(`Failed to initialize collection for store ${this.getStoreName()}:`,e)})),this.eventBus=m.getEventBus(this.config.database,this.getStoreName()),this.eventBus.subscribe("store:updated",(({storageKey:e})=>{e===this.getStoreName()&&(this.doc=null)}))}async initialize(){this._initializing=!0;try{const e=await this._get();if(e&&e.version!==this.config.version&&this.config.onUpgrade){const{state:t}=await this.config.onUpgrade({data:e.data,version:e.version,app:e.app});await this.set("migration",t)}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize and upgrade store ${this.getStoreName()}:`,e)}finally{this._initializing=!1}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}async getCollection(){return this.collection??this.collectionPromise}async set(e,t){this.initialized||this._initializing||await new Promise((e=>this._onInitialized(e)));try{const i=await this.getCollection(),r=await this._read(),s={store:this.getStoreName(),data:t,version:this.config.version,app:this.config.app};let n;return r?n=await r.update(s):(this.doc=await i.create(s),n=!0),n&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:e,state:t,version:this.config.version,app:this.config.app}}),n}catch(t){return console.error(`Failed to set state for store ${this.getStoreName()} in database ${this.config.database} by instance ${e}:`,t),!1}}async _read(){if(this.doc)return this.doc;const e=await this.getCollection(),t=(new h.QueryBuilder).where({field:"store",operator:"eq",value:this.getStoreName()}).build();return this.doc=await e.find(t.filters),this.doc}async _get(){try{const e=await this._read();return e?e.read().then((()=>e)):null}catch(e){return console.error(`Failed to get state for store ${this.getStoreName()} in database ${this.config.database}:`,e),null}}async get(){this.initialized||await new Promise((e=>this._onInitialized(e)));const e=await this._get();return e?e.data:null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.getStoreName()&&r!==e&&t(s)}))}async clear(){this.initialized||await new Promise((e=>this._onInitialized(e)));try{const e=await this._read();return!e||!await e.delete()||(this.doc=null,this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:"",state:null,version:this.config.version,app:this.config.app}}),!0)}catch(e){return console.error(`Failed to clear state for store ${this.getStoreName()} in database ${this.config.database}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}async close(){m.releaseDatabase(this.config.database)}},exports.WebStoragePersistence=class{eventBus;storage;config;initialized=!1;initializationCallbacks=[];constructor(e){this.config=e,this.storage=e.session?sessionStorage:localStorage,this.eventBus=this.initializeEventBus(),this.initialize(),e.session||this.setupStorageEventListener()}async initialize(){try{const e=this.storage.getItem(this.getStoreName());if(e){const t=JSON.parse(e);if(t.version!==this.config.version&&this.config.onUpgrade){const{state:e}=await this.config.onUpgrade({data:t.state,version:t.version,app:t.app});this.set("migration",e)}}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize WebStoragePersistence for ${this.config.storageKey}:`,e)}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}initializeEventBus(){const e={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey}:`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(e.broadcast={channel:`storage_${this.getStoreName()}`}),i.createEventBus(e)}getStoreName(){return`_${this.config.app}_${this.config.storageKey}_`}setupStorageEventListener(){window.addEventListener("storage",(e=>{if(e.key===this.getStoreName()&&e.newValue)try{const t=JSON.parse(e.newValue);t&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"external",state:t.state,version:t.version,app:t.app}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const i={state:structuredClone(t),version:this.config.version,app:this.config.app},r=JSON.stringify(i);return this.storage.setItem(this.getStoreName(),r),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to persist state to web storage for ${this.config.storageKey}:`,e),!1}}_get(){try{const e=this.storage.getItem(this.getStoreName());if(!e)return null;return JSON.parse(e).state}catch(e){return console.error(`Failed to retrieve state from web storage for ${this.config.storageKey}:`,e),null}}async get(){return this.initialized||await new Promise((e=>{this._onInitialized(e)})),this._get()}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{return this.storage.removeItem(this.getStoreName()),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"clear-initiator",state:null,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}};
package/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createEventBus as e}from"@asaidimu/utils-events";import{DatabaseConnection as t,createIndexedDbStore as i,DatabaseError as r}from"@asaidimu/utils-database";var s,n,o=Object.create,a=Object.defineProperty,c=Object.getOwnPropertyDescriptor,u=Object.getOwnPropertyNames,l=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty,p=(s={"node_modules/@asaidimu/query/index.js"(e,t){var i,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var i in t)r(e,i,{get:t[i],enumerable:!0})})(a,{QueryBuilder:()=>c,createJoiner:()=>g,createMatcher:()=>l,createPaginator:()=>O,createProjector:()=>k,createSorter:()=>b}),t.exports=(i=a,((e,t,i,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))!o.call(e,c)&&c!==i&&r(e,c,{get:()=>t[c],enumerable:!(a=s(t,c))||a.enumerable});return e})(r({},"__esModule",{value:!0}),i));var c=class{query;constructor(){this.query={}}where(e){return this.query.filters=e,this}orderBy(e,t){return this.query.sort||(this.query.sort=[]),this.query.sort.push({field:e,direction:t}),this}offset(e,t){return this.query.pagination={type:"offset",offset:e,limit:t},this}cursor(e,t,i){return this.query.pagination={type:"cursor",cursor:e,limit:t,direction:i},this}include(e){return this.query.projection||(this.query.projection={}),this.query.projection.include=e,this}exclude(e){return this.query.projection||(this.query.projection={}),this.query.projection.exclude=e,this}computed(e,t){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"computed",expression:e,alias:t}),this}case(e,t,i){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"case",conditions:e,else:t,alias:i}),this}join(e,t,i){return this.query.joins||(this.query.joins=[]),this.query.joins.push({relation:e,alias:i,query:t}),this}aggregate(e,t){return this.query.aggregations={groupBy:e,metrics:t},this}window(e){return this.query.window||(this.query.window=[]),this.query.window.push(e),this}hint(e){return this.query.hints||(this.query.hints=[]),this.query.hints.push(e),this}build(){return this.query}};function u(e){function t(e,t){return function(e){return"field"===e?.type}(t)?e[t.field]:function(e){return"value"===e?.type}(t)?t.value:function(e){return"function"===e?.type}(t)?r(t,e):function(e){return"computed"===e?.type}(t)?r(t.expression,e):function(e){return"case"===e?.type}(t)?function(e,t){for(let i of t.conditions)if(s(e,i.when))return i.then;return t.else}(e,t):t}let i=new Map([["and",(e,t)=>t.every((t=>s(e,t)))],["or",(e,t)=>t.some((t=>s(e,t)))],["not",(e,t)=>!t.every((t=>s(e,t)))],["nor",(e,t)=>!t.some((t=>s(e,t)))],["xor",(e,t)=>1===t.filter((t=>s(e,t))).length]]);function r(i,r){let s=i.arguments.map((e=>t(r,e)));if(e[i.function])return e[i.function](...s);throw new Error(`Function ${i.function} not found!`)}function s(r,s){if(function(e){return!!e&&void 0!==e.conditions}(s))return function(e,t){let{operator:r,conditions:s}=t,n=i.get(r);if(n)return n(e,s);throw new Error(`Unsupported logical operator: ${r}`)}(r,s);if(!s||!s.field)return!1;let{field:n,operator:o,value:a}=s,c=r[n],u=t(r,a),l=new Map([["eq",(e,t)=>e===t],["neq",(e,t)=>e!==t],["lt",(e,t)=>e<t],["lte",(e,t)=>e<=t],["gt",(e,t)=>e>t],["gte",(e,t)=>e>=t],["in",(e,t)=>Array.isArray(t)&&t.includes(e)],["nin",(e,t)=>Array.isArray(t)&&!t.includes(e)],["contains",(e,t)=>"string"==typeof e?e.includes(t):!!Array.isArray(e)&&e.includes(a)],["ncontains",(e,t)=>"string"==typeof e&&!e.includes(t)],["startswith",(e,t)=>"string"==typeof e&&e.startsWith(t)],["endswith",(e,t)=>"string"==typeof e&&e.endsWith(t)],["exists",e=>null!=e],["nexists",e=>null==e]]),h=e[o]||l.get(o);if(h)return h(c,u);throw new Error(`Unsupported comparison operator: ${o}`)}return{resolve:t,evaluate:s}}function l(e){let{evaluate:t}=u(e),i=new WeakMap;function r(e,r){let s=i.get(e);s||(s=new Map,i.set(e,s));let n=JSON.stringify(r);if(s.has(n))return s.get(n);let o=t(e,r);return s.set(n,o),o}return{matcher:r,match:r}}var h=class extends Error{constructor(e,t){super(e),this.code=t,this.name="JoinError"}},p=e=>e&&"field"in e&&"operator"in e&&"value"in e,d=(e,t)=>{if(e){if(p(e)&&e){let i=(e=>"object"==typeof e&&null!==e&&"type"in e&&"field"===e.type)(e.value)?((e,t)=>t.split(".").reduce(((e,t)=>e?.[t]),e))(t,e.value.field):e.value;return{...e,value:i}}if((e=>"operator"in e&&"conditions"in e)(e)){let i={...e};return e.conditions&&(i.conditions=e.conditions.map((e=>d(e,t)))),i}return e}},f=async(e,t,i,r)=>{try{if(((e,t)=>{if(!e.relation)throw new h("Join configuration must specify a relation","INVALID_CONFIG");if(!t[e.relation])throw new h(`Collection "${e.relation}" not found in database`,"COLLECTION_NOT_FOUND");if(e.alias&&"string"!=typeof e.alias)throw new h("Join alias must be a string","INVALID_ALIAS")})(i,e),!Array.isArray(t))throw new h("Source data must be an array","INVALID_SOURCE_DATA");let s,n=e[i.relation];return i.query?.filters&&p(i.query.filters)&&(s=((e,t)=>{let i=new Map;return e.forEach((e=>{let r=e[t];i.has(r)||i.set(r,[]),i.get(r).push(e)})),i})(n,i.query.filters.field)),(await Promise.all(t.map((async e=>{let t,o=((e,t)=>{if(!e)return{};let i=d(e.filters,t);return{...e,filters:i}})(i.query,e);return t=o.filters&&p(o.filters)&&s?.has(o.filters.value)?s.get(o.filters.value)||[]:n.filter((e=>r.matcher(e,o.filters))),((e,t,i)=>{let r=i.alias||i.relation;return[{...e,[r]:t}]})(e,await[e=>o.sort?r.sorter(e,o.sort):e,e=>o.projection?r.projector(e,o.projection):e,async e=>o.pagination?await r.paginator(e,o.pagination):e].reduce((async(e,t)=>t(await e)),Promise.resolve(t)),i)})))).flat()}catch(e){throw e instanceof h?e:new h(`Join operation failed: ${e.message}`,"JOIN_EXECUTION_ERROR")}},y=async(e,t,i,r)=>i.reduce((async(t,i)=>f(e,await t,i,r)),Promise.resolve(t));function g(){return{join:y}}var m=class extends Error{constructor(e,t){super(`Unsupported comparison between ${typeof e} and ${typeof t}`),this.name="UnsupportedComparisonError"}},w=class extends Error{constructor(e){super(`Unsupported sort direction: ${e}`),this.name="InvalidSortDirectionError"}};function v(e,t,i){if(e===t)return 0;if(null==e||null==t)return null==e?"asc"===i?-1:1:"asc"===i?1:-1;if("string"==typeof e&&"string"==typeof t)return"asc"===i?e.localeCompare(t):t.localeCompare(e);if("number"==typeof e&&"number"==typeof t)return"asc"===i?e-t:t-e;throw new m(e,t)}function _(e,t){let i=Array.from(e);return 0===t.length?i:i.sort(((e,i)=>{for(let r of t){let{field:t,direction:s}=r,n=e[t],o=i[t];try{if("asc"!==s&&"desc"!==s)throw new w(s);let e=v(n,o,s);if(0!==e)return e}catch(e){throw e instanceof m||e instanceof w?e:new Error(`Error comparing field '${t}': ${e.message}`)}}return 0}))}function b(){return{sort:_}}function k(e){let{resolve:t}=u(e);function i(e,r){let s={};return r.include?.length&&function(e,t,r){for(let s of t)if("string"!=typeof s){for(let[t,n]of Object.entries(s))if(Object.prototype.hasOwnProperty.call(e,t)){let s=i(e[t],n);r[t]=s}}else{let t=s;r[t]=e[t]}}(e,r.include,s),r.exclude?.length&&function(e,t,r){0===Object.keys(r).length&&Object.assign(r,e);for(let e of t)if("string"!=typeof e)for(let[t,s]of Object.entries(e)){if(!Object.prototype.hasOwnProperty.call(r,t))continue;let e=r[t];e&&"object"==typeof e?r[t]=i(e,s):delete r[t]}else delete r[e]}(e,r.exclude,s),r.computed?.length&&function(e,i,r){for(let s of i)r[s.alias]=t(e,s)}(e,r.computed,s),s}return{project:i}}async function*q(e,t,i=e=>String(e)){"offset"===t.type?yield*async function*(e,t){let{offset:i,limit:r}=t,s=0,n=r,o=[];for await(let t of e)n<=0&&(yield o,o=[],n=r),s<i?s++:(o.push(t),n--);o.length>0&&(yield o)}(e,t):yield*async function*(e,t,i){let{cursor:r,limit:s,direction:n}=t,o=s,a=void 0===r,c=[];if("forward"===n)for await(let t of e)o<=0&&(yield c,c=[],o=s),a?(c.push(t),o--):a=i(t)===r;else{let t=[];for await(let i of e)t.push(i);for(let e=t.length-1;e>=0;e--){let n=t[e];a?(c.push(n),o--,o<=0&&(yield c,c=[],o=s)):a=i(n)===r}}c.length>0&&(yield c)}(e,t,i)}function O(){return{paginate:q}}}},function(){return n||(0,s[u(s)[0]])((n={exports:{}}).exports,n),n.exports}),d=class{eventBus;storage;config;initialized=!1;initializationCallbacks=[];constructor(e){this.config=e,this.storage=e.session?sessionStorage:localStorage,this.eventBus=this.initializeEventBus(),this.initialize(),e.session||this.setupStorageEventListener()}async initialize(){try{const e=this.storage.getItem(this.getStoreName());if(e){const t=JSON.parse(e);if(t.version!==this.config.version&&this.config.onUpgrade){const{state:e}=await this.config.onUpgrade({data:t.state,version:t.version,app:t.app});this.set("migration",e)}}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize WebStoragePersistence for ${this.config.storageKey}:`,e)}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}initializeEventBus(){const t={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey}:`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(t.broadcast={channel:`storage_${this.getStoreName()}`}),e(t)}getStoreName(){return`_${this.config.app}_${this.config.storageKey}_`}setupStorageEventListener(){window.addEventListener("storage",(e=>{if(e.key===this.getStoreName()&&e.newValue)try{const t=JSON.parse(e.newValue);t&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"external",state:t.state,version:t.version,app:t.app}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const i={state:structuredClone(t),version:this.config.version,app:this.config.app},r=JSON.stringify(i);return this.storage.setItem(this.getStoreName(),r),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to persist state to web storage for ${this.config.storageKey}:`,e),!1}}_get(){try{const e=this.storage.getItem(this.getStoreName());if(!e)return null;return JSON.parse(e).state}catch(e){return console.error(`Failed to retrieve state from web storage for ${this.config.storageKey}:`,e),null}}async get(){return this.initialized||await new Promise((e=>{this._onInitialized(e)})),this._get()}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{return this.storage.removeItem(this.getStoreName()),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"clear-initiator",state:null,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}},f=((e,t,i)=>(i=null!=e?o(l(e)):{},((e,t,i,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of u(t))h.call(e,s)||s===i||a(e,s,{get:()=>t[s],enumerable:!(r=c(t,s))||r.enumerable});return e})(e&&e.__esModule?i:a(i,"default",{value:e,enumerable:!0}),e)))(p()),y=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},g=class extends y{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},m=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const i=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await i;let r;await Promise.race([i.then((()=>clearTimeout(r))),new Promise(((i,s)=>{r=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new g("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},w=class{mutex=new m({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,t,i="Operation timed out"){if(null==t)return e;let r;return Promise.race([e.then((e=>(clearTimeout(r),e))),new Promise(((e,s)=>{r=setTimeout((()=>s(new g(i))),t)}))])}},v=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new w({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}},_=class s{static dbResources=new Map;static eventBusMap=new Map;static createDatabaseFactory(e){return async()=>{const{database:s,enableTelemetry:n=!1,collection:o="stores"}=e,a=await t({database:s,enableTelemetry:n},i);try{await a.createCollection({name:o,version:"1.0.0",nestedSchemas:{},fields:{store:{name:"store",type:"string",required:!0},data:{name:"data",type:"object",required:!0},version:{name:"version",type:"string",required:!0},app:{name:"app",type:"string",required:!0}}})}catch(e){if(e instanceof r&&"SCHEMA_ALREADY_EXISTS"!==e.type)throw e}return a}}static createDatabaseCleanup(e){return t=>{t?.close();const i=[];s.eventBusMap.forEach(((t,r)=>{r.startsWith(`${e}_store_`)&&(t.clear(),i.push(r))})),i.forEach((e=>s.eventBusMap.delete(e)))}}static async acquireDatabase(e){const t=e.database;if(!s.dbResources.has(t)){const i=new v(s.createDatabaseFactory(e),s.createDatabaseCleanup(t),{gracePeriod:"microtask"});s.dbResources.set(t,i)}return s.dbResources.get(t).acquire()}static releaseDatabase(e){const t=s.dbResources.get(e);t&&t.release()}static async getCollection(e){const t=await s.acquireDatabase(e),i=e.collection??"stores";return t.collection(i)}static getEventBus(t,i){const r=`${t}_store_${i}`;return s.eventBusMap.has(r)||s.eventBusMap.set(r,e({errorHandler:e=>console.error(`Event bus error for ${t}:${i}:`,e),broadcast:{channel:r}})),s.eventBusMap.get(r)}},b=class{collection=null;collectionPromise;config;eventBus;initialized=!1;_initializing=!1;initializationCallbacks=[];doc=null;getStoreName(){return`_${this.config.app}_${this.config.store}_`}constructor(e){this.config=e,this.collectionPromise=_.getCollection(this.config),this.collectionPromise.then((e=>{this.collection=e,this.initialize()})).catch((e=>{console.error(`Failed to initialize collection for store ${this.getStoreName()}:`,e)})),this.eventBus=_.getEventBus(this.config.database,this.getStoreName()),this.eventBus.subscribe("store:updated",(({storageKey:e})=>{e===this.getStoreName()&&(this.doc=null)}))}async initialize(){this._initializing=!0;try{const e=await this._get();if(e&&e.version!==this.config.version&&this.config.onUpgrade){const{state:t}=await this.config.onUpgrade({data:e.data,version:e.version,app:e.app});await this.set("migration",t)}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize and upgrade store ${this.getStoreName()}:`,e)}finally{this._initializing=!1}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}async getCollection(){return this.collection??this.collectionPromise}async set(e,t){this.initialized||this._initializing||await new Promise((e=>this._onInitialized(e)));try{const i=await this.getCollection(),r=await this._read(),s={store:this.getStoreName(),data:t,version:this.config.version,app:this.config.app};let n;return r?n=await r.update(s):(this.doc=await i.create(s),n=!0),n&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:e,state:t,version:this.config.version,app:this.config.app}}),n}catch(t){return console.error(`Failed to set state for store ${this.getStoreName()} in database ${this.config.database} by instance ${e}:`,t),!1}}async _read(){if(this.doc)return this.doc;const e=await this.getCollection(),t=(new f.QueryBuilder).where({field:"store",operator:"eq",value:this.getStoreName()}).build();return this.doc=await e.find(t.filters),this.doc}async _get(){try{const e=await this._read();return e?e.read().then((()=>e)):null}catch(e){return console.error(`Failed to get state for store ${this.getStoreName()} in database ${this.config.database}:`,e),null}}async get(){this.initialized||await new Promise((e=>this._onInitialized(e)));const e=await this._get();return e?e.data:null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.getStoreName()&&r!==e&&t(s)}))}async clear(){this.initialized||await new Promise((e=>this._onInitialized(e)));try{const e=await this._read();return!e||!await e.delete()||(this.doc=null,this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:"",state:null,version:this.config.version,app:this.config.app}}),!0)}catch(e){return console.error(`Failed to clear state for store ${this.getStoreName()} in database ${this.config.database}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}async close(){_.releaseDatabase(this.config.database)}};export{b as IndexedDBPersistence,d as WebStoragePersistence};
1
+ import{createEventBus as e}from"@asaidimu/utils-events";import{DatabaseConnection as t,createIndexedDbStore as i,DatabaseError as r}from"@asaidimu/utils-database";var s,n,o=Object.create,a=Object.defineProperty,c=Object.getOwnPropertyDescriptor,u=Object.getOwnPropertyNames,l=Object.getPrototypeOf,h=Object.prototype.hasOwnProperty,p=(s={"node_modules/@asaidimu/query/index.js"(e,t){var i,r=Object.defineProperty,s=Object.getOwnPropertyDescriptor,n=Object.getOwnPropertyNames,o=Object.prototype.hasOwnProperty,a={};((e,t)=>{for(var i in t)r(e,i,{get:t[i],enumerable:!0})})(a,{QueryBuilder:()=>c,createJoiner:()=>g,createMatcher:()=>l,createPaginator:()=>q,createProjector:()=>S,createSorter:()=>b}),t.exports=(i=a,((e,t,i,a)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let c of n(t))!o.call(e,c)&&c!==i&&r(e,c,{get:()=>t[c],enumerable:!(a=s(t,c))||a.enumerable});return e})(r({},"__esModule",{value:!0}),i));var c=class{query;constructor(){this.query={}}where(e){return this.query.filters=e,this}orderBy(e,t){return this.query.sort||(this.query.sort=[]),this.query.sort.push({field:e,direction:t}),this}offset(e,t){return this.query.pagination={type:"offset",offset:e,limit:t},this}cursor(e,t,i){return this.query.pagination={type:"cursor",cursor:e,limit:t,direction:i},this}include(e){return this.query.projection||(this.query.projection={}),this.query.projection.include=e,this}exclude(e){return this.query.projection||(this.query.projection={}),this.query.projection.exclude=e,this}computed(e,t){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"computed",expression:e,alias:t}),this}case(e,t,i){return this.query.projection||(this.query.projection={}),this.query.projection.computed||(this.query.projection.computed=[]),this.query.projection.computed.push({type:"case",conditions:e,else:t,alias:i}),this}join(e,t,i){return this.query.joins||(this.query.joins=[]),this.query.joins.push({relation:e,alias:i,query:t}),this}aggregate(e,t){return this.query.aggregations={groupBy:e,metrics:t},this}window(e){return this.query.window||(this.query.window=[]),this.query.window.push(e),this}hint(e){return this.query.hints||(this.query.hints=[]),this.query.hints.push(e),this}build(){return this.query}};function u(e){function t(e,t){return function(e){return"field"===e?.type}(t)?e[t.field]:function(e){return"value"===e?.type}(t)?t.value:function(e){return"function"===e?.type}(t)?r(t,e):function(e){return"computed"===e?.type}(t)?r(t.expression,e):function(e){return"case"===e?.type}(t)?function(e,t){for(let i of t.conditions)if(s(e,i.when))return i.then;return t.else}(e,t):t}let i=new Map([["and",(e,t)=>t.every((t=>s(e,t)))],["or",(e,t)=>t.some((t=>s(e,t)))],["not",(e,t)=>!t.every((t=>s(e,t)))],["nor",(e,t)=>!t.some((t=>s(e,t)))],["xor",(e,t)=>1===t.filter((t=>s(e,t))).length]]);function r(i,r){let s=i.arguments.map((e=>t(r,e)));if(e[i.function])return e[i.function](...s);throw new Error(`Function ${i.function} not found!`)}function s(r,s){if(function(e){return!!e&&void 0!==e.conditions}(s))return function(e,t){let{operator:r,conditions:s}=t,n=i.get(r);if(n)return n(e,s);throw new Error(`Unsupported logical operator: ${r}`)}(r,s);if(!s||!s.field)return!1;let{field:n,operator:o,value:a}=s,c=r[n],u=t(r,a),l=new Map([["eq",(e,t)=>e===t],["neq",(e,t)=>e!==t],["lt",(e,t)=>e<t],["lte",(e,t)=>e<=t],["gt",(e,t)=>e>t],["gte",(e,t)=>e>=t],["in",(e,t)=>Array.isArray(t)&&t.includes(e)],["nin",(e,t)=>Array.isArray(t)&&!t.includes(e)],["contains",(e,t)=>"string"==typeof e?e.includes(t):!!Array.isArray(e)&&e.includes(a)],["ncontains",(e,t)=>"string"==typeof e&&!e.includes(t)],["startswith",(e,t)=>"string"==typeof e&&e.startsWith(t)],["endswith",(e,t)=>"string"==typeof e&&e.endsWith(t)],["exists",e=>null!=e],["nexists",e=>null==e]]),h=e[o]||l.get(o);if(h)return h(c,u);throw new Error(`Unsupported comparison operator: ${o}`)}return{resolve:t,evaluate:s}}function l(e){let{evaluate:t}=u(e),i=new WeakMap;function r(e,r){let s=i.get(e);s||(s=new Map,i.set(e,s));let n=JSON.stringify(r);if(s.has(n))return s.get(n);let o=t(e,r);return s.set(n,o),o}return{matcher:r,match:r}}var h=class extends Error{constructor(e,t){super(e),this.code=t,this.name="JoinError"}},p=e=>e&&"field"in e&&"operator"in e&&"value"in e,d=(e,t)=>{if(e){if(p(e)&&e){let i=(e=>"object"==typeof e&&null!==e&&"type"in e&&"field"===e.type)(e.value)?((e,t)=>t.split(".").reduce(((e,t)=>e?.[t]),e))(t,e.value.field):e.value;return{...e,value:i}}if((e=>"operator"in e&&"conditions"in e)(e)){let i={...e};return e.conditions&&(i.conditions=e.conditions.map((e=>d(e,t)))),i}return e}},f=async(e,t,i,r)=>{try{if(((e,t)=>{if(!e.relation)throw new h("Join configuration must specify a relation","INVALID_CONFIG");if(!t[e.relation])throw new h(`Collection "${e.relation}" not found in database`,"COLLECTION_NOT_FOUND");if(e.alias&&"string"!=typeof e.alias)throw new h("Join alias must be a string","INVALID_ALIAS")})(i,e),!Array.isArray(t))throw new h("Source data must be an array","INVALID_SOURCE_DATA");let s,n=e[i.relation];return i.query?.filters&&p(i.query.filters)&&(s=((e,t)=>{let i=new Map;return e.forEach((e=>{let r=e[t];i.has(r)||i.set(r,[]),i.get(r).push(e)})),i})(n,i.query.filters.field)),(await Promise.all(t.map((async e=>{let t,o=((e,t)=>{if(!e)return{};let i=d(e.filters,t);return{...e,filters:i}})(i.query,e);return t=o.filters&&p(o.filters)&&s?.has(o.filters.value)?s.get(o.filters.value)||[]:n.filter((e=>r.matcher(e,o.filters))),((e,t,i)=>{let r=i.alias||i.relation;return[{...e,[r]:t}]})(e,await[e=>o.sort?r.sorter(e,o.sort):e,e=>o.projection?r.projector(e,o.projection):e,async e=>o.pagination?await r.paginator(e,o.pagination):e].reduce((async(e,t)=>t(await e)),Promise.resolve(t)),i)})))).flat()}catch(e){throw e instanceof h?e:new h(`Join operation failed: ${e.message}`,"JOIN_EXECUTION_ERROR")}},y=async(e,t,i,r)=>i.reduce((async(t,i)=>f(e,await t,i,r)),Promise.resolve(t));function g(){return{join:y}}var m=class extends Error{constructor(e,t){super(`Unsupported comparison between ${typeof e} and ${typeof t}`),this.name="UnsupportedComparisonError"}},w=class extends Error{constructor(e){super(`Unsupported sort direction: ${e}`),this.name="InvalidSortDirectionError"}};function v(e,t,i){if(e===t)return 0;if(null==e||null==t)return null==e?"asc"===i?-1:1:"asc"===i?1:-1;if("string"==typeof e&&"string"==typeof t)return"asc"===i?e.localeCompare(t):t.localeCompare(e);if("number"==typeof e&&"number"==typeof t)return"asc"===i?e-t:t-e;throw new m(e,t)}function _(e,t){let i=Array.from(e);return 0===t.length?i:i.sort(((e,i)=>{for(let r of t){let{field:t,direction:s}=r,n=e[t],o=i[t];try{if("asc"!==s&&"desc"!==s)throw new w(s);let e=v(n,o,s);if(0!==e)return e}catch(e){throw e instanceof m||e instanceof w?e:new Error(`Error comparing field '${t}': ${e.message}`)}}return 0}))}function b(){return{sort:_}}function S(e){let{resolve:t}=u(e);function i(e,r){let s={};return r.include?.length&&function(e,t,r){for(let s of t)if("string"!=typeof s){for(let[t,n]of Object.entries(s))if(Object.prototype.hasOwnProperty.call(e,t)){let s=i(e[t],n);r[t]=s}}else{let t=s;r[t]=e[t]}}(e,r.include,s),r.exclude?.length&&function(e,t,r){0===Object.keys(r).length&&Object.assign(r,e);for(let e of t)if("string"!=typeof e)for(let[t,s]of Object.entries(e)){if(!Object.prototype.hasOwnProperty.call(r,t))continue;let e=r[t];e&&"object"==typeof e?r[t]=i(e,s):delete r[t]}else delete r[e]}(e,r.exclude,s),r.computed?.length&&function(e,i,r){for(let s of i)r[s.alias]=t(e,s)}(e,r.computed,s),s}return{project:i}}async function*k(e,t,i=e=>String(e)){"offset"===t.type?yield*async function*(e,t){let{offset:i,limit:r}=t,s=0,n=r,o=[];for await(let t of e)n<=0&&(yield o,o=[],n=r),s<i?s++:(o.push(t),n--);o.length>0&&(yield o)}(e,t):yield*async function*(e,t,i){let{cursor:r,limit:s,direction:n}=t,o=s,a=void 0===r,c=[];if("forward"===n)for await(let t of e)o<=0&&(yield c,c=[],o=s),a?(c.push(t),o--):a=i(t)===r;else{let t=[];for await(let i of e)t.push(i);for(let e=t.length-1;e>=0;e--){let n=t[e];a?(c.push(n),o--,o<=0&&(yield c,c=[],o=s)):a=i(n)===r}}c.length>0&&(yield c)}(e,t,i)}function q(){return{paginate:k}}}},function(){return n||(0,s[u(s)[0]])((n={exports:{}}).exports,n),n.exports}),d=class{eventBus;storage;config;initialized=!1;initializationCallbacks=[];constructor(e){this.config=e,this.storage=e.session?sessionStorage:localStorage,this.eventBus=this.initializeEventBus(),this.initialize(),e.session||this.setupStorageEventListener()}async initialize(){try{const e=this.storage.getItem(this.getStoreName());if(e){const t=JSON.parse(e);if(t.version!==this.config.version&&this.config.onUpgrade){const{state:e}=await this.config.onUpgrade({data:t.state,version:t.version,app:t.app});this.set("migration",e)}}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize WebStoragePersistence for ${this.config.storageKey}:`,e)}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}initializeEventBus(){const t={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey}:`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(t.broadcast={channel:`storage_${this.getStoreName()}`}),e(t)}getStoreName(){return`_${this.config.app}_${this.config.storageKey}_`}setupStorageEventListener(){window.addEventListener("storage",(e=>{if(e.key===this.getStoreName()&&e.newValue)try{const t=JSON.parse(e.newValue);t&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"external",state:t.state,version:t.version,app:t.app}})}catch(e){console.error("Failed to parse storage event data:",e)}}))}set(e,t){try{const i={state:structuredClone(t),version:this.config.version,app:this.config.app},r=JSON.stringify(i);return this.storage.setItem(this.getStoreName(),r),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to persist state to web storage for ${this.config.storageKey}:`,e),!1}}_get(){try{const e=this.storage.getItem(this.getStoreName());if(!e)return null;return JSON.parse(e).state}catch(e){return console.error(`Failed to retrieve state from web storage for ${this.config.storageKey}:`,e),null}}async get(){return this.initialized||await new Promise((e=>{this._onInitialized(e)})),this._get()}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{return this.storage.removeItem(this.getStoreName()),this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:"clear-initiator",state:null,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear persisted state for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}},f=((e,t,i)=>(i=null!=e?o(l(e)):{},((e,t,i,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let s of u(t))h.call(e,s)||s===i||a(e,s,{get:()=>t[s],enumerable:!(r=c(t,s))||r.enumerable});return e})(e&&e.__esModule?i:a(i,"default",{value:e,enumerable:!0}),e)))(p()),y=class e extends Error{constructor(t,i){super(t,{cause:i}),this.name="SyncError",Object.setPrototypeOf(this,e.prototype)}},g=class extends y{constructor(e){super(`[ArtifactContainer] Operation timed out: ${e}`)}},m=class{_locked=!1;_capacity;_yieldMode;waiters=[];constructor(e){this._capacity=e?.capacity??1/0,this._yieldMode=e?.yieldMode??"macrotask"}async lock(e){if(!this._locked)return void(this._locked=!0);if(this.waiters.length>=this._capacity)throw new Error(`Mutex queue is full (capacity: ${this._capacity})`);let t;const i=new Promise((e=>t=e));if(this.waiters.push(t),null==e)return void await i;let r;await Promise.race([i.then((()=>clearTimeout(r))),new Promise(((i,s)=>{r=setTimeout((()=>{const e=this.waiters.indexOf(t);-1!==e&&this.waiters.splice(e,1),s(new g("Mutex lock timed out"))}),e)}))])}tryLock(){return!this._locked&&(this._locked=!0,!0)}unlock(){if(!this._locked)throw new Error("Mutex is not locked");const e=this.waiters.shift();e?"microtask"===this._yieldMode?queueMicrotask(e):setTimeout(e,0):this._locked=!1}locked(){return this._locked}pending(){return this.waiters.length}},w=class{mutex=new m({yieldMode:"microtask"});promise=null;_value=null;_error;_done=!1;retry;throws;constructor({retry:e,throws:t}={}){this.retry=Boolean(e),this.throws=Boolean(t)}async do(e,t){return this._done?this.peek():this.promise?this._awaitWithTimeout(this.promise,t,"Once do() timed out"):(await this.mutex.lock(),this.promise?(this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")):(this.promise=(async()=>{try{this._value=await e(),this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.retry&&!this._done&&(this.promise=null)}return this.peek()})(),this.mutex.unlock(),this._awaitWithTimeout(this.promise,t,"Once do() timed out")))}doSync(e){if(this._done){if(this.throws&&this._error)throw this._error;return this.peek()}if(this.promise){const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}if(!this.mutex.tryLock()){const e=new Error("Cannot execute doSync: lock is currently held.");if(this.throws)throw e;return{value:null,error:e}}if(this.promise||this._done){if(this.mutex.unlock(),this._done){if(this.throws&&this._error)throw this._error;return this.peek()}const e=new Error("Cannot execute doSync while an async operation is pending.");if(this.throws)throw e;return{value:null,error:e}}try{const t=e();this._value=t,this._done=!0}catch(e){if(this._error=e,this.retry||(this._done=!0),this.throws)throw e}finally{this.mutex.unlock()}return this.peek()}running(){return null!==this.promise&&!this.done()}peek(){return{value:this._value,error:this._error}}get(){if(!this._done)throw new Error("Once operation is not yet complete");if(this._error)throw this._error;return this._value}reset(){if(this.running())throw new Error("Cannot reset Once while an operation is in progress.");this._done=!1,this.promise=null,this._value=null,this._error=void 0}done(){return this._done}current(){return this.promise}_awaitWithTimeout(e,t,i="Operation timed out"){if(null==t)return e;let r;return Promise.race([e.then((e=>(clearTimeout(r),e))),new Promise(((e,s)=>{r=setTimeout((()=>s(new g(i))),t)}))])}},v=class{constructor(e,t,i={}){this.factory=e,this.onCleanup=t,this.options=i}_count=0;init=new w({retry:!1,throws:!1});pendingMicrotask=!1;cleanupTimer;get subscribers(){return this._count}async acquire(){this.cancelPendingCleanup(),this._count++;const e=await this.init.do(this.factory);if(e.error)throw e.error;return e.value}release(){this._count<=0?console.warn("SharedResource.release() called, but count is already 0."):(this._count--,0===this._count&&this.scheduleCleanup())}peek(){const e=this.init.peek();return e.error?null:e.value}forceCleanup(){this.cancelPendingCleanup(),this._count=0,this.executeCleanup()}cancelPendingCleanup(){this.pendingMicrotask=!1,void 0!==this.cleanupTimer&&(clearTimeout(this.cleanupTimer),this.cleanupTimer=void 0)}scheduleCleanup(){const e=this.options.gracePeriod??"microtask";if("sync"!==e)return"microtask"===e?(this.pendingMicrotask=!0,void queueMicrotask((()=>{!this.pendingMicrotask||this._count>0||(this.pendingMicrotask=!1,this.executeCleanup())}))):void(this.cleanupTimer=setTimeout((()=>{this.cleanupTimer=void 0,this._count>0||this.executeCleanup()}),e));this.executeCleanup()}executeCleanup(){const e=this.init.peek();try{this.onCleanup(e.value)}catch(e){console.error("[SharedResource] Error during cleanup callback:",e)}this.init.running()||this.init.reset()}},_=class s{static dbResources=new Map;static eventBusMap=new Map;static createDatabaseFactory(e){return async()=>{const{database:s,enableTelemetry:n=!1,collection:o="stores"}=e,a=await t({database:s,enableTelemetry:n},i);try{await a.createCollection({name:o,version:"1.0.0",nestedSchemas:{},fields:{store:{name:"store",type:"string",required:!0},data:{name:"data",type:"object",required:!0},version:{name:"version",type:"string",required:!0},app:{name:"app",type:"string",required:!0}}})}catch(e){if(e instanceof r&&"SCHEMA_ALREADY_EXISTS"!==e.type)throw e}return a}}static createDatabaseCleanup(e){return t=>{t?.close();const i=[];s.eventBusMap.forEach(((t,r)=>{r.startsWith(`${e}_store_`)&&(t.clear(),i.push(r))})),i.forEach((e=>s.eventBusMap.delete(e)))}}static async acquireDatabase(e){const t=e.database;if(!s.dbResources.has(t)){const i=new v(s.createDatabaseFactory(e),s.createDatabaseCleanup(t),{gracePeriod:"microtask"});s.dbResources.set(t,i)}return s.dbResources.get(t).acquire()}static releaseDatabase(e){const t=s.dbResources.get(e);t&&t.release()}static async getCollection(e){const t=await s.acquireDatabase(e),i=e.collection??"stores";return t.collection(i)}static getEventBus(t,i){const r=`${t}_store_${i}`;return s.eventBusMap.has(r)||s.eventBusMap.set(r,e({errorHandler:e=>console.error(`Event bus error for ${t}:${i}:`,e),broadcast:{channel:r}})),s.eventBusMap.get(r)}},b=class{collection=null;collectionPromise;config;eventBus;initialized=!1;_initializing=!1;initializationCallbacks=[];doc=null;getStoreName(){return`_${this.config.app}_${this.config.store}_`}constructor(e){this.config=e,this.collectionPromise=_.getCollection(this.config),this.collectionPromise.then((e=>{this.collection=e,this.initialize()})).catch((e=>{console.error(`Failed to initialize collection for store ${this.getStoreName()}:`,e)})),this.eventBus=_.getEventBus(this.config.database,this.getStoreName()),this.eventBus.subscribe("store:updated",(({storageKey:e})=>{e===this.getStoreName()&&(this.doc=null)}))}async initialize(){this._initializing=!0;try{const e=await this._get();if(e&&e.version!==this.config.version&&this.config.onUpgrade){const{state:t}=await this.config.onUpgrade({data:e.data,version:e.version,app:e.app});await this.set("migration",t)}this.initialized=!0,this.initializationCallbacks.forEach((e=>e())),this.initializationCallbacks=[]}catch(e){console.error(`Failed to initialize and upgrade store ${this.getStoreName()}:`,e)}finally{this._initializing=!1}}_onInitialized(e){this.initialized?e():this.initializationCallbacks.push(e)}async getCollection(){return this.collection??this.collectionPromise}async set(e,t){this.initialized||this._initializing||await new Promise((e=>this._onInitialized(e)));try{const i=await this.getCollection(),r=await this._read(),s={store:this.getStoreName(),data:t,version:this.config.version,app:this.config.app};let n;return r?n=await r.update(s):(this.doc=await i.create(s),n=!0),n&&this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:e,state:t,version:this.config.version,app:this.config.app}}),n}catch(t){return console.error(`Failed to set state for store ${this.getStoreName()} in database ${this.config.database} by instance ${e}:`,t),!1}}async _read(){if(this.doc)return this.doc;const e=await this.getCollection(),t=(new f.QueryBuilder).where({field:"store",operator:"eq",value:this.getStoreName()}).build();return this.doc=await e.find(t.filters),this.doc}async _get(){try{const e=await this._read();return e?e.read().then((()=>e)):null}catch(e){return console.error(`Failed to get state for store ${this.getStoreName()} in database ${this.config.database}:`,e),null}}async get(){this.initialized||await new Promise((e=>this._onInitialized(e)));const e=await this._get();return e?e.data:null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.getStoreName()&&r!==e&&t(s)}))}async clear(){this.initialized||await new Promise((e=>this._onInitialized(e)));try{const e=await this._read();return!e||!await e.delete()||(this.doc=null,this.eventBus.emit({name:"store:updated",payload:{storageKey:this.getStoreName(),instanceId:"",state:null,version:this.config.version,app:this.config.app}}),!0)}catch(e){return console.error(`Failed to clear state for store ${this.getStoreName()} in database ${this.config.database}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}async close(){_.releaseDatabase(this.config.database)}},S=class{eventBus;inMemoryState=null;config;constructor(e){this.config=e,this.eventBus=this.initializeEventBus(),this.setupLwwSynchronizationListener()}initializeEventBus(){const t={errorHandler:e=>console.error(`Event bus error for ${this.config.storageKey} (Ephemeral LWW):`,e)};return"undefined"!=typeof process&&"test"===process.env.NODE_ENV||(t.broadcast={channel:`ephemeral_lww_${this.config.storageKey}`}),e(t)}setupLwwSynchronizationListener(){this.eventBus.subscribe("store:updated",(({storageKey:e,instanceId:t,state:i,timestamp:r,version:s})=>{e===this.config.storageKey&&(!this.inMemoryState||r&&r>this.inMemoryState.timestamp)&&(this.inMemoryState={data:i,timestamp:r,instanceId:t,version:s})}))}set(e,t){try{const i=Date.now();return this.inMemoryState={data:structuredClone(t),timestamp:i,instanceId:e,version:this.config.version},this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:e,state:t,timestamp:i,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to set state in EphemeralPersistence (LWW) for ${this.config.storageKey}:`,e),!1}}get(){return this.inMemoryState?.data??null}subscribe(e,t){return this.eventBus.subscribe("store:updated",(({storageKey:i,instanceId:r,state:s})=>{i===this.config.storageKey&&r!==e&&t(s)}))}clear(){try{const e=Date.now(),t="clear-initiator";return this.inMemoryState={data:null,timestamp:e,instanceId:t,version:this.config.version},this.eventBus.emit({name:"store:updated",payload:{storageKey:this.config.storageKey,instanceId:t,state:null,timestamp:e,version:this.config.version,app:this.config.app}}),!0}catch(e){return console.error(`Failed to clear state in EphemeralPersistence (LWW) for ${this.config.storageKey}:`,e),!1}}stats(){return{version:this.config.version,id:this.config.app}}};export{S as EphemeralPersistence,b as IndexedDBPersistence,d as WebStoragePersistence};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asaidimu/utils-persistence",
3
- "version": "6.1.4",
3
+ "version": "6.1.6",
4
4
  "description": "Persistence utilities.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -31,8 +31,8 @@
31
31
  "dependencies": {
32
32
  "@asaidimu/indexed": "^3.0.1",
33
33
  "@asaidimu/query": "^1.1.2",
34
- "@asaidimu/utils-database": "3.1.3",
35
- "@asaidimu/utils-events": "1.1.0",
34
+ "@asaidimu/utils-database": "^3.1.2",
35
+ "@asaidimu/utils-events": "^1.0.0",
36
36
  "uuid": "^11.1.0"
37
37
  },
38
38
  "exports": {