@actdim/utico 0.9.4 → 0.9.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"persistentCache.es.js","sources":["../../src/cache/persistentCache.ts"],"sourcesContent":["import { keyOf } from \"@/typeUtils\";\r\nimport { DataStore, IDataEntry, IDataItem } from \"../store/dataStore\";\r\nimport { StructEvent, StructEventTarget } from \"@/structEvent\";\r\nimport { v4 as uuid } from \"uuid\";\r\n\r\ntype Duration = number | { seconds?: number; minutes?: number; hours?: number };\r\n\r\nexport type PersistentCacheOptions = {\r\n cleanupTimeout: number;\r\n};\r\n\r\nconst defaultPersistentCacheOptions = {\r\n cleanupTimeout: 1000\r\n} satisfies PersistentCacheOptions;\r\n\r\nexport type CacheOptions = {\r\n absoluteExpiration?: Date | number;\r\n ttl?: Duration;\r\n slidingExpiration?: Date | number; // \"autoRenewOnUse\" pattern\r\n};\r\n\r\n// CacheEntryEvictionEvent\r\nexport type CacheEvictionEvent = {\r\n entry: IDataEntry;\r\n // or optionsOverride?\r\n keepAliveOptions?: CacheOptions; // TODO: delayed eviction (pending eviction) after gracePeriod\r\n};\r\n\r\ntype PersistentCacheEventStruct = {\r\n evict: CacheEvictionEvent;\r\n};\r\n\r\n// TODO: remove class, create factory method\r\n// implements Struct<StructEventTarget<PersistentCacheEventStruct>>\r\n// PersistentCacheManager\r\nexport class PersistentCache extends StructEventTarget<PersistentCacheEventStruct> {\r\n // https://demo.agektmr.com/storage/\r\n // https://www.html5rocks.com/en/tutorials/offline/quota-research/\r\n // https://www.raymondcamden.com/2015/04/17/indexeddb-and-limits\r\n // https://github.com/jonnysmith1981/getIndexedDbSize/blob/master/getIndexedDbSize.js\r\n // https://developer.chrome.com/apps/offline_storage#query\r\n // https://golb.hplar.ch/2018/01/IndexedDB-programming-with-Dexie-js.html\r\n // http://www.forerunnerdb.com/licensing.html\r\n // https://github.com/ignasbernotas/dexie-relationships\r\n\r\n private _db: DataStore;\r\n\r\n private _isDisposed: boolean;\r\n\r\n private _jobTimerId: number;\r\n\r\n private readonly _options: PersistentCacheOptions;\r\n\r\n // cleanupTimeout - serviceJobTimeout\r\n constructor(name: string, options: PersistentCacheOptions) {\r\n super();\r\n if (!name) {\r\n throw new Error(\"Name cannot be empty\");\r\n }\r\n this._isDisposed = false;\r\n this._jobTimerId = null;\r\n this._db = new DataStore(name);\r\n // https://docs.nestjs.com/techniques/task-scheduling\r\n this._options = { ...options, ...defaultPersistentCacheOptions };\r\n this.scheduleServiceJob();\r\n }\r\n\r\n scheduleServiceJob() {\r\n if (this._options.cleanupTimeout) {\r\n const doWork = async () => {\r\n try {\r\n // purge expired entries\r\n await this.deleteExpiredAsync();\r\n } catch (err) {\r\n console.error(\"Cache cleanup failed:\", err);\r\n } finally {\r\n setTimeout(doWork, this._options.cleanupTimeout);\r\n }\r\n };\r\n setTimeout(doWork, this._options.cleanupTimeout);\r\n }\r\n }\r\n\r\n // evictExpiredAsync/clearExpiredAsync\r\n async deleteExpiredAsync(date?: Date) {\r\n const result: string[] = []; // output ids\r\n if (!date) {\r\n date = new Date();\r\n }\r\n\r\n await this.execAsync(async () => {\r\n const entries = await this._db.registry.where(keyOf<IDataEntry>(\"expiresAt\")).below(date.getTime()).toArray();\r\n for (const entry of entries) {\r\n const evt = new StructEvent<PersistentCacheEventStruct, this>(\"evict\", {\r\n detail: {\r\n entry: entry\r\n // keepAliveOptions: {}\r\n },\r\n target: this,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(evt);\r\n // evt.defaultPrevented?\r\n // TODO: support evt.detail.keepAliveOptions\r\n await this._db.registry.delete(entry.id);\r\n // await this._db.data.delete(entry.id);\r\n result.push(entry.id);\r\n // TODO: use bulkDelete\r\n // TODO: use transaction\r\n }\r\n });\r\n return result;\r\n }\r\n\r\n dispose() {\r\n if (!this._isDisposed) {\r\n this._isDisposed = true;\r\n\r\n if (this._jobTimerId) {\r\n window.clearTimeout(this._jobTimerId);\r\n this._jobTimerId = null;\r\n }\r\n\r\n if (this._db) {\r\n // this.exec(async () => {\r\n // \t// ...\r\n // }).then(() => {\r\n // \tthis._db = null;\r\n // });\r\n if (this._db.isOpen()) {\r\n this._db.close();\r\n }\r\n this._db = null;\r\n }\r\n }\r\n }\r\n\r\n private async execAsync<T>(action: () => Promise<T>) {\r\n if (!this._db.isOpen()) {\r\n await this._db.open();\r\n }\r\n try {\r\n const result = await action();\r\n return result;\r\n } catch (err) {\r\n if (this._db.isOpen()) {\r\n // this._db.close(); // generally speaking: we don't (never) need to close a connection\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n async getKeysAsync() {\r\n return await this._db.registry.filter((_) => true).primaryKeys();\r\n }\r\n\r\n async getAsync(key: string): Promise<Readonly<IDataEntry & IDataItem>> {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<ICacheEntry>(\"id\")).equals(key).first();\r\n if (entry) {\r\n const data = await this._db.data.get(key);\r\n // const data = await this._db.data.where(keyOf<ICacheEntry>(\"id\")).equals(key).first();\r\n return { ...entry, ...data };\r\n }\r\n return null;\r\n });\r\n }\r\n\r\n // getMany\r\n async bulkGetAsync(ids: string[]): Promise<{ [key: string]: Readonly<IDataEntry & IDataItem> }> {\r\n const result: { [key: string]: Readonly<IDataEntry & IDataItem> } = {};\r\n return await this.execAsync(async () => {\r\n // const entries = await this._db.registry.where(keyOf<ICacheEntry>(\"id\")).anyOf(ids).toArray();\r\n const entries = await this._db.registry.bulkGet(ids);\r\n const entryMap: { [key: string]: IDataEntry } = entries.reduce((map, entry, i) => {\r\n map[entry.id] = entry;\r\n return map;\r\n }, {});\r\n\r\n // const dataItems = this._db.data.where(keyOf<ICacheEntry>(\"id\")).anyOf(ids);\r\n // await dataItems.each((dataItem) => {\r\n // result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n // delete entryMap[dataItem.id];\r\n // });\r\n\r\n const dataItems = await this._db.data.bulkGet(ids);\r\n for (const dataItem of dataItems) {\r\n result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n delete entryMap[dataItem.id];\r\n }\r\n\r\n // TODO: update cache entry \r\n // accessedAt,\r\n // updatedAt,\r\n // expiresAt // for sliding expiration\r\n\r\n for (const key of Object.keys(entryMap)) {\r\n // abandoned/orphaned entries:\r\n result[key] = { ...entryMap[key], value: undefined };\r\n\r\n // Object.defineProperty(result[key], keyOf<ICacheDataItem>(\"value\"), {\r\n // writable: false,\r\n // get: function () {\r\n // throw new Error(\"Not found\");\r\n // }\r\n // });\r\n }\r\n\r\n return result;\r\n });\r\n }\r\n\r\n async containsAsync(key: string) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<ICacheEntry>(\"id\")).equals(key).first();\r\n return entry != undefined;\r\n });\r\n }\r\n\r\n async deleteAsync(id: string) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.delete(id);\r\n });\r\n }\r\n\r\n // deleteManyAsync\r\n async bulkDeleteAsync(ids: string[]) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.bulkDelete(ids);\r\n });\r\n }\r\n\r\n // upsertAsync\r\n async setAsync(id: string, value: any, options: CacheOptions) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(id);\r\n // const entry = await this._db.registry.where(keyOf<ICacheEntry>(\"id\")).equals(key).first();\r\n const now = new Date().getTime();\r\n if (!id) {\r\n id = uuid();\r\n }\r\n const expiresAt =\r\n typeof options.absoluteExpiration === \"number\" ? options.absoluteExpiration : options.absoluteExpiration?.getTime();\r\n const slidingExpiration =\r\n typeof options.slidingExpiration === \"number\" ? options.slidingExpiration : options.slidingExpiration?.getTime();\r\n\r\n await this._db.registry.put({\r\n id: id,\r\n createdAt: entry ? entry.createdAt : now,\r\n accessedAt: now,\r\n updatedAt: now,\r\n expiresAt: expiresAt,\r\n slidingExpiration: slidingExpiration\r\n });\r\n await this._db.data.put({\r\n id: id,\r\n value: value\r\n });\r\n });\r\n }\r\n\r\n // getOrAddAsync\r\n async getOrSetAsync(key: string, factory: () => any, options: CacheOptions) {\r\n await this.execAsync(async () => {\r\n if (!(await this.containsAsync(key))) {\r\n await this.setAsync(key, factory(), options);\r\n }\r\n return await this.getAsync(key);\r\n });\r\n }\r\n\r\n // clearAllAsync/evictAllAsync\r\n async clearAsync() {\r\n await this.execAsync(async () => {\r\n await this._db.registry.clear();\r\n await this._db.data.clear();\r\n });\r\n }\r\n\r\n // TODO: support bulkSetAsync\r\n}\r\n\r\n// https://medium.com/square-corner-blog/useful-tools-headless-chrome-puppeteer-for-browser-automation-testing-1ac7707bad40\r\n// https://developers.google.com/web/updates/2017/06/headless-karma-mocha-chai?hl=ru\r\n// https://github.com/puppeteer/puppeteer\r\n// https://medium.com/web-standards/puppeteer-crawl-to-markdown-7752dff36b68\r\n"],"names":["defaultPersistentCacheOptions","PersistentCache","StructEventTarget","name","options","DataStore","doWork","err","date","result","entries","keyOf","entry","evt","StructEvent","action","_","key","data","ids","entryMap","map","i","dataItems","dataItem","id","value","now","uuid","expiresAt","slidingExpiration","factory"],"mappings":"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"}
1
+ {"version":3,"file":"persistentCache.es.js","sources":["../../src/cache/persistentCache.ts"],"sourcesContent":["import { keyOf } from \"@/typeUtils\";\r\nimport { DataStore, IDataEntry, IDataItem } from \"../store/dataStore\";\r\nimport { StructEvent, StructEventTarget } from \"@/structEvent\";\r\nimport { v4 as uuid } from \"uuid\";\r\n\r\ntype Duration = number | { seconds?: number; minutes?: number; hours?: number };\r\n\r\nexport type PersistentCacheOptions = {\r\n cleanupTimeout: number;\r\n};\r\n\r\nconst defaultPersistentCacheOptions = {\r\n cleanupTimeout: 1000\r\n} satisfies PersistentCacheOptions;\r\n\r\nexport type CacheOptions = {\r\n absoluteExpiration?: Date | number;\r\n ttl?: Duration;\r\n slidingExpiration?: Date | number; // \"autoRenewOnUse\" pattern\r\n};\r\n\r\n// CacheEntryEvictionEvent\r\nexport type CacheEvictionEvent = {\r\n entry: IDataEntry;\r\n // or optionsOverride?\r\n keepAliveOptions?: CacheOptions; // TODO: delayed eviction (pending eviction) after gracePeriod\r\n};\r\n\r\ntype PersistentCacheEventStruct = {\r\n evict: CacheEvictionEvent;\r\n};\r\n\r\n// TODO: remove class, create factory method\r\n// implements Struct<StructEventTarget<PersistentCacheEventStruct>>\r\n// PersistentCacheManager\r\nexport class PersistentCache extends StructEventTarget<PersistentCacheEventStruct> {\r\n // https://demo.agektmr.com/storage/\r\n // https://www.html5rocks.com/en/tutorials/offline/quota-research/\r\n // https://www.raymondcamden.com/2015/04/17/indexeddb-and-limits\r\n // https://github.com/jonnysmith1981/getIndexedDbSize/blob/master/getIndexedDbSize.js\r\n // https://developer.chrome.com/apps/offline_storage#query\r\n // https://golb.hplar.ch/2018/01/IndexedDB-programming-with-Dexie-js.html\r\n // http://www.forerunnerdb.com/licensing.html\r\n // https://github.com/ignasbernotas/dexie-relationships\r\n\r\n private _db: DataStore;\r\n\r\n private _isDisposed: boolean;\r\n\r\n private _jobTimerId: number;\r\n\r\n private readonly _options: PersistentCacheOptions;\r\n\r\n // cleanupTimeout - serviceJobTimeout\r\n constructor(name: string, options: PersistentCacheOptions) {\r\n super();\r\n if (!name) {\r\n throw new Error(\"Name cannot be empty\");\r\n }\r\n this._isDisposed = false;\r\n this._jobTimerId = null;\r\n this._db = new DataStore(name);\r\n // https://docs.nestjs.com/techniques/task-scheduling\r\n this._options = { ...options, ...defaultPersistentCacheOptions };\r\n this.scheduleServiceJob();\r\n }\r\n\r\n scheduleServiceJob() {\r\n if (this._options.cleanupTimeout) {\r\n const doWork = async () => {\r\n try {\r\n // purge expired entries\r\n await this.deleteExpiredAsync();\r\n } catch (err) {\r\n console.error(\"Cache cleanup failed:\", err);\r\n } finally {\r\n setTimeout(doWork, this._options.cleanupTimeout);\r\n }\r\n };\r\n setTimeout(doWork, this._options.cleanupTimeout);\r\n }\r\n }\r\n\r\n // evictExpiredAsync/clearExpiredAsync\r\n async deleteExpiredAsync(date?: Date) {\r\n const result: string[] = []; // output ids\r\n if (!date) {\r\n date = new Date();\r\n }\r\n\r\n await this.execAsync(async () => {\r\n const entries = await this._db.registry.where(keyOf<IDataEntry>(\"expiresAt\")).below(date.getTime()).toArray();\r\n for (const entry of entries) {\r\n const evt = new StructEvent<PersistentCacheEventStruct, this>(\"evict\", {\r\n detail: {\r\n entry: entry\r\n // keepAliveOptions: {}\r\n },\r\n target: this,\r\n cancelable: true\r\n });\r\n this.dispatchEvent(evt);\r\n // evt.defaultPrevented?\r\n // TODO: support evt.detail.keepAliveOptions\r\n await this._db.registry.delete(entry.id);\r\n // await this._db.data.delete(entry.id);\r\n result.push(entry.id);\r\n // TODO: use bulkDelete\r\n // TODO: use transaction\r\n }\r\n });\r\n return result;\r\n }\r\n\r\n dispose() {\r\n if (!this._isDisposed) {\r\n this._isDisposed = true;\r\n\r\n if (this._jobTimerId) {\r\n window.clearTimeout(this._jobTimerId);\r\n this._jobTimerId = null;\r\n }\r\n\r\n if (this._db) {\r\n // this.exec(async () => {\r\n // \t// ...\r\n // }).then(() => {\r\n // \tthis._db = null;\r\n // });\r\n if (this._db.isOpen()) {\r\n this._db.close();\r\n }\r\n this._db = null;\r\n }\r\n }\r\n }\r\n\r\n private async execAsync<T>(action: () => Promise<T>) {\r\n if (!this._db.isOpen()) {\r\n await this._db.open();\r\n }\r\n try {\r\n const result = await action();\r\n return result;\r\n } catch (err) {\r\n if (this._db.isOpen()) {\r\n // this._db.close(); // generally speaking: we don't (never) need to close a connection\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n async getKeysAsync() {\r\n return await this._db.registry.filter((_) => true).primaryKeys();\r\n }\r\n\r\n async getAsync(key: string): Promise<Readonly<IDataEntry & IDataItem>> {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n if (entry) {\r\n const data = await this._db.data.get(key);\r\n // const data = await this._db.data.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return { ...entry, ...data };\r\n }\r\n return null;\r\n });\r\n }\r\n\r\n // getMany\r\n async bulkGetAsync(ids: string[]): Promise<{ [key: string]: Readonly<IDataEntry & IDataItem> }> {\r\n const result: { [key: string]: Readonly<IDataEntry & IDataItem> } = {};\r\n return await this.execAsync(async () => {\r\n // const entries = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).anyOf(ids).toArray();\r\n const entries = await this._db.registry.bulkGet(ids);\r\n const entryMap: { [key: string]: IDataEntry } = entries.reduce((map, entry, i) => {\r\n map[entry.id] = entry;\r\n return map;\r\n }, {});\r\n\r\n // const dataItems = this._db.data.where(keyOf<IDataEntry>(\"id\")).anyOf(ids);\r\n // await dataItems.each((dataItem) => {\r\n // result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n // delete entryMap[dataItem.id];\r\n // });\r\n\r\n const dataItems = await this._db.data.bulkGet(ids);\r\n for (const dataItem of dataItems) {\r\n result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n delete entryMap[dataItem.id];\r\n }\r\n\r\n // TODO: update data entry \r\n // accessedAt,\r\n // updatedAt,\r\n // expiresAt // for sliding expiration\r\n\r\n for (const key of Object.keys(entryMap)) {\r\n // abandoned/orphaned entries:\r\n result[key] = { ...entryMap[key], value: undefined };\r\n\r\n // Object.defineProperty(result[key], keyOf<IDataItem>(\"value\"), {\r\n // writable: false,\r\n // get: function () {\r\n // throw new Error(\"Not found\");\r\n // }\r\n // });\r\n }\r\n\r\n return result;\r\n });\r\n }\r\n\r\n async containsAsync(key: string) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return entry != undefined;\r\n });\r\n }\r\n\r\n async deleteAsync(id: string) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.delete(id);\r\n });\r\n }\r\n\r\n // deleteManyAsync\r\n async bulkDeleteAsync(ids: string[]) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.bulkDelete(ids);\r\n });\r\n }\r\n\r\n // upsertAsync\r\n async setAsync(id: string, value: any, options: CacheOptions) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(id);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n const now = new Date().getTime();\r\n if (!id) {\r\n id = uuid();\r\n }\r\n const expiresAt =\r\n typeof options.absoluteExpiration === \"number\" ? options.absoluteExpiration : options.absoluteExpiration?.getTime();\r\n const slidingExpiration =\r\n typeof options.slidingExpiration === \"number\" ? options.slidingExpiration : options.slidingExpiration?.getTime();\r\n\r\n await this._db.registry.put({\r\n id: id,\r\n createdAt: entry ? entry.createdAt : now,\r\n accessedAt: now,\r\n updatedAt: now,\r\n expiresAt: expiresAt,\r\n slidingExpiration: slidingExpiration\r\n });\r\n await this._db.data.put({\r\n id: id,\r\n value: value\r\n });\r\n });\r\n }\r\n\r\n // getOrAddAsync\r\n async getOrSetAsync(key: string, factory: () => any, options: CacheOptions) {\r\n await this.execAsync(async () => {\r\n if (!(await this.containsAsync(key))) {\r\n await this.setAsync(key, factory(), options);\r\n }\r\n return await this.getAsync(key);\r\n });\r\n }\r\n\r\n // clearAllAsync/evictAllAsync\r\n async clearAsync() {\r\n await this.execAsync(async () => {\r\n await this._db.registry.clear();\r\n await this._db.data.clear();\r\n });\r\n }\r\n\r\n // TODO: support bulkSetAsync\r\n}\r\n\r\n// https://medium.com/square-corner-blog/useful-tools-headless-chrome-puppeteer-for-browser-automation-testing-1ac7707bad40\r\n// https://developers.google.com/web/updates/2017/06/headless-karma-mocha-chai?hl=ru\r\n// https://github.com/puppeteer/puppeteer\r\n// https://medium.com/web-standards/puppeteer-crawl-to-markdown-7752dff36b68\r\n"],"names":["defaultPersistentCacheOptions","PersistentCache","StructEventTarget","name","options","DataStore","doWork","err","date","result","entries","keyOf","entry","evt","StructEvent","action","_","key","data","ids","entryMap","map","i","dataItems","dataItem","id","value","now","uuid","expiresAt","slidingExpiration","factory"],"mappings":"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"}
@@ -13,7 +13,7 @@ export declare class PersistentStore implements IPersistentStore {
13
13
  containsAsync(key: string): Promise<boolean>;
14
14
  deleteAsync(id: string): Promise<void>;
15
15
  bulkDeleteAsync(ids: string[]): Promise<void>;
16
- setAsync(id: string, value: any): Promise<void>;
16
+ setAsync<T = any>(id: string, value: T): Promise<void>;
17
17
  getOrSetAsync(key: string, factory: () => any): Promise<void>;
18
18
  clearAsync(): Promise<void>;
19
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"persistentStore.d.ts","sourceRoot":"","sources":["../../src/store/persistentStore.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAuBpD,qBAAa,eAAgB,YAAW,gBAAgB;IAEpD,OAAO,CAAC,GAAG,CAAY;IAEvB,OAAO,CAAC,WAAW,CAAU;IAE7B,OAAO,CAAC,cAAc,CAAU;gBAEpB,IAAI,EAAE,MAAM,EAAE,aAAa,UAAQ;IAQ/C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM;IAI1B,OAAO;YAaO,SAAS;IAevB,YAAY;IAIN,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAc1C,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IA+BzD,aAAa,CAAC,GAAG,EAAE,MAAM;IAQzB,WAAW,CAAC,EAAE,EAAE,MAAM;IAOtB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE;IAO7B,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;IAyB/B,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAU7C,UAAU;CAQnB"}
1
+ {"version":3,"file":"persistentStore.d.ts","sourceRoot":"","sources":["../../src/store/persistentStore.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAuBpD,qBAAa,eAAgB,YAAW,gBAAgB;IAEpD,OAAO,CAAC,GAAG,CAAY;IAEvB,OAAO,CAAC,WAAW,CAAU;IAE7B,OAAO,CAAC,cAAc,CAAU;gBAEpB,IAAI,EAAE,MAAM,EAAE,aAAa,UAAQ;IAQ/C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM;IAI1B,OAAO;YAaO,SAAS;IAevB,YAAY;IAIN,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAc1C,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IA+BzD,aAAa,CAAC,GAAG,EAAE,MAAM;IAQzB,WAAW,CAAC,EAAE,EAAE,MAAM;IAOtB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE;IAO7B,QAAQ,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAyBtC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;IAU7C,UAAU;CAQnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"persistentStore.es.js","sources":["../../src/store/persistentStore.ts"],"sourcesContent":["// TODO: implement real encryption:\r\n// https://stackoverflow.com/questions/18279141/javascript-string-encryption-and-decryption\r\n\r\nimport Dexie from \"dexie\";\r\nimport { DataStore, IDataEntry } from \"./dataStore\";\r\nimport { IPersistentStore } from \"./storeContracts\";\r\nimport { v4 as uuid } from \"uuid\";\r\n\r\n/*\r\n(async () => {\r\n const alreadyPersisted = await window.navigator.storage?.persisted()\r\n\r\n if (alreadyPersisted) {\r\n return;\r\n }\r\n\r\n const persistentModeEnabled = await window.navigator.storage?.persist()\r\n\r\n if (!persistentModeEnabled) {\r\n // Storage may be cleared by the UA under storage pressure\r\n } else {\r\n // Storage will be persistent\r\n // Storage will not be cleared except by explicit user action\r\n }\r\n})();\r\n*/\r\n\r\n// TODO: remove class, create factory method\r\nexport class PersistentStore implements IPersistentStore {\r\n\r\n private _db: DataStore;\r\n\r\n private _isDisposed: boolean;\r\n\r\n private _useEncryption: boolean; // TODO: support\r\n\r\n constructor(name: string, useEncryption = false) {\r\n if (!name) {\r\n throw new Error(\"Name cannot be empty\");\r\n }\r\n this._useEncryption = useEncryption;\r\n this._db = new DataStore(name);\r\n }\r\n\r\n static delete(name: string) {\r\n return Dexie.delete(name);\r\n }\r\n\r\n dispose() {\r\n if (!this._isDisposed) {\r\n this._isDisposed = true;\r\n\r\n if (this._db) {\r\n if (this._db.isOpen()) {\r\n this._db.close();\r\n }\r\n this._db = null;\r\n }\r\n }\r\n }\r\n\r\n private async execAsync<T>(action: () => Promise<T>) {\r\n if (!this._db.isOpen()) {\r\n await this._db.open();\r\n }\r\n try {\r\n const result = await action();\r\n return result;\r\n } catch (err) {\r\n if (this._db.isOpen()) {\r\n // this._db.close(); // generally speaking: we don't (never) need to close a connection\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n getKeysAsync() {\r\n return this._db.registry.filter((_) => true).primaryKeys();\r\n }\r\n\r\n async getAsync<T = any>(key: string): Promise<T> {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n if (entry) {\r\n const data = await this._db.data.get(key);\r\n // const data = await this._db.data.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return data?.value;\r\n }\r\n return undefined;\r\n });\r\n }\r\n\r\n // getManyAsync\r\n async bulkGetAsync(ids: string[]): Promise<Record<string, any>> {\r\n const result: Record<string, any> = {};\r\n return await this.execAsync(async () => {\r\n // const entries = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).anyOf(ids).toArray();\r\n const entries = await this._db.registry.bulkGet(ids);\r\n const entryMap: { [key: string]: IDataEntry } = entries.reduce((map, entry, i) => {\r\n map[entry.id] = entry;\r\n return map;\r\n }, {});\r\n\r\n // const dataItems = this._db.data.where(keyOf<IDataEntry>(\"id\")).anyOf(ids);\r\n // await dataItems.each((dataItem) => {\r\n // result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n // delete entryMap[dataItem.id];\r\n // });\r\n\r\n const dataItems = await this._db.data.bulkGet(ids);\r\n for (const dataItem of dataItems) {\r\n result[dataItem.id] = dataItem.value;\r\n delete entryMap[dataItem.id];\r\n }\r\n\r\n for (const key of Object.keys(entryMap)) {\r\n // abandoned/orphaned entries:\r\n result[key] = undefined;\r\n }\r\n\r\n return result;\r\n });\r\n }\r\n\r\n async containsAsync(key: string) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return entry != undefined;\r\n });\r\n }\r\n\r\n async deleteAsync(id: string) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.delete(id);\r\n });\r\n }\r\n\r\n // deleteManyAsync\r\n async bulkDeleteAsync(ids: string[]) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.bulkDelete(ids);\r\n });\r\n }\r\n\r\n // upsertAsync\r\n async setAsync(id: string, value: any) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(id);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n const now = new Date().getTime();\r\n if (!id) {\r\n id = uuid();\r\n }\r\n\r\n await this._db.registry.put({\r\n id: id,\r\n createdAt: entry ? entry.createdAt : now,\r\n accessedAt: entry ? entry.accessedAt : null, // now\r\n updatedAt: now,\r\n expiresAt: undefined,\r\n slidingExpiration: undefined\r\n });\r\n await this._db.data.put({\r\n id: id,\r\n value: value\r\n });\r\n });\r\n }\r\n\r\n // getOrAddAsync\r\n async getOrSetAsync(key: string, factory: () => any) {\r\n await this.execAsync(async () => {\r\n if (!(await this.containsAsync(key))) {\r\n await this.setAsync(key, factory());\r\n }\r\n return await this.getAsync(key);\r\n });\r\n }\r\n\r\n // clearAllAsync\r\n async clearAsync() {\r\n await this.execAsync(async () => {\r\n await this._db.registry.clear();\r\n await this._db.data.clear();\r\n });\r\n }\r\n\r\n // TODO: support bulkSetAsync\r\n}\r\n"],"names":["PersistentStore","name","useEncryption","DataStore","Dexie","action","err","_","key","ids","result","entryMap","map","entry","i","dataItems","dataItem","id","value","now","uuid","factory"],"mappings":"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"}
1
+ {"version":3,"file":"persistentStore.es.js","sources":["../../src/store/persistentStore.ts"],"sourcesContent":["// TODO: implement real encryption:\r\n// https://stackoverflow.com/questions/18279141/javascript-string-encryption-and-decryption\r\n\r\nimport Dexie from \"dexie\";\r\nimport { DataStore, IDataEntry } from \"./dataStore\";\r\nimport { IPersistentStore } from \"./storeContracts\";\r\nimport { v4 as uuid } from \"uuid\";\r\n\r\n/*\r\n(async () => {\r\n const alreadyPersisted = await window.navigator.storage?.persisted()\r\n\r\n if (alreadyPersisted) {\r\n return;\r\n }\r\n\r\n const persistentModeEnabled = await window.navigator.storage?.persist()\r\n\r\n if (!persistentModeEnabled) {\r\n // Storage may be cleared by the UA under storage pressure\r\n } else {\r\n // Storage will be persistent\r\n // Storage will not be cleared except by explicit user action\r\n }\r\n})();\r\n*/\r\n\r\n// TODO: remove class, create factory method\r\nexport class PersistentStore implements IPersistentStore {\r\n\r\n private _db: DataStore;\r\n\r\n private _isDisposed: boolean;\r\n\r\n private _useEncryption: boolean; // TODO: support\r\n\r\n constructor(name: string, useEncryption = false) {\r\n if (!name) {\r\n throw new Error(\"Name cannot be empty\");\r\n }\r\n this._useEncryption = useEncryption;\r\n this._db = new DataStore(name);\r\n }\r\n\r\n static delete(name: string) {\r\n return Dexie.delete(name);\r\n }\r\n\r\n dispose() {\r\n if (!this._isDisposed) {\r\n this._isDisposed = true;\r\n\r\n if (this._db) {\r\n if (this._db.isOpen()) {\r\n this._db.close();\r\n }\r\n this._db = null;\r\n }\r\n }\r\n }\r\n\r\n private async execAsync<T>(action: () => Promise<T>) {\r\n if (!this._db.isOpen()) {\r\n await this._db.open();\r\n }\r\n try {\r\n const result = await action();\r\n return result;\r\n } catch (err) {\r\n if (this._db.isOpen()) {\r\n // this._db.close(); // generally speaking: we don't (never) need to close a connection\r\n }\r\n throw err;\r\n }\r\n }\r\n\r\n getKeysAsync() {\r\n return this._db.registry.filter((_) => true).primaryKeys();\r\n }\r\n\r\n async getAsync<T = any>(key: string): Promise<T> {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n if (entry) {\r\n const data = await this._db.data.get(key);\r\n // const data = await this._db.data.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return data?.value;\r\n }\r\n return undefined;\r\n });\r\n }\r\n\r\n // getManyAsync\r\n async bulkGetAsync(ids: string[]): Promise<Record<string, any>> {\r\n const result: Record<string, any> = {};\r\n return await this.execAsync(async () => {\r\n // const entries = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).anyOf(ids).toArray();\r\n const entries = await this._db.registry.bulkGet(ids);\r\n const entryMap: { [key: string]: IDataEntry } = entries.reduce((map, entry, i) => {\r\n map[entry.id] = entry;\r\n return map;\r\n }, {});\r\n\r\n // const dataItems = this._db.data.where(keyOf<IDataEntry>(\"id\")).anyOf(ids);\r\n // await dataItems.each((dataItem) => {\r\n // result[dataItem.id] = { ...entryMap[dataItem.id], ...dataItem };\r\n // delete entryMap[dataItem.id];\r\n // });\r\n\r\n const dataItems = await this._db.data.bulkGet(ids);\r\n for (const dataItem of dataItems) {\r\n result[dataItem.id] = dataItem.value;\r\n delete entryMap[dataItem.id];\r\n }\r\n\r\n for (const key of Object.keys(entryMap)) {\r\n // abandoned/orphaned entries:\r\n result[key] = undefined;\r\n }\r\n\r\n return result;\r\n });\r\n }\r\n\r\n async containsAsync(key: string) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(key);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n return entry != undefined;\r\n });\r\n }\r\n\r\n async deleteAsync(id: string) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.delete(id);\r\n });\r\n }\r\n\r\n // deleteManyAsync\r\n async bulkDeleteAsync(ids: string[]) {\r\n await this.execAsync(async () => {\r\n await this._db.registry.bulkDelete(ids);\r\n });\r\n }\r\n\r\n // upsertAsync\r\n async setAsync<T = any>(id: string, value: T) {\r\n return await this.execAsync(async () => {\r\n const entry = await this._db.registry.get(id);\r\n // const entry = await this._db.registry.where(keyOf<IDataEntry>(\"id\")).equals(key).first();\r\n const now = new Date().getTime();\r\n if (!id) {\r\n id = uuid();\r\n }\r\n\r\n await this._db.registry.put({\r\n id: id,\r\n createdAt: entry ? entry.createdAt : now,\r\n accessedAt: entry ? entry.accessedAt : null, // now\r\n updatedAt: now,\r\n expiresAt: undefined,\r\n slidingExpiration: undefined\r\n });\r\n await this._db.data.put({\r\n id: id,\r\n value: value\r\n });\r\n });\r\n }\r\n\r\n // getOrAddAsync\r\n async getOrSetAsync(key: string, factory: () => any) {\r\n await this.execAsync(async () => {\r\n if (!(await this.containsAsync(key))) {\r\n await this.setAsync(key, factory());\r\n }\r\n return await this.getAsync(key);\r\n });\r\n }\r\n\r\n // clearAllAsync\r\n async clearAsync() {\r\n await this.execAsync(async () => {\r\n await this._db.registry.clear();\r\n await this._db.data.clear();\r\n });\r\n }\r\n\r\n // TODO: support bulkSetAsync\r\n}\r\n"],"names":["PersistentStore","name","useEncryption","DataStore","Dexie","action","err","_","key","ids","result","entryMap","map","entry","i","dataItems","dataItem","id","value","now","uuid","factory"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actdim/utico",
3
- "version": "0.9.4",
3
+ "version": "0.9.5",
4
4
  "description": "A modern foundation toolkit for complex TypeScript apps",
5
5
  "author": "Pavel Borodaev",
6
6
  "license": "Proprietary",
@@ -66,21 +66,15 @@
66
66
  "moment": "^2.30.1",
67
67
  "uuid": "^13.0.0"
68
68
  },
69
- "devDependencies": {
70
- "@swc/core": "^1.13.5",
69
+ "devDependencies": {
71
70
  "@types/node": "^24.7.0",
72
71
  "@types/uuid": "^11.0.0",
73
72
  "@typescript-eslint/eslint-plugin": "^8.45.0",
74
73
  "@typescript-eslint/parser": "^8.45.0",
75
- "@vitejs/plugin-react-swc": "^4.1.0",
76
74
  "eslint": "^9.37.0",
77
75
  "eslint-config-prettier": "^10.1.8",
78
76
  "eslint-formatter-visualstudio": "^8.40.0",
79
- "eslint-plugin-jsx-a11y": "^6.10.2",
80
77
  "eslint-plugin-prettier": "^5.5.4",
81
- "eslint-plugin-react": "^7.37.5",
82
- "eslint-plugin-react-hooks": "^6.1.1",
83
- "eslint-plugin-react-refresh": "^0.4.23",
84
78
  "fake-indexeddb": "^6.2.2",
85
79
  "globals": "^16.4.0",
86
80
  "npm-check-updates": "^19.0.0",