@xyo-network/archivist-indexeddb 3.5.2 → 3.6.0-rc.1
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/dist/browser/Archivist.d.ts +9 -13
- package/dist/browser/Archivist.d.ts.map +1 -1
- package/dist/browser/index.mjs +9 -6
- package/dist/browser/index.mjs.map +1 -1
- package/package.json +17 -16
- package/src/Archivist.ts +17 -18
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { Hash } from '@xylabs/hex';
|
|
2
|
-
import { AbstractArchivist } from '@xyo-network/archivist-abstract';
|
|
2
|
+
import { AbstractArchivist, WithStorageMeta } from '@xyo-network/archivist-abstract';
|
|
3
3
|
import { ArchivistModuleEventData, ArchivistNextOptions } from '@xyo-network/archivist-model';
|
|
4
|
-
import { Payload,
|
|
4
|
+
import { Payload, Schema } from '@xyo-network/payload-model';
|
|
5
5
|
import { IDBPDatabase } from 'idb';
|
|
6
6
|
import { IndexedDbArchivistParams } from './Params.ts';
|
|
7
|
-
type StoredPayload = PayloadWithMeta & {
|
|
8
|
-
_hash: string;
|
|
9
|
-
};
|
|
10
7
|
export interface PayloadStore {
|
|
11
|
-
[s: string]:
|
|
8
|
+
[s: string]: WithStorageMeta;
|
|
12
9
|
}
|
|
13
10
|
export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams, TEventData extends ArchivistModuleEventData = ArchivistModuleEventData> extends AbstractArchivist<TParams, TEventData> {
|
|
14
11
|
static readonly configSchemas: Schema[];
|
|
@@ -46,7 +43,7 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
|
|
|
46
43
|
* The indexes to create on the store
|
|
47
44
|
*/
|
|
48
45
|
private get indexes();
|
|
49
|
-
protected allHandler(): Promise<
|
|
46
|
+
protected allHandler(): Promise<Payload[]>;
|
|
50
47
|
protected clearHandler(): Promise<void>;
|
|
51
48
|
protected deleteHandler(hashes: Hash[]): Promise<Hash[]>;
|
|
52
49
|
/**
|
|
@@ -57,11 +54,11 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
|
|
|
57
54
|
* @param key The key to get from the index
|
|
58
55
|
* @returns The primary key and the payload, or undefined if not found
|
|
59
56
|
*/
|
|
60
|
-
protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number,
|
|
61
|
-
protected getFromOffset(db: IDBPDatabase<PayloadStore>, storeName: string, order?: 'asc' | 'desc', limit?: number, offset?: Hash): Promise<
|
|
62
|
-
protected getHandler(hashes: string[]): Promise<
|
|
63
|
-
protected insertHandler(payloads: Payload[]): Promise<
|
|
64
|
-
protected nextHandler(options?: ArchivistNextOptions): Promise<
|
|
57
|
+
protected getFromIndexWithPrimaryKey(db: IDBPDatabase<PayloadStore>, storeName: string, indexName: string, key: IDBValidKey): Promise<[number, WithStorageMeta] | undefined>;
|
|
58
|
+
protected getFromOffset(db: IDBPDatabase<PayloadStore>, storeName: string, order?: 'asc' | 'desc', limit?: number, offset?: Hash): Promise<WithStorageMeta[]>;
|
|
59
|
+
protected getHandler(hashes: string[]): Promise<WithStorageMeta[]>;
|
|
60
|
+
protected insertHandler(payloads: Payload[]): Promise<Payload[]>;
|
|
61
|
+
protected nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta[]>;
|
|
65
62
|
protected startHandler(): Promise<boolean>;
|
|
66
63
|
/**
|
|
67
64
|
* Returns that the desired DB/Store initialized to the correct version
|
|
@@ -75,5 +72,4 @@ export declare class IndexedDbArchivist<TParams extends IndexedDbArchivistParams
|
|
|
75
72
|
*/
|
|
76
73
|
private useDb;
|
|
77
74
|
}
|
|
78
|
-
export {};
|
|
79
75
|
//# sourceMappingURL=Archivist.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;
|
|
1
|
+
{"version":3,"file":"Archivist.d.ts","sourceRoot":"","sources":["../../src/Archivist.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AACpF,OAAO,EAKL,wBAAwB,EACxB,oBAAoB,EAIrB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAC5D,OAAO,EACgB,YAAY,EAClC,MAAM,KAAK,CAAA;AAGZ,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAEtD,MAAM,WAAW,YAAY;IAC3B,CAAC,CAAC,EAAE,MAAM,GAAG,eAAe,CAAA;CAC7B;AAED,qBACa,kBAAkB,CAC7B,OAAO,SAAS,wBAAwB,GAAG,wBAAwB,EACnE,UAAU,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC;IAC9C,gBAAyB,aAAa,EAAE,MAAM,EAAE,CAA2D;IAC3G,gBAAyB,mBAAmB,EAAE,MAAM,CAAiC;IACrF,MAAM,CAAC,QAAQ,CAAC,aAAa,eAAc;IAC3C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,KAAI;IACpC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,cAAa;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAEpC;IAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAEhC;IAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAElC;IAGD,MAAM,CAAC,QAAQ,CAAC,aAAa,SAAuD;IAEpF,MAAM,CAAC,QAAQ,CAAC,iBAAiB,SAA2D;IAE5F,MAAM,CAAC,QAAQ,CAAC,eAAe,SAAyD;IAExF,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAC,CAAQ;IAE3B;;;;;;OAMG;IACH,IAAI,MAAM,WAeT;IAED;;OAEG;IACH,IAAI,SAAS,WAEZ;IAED,IAAa,OAAO,aASnB;IAED;;;OAGG;IACH,IAAI,SAAS,WAUZ;IAED;;OAEG;IACH,OAAO,KAAK,OAAO,GAElB;cAEwB,UAAU,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;cAOhC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;cAI7B,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IA+BvE;;;;;;;OAOG;cACa,0BAA0B,CACxC,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,CAAC;cAiBjC,aAAa,CAC3B,EAAE,EAAE,YAAY,CAAC,YAAY,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,KAAK,GAAG,MAAc,EAC7B,KAAK,GAAE,MAAW,EAClB,MAAM,CAAC,EAAE,IAAI,GACZ,OAAO,CAAC,eAAe,EAAE,CAAC;cA0CJ,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cAmCxD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;cAmCtD,WAAW,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;cASvE,YAAY;IAQrC;;;OAGG;YACW,gBAAgB;IAoD9B;;;;OAIG;YACW,KAAK;CAWpB"}
|
package/dist/browser/index.mjs
CHANGED
|
@@ -39,7 +39,7 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
|
39
39
|
static defaultStoreName = "payloads";
|
|
40
40
|
static dataHashIndex = {
|
|
41
41
|
key: {
|
|
42
|
-
|
|
42
|
+
_dataHash: 1
|
|
43
43
|
},
|
|
44
44
|
multiEntry: false,
|
|
45
45
|
unique: false
|
|
@@ -133,7 +133,7 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
|
133
133
|
}
|
|
134
134
|
async allHandler() {
|
|
135
135
|
const payloads = await this.useDb((db) => db.getAll(this.storeName));
|
|
136
|
-
return
|
|
136
|
+
return payloads;
|
|
137
137
|
}
|
|
138
138
|
async clearHandler() {
|
|
139
139
|
await this.useDb((db) => db.clear(this.storeName));
|
|
@@ -143,10 +143,13 @@ var IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
|
143
143
|
...new Set(hashes)
|
|
144
144
|
];
|
|
145
145
|
const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes));
|
|
146
|
-
const hashesToDelete = pairs.
|
|
147
|
-
pair[0]
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {
|
|
147
|
+
const dataHash0 = await PayloadBuilder.dataHash(pair[0]);
|
|
148
|
+
return [
|
|
149
|
+
dataHash0,
|
|
150
|
+
pair[1]
|
|
151
|
+
];
|
|
152
|
+
}))).flat();
|
|
150
153
|
const distinctHashes = [
|
|
151
154
|
...new Set(hashesToDelete)
|
|
152
155
|
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { uniq, uniqBy } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n ArchivistNextOptions,\n ArchivistNextQuerySchema,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport {\n Payload, PayloadWithMeta, Schema,\n} from '@xyo-network/payload-model'\nimport {\n IDBPCursorWithValue, IDBPDatabase, openDB,\n} from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config.ts'\nimport { IndexedDbArchivistParams } from './Params.ts'\n\ntype StoredPayload = PayloadWithMeta & { _hash: string }\n\nexport interface PayloadStore {\n [s: string]: StoredPayload\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbArchivistConfigSchema]\n static override readonly defaultConfigSchema: Schema = IndexedDbArchivistConfigSchema\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly dataHashIndex: IndexDescription = {\n key: { $hash: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly hashIndex: IndexDescription = {\n key: { _hash: 1 }, multiEntry: false, unique: true,\n }\n\n private static readonly schemaIndex: IndexDescription = {\n key: { schema: 1 }, multiEntry: false, unique: false,\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n private _dbName?: string\n private _storeName?: string\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n if (!this._dbName) {\n if (this.config?.dbName) {\n this._dbName = this.config?.dbName\n } else {\n if (this.config?.name) {\n this.logger.warn('No dbName provided, using module name: ', this.config?.name)\n this._dbName = this.config?.name\n } else {\n this.logger.warn('No dbName provided, using default name: ', IndexedDbArchivist.defaultDbName)\n this._dbName = IndexedDbArchivist.defaultDbName\n }\n }\n }\n return assertEx(this._dbName)\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [\n ArchivistNextQuerySchema,\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ...super.queries,\n ]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n if (!this._storeName) {\n if (this.config?.storeName) {\n this._storeName = this.config?.storeName\n } else {\n this.logger.warn('No storeName provided, using default name: ', IndexedDbArchivist.defaultStoreName)\n this._storeName = IndexedDbArchivist.defaultStoreName\n }\n }\n return assertEx(this._storeName)\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<StoredPayload[]> {\n // Get all payloads from the store\n const payloads = await this.useDb(db => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return await Promise.all(payloads.map(payload => PayloadBuilder.build(payload)))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb(db => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n // Filter duplicates to prevent unnecessary DB queries\n const uniqueHashes = [...new Set(hashes)]\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))\n const hashesToDelete = pairs.flatMap<Hash>(pair => [pair[0].$hash, pair[1]])\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing\n = (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))\n ?? (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter(hash => uniqueHashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, StoredPayload] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n if (typeof cursor.primaryKey !== 'number') {\n throw new TypeError('primaryKey must be a number')\n }\n\n return [cursor.primaryKey, singleValue]\n }\n }\n\n // eslint-disable-next-line complexity\n protected async getFromOffset(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n order: 'asc' | 'desc' = 'asc',\n limit: number = 10,\n offset?: Hash,\n ): Promise<StoredPayload[]> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const hashIndex = store.index(IndexedDbArchivist.hashIndexName)\n let primaryCursor: IDBPCursorWithValue<PayloadStore, [string]> | null | undefined = undefined\n if (offset) {\n const hashCursor = assertEx(await hashIndex.openCursor(offset), () => 'Failed to get cursor')\n const startPrimaryKey = (hashCursor?.primaryKey ?? 0) as number // we know the primary key is a number and starts at 1\n primaryCursor = await (order === 'desc'\n ? store.openCursor(IDBKeyRange.upperBound(startPrimaryKey), 'prev')\n : store.openCursor(IDBKeyRange.lowerBound(startPrimaryKey), 'next'))\n if (!primaryCursor?.value) return []\n try {\n primaryCursor = await primaryCursor?.advance(1) // advance to skip the offset value\n } catch {\n return []\n }\n } else {\n primaryCursor = await store.openCursor(null, order === 'desc' ? 'prev' : 'next')\n if (!primaryCursor?.value) return []\n }\n\n let remaining = limit\n const result: StoredPayload[] = []\n while (remaining) {\n const value = primaryCursor?.value\n if (value) {\n result.push(value)\n try {\n primaryCursor = await primaryCursor?.advance(1)\n } catch {\n break\n }\n if (primaryCursor === null) {\n break\n }\n }\n remaining--\n }\n return result\n }\n\n protected override async getHandler(hashes: string[]): Promise<StoredPayload[]> {\n const payloads = await this.useDb(db =>\n Promise.all(\n // Filter duplicates to prevent unnecessary DB queries\n uniq(hashes).map(async (hash) => {\n // Find by hash\n const payload = await this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash)\n // If found, return\n if (payload) return payload\n // Otherwise, find by data hash\n return this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash)\n }),\n ))\n\n const found = new Set<string>()\n return (\n payloads\n // Filter out not found\n .filter(exists)\n // Sort by primary key\n .sort((a, b) => a![0] - b![0])\n // Filter out duplicates by hash\n .filter(([_key, payload]) => {\n if (found.has(payload._hash)) {\n return false\n } else {\n found.add(payload._hash)\n return true\n }\n })\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<PayloadWithMeta[]> {\n // Get the unique pairs of payloads and their hashes\n const uniquePayloadHashPairs = uniqBy(await PayloadBuilder.hashPairs(payloads), ([, _hash]) => _hash)\n return await this.useDb(async (db) => {\n // Perform all inserts via a single transaction to ensure atomicity\n // with respect to checking for the pre-existence of the hash.\n // This is done to prevent duplicate root hashes due to race\n // conditions between checking vs insertion.\n const tx = db.transaction(this.storeName, 'readwrite')\n // Get the object store\n const store = tx.objectStore(this.storeName)\n // Return only the payloads that were successfully inserted\n const inserted: PayloadWithMeta[] = []\n try {\n await Promise.all(\n uniquePayloadHashPairs.map(async ([payload, _hash]) => {\n // Check if the root hash already exists\n const existingRootHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingRootHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n // Add it to the inserted list\n inserted.push(payload)\n }\n }),\n )\n } finally {\n // Ensure the transaction is closed\n await tx.done\n }\n return inserted\n })\n }\n\n protected override async nextHandler(options?: ArchivistNextOptions): Promise<StoredPayload[]> {\n const {\n limit, offset, order,\n } = options ?? {}\n return await this.useDb(async (db) => {\n return await this.getFromOffset(db, this.storeName, order, limit ?? 10, offset)\n })\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const {\n dbName, dbVersion, indexes, storeName, logger,\n } = this\n return await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n logger.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n logger.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n logger.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const {\n key, multiEntry, unique,\n } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import type { ArchivistConfig } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema.ts'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["uniq","uniqBy","assertEx","exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","ArchivistNextQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","dataHashIndex","key","$hash","multiEntry","unique","hashIndex","_hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","_dbName","_storeName","dbName","config","name","logger","warn","assertEx","dbVersion","queries","ArchivistNextQuerySchema","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","Promise","all","map","payload","PayloadBuilder","build","clearHandler","clear","deleteHandler","hashes","uniqueHashes","Set","pairs","hashPairs","getHandler","hashesToDelete","flatMap","pair","distinctHashes","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","TypeError","getFromOffset","order","limit","offset","primaryCursor","undefined","hashCursor","startPrimaryKey","IDBKeyRange","upperBound","lowerBound","advance","remaining","result","push","uniq","sort","a","b","_key","has","add","insertHandler","uniquePayloadHashPairs","uniqBy","tx","inserted","existingRootHash","get","put","done","nextHandler","options","startHandler","getInitializedDb","openDB","blocked","currentVersion","blockedVersion","event","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","createIndex","callback","close"]}
|
|
1
|
+
{"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { uniq, uniqBy } from '@xylabs/array'\nimport { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { Hash } from '@xylabs/hex'\nimport { AbstractArchivist, WithStorageMeta } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n ArchivistNextOptions,\n ArchivistNextQuerySchema,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { PayloadBuilder } from '@xyo-network/payload-builder'\nimport { Payload, Schema } from '@xyo-network/payload-model'\nimport {\n IDBPCursorWithValue, IDBPDatabase, openDB,\n} from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config.ts'\nimport { IndexedDbArchivistParams } from './Params.ts'\n\nexport interface PayloadStore {\n [s: string]: WithStorageMeta\n}\n\n@creatableModule()\nexport class IndexedDbArchivist<\n TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,\n TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,\n> extends AbstractArchivist<TParams, TEventData> {\n static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbArchivistConfigSchema]\n static override readonly defaultConfigSchema: Schema = IndexedDbArchivistConfigSchema\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly dataHashIndex: IndexDescription = {\n key: { _dataHash: 1 }, multiEntry: false, unique: false,\n }\n\n private static readonly hashIndex: IndexDescription = {\n key: { _hash: 1 }, multiEntry: false, unique: true,\n }\n\n private static readonly schemaIndex: IndexDescription = {\n key: { schema: 1 }, multiEntry: false, unique: false,\n }\n\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)\n // eslint-disable-next-line @typescript-eslint/member-ordering\n static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n private _dbName?: string\n private _storeName?: string\n\n /**\n * The database name. If not supplied via config, it defaults\n * to the module name (not guaranteed to be unique) and if module\n * name is not supplied, it defaults to `archivist`. This behavior\n * biases towards a single, isolated DB per archivist which seems to\n * make the most sense for 99% of use cases.\n */\n get dbName() {\n if (!this._dbName) {\n if (this.config?.dbName) {\n this._dbName = this.config?.dbName\n } else {\n if (this.config?.name) {\n this.logger.warn('No dbName provided, using module name: ', this.config?.name)\n this._dbName = this.config?.name\n } else {\n this.logger.warn('No dbName provided, using default name: ', IndexedDbArchivist.defaultDbName)\n this._dbName = IndexedDbArchivist.defaultDbName\n }\n }\n }\n return assertEx(this._dbName)\n }\n\n /**\n * The database version. If not supplied via config, it defaults to 1.\n */\n get dbVersion() {\n return this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion\n }\n\n override get queries() {\n return [\n ArchivistNextQuerySchema,\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ...super.queries,\n ]\n }\n\n /**\n * The name of the object store. If not supplied via config, it defaults\n * to `payloads`.\n */\n get storeName() {\n if (!this._storeName) {\n if (this.config?.storeName) {\n this._storeName = this.config?.storeName\n } else {\n this.logger.warn('No storeName provided, using default name: ', IndexedDbArchivist.defaultStoreName)\n this._storeName = IndexedDbArchivist.defaultStoreName\n }\n }\n return assertEx(this._storeName)\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]\n }\n\n protected override async allHandler(): Promise<Payload[]> {\n // Get all payloads from the store\n const payloads = await this.useDb(db => db.getAll(this.storeName))\n // Remove any metadata before returning to the client\n return payloads\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.useDb(db => db.clear(this.storeName))\n }\n\n protected override async deleteHandler(hashes: Hash[]): Promise<Hash[]> {\n // Filter duplicates to prevent unnecessary DB queries\n const uniqueHashes = [...new Set(hashes)]\n const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))\n const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {\n const dataHash0 = await PayloadBuilder.dataHash(pair[0])\n return [dataHash0, pair[1]]\n }))).flat()\n // Remove any duplicates\n const distinctHashes = [...new Set(hashesToDelete)]\n return await this.useDb(async (db) => {\n // Only return hashes that were successfully deleted\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n // Check if the hash exists\n const existing\n = (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))\n ?? (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))\n // If it does exist\n if (existing) {\n // Delete it\n await db.delete(this.storeName, existing)\n // Return the hash so it gets added to the list of deleted hashes\n return hash\n }\n }),\n )\n return found.filter(exists).filter(hash => uniqueHashes.includes(hash))\n })\n }\n\n /**\n * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)\n * @param db The db instance to use\n * @param storeName The name of the store to use\n * @param indexName The index to use\n * @param key The key to get from the index\n * @returns The primary key and the payload, or undefined if not found\n */\n protected async getFromIndexWithPrimaryKey(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n indexName: string,\n key: IDBValidKey,\n ): Promise<[number, WithStorageMeta] | undefined> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const index = store.index(indexName)\n const cursor = await index.openCursor(key)\n if (cursor) {\n const singleValue = cursor.value\n // NOTE: It's known to be a number because we are using IndexedDB supplied auto-incrementing keys\n if (typeof cursor.primaryKey !== 'number') {\n throw new TypeError('primaryKey must be a number')\n }\n\n return [cursor.primaryKey, singleValue]\n }\n }\n\n // eslint-disable-next-line complexity\n protected async getFromOffset(\n db: IDBPDatabase<PayloadStore>,\n storeName: string,\n order: 'asc' | 'desc' = 'asc',\n limit: number = 10,\n offset?: Hash,\n ): Promise<WithStorageMeta[]> {\n const transaction = db.transaction(storeName, 'readonly')\n const store = transaction.objectStore(storeName)\n const hashIndex = store.index(IndexedDbArchivist.hashIndexName)\n let primaryCursor: IDBPCursorWithValue<PayloadStore, [string]> | null | undefined = undefined\n if (offset) {\n const hashCursor = assertEx(await hashIndex.openCursor(offset), () => 'Failed to get cursor')\n const startPrimaryKey = (hashCursor?.primaryKey ?? 0) as number // we know the primary key is a number and starts at 1\n primaryCursor = await (order === 'desc'\n ? store.openCursor(IDBKeyRange.upperBound(startPrimaryKey), 'prev')\n : store.openCursor(IDBKeyRange.lowerBound(startPrimaryKey), 'next'))\n if (!primaryCursor?.value) return []\n try {\n primaryCursor = await primaryCursor?.advance(1) // advance to skip the offset value\n } catch {\n return []\n }\n } else {\n primaryCursor = await store.openCursor(null, order === 'desc' ? 'prev' : 'next')\n if (!primaryCursor?.value) return []\n }\n\n let remaining = limit\n const result: WithStorageMeta[] = []\n while (remaining) {\n const value = primaryCursor?.value\n if (value) {\n result.push(value)\n try {\n primaryCursor = await primaryCursor?.advance(1)\n } catch {\n break\n }\n if (primaryCursor === null) {\n break\n }\n }\n remaining--\n }\n return result\n }\n\n protected override async getHandler(hashes: string[]): Promise<WithStorageMeta[]> {\n const payloads = await this.useDb(db =>\n Promise.all(\n // Filter duplicates to prevent unnecessary DB queries\n uniq(hashes).map(async (hash) => {\n // Find by hash\n const payload = await this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash)\n // If found, return\n if (payload) return payload\n // Otherwise, find by data hash\n return this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash)\n }),\n ))\n\n const found = new Set<string>()\n return (\n payloads\n // Filter out not found\n .filter(exists)\n // Sort by primary key\n .sort((a, b) => a![0] - b![0])\n // Filter out duplicates by hash\n .filter(([_key, payload]) => {\n if (found.has(payload._hash)) {\n return false\n } else {\n found.add(payload._hash)\n return true\n }\n })\n // Return just the payloads\n .map(([_key, payload]) => payload)\n )\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<Payload[]> {\n // Get the unique pairs of payloads and their hashes\n const uniquePayloadHashPairs = uniqBy(await PayloadBuilder.hashPairs(payloads), ([, _hash]) => _hash)\n return await this.useDb(async (db) => {\n // Perform all inserts via a single transaction to ensure atomicity\n // with respect to checking for the pre-existence of the hash.\n // This is done to prevent duplicate root hashes due to race\n // conditions between checking vs insertion.\n const tx = db.transaction(this.storeName, 'readwrite')\n // Get the object store\n const store = tx.objectStore(this.storeName)\n // Return only the payloads that were successfully inserted\n const inserted: Payload[] = []\n try {\n await Promise.all(\n uniquePayloadHashPairs.map(async ([payload, _hash]) => {\n // Check if the root hash already exists\n const existingRootHash = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existingRootHash) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n // Add it to the inserted list\n inserted.push(payload)\n }\n }),\n )\n } finally {\n // Ensure the transaction is closed\n await tx.done\n }\n return inserted\n })\n }\n\n protected override async nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta[]> {\n const {\n limit, offset, order,\n } = options ?? {}\n return await this.useDb(async (db) => {\n return await this.getFromOffset(db, this.storeName, order, limit ?? 10, offset)\n })\n }\n\n protected override async startHandler() {\n await super.startHandler()\n // NOTE: We could defer this creation to first access but we\n // want to fail fast here in case something is wrong\n await this.useDb(() => {})\n return true\n }\n\n /**\n * Returns that the desired DB/Store initialized to the correct version\n * @returns The initialized DB\n */\n private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {\n const {\n dbName, dbVersion, indexes, storeName, logger,\n } = this\n return await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n logger.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n logger.log('IndexedDbArchivist: Terminated')\n },\n upgrade(database, oldVersion, newVersion, transaction) {\n // NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end\n // state but, out of an abundance of caution, we will just delete (so we know where we are starting\n // from a known good point) and recreate the desired state. This prioritizes resilience over data\n // retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser\n // state is extremely hard to debug, this seems like fair tradeoff for now.\n if (oldVersion !== newVersion) {\n logger.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)\n // Delete any existing databases that are not the current version\n const objectStores = transaction.objectStoreNames\n for (const name of objectStores) {\n try {\n database.deleteObjectStore(name)\n } catch {\n logger.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)\n }\n }\n }\n // Create the store\n const store = database.createObjectStore(storeName, {\n // If it isn't explicitly set, create a value by auto incrementing.\n autoIncrement: true,\n })\n // Name the store\n store.name = storeName\n // Create an index on the hash\n for (const {\n key, multiEntry, unique,\n } of indexes) {\n const indexKeys = Object.keys(key)\n const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys\n const indexName = buildStandardIndexName({ key, unique })\n store.createIndex(indexName, keys, { multiEntry, unique })\n }\n },\n })\n }\n\n /**\n * Executes a callback with the initialized DB and then closes the db\n * @param callback The method to execute with the initialized DB\n * @returns\n */\n private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {\n // Get the initialized DB\n const db = await this.getInitializedDb()\n try {\n // Perform the callback\n return await callback(db)\n } finally {\n // Close the DB\n db.close()\n }\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import type { ArchivistConfig } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema.ts'\n\nexport type IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\nexport const IndexedDbArchivistConfigSchema: IndexedDbArchivistConfigSchema = `${IndexedDbArchivistSchema}.config`\n\nexport type IndexedDbArchivistConfig = ArchivistConfig<{\n /**\n * The database name\n */\n dbName?: string\n /**\n * The version of the DB, defaults to 1\n */\n dbVersion?: number\n schema: IndexedDbArchivistConfigSchema\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["uniq","uniqBy","assertEx","exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","ArchivistNextQuerySchema","buildStandardIndexName","creatableModule","PayloadBuilder","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","configSchemas","IndexedDbArchivistConfigSchema","defaultConfigSchema","defaultDbName","defaultDbVersion","defaultStoreName","dataHashIndex","key","_dataHash","multiEntry","unique","hashIndex","_hash","schemaIndex","schema","hashIndexName","buildStandardIndexName","dataHashIndexName","schemaIndexName","_dbName","_storeName","dbName","config","name","logger","warn","assertEx","dbVersion","queries","ArchivistNextQuerySchema","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","indexes","storage","allHandler","payloads","useDb","db","getAll","clearHandler","clear","deleteHandler","hashes","uniqueHashes","Set","pairs","PayloadBuilder","hashPairs","getHandler","hashesToDelete","Promise","all","map","pair","dataHash0","dataHash","flat","distinctHashes","found","hash","existing","getKeyFromIndex","delete","filter","exists","includes","getFromIndexWithPrimaryKey","indexName","transaction","store","objectStore","index","cursor","openCursor","singleValue","value","primaryKey","TypeError","getFromOffset","order","limit","offset","primaryCursor","undefined","hashCursor","startPrimaryKey","IDBKeyRange","upperBound","lowerBound","advance","remaining","result","push","uniq","payload","sort","a","b","_key","has","add","insertHandler","uniquePayloadHashPairs","uniqBy","tx","inserted","existingRootHash","get","put","done","nextHandler","options","startHandler","getInitializedDb","openDB","blocked","currentVersion","blockedVersion","event","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","indexKeys","Object","keys","length","createIndex","callback","close"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xyo-network/archivist-indexeddb",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0-rc.1",
|
|
4
4
|
"description": "Primary SDK for using XYO Protocol 2.0",
|
|
5
5
|
"homepage": "https://xyo.network",
|
|
6
6
|
"bugs": {
|
|
@@ -29,29 +29,30 @@
|
|
|
29
29
|
"module": "dist/browser/index.mjs",
|
|
30
30
|
"types": "dist/browser/index.d.ts",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@xylabs/array": "^4.4.
|
|
33
|
-
"@xylabs/assert": "^4.4.
|
|
34
|
-
"@xylabs/exists": "^4.4.
|
|
35
|
-
"@xylabs/hex": "^4.4.
|
|
36
|
-
"@xyo-network/archivist-abstract": "^3.
|
|
37
|
-
"@xyo-network/archivist-model": "^3.
|
|
38
|
-
"@xyo-network/module-model": "^3.
|
|
39
|
-
"@xyo-network/payload-builder": "^3.
|
|
40
|
-
"@xyo-network/payload-model": "^3.
|
|
32
|
+
"@xylabs/array": "^4.4.12",
|
|
33
|
+
"@xylabs/assert": "^4.4.12",
|
|
34
|
+
"@xylabs/exists": "^4.4.12",
|
|
35
|
+
"@xylabs/hex": "^4.4.12",
|
|
36
|
+
"@xyo-network/archivist-abstract": "^3.6.0-rc.1",
|
|
37
|
+
"@xyo-network/archivist-model": "^3.6.0-rc.1",
|
|
38
|
+
"@xyo-network/module-model": "^3.6.0-rc.1",
|
|
39
|
+
"@xyo-network/payload-builder": "^3.6.0-rc.1",
|
|
40
|
+
"@xyo-network/payload-model": "^3.6.0-rc.1",
|
|
41
41
|
"idb": "^8.0.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@xylabs/object": "^4.4.
|
|
44
|
+
"@xylabs/object": "^4.4.12",
|
|
45
45
|
"@xylabs/ts-scripts-yarn3": "^4.2.4",
|
|
46
46
|
"@xylabs/tsconfig": "^4.2.4",
|
|
47
|
-
"@xyo-network/account": "^3.
|
|
48
|
-
"@xyo-network/id-payload-plugin": "^3.
|
|
49
|
-
"@xyo-network/payload-wrapper": "^3.
|
|
47
|
+
"@xyo-network/account": "^3.6.0-rc.1",
|
|
48
|
+
"@xyo-network/id-payload-plugin": "^3.6.0-rc.1",
|
|
49
|
+
"@xyo-network/payload-wrapper": "^3.6.0-rc.1",
|
|
50
50
|
"fake-indexeddb": "^6.0.0",
|
|
51
51
|
"typescript": "^5.7.2",
|
|
52
|
-
"vitest": "^2.1.
|
|
52
|
+
"vitest": "^2.1.8"
|
|
53
53
|
},
|
|
54
54
|
"publishConfig": {
|
|
55
55
|
"access": "public"
|
|
56
|
-
}
|
|
56
|
+
},
|
|
57
|
+
"stableVersion": "3.5.2"
|
|
57
58
|
}
|
package/src/Archivist.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { uniq, uniqBy } from '@xylabs/array'
|
|
|
2
2
|
import { assertEx } from '@xylabs/assert'
|
|
3
3
|
import { exists } from '@xylabs/exists'
|
|
4
4
|
import { Hash } from '@xylabs/hex'
|
|
5
|
-
import { AbstractArchivist } from '@xyo-network/archivist-abstract'
|
|
5
|
+
import { AbstractArchivist, WithStorageMeta } from '@xyo-network/archivist-abstract'
|
|
6
6
|
import {
|
|
7
7
|
ArchivistAllQuerySchema,
|
|
8
8
|
ArchivistClearQuerySchema,
|
|
@@ -16,9 +16,7 @@ import {
|
|
|
16
16
|
} from '@xyo-network/archivist-model'
|
|
17
17
|
import { creatableModule } from '@xyo-network/module-model'
|
|
18
18
|
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
19
|
-
import {
|
|
20
|
-
Payload, PayloadWithMeta, Schema,
|
|
21
|
-
} from '@xyo-network/payload-model'
|
|
19
|
+
import { Payload, Schema } from '@xyo-network/payload-model'
|
|
22
20
|
import {
|
|
23
21
|
IDBPCursorWithValue, IDBPDatabase, openDB,
|
|
24
22
|
} from 'idb'
|
|
@@ -26,10 +24,8 @@ import {
|
|
|
26
24
|
import { IndexedDbArchivistConfigSchema } from './Config.ts'
|
|
27
25
|
import { IndexedDbArchivistParams } from './Params.ts'
|
|
28
26
|
|
|
29
|
-
type StoredPayload = PayloadWithMeta & { _hash: string }
|
|
30
|
-
|
|
31
27
|
export interface PayloadStore {
|
|
32
|
-
[s: string]:
|
|
28
|
+
[s: string]: WithStorageMeta
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
@creatableModule()
|
|
@@ -43,7 +39,7 @@ export class IndexedDbArchivist<
|
|
|
43
39
|
static readonly defaultDbVersion = 1
|
|
44
40
|
static readonly defaultStoreName = 'payloads'
|
|
45
41
|
private static readonly dataHashIndex: IndexDescription = {
|
|
46
|
-
key: {
|
|
42
|
+
key: { _dataHash: 1 }, multiEntry: false, unique: false,
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
private static readonly hashIndex: IndexDescription = {
|
|
@@ -129,11 +125,11 @@ export class IndexedDbArchivist<
|
|
|
129
125
|
return [IndexedDbArchivist.dataHashIndex, IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
protected override async allHandler(): Promise<
|
|
128
|
+
protected override async allHandler(): Promise<Payload[]> {
|
|
133
129
|
// Get all payloads from the store
|
|
134
130
|
const payloads = await this.useDb(db => db.getAll(this.storeName))
|
|
135
131
|
// Remove any metadata before returning to the client
|
|
136
|
-
return
|
|
132
|
+
return payloads
|
|
137
133
|
}
|
|
138
134
|
|
|
139
135
|
protected override async clearHandler(): Promise<void> {
|
|
@@ -144,7 +140,10 @@ export class IndexedDbArchivist<
|
|
|
144
140
|
// Filter duplicates to prevent unnecessary DB queries
|
|
145
141
|
const uniqueHashes = [...new Set(hashes)]
|
|
146
142
|
const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))
|
|
147
|
-
const hashesToDelete = pairs.
|
|
143
|
+
const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {
|
|
144
|
+
const dataHash0 = await PayloadBuilder.dataHash(pair[0])
|
|
145
|
+
return [dataHash0, pair[1]]
|
|
146
|
+
}))).flat()
|
|
148
147
|
// Remove any duplicates
|
|
149
148
|
const distinctHashes = [...new Set(hashesToDelete)]
|
|
150
149
|
return await this.useDb(async (db) => {
|
|
@@ -181,7 +180,7 @@ export class IndexedDbArchivist<
|
|
|
181
180
|
storeName: string,
|
|
182
181
|
indexName: string,
|
|
183
182
|
key: IDBValidKey,
|
|
184
|
-
): Promise<[number,
|
|
183
|
+
): Promise<[number, WithStorageMeta] | undefined> {
|
|
185
184
|
const transaction = db.transaction(storeName, 'readonly')
|
|
186
185
|
const store = transaction.objectStore(storeName)
|
|
187
186
|
const index = store.index(indexName)
|
|
@@ -204,7 +203,7 @@ export class IndexedDbArchivist<
|
|
|
204
203
|
order: 'asc' | 'desc' = 'asc',
|
|
205
204
|
limit: number = 10,
|
|
206
205
|
offset?: Hash,
|
|
207
|
-
): Promise<
|
|
206
|
+
): Promise<WithStorageMeta[]> {
|
|
208
207
|
const transaction = db.transaction(storeName, 'readonly')
|
|
209
208
|
const store = transaction.objectStore(storeName)
|
|
210
209
|
const hashIndex = store.index(IndexedDbArchivist.hashIndexName)
|
|
@@ -227,7 +226,7 @@ export class IndexedDbArchivist<
|
|
|
227
226
|
}
|
|
228
227
|
|
|
229
228
|
let remaining = limit
|
|
230
|
-
const result:
|
|
229
|
+
const result: WithStorageMeta[] = []
|
|
231
230
|
while (remaining) {
|
|
232
231
|
const value = primaryCursor?.value
|
|
233
232
|
if (value) {
|
|
@@ -246,7 +245,7 @@ export class IndexedDbArchivist<
|
|
|
246
245
|
return result
|
|
247
246
|
}
|
|
248
247
|
|
|
249
|
-
protected override async getHandler(hashes: string[]): Promise<
|
|
248
|
+
protected override async getHandler(hashes: string[]): Promise<WithStorageMeta[]> {
|
|
250
249
|
const payloads = await this.useDb(db =>
|
|
251
250
|
Promise.all(
|
|
252
251
|
// Filter duplicates to prevent unnecessary DB queries
|
|
@@ -281,7 +280,7 @@ export class IndexedDbArchivist<
|
|
|
281
280
|
)
|
|
282
281
|
}
|
|
283
282
|
|
|
284
|
-
protected override async insertHandler(payloads: Payload[]): Promise<
|
|
283
|
+
protected override async insertHandler(payloads: Payload[]): Promise<Payload[]> {
|
|
285
284
|
// Get the unique pairs of payloads and their hashes
|
|
286
285
|
const uniquePayloadHashPairs = uniqBy(await PayloadBuilder.hashPairs(payloads), ([, _hash]) => _hash)
|
|
287
286
|
return await this.useDb(async (db) => {
|
|
@@ -293,7 +292,7 @@ export class IndexedDbArchivist<
|
|
|
293
292
|
// Get the object store
|
|
294
293
|
const store = tx.objectStore(this.storeName)
|
|
295
294
|
// Return only the payloads that were successfully inserted
|
|
296
|
-
const inserted:
|
|
295
|
+
const inserted: Payload[] = []
|
|
297
296
|
try {
|
|
298
297
|
await Promise.all(
|
|
299
298
|
uniquePayloadHashPairs.map(async ([payload, _hash]) => {
|
|
@@ -316,7 +315,7 @@ export class IndexedDbArchivist<
|
|
|
316
315
|
})
|
|
317
316
|
}
|
|
318
317
|
|
|
319
|
-
protected override async nextHandler(options?: ArchivistNextOptions): Promise<
|
|
318
|
+
protected override async nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta[]> {
|
|
320
319
|
const {
|
|
321
320
|
limit, offset, order,
|
|
322
321
|
} = options ?? {}
|