From f3fffb619444ee20c62bb2ddcab00bb9ca6519b3 Mon Sep 17 00:00:00 2001 From: Deggen Date: Thu, 7 May 2026 16:39:17 -0500 Subject: [PATCH 1/2] refactor(overlays): address S7059 async constructors in topic storage managers Replace fire-and-forget async index creation in constructors with the `.ready: Promise` pattern, extracting index setup into private async methods and parallelising independent createIndex calls via Promise.all. Covers all 14 affected storage managers across @bsv/overlay-topics and @bsv/btms-backend. Refs #38 #45. Co-Authored-By: Claude Sonnet 4.6 --- .../src/lookup-services/BTMSStorageManager.ts | 23 +++++++---------- .../overlays/topics/src/any/AnyStorage.ts | 4 ++- .../topics/src/apps/AppsStorageManager.ts | 10 ++++++-- .../topics/src/btms/BTMSStorageManager.ts | 23 +++++++---------- .../DesktopIntegrityStorage.ts | 4 ++- .../topics/src/did/DIDStorageManager.ts | 7 +++++- .../src/fractionalize/FractionalizeStorage.ts | 4 ++- .../topics/src/hello/HelloWorldStorage.ts | 4 ++- .../src/identity/IdentityStorageManager.ts | 25 ++++++++++++------- .../src/kvstore/KVStoreStorageManager.ts | 17 +++++++++---- .../src/monsterbattle/MonsterBattleStorage.ts | 4 ++- .../src/slackthreads/SlackThreadsStorage.ts | 4 ++- .../src/supplychain/SupplyChainStorage.ts | 4 ++- .../src/utility-tokens/TokenDemoStorage.ts | 10 +++++--- 14 files changed, 88 insertions(+), 55 deletions(-) diff --git a/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts b/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts index 728e6acbc..c1fa10e8a 100644 --- a/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts +++ b/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts @@ -7,27 +7,22 @@ import { PubKeyHex } from '@bsv/sdk' */ export class BTMSStorageManager { private readonly records: Collection + readonly ready: Promise /** * @param db A connected MongoDB database handle. */ constructor (private readonly db: Db) { this.records = db.collection('btmsRecords') + this.ready = this.createIndices() + } - // Create index on assetId for efficient lookups - this.records - .createIndex({ assetId: 1 }) - .catch(console.error) - - // Create index on ownerKey for efficient lookups - this.records - .createIndex({ ownerKey: 1 }) - .catch(console.error) - - // Create compound index for txid and outputIndex (unique identifier) - this.records - .createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) - .catch(console.error) + private async createIndices (): Promise { + await Promise.all([ + this.records.createIndex({ assetId: 1 }), + this.records.createIndex({ ownerKey: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) + ]) } /** diff --git a/packages/overlays/topics/src/any/AnyStorage.ts b/packages/overlays/topics/src/any/AnyStorage.ts index 4a14f6d6e..cb6494791 100644 --- a/packages/overlays/topics/src/any/AnyStorage.ts +++ b/packages/overlays/topics/src/any/AnyStorage.ts @@ -5,9 +5,11 @@ import { AnyRecord, UTXOReference } from './types.js' export class AnyStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('anyRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/apps/AppsStorageManager.ts b/packages/overlays/topics/src/apps/AppsStorageManager.ts index 3501307f8..968ddf6a2 100644 --- a/packages/overlays/topics/src/apps/AppsStorageManager.ts +++ b/packages/overlays/topics/src/apps/AppsStorageManager.ts @@ -5,14 +5,20 @@ import { AppCatalogRecord, PublishedAppMetadata } from './types.js' export class AppsStorageManager { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('appsCatalogRecords') - this.records.createIndex({ + this.ready = this.createIndices() + } + + private async createIndices(): Promise { + await this.records.createIndex({ 'metadata.name': 'text', 'metadata.description': 'text', 'metadata.tags': 'text', 'metadata.domain': 'text' - }).catch(console.error) + }) } async storeRecord(txid: string, outputIndex: number, metadata: PublishedAppMetadata): Promise { diff --git a/packages/overlays/topics/src/btms/BTMSStorageManager.ts b/packages/overlays/topics/src/btms/BTMSStorageManager.ts index 728e6acbc..c1fa10e8a 100644 --- a/packages/overlays/topics/src/btms/BTMSStorageManager.ts +++ b/packages/overlays/topics/src/btms/BTMSStorageManager.ts @@ -7,27 +7,22 @@ import { PubKeyHex } from '@bsv/sdk' */ export class BTMSStorageManager { private readonly records: Collection + readonly ready: Promise /** * @param db A connected MongoDB database handle. */ constructor (private readonly db: Db) { this.records = db.collection('btmsRecords') + this.ready = this.createIndices() + } - // Create index on assetId for efficient lookups - this.records - .createIndex({ assetId: 1 }) - .catch(console.error) - - // Create index on ownerKey for efficient lookups - this.records - .createIndex({ ownerKey: 1 }) - .catch(console.error) - - // Create compound index for txid and outputIndex (unique identifier) - this.records - .createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) - .catch(console.error) + private async createIndices (): Promise { + await Promise.all([ + this.records.createIndex({ assetId: 1 }), + this.records.createIndex({ ownerKey: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) + ]) } /** diff --git a/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts b/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts index 1f348806f..dd7e4964a 100644 --- a/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts +++ b/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts @@ -4,9 +4,11 @@ import { DesktopIntegrityRecord, UTXOReference } from './types.js' export class DesktopIntegrityStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('desktopIntegrityRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/did/DIDStorageManager.ts b/packages/overlays/topics/src/did/DIDStorageManager.ts index e80957e1d..62ddcf24e 100644 --- a/packages/overlays/topics/src/did/DIDStorageManager.ts +++ b/packages/overlays/topics/src/did/DIDStorageManager.ts @@ -5,10 +5,15 @@ import { LookupFormula } from '@bsv/overlay' export class DIDStorageManager { private readonly records: Collection + readonly ready: Promise constructor(private readonly db: Db) { this.records = db.collection('didRecords') - this.records.createIndex({ searchableAttributes: 'text' }).catch((e) => console.error(e)) + this.ready = this.createIndices() + } + + private async createIndices(): Promise { + await this.records.createIndex({ searchableAttributes: 'text' }) } async storeRecord(txid: string, outputIndex: number, serialNumber: Base64String): Promise { diff --git a/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts b/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts index 81d08fcca..b6c5436a7 100644 --- a/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts +++ b/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts @@ -4,9 +4,11 @@ import { FractionalizeRecord, UTXOReference } from './types.js' export class FractionalizeStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('fractionalizeRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/hello/HelloWorldStorage.ts b/packages/overlays/topics/src/hello/HelloWorldStorage.ts index 5aa280a46..20fbffb9e 100644 --- a/packages/overlays/topics/src/hello/HelloWorldStorage.ts +++ b/packages/overlays/topics/src/hello/HelloWorldStorage.ts @@ -4,9 +4,11 @@ import { HelloWorldRecord, UTXOReference } from './types.js' export class HelloWorldStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('helloWorldRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/identity/IdentityStorageManager.ts b/packages/overlays/topics/src/identity/IdentityStorageManager.ts index 0429093b4..0f9ff819a 100644 --- a/packages/overlays/topics/src/identity/IdentityStorageManager.ts +++ b/packages/overlays/topics/src/identity/IdentityStorageManager.ts @@ -8,18 +8,25 @@ interface Query { export class IdentityStorageManager { private readonly records: Collection + readonly ready: Promise constructor(private readonly db: Db) { this.records = db.collection('identityRecords') - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.serialNumber': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.subject': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.certifier': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.subject': 1, 'certificate.certifier': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.subject': 1, 'certificate.type': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.fields.userName': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ 'certificate.fields.userName': 1, 'certificate.certifier': 1 }).catch((e) => console.error(e)) - this.records.createIndex({ searchableAttributes: 'text' }).catch((e) => console.error(e)) + this.ready = this.createIndices() + } + + private async createIndices(): Promise { + await Promise.all([ + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), + this.records.createIndex({ 'certificate.serialNumber': 1 }), + this.records.createIndex({ 'certificate.subject': 1 }), + this.records.createIndex({ 'certificate.certifier': 1 }), + this.records.createIndex({ 'certificate.subject': 1, 'certificate.certifier': 1 }), + this.records.createIndex({ 'certificate.subject': 1, 'certificate.type': 1 }), + this.records.createIndex({ 'certificate.fields.userName': 1 }), + this.records.createIndex({ 'certificate.fields.userName': 1, 'certificate.certifier': 1 }), + this.records.createIndex({ searchableAttributes: 'text' }) + ]) } async storeRecord(txid: string, outputIndex: number, certificate: Certificate): Promise { diff --git a/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts b/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts index 2103424be..6ee88981e 100644 --- a/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts +++ b/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts @@ -4,14 +4,21 @@ import { PubKeyHex, WalletProtocol } from '@bsv/sdk' export class KVStoreStorageManager { private readonly records: Collection + readonly ready: Promise constructor(private readonly db: Db) { this.records = db.collection('kvstoreRecords') - this.records.createIndex({ key: 1 }).catch(console.error) - this.records.createIndex({ protocolID: 1 }).catch(console.error) - this.records.createIndex({ controller: 1 }).catch(console.error) - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }).catch(console.error) - this.records.createIndex({ tags: 1 }).catch(console.error) + this.ready = this.createIndices() + } + + private async createIndices(): Promise { + await Promise.all([ + this.records.createIndex({ key: 1 }), + this.records.createIndex({ protocolID: 1 }), + this.records.createIndex({ controller: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), + this.records.createIndex({ tags: 1 }) + ]) } async storeRecord( diff --git a/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts b/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts index 9387282aa..e0c52399d 100644 --- a/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts +++ b/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts @@ -4,9 +4,11 @@ import { MonsterBattleRecord, UTXOReference } from './types.js' export class MonsterBattleStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('monsterBattleRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts b/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts index 68a6065f6..71610c610 100644 --- a/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts +++ b/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts @@ -4,9 +4,11 @@ import { SlackThreadRecord, UTXOReference } from './types.js' export class SlackThreadsStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('slackThreadRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts b/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts index 399552519..9eb640abb 100644 --- a/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts +++ b/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts @@ -4,9 +4,11 @@ import { SupplyChainRecord, UTXOReference } from './types.js' export class SupplyChainStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('supplyChainRecords') - this.createSearchableIndex() + this.ready = this.createSearchableIndex() } private async createSearchableIndex(): Promise { diff --git a/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts b/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts index a9b62442d..efd2d0338 100644 --- a/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts +++ b/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts @@ -4,14 +4,18 @@ import { TokenDemoRecord, TokenDemoDetails, UTXOReference } from './types.js' export class TokenDemoStorage { private readonly records: Collection + readonly ready: Promise + constructor(private readonly db: Db) { this.records = db.collection('TokenDemoRecords') - this.createIndices() + this.ready = this.createIndices() } private async createIndices(): Promise { - await this.records.createIndex({ txid: 1, outputIndex: 1 }, { name: 'OutpointIndex' }) - await this.records.createIndex({ tokenId: 'hashed' }, { name: 'TokenIdTextIndex' }) + await Promise.all([ + this.records.createIndex({ txid: 1, outputIndex: 1 }, { name: 'OutpointIndex' }), + this.records.createIndex({ tokenId: 'hashed' }, { name: 'TokenIdTextIndex' }) + ]) } async storeRecord(txid: string, outputIndex: number, details: TokenDemoDetails): Promise { From d1db65b440a24ee139d4bf6c3ad1c603001b8295 Mon Sep 17 00:00:00 2001 From: Deggen Date: Thu, 7 May 2026 22:57:22 -0500 Subject: [PATCH 2/2] fix(overlays): lazy ensureIndexes pattern to clear S7059 Sonar still flagged \`this.ready = this.createIndices()\` because calling an async method from a constructor body is async work. Switch to memoized lazy init: each public method awaits ensureIndexes() on first call. Constructor stays purely synchronous. --- .../src/lookup-services/BTMSStorageManager.ts | 25 ++++++++---- .../overlays/topics/src/any/AnyStorage.ts | 18 ++++++--- .../topics/src/apps/AppsStorageManager.ts | 32 ++++++++++----- .../topics/src/btms/BTMSStorageManager.ts | 25 ++++++++---- .../DesktopIntegrityStorage.ts | 18 ++++++--- .../topics/src/did/DIDStorageManager.ts | 16 ++++++-- .../src/fractionalize/FractionalizeStorage.ts | 18 ++++++--- .../topics/src/hello/HelloWorldStorage.ts | 17 +++++--- .../src/identity/IdentityStorageManager.ts | 39 ++++++++++++------- .../src/kvstore/KVStoreStorageManager.ts | 28 ++++++++----- .../src/monsterbattle/MonsterBattleStorage.ts | 17 +++++--- .../src/slackthreads/SlackThreadsStorage.ts | 18 ++++++--- .../src/supplychain/SupplyChainStorage.ts | 19 ++++++--- .../src/utility-tokens/TokenDemoStorage.ts | 24 ++++++++---- 14 files changed, 217 insertions(+), 97 deletions(-) diff --git a/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts b/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts index c1fa10e8a..cb47a3012 100644 --- a/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts +++ b/packages/overlays/btms-backend/src/lookup-services/BTMSStorageManager.ts @@ -7,22 +7,26 @@ import { PubKeyHex } from '@bsv/sdk' */ export class BTMSStorageManager { private readonly records: Collection - readonly ready: Promise + private indexInit?: Promise /** * @param db A connected MongoDB database handle. */ constructor (private readonly db: Db) { this.records = db.collection('btmsRecords') - this.ready = this.createIndices() } - private async createIndices (): Promise { - await Promise.all([ - this.records.createIndex({ assetId: 1 }), - this.records.createIndex({ ownerKey: 1 }), - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) - ]) + private ensureIndexes (): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await Promise.all([ + this.records.createIndex({ assetId: 1 }), + this.records.createIndex({ ownerKey: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) + ]) + })() + } + return this.indexInit } /** @@ -36,6 +40,7 @@ export class BTMSStorageManager { ownerKey: PubKeyHex, metadata?: string ): Promise { + await this.ensureIndexes() const record: BTMSRecord = { txid, outputIndex, @@ -52,6 +57,7 @@ export class BTMSStorageManager { * Remove a BTMS record identified by its UTXO. */ async deleteRecord (txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } @@ -67,6 +73,7 @@ export class BTMSStorageManager { skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() const query: Record = {} if (filters.assetId) { @@ -88,6 +95,7 @@ export class BTMSStorageManager { skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() return await this.findRecordWithQuery({}, limit, skip, sortOrder) } @@ -95,6 +103,7 @@ export class BTMSStorageManager { * Find a specific record by txid and outputIndex. */ async findByTxidOutputIndex (txid: string, outputIndex: number): Promise { + await this.ensureIndexes() return await this.records.findOne({ txid, outputIndex }) } diff --git a/packages/overlays/topics/src/any/AnyStorage.ts b/packages/overlays/topics/src/any/AnyStorage.ts index cb6494791..192c9654d 100644 --- a/packages/overlays/topics/src/any/AnyStorage.ts +++ b/packages/overlays/topics/src/any/AnyStorage.ts @@ -4,19 +4,23 @@ import { AnyRecord, UTXOReference } from './types.js' // Implements a Lookup StorageEngine for Any export class AnyStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('anyRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ txid: 1 }, { name: 'txidIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ txid: 1 }, { name: 'txidIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, @@ -25,14 +29,17 @@ export class AnyStorage { } async spendRecord(txid: string, outputIndex: number, spendingTxid: string): Promise { + await this.ensureIndexes() await this.records.updateOne({ txid, outputIndex }, { $set: { spendingTxid } }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByTxid(txid: string): Promise { + await this.ensureIndexes() if (!txid) return null return this.records.findOne( { txid }, @@ -47,6 +54,7 @@ export class AnyStorage { endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/apps/AppsStorageManager.ts b/packages/overlays/topics/src/apps/AppsStorageManager.ts index 968ddf6a2..616668d37 100644 --- a/packages/overlays/topics/src/apps/AppsStorageManager.ts +++ b/packages/overlays/topics/src/apps/AppsStorageManager.ts @@ -4,40 +4,48 @@ import { AppCatalogRecord, PublishedAppMetadata } from './types.js' export class AppsStorageManager { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('appsCatalogRecords') - this.ready = this.createIndices() } - private async createIndices(): Promise { - await this.records.createIndex({ - 'metadata.name': 'text', - 'metadata.description': 'text', - 'metadata.tags': 'text', - 'metadata.domain': 'text' - }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ + 'metadata.name': 'text', + 'metadata.description': 'text', + 'metadata.tags': 'text', + 'metadata.domain': 'text' + }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, metadata: PublishedAppMetadata): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, metadata, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByDomain(domain: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({ 'metadata.domain': domain }, limit, skip, sortOrder) } async findByPublisher(publisher: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({ 'metadata.publisher': publisher }, limit, skip, sortOrder) } async findByOutpoint(outpoint: string): Promise { + await this.ensureIndexes() const [txid, indexStr] = outpoint.split('.') const outputIndex = Number(indexStr) if (!txid || Number.isNaN(outputIndex)) throw new Error('Invalid outpoint format') @@ -45,20 +53,24 @@ export class AppsStorageManager { } async findByNameFuzzy(partialName: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const escaped = partialName.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`) const regex = new RegExp(escaped, 'i') return this.findRecordWithQuery({ 'metadata.name': { $regex: regex } }, limit, skip, sortOrder) } async findByTags(tags: string[], limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({ 'metadata.tags': { $in: tags } }, limit, skip, sortOrder) } async findByCategory(category: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({ 'metadata.category': category }, limit, skip, sortOrder) } async findAllApps(limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({}, limit, skip, sortOrder) } diff --git a/packages/overlays/topics/src/btms/BTMSStorageManager.ts b/packages/overlays/topics/src/btms/BTMSStorageManager.ts index c1fa10e8a..cb47a3012 100644 --- a/packages/overlays/topics/src/btms/BTMSStorageManager.ts +++ b/packages/overlays/topics/src/btms/BTMSStorageManager.ts @@ -7,22 +7,26 @@ import { PubKeyHex } from '@bsv/sdk' */ export class BTMSStorageManager { private readonly records: Collection - readonly ready: Promise + private indexInit?: Promise /** * @param db A connected MongoDB database handle. */ constructor (private readonly db: Db) { this.records = db.collection('btmsRecords') - this.ready = this.createIndices() } - private async createIndices (): Promise { - await Promise.all([ - this.records.createIndex({ assetId: 1 }), - this.records.createIndex({ ownerKey: 1 }), - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) - ]) + private ensureIndexes (): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await Promise.all([ + this.records.createIndex({ assetId: 1 }), + this.records.createIndex({ ownerKey: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }) + ]) + })() + } + return this.indexInit } /** @@ -36,6 +40,7 @@ export class BTMSStorageManager { ownerKey: PubKeyHex, metadata?: string ): Promise { + await this.ensureIndexes() const record: BTMSRecord = { txid, outputIndex, @@ -52,6 +57,7 @@ export class BTMSStorageManager { * Remove a BTMS record identified by its UTXO. */ async deleteRecord (txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } @@ -67,6 +73,7 @@ export class BTMSStorageManager { skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() const query: Record = {} if (filters.assetId) { @@ -88,6 +95,7 @@ export class BTMSStorageManager { skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() return await this.findRecordWithQuery({}, limit, skip, sortOrder) } @@ -95,6 +103,7 @@ export class BTMSStorageManager { * Find a specific record by txid and outputIndex. */ async findByTxidOutputIndex (txid: string, outputIndex: number): Promise { + await this.ensureIndexes() return await this.records.findOne({ txid, outputIndex }) } diff --git a/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts b/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts index dd7e4964a..2104f2dbf 100644 --- a/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts +++ b/packages/overlays/topics/src/desktopintegrity/DesktopIntegrityStorage.ts @@ -3,27 +3,33 @@ import { DesktopIntegrityRecord, UTXOReference } from './types.js' export class DesktopIntegrityStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('desktopIntegrityRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ fileHash: 1 }, { name: 'fileHashIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ fileHash: 1 }, { name: 'fileHashIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, fileHash: string): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, fileHash, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByFileHash(fileHash: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!fileHash) return [] const direction = sortOrder === 'asc' ? 1 : -1 const results = await this.records.find({ fileHash }) @@ -36,6 +42,7 @@ export class DesktopIntegrityStorage { } async findByTxid(txid: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!txid) return [] const direction = sortOrder === 'asc' ? 1 : -1 const results = await this.records.find({ txid }) @@ -48,6 +55,7 @@ export class DesktopIntegrityStorage { } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/did/DIDStorageManager.ts b/packages/overlays/topics/src/did/DIDStorageManager.ts index 62ddcf24e..8152fcda3 100644 --- a/packages/overlays/topics/src/did/DIDStorageManager.ts +++ b/packages/overlays/topics/src/did/DIDStorageManager.ts @@ -5,30 +5,38 @@ import { LookupFormula } from '@bsv/overlay' export class DIDStorageManager { private readonly records: Collection - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('didRecords') - this.ready = this.createIndices() } - private async createIndices(): Promise { - await this.records.createIndex({ searchableAttributes: 'text' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ searchableAttributes: 'text' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, serialNumber: Base64String): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, serialNumber, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByCertificateSerialNumber(serialNumber: Base64String): Promise { + await this.ensureIndexes() return await this.findRecordWithQuery({ serialNumber }) } async findByOutpoint(outpoint: string): Promise { + await this.ensureIndexes() const [txid, outputIndexStr] = outpoint.split('.') const outputIndex = Number.parseInt(outputIndexStr, 10) if (!txid || Number.isNaN(outputIndex)) { diff --git a/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts b/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts index b6c5436a7..fcb72862f 100644 --- a/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts +++ b/packages/overlays/topics/src/fractionalize/FractionalizeStorage.ts @@ -3,36 +3,44 @@ import { FractionalizeRecord, UTXOReference } from './types.js' export class FractionalizeStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('fractionalizeRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ txid: 1 }, { name: 'txidIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ txid: 1 }, { name: 'txidIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, createdAt: new Date() }) } async spendRecord(txid: string, outputIndex: number, spendingTxid: string): Promise { + await this.ensureIndexes() await this.records.updateOne({ txid, outputIndex }, { $set: { spendingTxid } }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByTxid(txid: string): Promise { + await this.ensureIndexes() if (!txid) return null return this.records.findOne({ txid }, { projection: { txid: 1, outputIndex: 1 } }) } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/hello/HelloWorldStorage.ts b/packages/overlays/topics/src/hello/HelloWorldStorage.ts index 20fbffb9e..017dfbd6d 100644 --- a/packages/overlays/topics/src/hello/HelloWorldStorage.ts +++ b/packages/overlays/topics/src/hello/HelloWorldStorage.ts @@ -3,27 +3,33 @@ import { HelloWorldRecord, UTXOReference } from './types.js' export class HelloWorldStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('helloWorldRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ message: 'text' }, { name: 'MessageTextIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ message: 'text' }, { name: 'MessageTextIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, message: string): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, message, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByMessage(message: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!message) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -36,6 +42,7 @@ export class HelloWorldStorage { } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/identity/IdentityStorageManager.ts b/packages/overlays/topics/src/identity/IdentityStorageManager.ts index 0f9ff819a..994900ba9 100644 --- a/packages/overlays/topics/src/identity/IdentityStorageManager.ts +++ b/packages/overlays/topics/src/identity/IdentityStorageManager.ts @@ -8,28 +8,33 @@ interface Query { export class IdentityStorageManager { private readonly records: Collection - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('identityRecords') - this.ready = this.createIndices() } - private async createIndices(): Promise { - await Promise.all([ - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), - this.records.createIndex({ 'certificate.serialNumber': 1 }), - this.records.createIndex({ 'certificate.subject': 1 }), - this.records.createIndex({ 'certificate.certifier': 1 }), - this.records.createIndex({ 'certificate.subject': 1, 'certificate.certifier': 1 }), - this.records.createIndex({ 'certificate.subject': 1, 'certificate.type': 1 }), - this.records.createIndex({ 'certificate.fields.userName': 1 }), - this.records.createIndex({ 'certificate.fields.userName': 1, 'certificate.certifier': 1 }), - this.records.createIndex({ searchableAttributes: 'text' }) - ]) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await Promise.all([ + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), + this.records.createIndex({ 'certificate.serialNumber': 1 }), + this.records.createIndex({ 'certificate.subject': 1 }), + this.records.createIndex({ 'certificate.certifier': 1 }), + this.records.createIndex({ 'certificate.subject': 1, 'certificate.certifier': 1 }), + this.records.createIndex({ 'certificate.subject': 1, 'certificate.type': 1 }), + this.records.createIndex({ 'certificate.fields.userName': 1 }), + this.records.createIndex({ 'certificate.fields.userName': 1, 'certificate.certifier': 1 }), + this.records.createIndex({ searchableAttributes: 'text' }) + ]) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, certificate: Certificate): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, @@ -43,6 +48,7 @@ export class IdentityStorageManager { } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } @@ -63,6 +69,7 @@ export class IdentityStorageManager { } async findByAttribute(attributes: IdentityAttributes, certifiers?: string[], limit?: number, offset?: number): Promise { + await this.ensureIndexes() if (attributes === undefined || Object.keys(attributes).length === 0) { return [] } @@ -100,6 +107,7 @@ export class IdentityStorageManager { } async findByIdentityKey(identityKey: PubKeyHex, certifiers?: PubKeyHex[], limit?: number, offset?: number): Promise { + await this.ensureIndexes() if (identityKey === undefined) return [] const query: any = { 'certificate.subject': identityKey } @@ -112,6 +120,7 @@ export class IdentityStorageManager { } async findByCertifier(certifiers: PubKeyHex[], limit?: number, offset?: number): Promise { + await this.ensureIndexes() if (certifiers === undefined || certifiers.length === 0) return [] const query = { 'certificate.certifier': { $in: certifiers } } @@ -119,6 +128,7 @@ export class IdentityStorageManager { } async findByCertificateType(certificateTypes: Base64String[], identityKey: PubKeyHex, certifiers?: PubKeyHex[], limit?: number, offset?: number): Promise { + await this.ensureIndexes() if (certificateTypes === undefined || certificateTypes.length === 0 || identityKey === undefined) return [] const query: any = { @@ -134,6 +144,7 @@ export class IdentityStorageManager { } async findByCertificateSerialNumber(serialNumber: Base64String, limit?: number, offset?: number): Promise { + await this.ensureIndexes() if (serialNumber === undefined || serialNumber === '') return [] const query = { 'certificate.serialNumber': serialNumber } diff --git a/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts b/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts index 6ee88981e..c3036ac27 100644 --- a/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts +++ b/packages/overlays/topics/src/kvstore/KVStoreStorageManager.ts @@ -4,21 +4,25 @@ import { PubKeyHex, WalletProtocol } from '@bsv/sdk' export class KVStoreStorageManager { private readonly records: Collection - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('kvstoreRecords') - this.ready = this.createIndices() } - private async createIndices(): Promise { - await Promise.all([ - this.records.createIndex({ key: 1 }), - this.records.createIndex({ protocolID: 1 }), - this.records.createIndex({ controller: 1 }), - this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), - this.records.createIndex({ tags: 1 }) - ]) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await Promise.all([ + this.records.createIndex({ key: 1 }), + this.records.createIndex({ protocolID: 1 }), + this.records.createIndex({ controller: 1 }), + this.records.createIndex({ txid: 1, outputIndex: 1 }, { unique: true }), + this.records.createIndex({ tags: 1 }) + ]) + })() + } + return this.indexInit } async storeRecord( @@ -29,6 +33,7 @@ export class KVStoreStorageManager { controller: PubKeyHex, tags?: string[] ): Promise { + await this.ensureIndexes() const record: KVStoreRecord = { txid, outputIndex, @@ -42,6 +47,7 @@ export class KVStoreStorageManager { } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } @@ -57,6 +63,7 @@ export class KVStoreStorageManager { skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc' ): Promise { + await this.ensureIndexes() const query: any = {} if (filters.key) query.key = filters.key @@ -75,6 +82,7 @@ export class KVStoreStorageManager { } async findAllRecords(limit: number = 50, skip: number = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() return this.findRecordWithQuery({}, limit, skip, sortOrder) } diff --git a/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts b/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts index e0c52399d..215f850cf 100644 --- a/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts +++ b/packages/overlays/topics/src/monsterbattle/MonsterBattleStorage.ts @@ -3,27 +3,33 @@ import { MonsterBattleRecord, UTXOReference } from './types.js' export class MonsterBattleStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('monsterBattleRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ txid: 1 }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ txid: 1 }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByTxid(txid: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!txid) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -36,6 +42,7 @@ export class MonsterBattleStorage { } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts b/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts index 71610c610..0fdfa5799 100644 --- a/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts +++ b/packages/overlays/topics/src/slackthreads/SlackThreadsStorage.ts @@ -3,27 +3,33 @@ import { SlackThreadRecord, UTXOReference } from './types.js' export class SlackThreadsStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('slackThreadRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ threadHash: 1 }, { name: 'threadHashIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ threadHash: 1 }, { name: 'threadHashIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, threadHash: string): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, threadHash, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByThreadHash(threadHash: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!threadHash) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -36,6 +42,7 @@ export class SlackThreadsStorage { } async findByTxid(txid: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!txid) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -48,6 +55,7 @@ export class SlackThreadsStorage { } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts b/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts index 9eb640abb..2e1172e6a 100644 --- a/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts +++ b/packages/overlays/topics/src/supplychain/SupplyChainStorage.ts @@ -3,31 +3,38 @@ import { SupplyChainRecord, UTXOReference } from './types.js' export class SupplyChainStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('supplyChainRecords') - this.ready = this.createSearchableIndex() } - private async createSearchableIndex(): Promise { - await this.records.createIndex({ 'offChainValues.chainId': 1 }, { name: 'offChainValuesIndex' }) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await this.records.createIndex({ 'offChainValues.chainId': 1 }, { name: 'offChainValuesIndex' }) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, offChainValues: Record): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, offChainValues, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async spendRecord(txid: string, outputIndex: number, spendingTxid: string): Promise { + await this.ensureIndexes() await this.records.updateOne({ txid, outputIndex }, { $set: { spendingTxid } }) } async findByChainId(chainId: string, limit = 8, skip = 0): Promise { + await this.ensureIndexes() if (!chainId) return [] return this.records .find({ 'offChainValues.chainId': chainId }, { projection: { txid: 1, outputIndex: 1, createdAt: 1 } }) @@ -39,6 +46,7 @@ export class SupplyChainStorage { } async findByTxid(txid: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!txid) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -51,6 +59,7 @@ export class SupplyChainStorage { } async findAll(limit = 50, skip = 0, startDate?: Date, endDate?: Date, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const query: any = {} if (startDate || endDate) { query.createdAt = {} diff --git a/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts b/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts index efd2d0338..ba89bd693 100644 --- a/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts +++ b/packages/overlays/topics/src/utility-tokens/TokenDemoStorage.ts @@ -3,30 +3,36 @@ import { TokenDemoRecord, TokenDemoDetails, UTXOReference } from './types.js' export class TokenDemoStorage { private readonly records: Collection - - readonly ready: Promise + private indexInit?: Promise constructor(private readonly db: Db) { this.records = db.collection('TokenDemoRecords') - this.ready = this.createIndices() } - private async createIndices(): Promise { - await Promise.all([ - this.records.createIndex({ txid: 1, outputIndex: 1 }, { name: 'OutpointIndex' }), - this.records.createIndex({ tokenId: 'hashed' }, { name: 'TokenIdTextIndex' }) - ]) + private ensureIndexes(): Promise { + if (this.indexInit === undefined) { + this.indexInit = (async () => { + await Promise.all([ + this.records.createIndex({ txid: 1, outputIndex: 1 }, { name: 'OutpointIndex' }), + this.records.createIndex({ tokenId: 'hashed' }, { name: 'TokenIdTextIndex' }) + ]) + })() + } + return this.indexInit } async storeRecord(txid: string, outputIndex: number, details: TokenDemoDetails): Promise { + await this.ensureIndexes() await this.records.insertOne({ txid, outputIndex, ...details, createdAt: new Date() }) } async deleteRecord(txid: string, outputIndex: number): Promise { + await this.ensureIndexes() await this.records.deleteOne({ txid, outputIndex }) } async findByOutpoint(outpoint: string): Promise { + await this.ensureIndexes() const [txid, outputIndex] = outpoint.split('.') return this.records .find({ txid, outputIndex: Number(outputIndex) }, { projection: { txid: 1, outputIndex: 1 } }) @@ -35,6 +41,7 @@ export class TokenDemoStorage { } async findByTokenId(tokenId: string, limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() if (!tokenId) return [] const direction = sortOrder === 'asc' ? 1 : -1 return this.records @@ -47,6 +54,7 @@ export class TokenDemoStorage { } async findAll(limit = 50, skip = 0, sortOrder: 'asc' | 'desc' = 'desc'): Promise { + await this.ensureIndexes() const direction = sortOrder === 'asc' ? 1 : -1 return this.records .find({}, { projection: { txid: 1, outputIndex: 1, createdAt: 1 } })