@xyo-network/archivist-indexeddb 5.3.22 → 5.3.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xyo-network/archivist-indexeddb",
3
- "version": "5.3.22",
3
+ "version": "5.3.24",
4
4
  "description": "Primary SDK for using XYO Protocol 2.0",
5
5
  "homepage": "https://xyo.network",
6
6
  "bugs": {
@@ -30,52 +30,48 @@
30
30
  "types": "dist/browser/index.d.ts",
31
31
  "files": [
32
32
  "dist",
33
- "src",
34
33
  "!**/*.bench.*",
35
34
  "!**/*.spec.*",
36
35
  "!**/*.test.*",
37
36
  "README.md"
38
37
  ],
39
38
  "dependencies": {
40
- "@xyo-network/archivist-abstract": "~5.3.22",
41
- "@xyo-network/archivist-model": "~5.3.22",
42
- "@xyo-network/module-model": "~5.3.22",
43
- "@xyo-network/payload-builder": "~5.3.22",
44
- "@xyo-network/payload-model": "~5.3.22"
39
+ "@xyo-network/archivist-abstract": "~5.3.24",
40
+ "@xyo-network/module-model": "~5.3.24",
41
+ "@xyo-network/archivist-model": "~5.3.24",
42
+ "@xyo-network/payload-builder": "~5.3.24",
43
+ "@xyo-network/payload-model": "~5.3.24"
45
44
  },
46
45
  "devDependencies": {
47
46
  "@opentelemetry/api": "^1.9.1",
48
47
  "@types/node": "^25.5.0",
49
- "@xylabs/indexed-db": "~5.0.91",
50
- "@xylabs/sdk-js": "^5.0.91",
51
- "@xylabs/ts-scripts-common": "~7.6.8",
52
- "@xylabs/ts-scripts-yarn3": "~7.6.8",
53
- "@xylabs/tsconfig": "~7.6.8",
54
- "@xyo-network/account": "~5.3.22",
55
- "@xyo-network/account-model": "~5.3.22",
56
- "@xyo-network/archivist-abstract": "~5.3.22",
57
- "@xyo-network/archivist-acceptance-tests": "~5.3.22",
58
- "@xyo-network/archivist-model": "~5.3.22",
59
- "@xyo-network/id-payload-plugin": "~5.3.22",
60
- "@xyo-network/module-model": "~5.3.22",
61
- "@xyo-network/payload-builder": "~5.3.22",
62
- "@xyo-network/payload-model": "~5.3.22",
63
- "@xyo-network/payload-wrapper": "~5.3.22",
48
+ "@xylabs/indexed-db": "~5.0.93",
49
+ "@xylabs/sdk-js": "^5.0.93",
50
+ "@xylabs/ts-scripts-common": "~7.6.16",
51
+ "@xylabs/ts-scripts-pnpm": "~7.6.16",
52
+ "@xylabs/tsconfig": "~7.6.16",
64
53
  "acorn": "^8.16.0",
65
54
  "axios": "^1.14.0",
66
- "cosmiconfig": "^9.0.1",
67
- "esbuild": "^0.27.4",
68
- "eslint": "^10.1.0",
55
+ "esbuild": "^0.28.0",
69
56
  "ethers": "^6.16.0",
70
57
  "fake-indexeddb": "~6.2.5",
71
58
  "idb": "^8.0.3",
72
- "rollup": "^4.60.1",
73
59
  "tslib": "^2.8.1",
74
60
  "typescript": "~5.9.3",
75
61
  "uuid": "~13.0.0",
76
62
  "vite": "^8.0.3",
77
63
  "vitest": "~4.1.2",
78
- "zod": "^4.3.6"
64
+ "zod": "^4.3.6",
65
+ "@xyo-network/archivist-abstract": "~5.3.24",
66
+ "@xyo-network/archivist-model": "~5.3.24",
67
+ "@xyo-network/archivist-acceptance-tests": "~5.3.24",
68
+ "@xyo-network/account-model": "~5.3.24",
69
+ "@xyo-network/id-payload-plugin": "~5.3.24",
70
+ "@xyo-network/module-model": "~5.3.24",
71
+ "@xyo-network/payload-builder": "~5.3.24",
72
+ "@xyo-network/account": "~5.3.24",
73
+ "@xyo-network/payload-model": "~5.3.24",
74
+ "@xyo-network/payload-wrapper": "~5.3.24"
79
75
  },
80
76
  "peerDependencies": {
81
77
  "@xylabs/indexed-db": "^5",
@@ -88,4 +84,4 @@
88
84
  "publishConfig": {
89
85
  "access": "public"
90
86
  }
91
- }
87
+ }
package/src/Archivist.ts DELETED
@@ -1,373 +0,0 @@
1
- import {
2
- ObjectStore,
3
- withDb,
4
- withReadOnlyStore, withReadWriteStore,
5
- } from '@xylabs/indexed-db'
6
- import {
7
- assertEx,
8
- exists,
9
- Hash, Hex, isDefined, isUndefined, uniq,
10
- } from '@xylabs/sdk-js'
11
- import { AbstractArchivist, StorageClassLabel } from '@xyo-network/archivist-abstract'
12
- import {
13
- ArchivistAllQuerySchema,
14
- ArchivistClearQuerySchema,
15
- ArchivistDeleteQuerySchema,
16
- ArchivistInsertQuerySchema,
17
- ArchivistModuleEventData,
18
- ArchivistNextOptions,
19
- ArchivistNextQuerySchema,
20
- buildStandardIndexName,
21
- IndexDescription,
22
- } from '@xyo-network/archivist-model'
23
- import { creatableModule } from '@xyo-network/module-model'
24
- import { PayloadBuilder } from '@xyo-network/payload-builder'
25
- import {
26
- Payload, Schema, WithStorageMeta,
27
- } from '@xyo-network/payload-model'
28
- import { IDBPCursorWithValue, IDBPDatabase } from 'idb'
29
-
30
- import { IndexedDbArchivistConfigSchema } from './Config.ts'
31
- import { IndexedDbArchivistParams } from './Params.ts'
32
-
33
- export interface PayloadStore {
34
- [s: string]: WithStorageMeta<Payload>
35
- }
36
-
37
- @creatableModule()
38
- export class IndexedDbArchivist<
39
- TParams extends IndexedDbArchivistParams = IndexedDbArchivistParams,
40
- TEventData extends ArchivistModuleEventData = ArchivistModuleEventData,
41
- > extends AbstractArchivist<TParams, TEventData> {
42
- static override readonly configSchemas: Schema[] = [...super.configSchemas, IndexedDbArchivistConfigSchema]
43
- static override readonly defaultConfigSchema: Schema = IndexedDbArchivistConfigSchema
44
- static readonly defaultDbName = 'archivist'
45
- static readonly defaultDbVersion = 1
46
- static readonly defaultStoreName = 'payloads'
47
- static override readonly labels = { ...super.labels, [StorageClassLabel]: 'disk' }
48
-
49
- private static readonly dataHashIndex: IndexDescription = {
50
- key: { _dataHash: 1 }, multiEntry: false, unique: false,
51
- }
52
-
53
- private static readonly hashIndex: IndexDescription = {
54
- key: { _hash: 1 }, multiEntry: false, unique: true,
55
- }
56
-
57
- private static readonly schemaIndex: IndexDescription = {
58
- key: { schema: 1 }, multiEntry: false, unique: false,
59
- }
60
-
61
- private static readonly sequenceIndex: IndexDescription = {
62
- key: { _sequence: 1 }, multiEntry: false, unique: true,
63
- }
64
-
65
- // eslint-disable-next-line @typescript-eslint/member-ordering
66
- static readonly hashIndexName = buildStandardIndexName(IndexedDbArchivist.hashIndex)
67
- // eslint-disable-next-line @typescript-eslint/member-ordering
68
- static readonly dataHashIndexName = buildStandardIndexName(IndexedDbArchivist.dataHashIndex)
69
- // eslint-disable-next-line @typescript-eslint/member-ordering
70
- static readonly schemaIndexName = buildStandardIndexName(IndexedDbArchivist.schemaIndex)
71
- // eslint-disable-next-line @typescript-eslint/member-ordering
72
- static readonly sequenceIndexName = buildStandardIndexName(IndexedDbArchivist.sequenceIndex)
73
-
74
- private _dbName?: string
75
- private _dbVersion?: number
76
- private _payloadCount = 0
77
- private _storeName?: string
78
-
79
- /**
80
- * The database name. If not supplied via config, it defaults
81
- * to the module name (not guaranteed to be unique) and if module
82
- * name is not supplied, it defaults to `archivist`. This behavior
83
- * biases towards a single, isolated DB per archivist which seems to
84
- * make the most sense for 99% of use cases.
85
- */
86
- get dbName() {
87
- if (isUndefined(this._dbName)) {
88
- if (isDefined(this.config?.dbName)) {
89
- this._dbName = this.config?.dbName
90
- } else {
91
- if (isDefined(this.config?.name)) {
92
- this.logger?.warn('No dbName provided, using module name: ', this.config?.name)
93
- this._dbName = this.config?.name
94
- } else {
95
- this.logger?.warn('No dbName provided, using default name: ', IndexedDbArchivist.defaultDbName)
96
- this._dbName = IndexedDbArchivist.defaultDbName
97
- }
98
- }
99
- }
100
- return assertEx(this._dbName)
101
- }
102
-
103
- /**
104
- * The database version. If not supplied via config, it defaults to 1.
105
- */
106
- get dbVersion() {
107
- this._dbVersion = this._dbVersion ?? this.config?.dbVersion ?? IndexedDbArchivist.defaultDbVersion
108
- return this._dbVersion
109
- }
110
-
111
- override get queries() {
112
- return [
113
- ArchivistNextQuerySchema,
114
- ArchivistAllQuerySchema,
115
- ArchivistClearQuerySchema,
116
- ArchivistDeleteQuerySchema,
117
- ArchivistInsertQuerySchema,
118
- ...super.queries,
119
- ]
120
- }
121
-
122
- /**
123
- * The name of the object store. If not supplied via config, it defaults
124
- * to `payloads`.
125
- */
126
- get storeName() {
127
- if (isUndefined(this._storeName)) {
128
- if (isDefined(this.config?.storeName)) {
129
- this._storeName = this.config?.storeName
130
- } else {
131
- this.logger?.warn('No storeName provided, using default name: ', IndexedDbArchivist.defaultStoreName)
132
- this._storeName = IndexedDbArchivist.defaultStoreName
133
- }
134
- }
135
- return assertEx(this._storeName)
136
- }
137
-
138
- /**
139
- * The indexes to create on the store
140
- */
141
- private get indexes() {
142
- return [
143
- IndexedDbArchivist.dataHashIndex,
144
- IndexedDbArchivist.hashIndex,
145
- IndexedDbArchivist.schemaIndex,
146
- IndexedDbArchivist.sequenceIndex,
147
- ...(this.config?.storage?.indexes ?? []),
148
- ]
149
- }
150
-
151
- protected override async allHandler(): Promise<WithStorageMeta<Payload>[]> {
152
- // Get all payloads from the store
153
- const payloads = await this.useDb(db => db.getAll(this.storeName))
154
- // Remove any metadata before returning to the client
155
- this._payloadCount = payloads.length
156
- return payloads
157
- }
158
-
159
- protected override async clearHandler(): Promise<void> {
160
- await this.useDb(db => db.clear(this.storeName))
161
- this._payloadCount = 0
162
- }
163
-
164
- protected override async deleteHandler(hashes: Hash[]): Promise<WithStorageMeta<Payload>[]> {
165
- // Filter duplicates to prevent unnecessary DB queries
166
- const uniqueHashes = [...new Set(hashes)]
167
- const pairs = await PayloadBuilder.hashPairs(await this.getHandler(uniqueHashes))
168
- const payloadsToDelete = await Promise.all(pairs.map(([payload]) => payload))
169
- const hashesToDelete = (await Promise.all(pairs.map(async (pair) => {
170
- const dataHash0 = await PayloadBuilder.dataHash(pair[0])
171
- return [dataHash0, pair[1]]
172
- }))).flat()
173
- // Remove any duplicates
174
- const distinctHashes = [...new Set(hashesToDelete)]
175
- const deletedHashes = await this.useDb(async (db) => {
176
- // Only return hashes that were successfully deleted
177
- const found = await Promise.all(
178
- distinctHashes.map(async (hash) => {
179
- // Check if the hash exists
180
- const existing
181
- = (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.hashIndexName, hash))
182
- ?? (await db.getKeyFromIndex(this.storeName, IndexedDbArchivist.dataHashIndexName, hash))
183
- // If it does exist
184
- if (isDefined(existing)) {
185
- // Delete it
186
- await db.delete(this.storeName, existing)
187
- // Return the hash so it gets added to the list of deleted hashes
188
- return hash
189
- }
190
- }),
191
- )
192
- return found.filter(exists).filter(hash => uniqueHashes.includes(hash))
193
- })
194
- const deletedPayloads = payloadsToDelete.filter(payload => deletedHashes.includes(payload._hash) || deletedHashes.includes(payload._dataHash))
195
- this._payloadCount = this._payloadCount - deletedPayloads.length
196
- return deletedPayloads
197
- }
198
-
199
- protected async getFromCursor(
200
- db: IDBPDatabase<ObjectStore>,
201
- storeName: string,
202
- order: 'asc' | 'desc' = 'asc',
203
- limit: number = 10,
204
- cursor?: Hex,
205
- open?: boolean,
206
- ): Promise<WithStorageMeta[]> {
207
- // TODO: We have to handle the case where the cursor is not found, and then find the correct cursor to start with (thunked cursor)
208
-
209
- return await withReadOnlyStore(db, storeName, async (store) => {
210
- const sequenceIndex = assertEx(store?.index(IndexedDbArchivist.sequenceIndexName), () => 'Failed to get sequence index')
211
- let sequenceCursor: IDBPCursorWithValue<ObjectStore, [string]> | null | undefined
212
- const parsedCursor = isDefined(cursor)
213
- ? order === 'asc'
214
- ? IDBKeyRange.lowerBound(cursor, open)
215
- : IDBKeyRange.upperBound(cursor, open)
216
- : null
217
-
218
- sequenceCursor = await sequenceIndex.openCursor(
219
- parsedCursor,
220
- order === 'desc' ? 'prev' : 'next',
221
- )
222
-
223
- let remaining = limit
224
- const result: WithStorageMeta[] = []
225
- while (remaining > 0) {
226
- const value = sequenceCursor?.value
227
- if (value) {
228
- result.push(value)
229
- }
230
- try {
231
- sequenceCursor = await sequenceCursor?.advance(1)
232
- } catch {
233
- break
234
- }
235
- if (sequenceCursor === null) {
236
- break
237
- }
238
- remaining--
239
- }
240
- return result
241
- })
242
- }
243
-
244
- /**
245
- * Uses an index to get a payload by the index value, but returns the value with the primary key (from the root store)
246
- * @param db The db instance to use
247
- * @param storeName The name of the store to use
248
- * @param indexName The index to use
249
- * @param key The key to get from the index
250
- * @returns The primary key and the payload, or undefined if not found
251
- */
252
- protected async getFromIndexWithPrimaryKey(
253
- db: IDBPDatabase<ObjectStore>,
254
- storeName: string,
255
- indexName: string,
256
- key: IDBValidKey,
257
- ): Promise<[number, WithStorageMeta] | undefined> {
258
- return await withReadOnlyStore(db, storeName, async (store) => {
259
- if (store) {
260
- const index = store.index(indexName)
261
- const cursor = await index.openCursor(key)
262
- if (cursor) {
263
- const singleValue = cursor.value
264
- // It's known to be a number because we are using IndexedDB supplied auto-incrementing keys
265
- if (typeof cursor.primaryKey !== 'number') {
266
- throw new TypeError('primaryKey must be a number')
267
- }
268
-
269
- return [cursor.primaryKey, singleValue]
270
- }
271
- }
272
- })
273
- }
274
-
275
- protected override async getHandler(hashes: string[]): Promise<WithStorageMeta[]> {
276
- const payloads = await this.useDb(db =>
277
- Promise.all(
278
- // Filter duplicates to prevent unnecessary DB queries
279
- uniq(hashes).map(async (hash) => {
280
- // Find by hash
281
- const payload = await this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.hashIndexName, hash)
282
- // If found, return
283
- if (payload) return payload
284
- // Otherwise, find by data hash
285
- return this.getFromIndexWithPrimaryKey(db, this.storeName, IndexedDbArchivist.dataHashIndexName, hash)
286
- }),
287
- ))
288
-
289
- const found = new Set<string>()
290
- return (
291
- payloads
292
- // Filter out not found
293
- .filter(exists)
294
- // Sort by primary key
295
- .toSorted((a, b) => a![0] - b![0])
296
- // Filter out duplicates by hash
297
- .filter(([_key, payload]) => {
298
- if (found.has(payload._hash)) {
299
- return false
300
- } else {
301
- found.add(payload._hash)
302
- return true
303
- }
304
- })
305
- // Return just the payloads
306
- .map(([_key, payload]) => payload)
307
- )
308
- }
309
-
310
- protected override async insertHandler(payloads: WithStorageMeta<Payload>[]): Promise<WithStorageMeta<Payload>[]> {
311
- return await this.useDb(async (db) => {
312
- // Perform all inserts via a single transaction to ensure atomicity
313
- // with respect to checking for the pre-existence of the hash.
314
- // This is done to prevent duplicate root hashes due to race
315
- // conditions between checking vs insertion.
316
- return await withReadWriteStore(db, this.storeName, async (store) => {
317
- // Return only the payloads that were successfully inserted
318
- if (store) {
319
- const inserted: WithStorageMeta<Payload>[] = []
320
- await Promise.all(
321
- payloads.map(async (payload) => {
322
- // only insert if hash does not already exist
323
- if (!await store.index(IndexedDbArchivist.hashIndexName).get(payload._hash)) {
324
- // Insert the payload
325
- await store.put(payload)
326
- // Add it to the inserted list
327
- inserted.push(payload)
328
- }
329
- }),
330
- )
331
- this._payloadCount = this._payloadCount + inserted.length
332
- return inserted
333
- } else {
334
- throw new Error('Failed to get store')
335
- }
336
- })
337
- })
338
- }
339
-
340
- protected override async nextHandler(options?: ArchivistNextOptions): Promise<WithStorageMeta<Payload>[]> {
341
- const {
342
- limit, cursor, order, open = true,
343
- } = options ?? {}
344
- return await this.useDb(async (db) => {
345
- return await this.getFromCursor(db, this.storeName, order, limit ?? 10, cursor, open)
346
- })
347
- }
348
-
349
- protected override payloadCountHandler() {
350
- return this._payloadCount
351
- }
352
-
353
- protected override async startHandler() {
354
- await super.startHandler()
355
- // We could defer this creation to first access but we
356
- // want to fail fast here in case something is wrong
357
- // also, gets current payloadCount
358
- this._payloadCount = await this.useDb(async (db) => {
359
- return await db.count(this.storeName)
360
- })
361
- }
362
-
363
- /**
364
- * Executes a callback with the initialized DB and then closes the db
365
- * @param callback The method to execute with the initialized DB
366
- * @returns
367
- */
368
- private async useDb<T>(callback: (db: IDBPDatabase<ObjectStore>) => Promise<T> | T): Promise<T> {
369
- return await withDb<ObjectStore, T>(this.dbName, async (db) => {
370
- return await callback(db)
371
- }, { [this.storeName]: this.indexes }, this.logger)
372
- }
373
- }
package/src/Config.ts DELETED
@@ -1,23 +0,0 @@
1
- import type { ArchivistConfig } from '@xyo-network/archivist-model'
2
- import { asSchema } from '@xyo-network/payload-model'
3
-
4
- import { IndexedDbArchivistSchema } from './Schema.ts'
5
-
6
- export const IndexedDbArchivistConfigSchema = asSchema(`${IndexedDbArchivistSchema}.config`, true)
7
- export type IndexedDbArchivistConfigSchema = typeof IndexedDbArchivistConfigSchema
8
-
9
- export type IndexedDbArchivistConfig<TStoreName extends string = string> = ArchivistConfig<{
10
- /**
11
- * The database name
12
- */
13
- dbName?: string
14
- /**
15
- * The version of the DB, defaults to 1
16
- */
17
- dbVersion?: number
18
- schema: IndexedDbArchivistConfigSchema
19
- /**
20
- * The name of the object store
21
- */
22
- storeName?: TStoreName
23
- }>
package/src/Params.ts DELETED
@@ -1,6 +0,0 @@
1
- import type { ArchivistParams } from '@xyo-network/archivist-model'
2
- import type { AnyConfigSchema } from '@xyo-network/module-model'
3
-
4
- import type { IndexedDbArchivistConfig } from './Config.ts'
5
-
6
- export type IndexedDbArchivistParams = ArchivistParams<AnyConfigSchema<IndexedDbArchivistConfig>>
package/src/Schema.ts DELETED
@@ -1,4 +0,0 @@
1
- import { asSchema } from '@xyo-network/payload-model'
2
-
3
- export const IndexedDbArchivistSchema = asSchema('network.xyo.archivist.indexeddb', true)
4
- export type IndexedDbArchivistSchema = typeof IndexedDbArchivistSchema
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './Archivist.ts'
2
- export * from './Config.ts'
3
- export * from './Params.ts'
4
- export * from './Schema.ts'