@xyo-network/archivist-indexeddb 2.87.1 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/Archivist.d.cts +14 -2
- package/dist/browser/Archivist.d.cts.map +1 -1
- package/dist/browser/Archivist.d.mts +14 -2
- package/dist/browser/Archivist.d.mts.map +1 -1
- package/dist/browser/Archivist.d.ts +14 -2
- package/dist/browser/Archivist.d.ts.map +1 -1
- package/dist/browser/index.cjs +88 -45
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.js +88 -45
- package/dist/browser/index.js.map +1 -1
- package/dist/node/Archivist.d.cts +14 -2
- package/dist/node/Archivist.d.cts.map +1 -1
- package/dist/node/Archivist.d.mts +14 -2
- package/dist/node/Archivist.d.mts.map +1 -1
- package/dist/node/Archivist.d.ts +14 -2
- package/dist/node/Archivist.d.ts.map +1 -1
- package/dist/node/index.cjs +88 -45
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +88 -45
- package/dist/node/index.js.map +1 -1
- package/package.json +10 -10
- package/src/Archivist.ts +113 -45
package/dist/node/index.js
CHANGED
|
@@ -7,7 +7,6 @@ var __publicField = (obj, key, value) => {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
// src/Archivist.ts
|
|
10
|
-
import { assertEx } from "@xylabs/assert";
|
|
11
10
|
import { exists } from "@xylabs/exists";
|
|
12
11
|
import { AbstractArchivist } from "@xyo-network/archivist-abstract";
|
|
13
12
|
import { ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, buildStandardIndexName } from "@xyo-network/archivist-model";
|
|
@@ -34,7 +33,6 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
34
33
|
}
|
|
35
34
|
__name(_ts_decorate, "_ts_decorate");
|
|
36
35
|
var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
37
|
-
_db;
|
|
38
36
|
/**
|
|
39
37
|
* The database name. If not supplied via config, it defaults
|
|
40
38
|
* to the module name (not guaranteed to be unique) and if module
|
|
@@ -70,76 +68,108 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
|
70
68
|
var _a;
|
|
71
69
|
return ((_a = this.config) == null ? void 0 : _a.storeName) ?? _IndexedDbArchivist.defaultStoreName;
|
|
72
70
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The indexes to create on the store
|
|
73
|
+
*/
|
|
76
74
|
get indexes() {
|
|
77
75
|
var _a, _b;
|
|
78
|
-
return
|
|
76
|
+
return [
|
|
77
|
+
_IndexedDbArchivist.hashIndex,
|
|
78
|
+
_IndexedDbArchivist.schemaIndex,
|
|
79
|
+
...((_b = (_a = this.config) == null ? void 0 : _a.storage) == null ? void 0 : _b.indexes) ?? []
|
|
80
|
+
];
|
|
79
81
|
}
|
|
80
82
|
async allHandler() {
|
|
81
|
-
const payloads = await this.db.getAll(this.storeName);
|
|
83
|
+
const payloads = await this.useDb((db) => db.getAll(this.storeName));
|
|
82
84
|
return payloads.map((payload) => PayloadHasher.jsonPayload(payload));
|
|
83
85
|
}
|
|
84
86
|
async clearHandler() {
|
|
85
|
-
await this.db.clear(this.storeName);
|
|
87
|
+
await this.useDb((db) => db.clear(this.storeName));
|
|
86
88
|
}
|
|
87
89
|
async deleteHandler(hashes) {
|
|
88
90
|
const distinctHashes = [
|
|
89
91
|
...new Set(hashes)
|
|
90
92
|
];
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
return await this.useDb(async (db) => {
|
|
94
|
+
const found = await Promise.all(distinctHashes.map(async (hash) => {
|
|
95
|
+
const existing = await db.getKeyFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash);
|
|
96
|
+
if (existing) {
|
|
97
|
+
await db.delete(this.storeName, existing);
|
|
98
|
+
return hash;
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
return found.filter(exists);
|
|
102
|
+
});
|
|
101
103
|
}
|
|
102
104
|
async getHandler(hashes) {
|
|
103
|
-
const payloads = await Promise.all(hashes.map((hash) =>
|
|
105
|
+
const payloads = await this.useDb((db) => Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, _IndexedDbArchivist.hashIndexName, hash))));
|
|
104
106
|
return payloads.filter(exists);
|
|
105
107
|
}
|
|
106
108
|
async insertHandler(payloads) {
|
|
107
109
|
const pairs = await PayloadHasher.hashPairs(payloads);
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await store.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
const db = await this.getInitializedDb();
|
|
111
|
+
try {
|
|
112
|
+
const inserted = await Promise.all(pairs.map(async ([payload, _hash]) => {
|
|
113
|
+
const tx = db.transaction(this.storeName, "readwrite");
|
|
114
|
+
try {
|
|
115
|
+
const store = tx.objectStore(this.storeName);
|
|
116
|
+
const existing = await store.index(_IndexedDbArchivist.hashIndexName).get(_hash);
|
|
117
|
+
if (!existing) {
|
|
118
|
+
await store.put({
|
|
119
|
+
...payload,
|
|
120
|
+
_hash
|
|
121
|
+
});
|
|
122
|
+
return payload;
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
await tx.done;
|
|
119
126
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
}));
|
|
128
|
+
return inserted.filter(exists);
|
|
129
|
+
} finally {
|
|
130
|
+
db.close();
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
async startHandler() {
|
|
127
134
|
await super.startHandler();
|
|
135
|
+
await this.useDb(() => {
|
|
136
|
+
});
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Returns that the desired DB/Store initialized to the correct version
|
|
141
|
+
* @returns The initialized DB
|
|
142
|
+
*/
|
|
143
|
+
async getInitializedDb() {
|
|
128
144
|
const { dbName, dbVersion, indexes, storeName } = this;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
const db = await openDB(dbName, dbVersion, {
|
|
146
|
+
blocked(currentVersion, blockedVersion, event) {
|
|
147
|
+
console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event);
|
|
148
|
+
},
|
|
149
|
+
blocking(currentVersion, blockedVersion, event) {
|
|
150
|
+
console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event);
|
|
151
|
+
},
|
|
152
|
+
terminated() {
|
|
153
|
+
console.log("IndexedDbArchivist: Terminated");
|
|
154
|
+
},
|
|
155
|
+
upgrade(database, oldVersion, newVersion, transaction) {
|
|
156
|
+
if (oldVersion !== newVersion) {
|
|
157
|
+
console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`);
|
|
158
|
+
const objectStores = transaction.objectStoreNames;
|
|
159
|
+
for (const name of objectStores) {
|
|
160
|
+
try {
|
|
161
|
+
database.deleteObjectStore(name);
|
|
162
|
+
} catch {
|
|
163
|
+
console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
132
167
|
const store = database.createObjectStore(storeName, {
|
|
133
168
|
// If it isn't explicitly set, create a value by auto incrementing.
|
|
134
169
|
autoIncrement: true
|
|
135
170
|
});
|
|
136
171
|
store.name = storeName;
|
|
137
|
-
const
|
|
138
|
-
_IndexedDbArchivist.hashIndex,
|
|
139
|
-
_IndexedDbArchivist.schemaIndex,
|
|
140
|
-
...indexes
|
|
141
|
-
];
|
|
142
|
-
for (const { key, multiEntry, unique } of indexesToCreate) {
|
|
172
|
+
for (const { key, multiEntry, unique } of indexes) {
|
|
143
173
|
const indexKeys = Object.keys(key);
|
|
144
174
|
const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys;
|
|
145
175
|
const indexName = buildStandardIndexName({
|
|
@@ -153,7 +183,20 @@ var _IndexedDbArchivist = class _IndexedDbArchivist extends AbstractArchivist {
|
|
|
153
183
|
}
|
|
154
184
|
}
|
|
155
185
|
});
|
|
156
|
-
return
|
|
186
|
+
return db;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Executes a callback with the initialized DB and then closes the db
|
|
190
|
+
* @param callback The method to execute with the initialized DB
|
|
191
|
+
* @returns
|
|
192
|
+
*/
|
|
193
|
+
async useDb(callback) {
|
|
194
|
+
const db = await this.getInitializedDb();
|
|
195
|
+
try {
|
|
196
|
+
return await callback(db);
|
|
197
|
+
} finally {
|
|
198
|
+
db.close();
|
|
199
|
+
}
|
|
157
200
|
}
|
|
158
201
|
};
|
|
159
202
|
__name(_IndexedDbArchivist, "IndexedDbArchivist");
|
package/dist/node/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { assertEx } from '@xylabs/assert'\nimport { exists } from '@xylabs/exists'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\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 configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\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 schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\n\n private _db: IDBPDatabase<PayloadStore> | undefined\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 return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\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 [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n private get db(): IDBPDatabase<PayloadStore> {\n return assertEx(this._db, 'DB not initialized')\n }\n\n private get indexes() {\n return 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.db.getAll(this.storeName)\n // Remove any metadata before returning to the client\n return payloads.map((payload) => PayloadHasher.jsonPayload(payload))\n }\n\n protected override async clearHandler(): Promise<void> {\n await this.db.clear(this.storeName)\n }\n\n protected override async deleteHandler(hashes: string[]): Promise<string[]> {\n const distinctHashes = [...new Set(hashes)]\n const found = await Promise.all(\n distinctHashes.map(async (hash) => {\n let existing: IDBValidKey | undefined\n do {\n existing = await this.db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)\n if (existing) await this.db.delete(this.storeName, existing)\n } while (!existing)\n return hash\n }),\n )\n // Return hashes removed\n return found.filter(exists)\n }\n\n protected override async getHandler(hashes: string[]): Promise<Payload[]> {\n const payloads = await Promise.all(hashes.map((hash) => this.db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)))\n return payloads.filter(exists)\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<Payload[]> {\n const pairs = await PayloadHasher.hashPairs(payloads)\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n const tx = this.db.transaction(this.storeName, 'readwrite')\n try {\n const store = tx.objectStore(this.storeName)\n const existing = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n if (!existing) {\n await store.put({ ...payload, _hash })\n return payload\n }\n } finally {\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\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 const { dbName, dbVersion, indexes, storeName } = this\n this._db = await openDB<PayloadStore>(dbName, dbVersion, {\n async upgrade(database) {\n await Promise.resolve() // Async to match spec\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 const indexesToCreate = [IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...indexes]\n for (const { key, multiEntry, unique } of indexesToCreate) {\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 return true\n }\n}\n","export type IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\nexport const IndexedDbArchivistSchema: IndexedDbArchivistSchema = 'network.xyo.archivist.indexeddb'\n","import { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\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 storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["assertEx","exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","PayloadHasher","creatableModule","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","_db","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","db","assertEx","indexes","storage","allHandler","payloads","getAll","map","payload","PayloadHasher","jsonPayload","clearHandler","clear","deleteHandler","hashes","distinctHashes","Set","found","Promise","all","hash","existing","getKeyFromIndex","hashIndexName","delete","filter","exists","getHandler","getFromIndex","insertHandler","pairs","hashPairs","inserted","_hash","tx","transaction","store","objectStore","index","get","put","done","startHandler","openDB","upgrade","database","resolve","createObjectStore","autoIncrement","indexesToCreate","hashIndex","schemaIndex","key","multiEntry","unique","indexKeys","Object","keys","length","indexName","buildStandardIndexName","createIndex","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
|
|
1
|
+
{"version":3,"sources":["../../src/Archivist.ts","../../src/Schema.ts","../../src/Config.ts"],"sourcesContent":["import { exists } from '@xylabs/exists'\nimport { AbstractArchivist } from '@xyo-network/archivist-abstract'\nimport {\n ArchivistAllQuerySchema,\n ArchivistClearQuerySchema,\n ArchivistDeleteQuerySchema,\n ArchivistInsertQuerySchema,\n ArchivistModuleEventData,\n buildStandardIndexName,\n IndexDescription,\n} from '@xyo-network/archivist-model'\nimport { PayloadHasher } from '@xyo-network/hash'\nimport { creatableModule } from '@xyo-network/module-model'\nimport { Payload } from '@xyo-network/payload-model'\nimport { IDBPDatabase, openDB } from 'idb'\n\nimport { IndexedDbArchivistConfigSchema } from './Config'\nimport { IndexedDbArchivistParams } from './Params'\n\nexport interface PayloadStore {\n [s: string]: Payload\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 configSchemas = [IndexedDbArchivistConfigSchema]\n static readonly defaultDbName = 'archivist'\n static readonly defaultDbVersion = 1\n static readonly defaultStoreName = 'payloads'\n private static readonly hashIndex: IndexDescription = { key: { _hash: 1 }, multiEntry: false, unique: true }\n private static readonly schemaIndex: IndexDescription = { key: { schema: 1 }, multiEntry: false, unique: false }\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 schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)\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 return this.config?.dbName ?? this.config?.name ?? IndexedDbArchivist.defaultDbName\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 [ArchivistAllQuerySchema, ArchivistClearQuerySchema, ArchivistDeleteQuerySchema, ArchivistInsertQuerySchema, ...super.queries]\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 return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName\n }\n\n /**\n * The indexes to create on the store\n */\n private get indexes() {\n return [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.map((payload) => PayloadHasher.jsonPayload(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: string[]): Promise<string[]> {\n // Remove any duplicates\n const distinctHashes = [...new Set(hashes)]\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 = await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, 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)\n })\n }\n\n protected override async getHandler(hashes: string[]): Promise<Payload[]> {\n const payloads = await this.useDb((db) =>\n Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),\n )\n return payloads.filter(exists)\n }\n\n protected override async insertHandler(payloads: Payload[]): Promise<Payload[]> {\n const pairs = await PayloadHasher.hashPairs(payloads)\n const db = await this.getInitializedDb()\n try {\n // Only return the payloads that were successfully inserted\n const inserted = await Promise.all(\n pairs.map(async ([payload, _hash]) => {\n // Perform each insert via a transaction to ensure it is atomic\n // with respect to checking for the pre-existence of the hash.\n // This is done to preserve iteration via insertion order.\n const tx = db.transaction(this.storeName, 'readwrite')\n try {\n // Get the object store\n const store = tx.objectStore(this.storeName)\n // Check if the hash already exists\n const existing = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)\n // If it does not already exist\n if (!existing) {\n // Insert the payload\n await store.put({ ...payload, _hash })\n // Return it so it gets added to the list of inserted payloads\n return payload\n }\n } finally {\n // Close the transaction\n await tx.done\n }\n }),\n )\n return inserted.filter(exists)\n } finally {\n db.close()\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 { dbName, dbVersion, indexes, storeName } = this\n const db = await openDB<PayloadStore>(dbName, dbVersion, {\n blocked(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)\n },\n blocking(currentVersion, blockedVersion, event) {\n console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)\n },\n terminated() {\n console.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 console.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 console.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 { key, multiEntry, unique } 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 return db\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 { ArchivistConfig, IndexDescription } from '@xyo-network/archivist-model'\n\nimport { IndexedDbArchivistSchema } from './Schema'\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 storage configuration\n * // TODO: Hoist to main archivist config\n */\n storage?: {\n /**\n * The indexes to create on the object store\n */\n indexes?: IndexDescription[]\n }\n /**\n * The name of the object store\n */\n storeName?: string\n}>\n"],"mappings":"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","names":["exists","AbstractArchivist","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","buildStandardIndexName","PayloadHasher","creatableModule","openDB","IndexedDbArchivistSchema","IndexedDbArchivistConfigSchema","IndexedDbArchivistSchema","IndexedDbArchivist","AbstractArchivist","dbName","config","name","defaultDbName","dbVersion","defaultDbVersion","queries","ArchivistAllQuerySchema","ArchivistClearQuerySchema","ArchivistDeleteQuerySchema","ArchivistInsertQuerySchema","storeName","defaultStoreName","indexes","hashIndex","schemaIndex","storage","allHandler","payloads","useDb","db","getAll","map","payload","PayloadHasher","jsonPayload","clearHandler","clear","deleteHandler","hashes","distinctHashes","Set","found","Promise","all","hash","existing","getKeyFromIndex","hashIndexName","delete","filter","exists","getHandler","getFromIndex","insertHandler","pairs","hashPairs","getInitializedDb","inserted","_hash","tx","transaction","store","objectStore","index","get","put","done","close","startHandler","openDB","blocked","currentVersion","blockedVersion","event","console","warn","blocking","terminated","log","upgrade","database","oldVersion","newVersion","objectStores","objectStoreNames","deleteObjectStore","createObjectStore","autoIncrement","key","multiEntry","unique","indexKeys","Object","keys","length","indexName","buildStandardIndexName","createIndex","callback","configSchemas","IndexedDbArchivistConfigSchema","schema","schemaIndexName","creatableModule"]}
|
package/package.json
CHANGED
|
@@ -10,21 +10,21 @@
|
|
|
10
10
|
"url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js/issues"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@xylabs/assert": "^2.13.23",
|
|
14
13
|
"@xylabs/exists": "^2.13.23",
|
|
15
|
-
"@xyo-network/archivist-abstract": "~2.
|
|
16
|
-
"@xyo-network/archivist-model": "~2.
|
|
17
|
-
"@xyo-network/hash": "~2.
|
|
18
|
-
"@xyo-network/module-model": "~2.
|
|
19
|
-
"@xyo-network/payload-model": "~2.
|
|
14
|
+
"@xyo-network/archivist-abstract": "~2.88.0",
|
|
15
|
+
"@xyo-network/archivist-model": "~2.88.0",
|
|
16
|
+
"@xyo-network/hash": "~2.88.0",
|
|
17
|
+
"@xyo-network/module-model": "~2.88.0",
|
|
18
|
+
"@xyo-network/payload-model": "~2.88.0",
|
|
20
19
|
"idb": "^8.0.0"
|
|
21
20
|
},
|
|
22
21
|
"devDependencies": {
|
|
22
|
+
"@xylabs/assert": "^2.13.23",
|
|
23
23
|
"@xylabs/ts-scripts-yarn3": "^3.2.41",
|
|
24
24
|
"@xylabs/tsconfig": "^3.2.41",
|
|
25
|
-
"@xyo-network/account": "~2.
|
|
26
|
-
"@xyo-network/id-payload-plugin": "~2.
|
|
27
|
-
"@xyo-network/payload-wrapper": "~2.
|
|
25
|
+
"@xyo-network/account": "~2.88.0",
|
|
26
|
+
"@xyo-network/id-payload-plugin": "~2.88.0",
|
|
27
|
+
"@xyo-network/payload-wrapper": "~2.88.0",
|
|
28
28
|
"fake-indexeddb": "^5.0.2",
|
|
29
29
|
"typescript": "^5.3.3"
|
|
30
30
|
},
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
"url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js.git"
|
|
68
68
|
},
|
|
69
69
|
"sideEffects": false,
|
|
70
|
-
"version": "2.
|
|
70
|
+
"version": "2.88.0",
|
|
71
71
|
"type": "module"
|
|
72
72
|
}
|
package/src/Archivist.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assertEx } from '@xylabs/assert'
|
|
2
1
|
import { exists } from '@xylabs/exists'
|
|
3
2
|
import { AbstractArchivist } from '@xyo-network/archivist-abstract'
|
|
4
3
|
import {
|
|
@@ -38,8 +37,6 @@ export class IndexedDbArchivist<
|
|
|
38
37
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
39
38
|
static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)
|
|
40
39
|
|
|
41
|
-
private _db: IDBPDatabase<PayloadStore> | undefined
|
|
42
|
-
|
|
43
40
|
/**
|
|
44
41
|
* The database name. If not supplied via config, it defaults
|
|
45
42
|
* to the module name (not guaranteed to be unique) and if module
|
|
@@ -70,75 +67,130 @@ export class IndexedDbArchivist<
|
|
|
70
67
|
return this.config?.storeName ?? IndexedDbArchivist.defaultStoreName
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The indexes to create on the store
|
|
72
|
+
*/
|
|
77
73
|
private get indexes() {
|
|
78
|
-
return this.config?.storage?.indexes ?? []
|
|
74
|
+
return [IndexedDbArchivist.hashIndex, IndexedDbArchivist.schemaIndex, ...(this.config?.storage?.indexes ?? [])]
|
|
79
75
|
}
|
|
80
76
|
|
|
81
77
|
protected override async allHandler(): Promise<Payload[]> {
|
|
82
78
|
// Get all payloads from the store
|
|
83
|
-
const payloads = await this.db.getAll(this.storeName)
|
|
79
|
+
const payloads = await this.useDb((db) => db.getAll(this.storeName))
|
|
84
80
|
// Remove any metadata before returning to the client
|
|
85
81
|
return payloads.map((payload) => PayloadHasher.jsonPayload(payload))
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
protected override async clearHandler(): Promise<void> {
|
|
89
|
-
await this.db.clear(this.storeName)
|
|
85
|
+
await this.useDb((db) => db.clear(this.storeName))
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
protected override async deleteHandler(hashes: string[]): Promise<string[]> {
|
|
89
|
+
// Remove any duplicates
|
|
93
90
|
const distinctHashes = [...new Set(hashes)]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
91
|
+
return await this.useDb(async (db) => {
|
|
92
|
+
// Only return hashes that were successfully deleted
|
|
93
|
+
const found = await Promise.all(
|
|
94
|
+
distinctHashes.map(async (hash) => {
|
|
95
|
+
// Check if the hash exists
|
|
96
|
+
const existing = await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash)
|
|
97
|
+
// If it does exist
|
|
98
|
+
if (existing) {
|
|
99
|
+
// Delete it
|
|
100
|
+
await db.delete(this.storeName, existing)
|
|
101
|
+
// Return the hash so it gets added to the list of deleted hashes
|
|
102
|
+
return hash
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
)
|
|
106
|
+
return found.filter(exists)
|
|
107
|
+
})
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
protected override async getHandler(hashes: string[]): Promise<Payload[]> {
|
|
109
|
-
const payloads = await
|
|
111
|
+
const payloads = await this.useDb((db) =>
|
|
112
|
+
Promise.all(hashes.map((hash) => db.getFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))),
|
|
113
|
+
)
|
|
110
114
|
return payloads.filter(exists)
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
protected override async insertHandler(payloads: Payload[]): Promise<Payload[]> {
|
|
114
118
|
const pairs = await PayloadHasher.hashPairs(payloads)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
const db = await this.getInitializedDb()
|
|
120
|
+
try {
|
|
121
|
+
// Only return the payloads that were successfully inserted
|
|
122
|
+
const inserted = await Promise.all(
|
|
123
|
+
pairs.map(async ([payload, _hash]) => {
|
|
124
|
+
// Perform each insert via a transaction to ensure it is atomic
|
|
125
|
+
// with respect to checking for the pre-existence of the hash.
|
|
126
|
+
// This is done to preserve iteration via insertion order.
|
|
127
|
+
const tx = db.transaction(this.storeName, 'readwrite')
|
|
128
|
+
try {
|
|
129
|
+
// Get the object store
|
|
130
|
+
const store = tx.objectStore(this.storeName)
|
|
131
|
+
// Check if the hash already exists
|
|
132
|
+
const existing = await store.index(IndexedDbArchivist.hashIndexName).get(_hash)
|
|
133
|
+
// If it does not already exist
|
|
134
|
+
if (!existing) {
|
|
135
|
+
// Insert the payload
|
|
136
|
+
await store.put({ ...payload, _hash })
|
|
137
|
+
// Return it so it gets added to the list of inserted payloads
|
|
138
|
+
return payload
|
|
139
|
+
}
|
|
140
|
+
} finally {
|
|
141
|
+
// Close the transaction
|
|
142
|
+
await tx.done
|
|
125
143
|
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
}),
|
|
145
|
+
)
|
|
146
|
+
return inserted.filter(exists)
|
|
147
|
+
} finally {
|
|
148
|
+
db.close()
|
|
149
|
+
}
|
|
132
150
|
}
|
|
133
151
|
|
|
134
152
|
protected override async startHandler() {
|
|
135
153
|
await super.startHandler()
|
|
136
154
|
// NOTE: We could defer this creation to first access but we
|
|
137
155
|
// want to fail fast here in case something is wrong
|
|
156
|
+
await this.useDb(() => {})
|
|
157
|
+
return true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Returns that the desired DB/Store initialized to the correct version
|
|
162
|
+
* @returns The initialized DB
|
|
163
|
+
*/
|
|
164
|
+
private async getInitializedDb(): Promise<IDBPDatabase<PayloadStore>> {
|
|
138
165
|
const { dbName, dbVersion, indexes, storeName } = this
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
166
|
+
const db = await openDB<PayloadStore>(dbName, dbVersion, {
|
|
167
|
+
blocked(currentVersion, blockedVersion, event) {
|
|
168
|
+
console.warn(`IndexedDbArchivist: Blocked from upgrading from ${currentVersion} to ${blockedVersion}`, event)
|
|
169
|
+
},
|
|
170
|
+
blocking(currentVersion, blockedVersion, event) {
|
|
171
|
+
console.warn(`IndexedDbArchivist: Blocking upgrade from ${currentVersion} to ${blockedVersion}`, event)
|
|
172
|
+
},
|
|
173
|
+
terminated() {
|
|
174
|
+
console.log('IndexedDbArchivist: Terminated')
|
|
175
|
+
},
|
|
176
|
+
upgrade(database, oldVersion, newVersion, transaction) {
|
|
177
|
+
// NOTE: This is called whenever the DB is created/updated. We could simply ensure the desired end
|
|
178
|
+
// state but, out of an abundance of caution, we will just delete (so we know where we are starting
|
|
179
|
+
// from a known good point) and recreate the desired state. This prioritizes resilience over data
|
|
180
|
+
// retention but we can revisit that tradeoff when it becomes limiting. Because distributed browser
|
|
181
|
+
// state is extremely hard to debug, this seems like fair tradeoff for now.
|
|
182
|
+
if (oldVersion !== newVersion) {
|
|
183
|
+
console.log(`IndexedDbArchivist: Upgrading from ${oldVersion} to ${newVersion}`)
|
|
184
|
+
// Delete any existing databases that are not the current version
|
|
185
|
+
const objectStores = transaction.objectStoreNames
|
|
186
|
+
for (const name of objectStores) {
|
|
187
|
+
try {
|
|
188
|
+
database.deleteObjectStore(name)
|
|
189
|
+
} catch {
|
|
190
|
+
console.log(`IndexedDbArchivist: Failed to delete existing object store ${name}`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
142
194
|
// Create the store
|
|
143
195
|
const store = database.createObjectStore(storeName, {
|
|
144
196
|
// If it isn't explicitly set, create a value by auto incrementing.
|
|
@@ -147,8 +199,7 @@ export class IndexedDbArchivist<
|
|
|
147
199
|
// Name the store
|
|
148
200
|
store.name = storeName
|
|
149
201
|
// Create an index on the hash
|
|
150
|
-
const
|
|
151
|
-
for (const { key, multiEntry, unique } of indexesToCreate) {
|
|
202
|
+
for (const { key, multiEntry, unique } of indexes) {
|
|
152
203
|
const indexKeys = Object.keys(key)
|
|
153
204
|
const keys = indexKeys.length === 1 ? indexKeys[0] : indexKeys
|
|
154
205
|
const indexName = buildStandardIndexName({ key, unique })
|
|
@@ -156,6 +207,23 @@ export class IndexedDbArchivist<
|
|
|
156
207
|
}
|
|
157
208
|
},
|
|
158
209
|
})
|
|
159
|
-
return
|
|
210
|
+
return db
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Executes a callback with the initialized DB and then closes the db
|
|
215
|
+
* @param callback The method to execute with the initialized DB
|
|
216
|
+
* @returns
|
|
217
|
+
*/
|
|
218
|
+
private async useDb<T>(callback: (db: IDBPDatabase<PayloadStore>) => Promise<T> | T): Promise<T> {
|
|
219
|
+
// Get the initialized DB
|
|
220
|
+
const db = await this.getInitializedDb()
|
|
221
|
+
try {
|
|
222
|
+
// Perform the callback
|
|
223
|
+
return await callback(db)
|
|
224
|
+
} finally {
|
|
225
|
+
// Close the DB
|
|
226
|
+
db.close()
|
|
227
|
+
}
|
|
160
228
|
}
|
|
161
229
|
}
|