@bsv/wallet-toolbox
Version:
BRC100 conforming wallet, wallet storage and wallet signer components
195 lines (173 loc) • 7.16 kB
text/typescript
import {
TableCertificateField,
TableCertificateX,
TableCommission,
TableMonitorEvent,
TableOutput,
TableOutputBasket,
TableOutputTag,
TableOutputTagMap,
TableProvenTx,
TableProvenTxReq,
TableSettings,
TableSyncState,
TableTransaction,
TableTxLabel,
TableTxLabelMap,
TableUser
} from './schema/tables'
import * as sdk from '../sdk/index'
import { validateSecondsSinceEpoch, verifyOneOrNone, verifyTruthy } from '../utility/utilityHelpers'
import { getSyncChunk } from './methods/getSyncChunk'
/**
* The `StorageReader` abstract class is the base of the concrete wallet storage provider classes.
*
* It is the minimal interface required to read all wallet state records and is the base class for sync readers.
*
* The next class in the heirarchy is the `StorageReaderWriter` which supports sync readers and writers.
*
* The last class in the heirarchy is the `Storage` class which supports all active wallet operations.
*
* The ability to construct a properly configured instance of this class implies authentication.
* As such there are no user specific authenticated access checks implied in the implementation of any of these methods.
*/
export abstract class StorageReader implements sdk.WalletStorageSyncReader {
chain: sdk.Chain
_settings?: TableSettings
whenLastAccess?: Date
get dbtype(): DBType | undefined {
return this._settings?.dbtype
}
constructor(options: StorageReaderOptions) {
this.chain = options.chain
}
isAvailable(): boolean {
return !!this._settings
}
async makeAvailable(): Promise<TableSettings> {
if (this._settings) return this._settings
return (this._settings = await this.readSettings())
}
getSettings(): TableSettings {
if (!this._settings) throw new sdk.WERR_INVALID_OPERATION('must call "makeAvailable" before accessing "settings"')
return this._settings
}
isStorageProvider(): boolean {
return false
}
abstract destroy(): Promise<void>
abstract transaction<T>(scope: (trx: sdk.TrxToken) => Promise<T>, trx?: sdk.TrxToken): Promise<T>
abstract readSettings(trx?: sdk.TrxToken): Promise<TableSettings>
abstract findCertificateFields(args: sdk.FindCertificateFieldsArgs): Promise<TableCertificateField[]>
abstract findCertificates(args: sdk.FindCertificatesArgs): Promise<TableCertificateX[]>
abstract findCommissions(args: sdk.FindCommissionsArgs): Promise<TableCommission[]>
abstract findMonitorEvents(args: sdk.FindMonitorEventsArgs): Promise<TableMonitorEvent[]>
abstract findOutputBaskets(args: sdk.FindOutputBasketsArgs): Promise<TableOutputBasket[]>
abstract findOutputs(args: sdk.FindOutputsArgs): Promise<TableOutput[]>
abstract findOutputTags(args: sdk.FindOutputTagsArgs): Promise<TableOutputTag[]>
abstract findSyncStates(args: sdk.FindSyncStatesArgs): Promise<TableSyncState[]>
abstract findTransactions(args: sdk.FindTransactionsArgs): Promise<TableTransaction[]>
abstract findTxLabels(args: sdk.FindTxLabelsArgs): Promise<TableTxLabel[]>
abstract findUsers(args: sdk.FindUsersArgs): Promise<TableUser[]>
abstract countCertificateFields(args: sdk.FindCertificateFieldsArgs): Promise<number>
abstract countCertificates(args: sdk.FindCertificatesArgs): Promise<number>
abstract countCommissions(args: sdk.FindCommissionsArgs): Promise<number>
abstract countMonitorEvents(args: sdk.FindMonitorEventsArgs): Promise<number>
abstract countOutputBaskets(args: sdk.FindOutputBasketsArgs): Promise<number>
abstract countOutputs(args: sdk.FindOutputsArgs): Promise<number>
abstract countOutputTags(args: sdk.FindOutputTagsArgs): Promise<number>
abstract countSyncStates(args: sdk.FindSyncStatesArgs): Promise<number>
abstract countTransactions(args: sdk.FindTransactionsArgs): Promise<number>
abstract countTxLabels(args: sdk.FindTxLabelsArgs): Promise<number>
abstract countUsers(args: sdk.FindUsersArgs): Promise<number>
abstract getProvenTxsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableProvenTx[]>
abstract getProvenTxReqsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableProvenTxReq[]>
abstract getTxLabelMapsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableTxLabelMap[]>
abstract getOutputTagMapsForUser(args: sdk.FindForUserSincePagedArgs): Promise<TableOutputTagMap[]>
async findUserByIdentityKey(key: string): Promise<TableUser | undefined> {
return verifyOneOrNone(await this.findUsers({ partial: { identityKey: key } }))
}
async getSyncChunk(args: sdk.RequestSyncChunkArgs): Promise<sdk.SyncChunk> {
return getSyncChunk(this, args)
}
/**
* Force dates to strings on SQLite and Date objects on MySQL
* @param date
* @returns
*/
validateEntityDate(date: Date | string | number): Date | string {
if (!this.dbtype) throw new sdk.WERR_INTERNAL('must call verifyReadyForDatabaseAccess first')
let r: Date | string = this.validateDate(date)
switch (this.dbtype) {
case 'IndexedDB':
case 'MySQL':
break
case 'SQLite':
r = r.toISOString()
break
default:
throw new sdk.WERR_INTERNAL(`Invalid dateScheme ${this.dbtype}`)
}
return r
}
/**
*
* @param date
* @param useNowAsDefault if true and date is null or undefiend, set to current time.
* @returns
*/
validateOptionalEntityDate(
date: Date | string | number | null | undefined,
useNowAsDefault?: boolean
): Date | string | undefined {
if (!this.dbtype) throw new sdk.WERR_INTERNAL('must call verifyReadyForDatabaseAccess first')
let r: Date | string | undefined = this.validateOptionalDate(date)
if (!r && useNowAsDefault) r = new Date()
switch (this.dbtype) {
case 'IndexedDB':
case 'MySQL':
break
case 'SQLite':
if (r) r = r.toISOString()
break
default:
throw new sdk.WERR_INTERNAL(`Invalid dateScheme ${this.dbtype}`)
}
return r
}
validateDate(date: Date | string | number): Date {
let r: Date
if (date instanceof Date) r = date
else r = new Date(date)
return r
}
validateOptionalDate(date: Date | string | number | null | undefined): Date | undefined {
if (date === null || date === undefined) return undefined
return this.validateDate(date)
}
validateDateForWhere(date: Date | string | number): Date | string | number {
if (!this.dbtype) throw new sdk.WERR_INTERNAL('must call verifyReadyForDatabaseAccess first')
if (typeof date === 'number') date = validateSecondsSinceEpoch(date)
const vdate = verifyTruthy(this.validateDate(date))
let r: Date | string | number
switch (this.dbtype) {
case 'IndexedDB':
case 'MySQL':
r = vdate
break
case 'SQLite':
r = vdate.toISOString()
break
default:
throw new sdk.WERR_INTERNAL(`Invalid dateScheme ${this.dbtype}`)
}
return r
}
}
export interface StorageReaderOptions {
chain: sdk.Chain
}
export type DBType = 'SQLite' | 'MySQL' | 'IndexedDB'
type DbEntityTimeStamp<T extends sdk.EntityTimeStamp> = {
[K in keyof T]: T[K] extends Date ? Date | string : T[K]
}