@bsv/wallet-toolbox-client
Version:
Client only Wallet Storage
334 lines • 13.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntitySyncState = void 0;
const WERR_errors_1 = require("../../../sdk/WERR_errors");
const utilityHelpers_1 = require("../../../utility/utilityHelpers");
const EntityBase_1 = require("./EntityBase");
const EntityCertificate_1 = require("./EntityCertificate");
const EntityCertificateField_1 = require("./EntityCertificateField");
const EntityCommission_1 = require("./EntityCommission");
const EntityOutput_1 = require("./EntityOutput");
const EntityOutputBasket_1 = require("./EntityOutputBasket");
const EntityOutputTag_1 = require("./EntityOutputTag");
const EntityOutputTagMap_1 = require("./EntityOutputTagMap");
const EntityProvenTx_1 = require("./EntityProvenTx");
const EntityProvenTxReq_1 = require("./EntityProvenTxReq");
const EntityTransaction_1 = require("./EntityTransaction");
const EntityTxLabel_1 = require("./EntityTxLabel");
const EntityTxLabelMap_1 = require("./EntityTxLabelMap");
const EntityUser_1 = require("./EntityUser");
const MergeEntity_1 = require("./MergeEntity");
class EntitySyncState extends EntityBase_1.EntityBase {
constructor(api) {
const now = new Date();
super(api || {
syncStateId: 0,
created_at: now,
updated_at: now,
userId: 0,
storageIdentityKey: '',
storageName: '',
init: false,
refNum: '',
status: 'unknown',
when: undefined,
errorLocal: undefined,
errorOther: undefined,
satoshis: undefined,
syncMap: JSON.stringify((0, EntityBase_1.createSyncMap)())
});
this.errorLocal = this.api.errorLocal ? JSON.parse(this.api.errorLocal) : undefined;
this.errorOther = this.api.errorOther ? JSON.parse(this.api.errorOther) : undefined;
this.syncMap = JSON.parse(this.api.syncMap);
this.validateSyncMap(this.syncMap);
}
validateSyncMap(sm) {
for (const key of Object.keys(sm)) {
const esm = sm[key];
if (typeof esm.maxUpdated_at === 'string')
esm.maxUpdated_at = new Date(esm.maxUpdated_at);
}
}
static async fromStorage(storage, userIdentityKey, remoteSettings) {
const { user } = (0, utilityHelpers_1.verifyTruthy)(await storage.findOrInsertUser(userIdentityKey));
let { syncState: api } = (0, utilityHelpers_1.verifyTruthy)(await storage.findOrInsertSyncStateAuth({ userId: user.userId, identityKey: userIdentityKey }, remoteSettings.storageIdentityKey, remoteSettings.storageName));
if (!api.syncMap || api.syncMap === '{}')
api.syncMap = JSON.stringify((0, EntityBase_1.createSyncMap)());
const ss = new EntitySyncState(api);
return ss;
}
/**
* Handles both insert and update based on id value: zero indicates insert.
* @param storage
* @param notSyncMap if not new and true, excludes updating syncMap in storage.
* @param trx
*/
async updateStorage(storage, notSyncMap, trx) {
this.updated_at = new Date();
this.updateApi(notSyncMap && this.id > 0);
if (this.id === 0) {
await storage.insertSyncState(this.api);
}
else {
const update = { ...this.api };
if (notSyncMap)
delete update.syncMap;
delete update.created_at;
await storage.updateSyncState((0, utilityHelpers_1.verifyId)(this.id), update, trx);
}
}
updateApi(notSyncMap) {
this.api.errorLocal = this.apiErrorLocal;
this.api.errorOther = this.apiErrorOther;
if (!notSyncMap)
this.api.syncMap = this.apiSyncMap;
}
// Pass through api properties
set created_at(v) {
this.api.created_at = v;
}
get created_at() {
return this.api.created_at;
}
set updated_at(v) {
this.api.updated_at = v;
}
get updated_at() {
return this.api.updated_at;
}
set userId(v) {
this.api.userId = v;
}
get userId() {
return this.api.userId;
}
set storageIdentityKey(v) {
this.api.storageIdentityKey = v;
}
get storageIdentityKey() {
return this.api.storageIdentityKey;
}
set storageName(v) {
this.api.storageName = v;
}
get storageName() {
return this.api.storageName;
}
set init(v) {
this.api.init = v;
}
get init() {
return this.api.init;
}
set refNum(v) {
this.api.refNum = v;
}
get refNum() {
return this.api.refNum;
}
set status(v) {
this.api.status = v;
}
get status() {
return this.api.status;
}
set when(v) {
this.api.when = v;
}
get when() {
return this.api.when;
}
set satoshis(v) {
this.api.satoshis = v;
}
get satoshis() {
return this.api.satoshis;
}
get apiErrorLocal() {
return this.errorToString(this.errorLocal);
}
get apiErrorOther() {
return this.errorToString(this.errorOther);
}
get apiSyncMap() {
return JSON.stringify(this.syncMap);
}
get id() {
return this.api.syncStateId;
}
set id(id) {
this.api.syncStateId = id;
}
get entityName() {
return 'syncState';
}
get entityTable() {
return 'sync_states';
}
static mergeIdMap(fromMap, toMap) {
for (const [key, value] of Object.entries(fromMap)) {
const fromValue = fromMap[key];
const toValue = toMap[key];
if (toValue !== undefined && toValue !== fromValue)
throw new WERR_errors_1.WERR_INVALID_PARAMETER('syncMap', `an unmapped id or the same mapped id. ${key} maps to ${toValue} not equal to ${fromValue}`);
if (toValue === undefined)
toMap[key] = value;
}
}
/**
* Merge additions to the syncMap
* @param iSyncMap
*/
mergeSyncMap(iSyncMap) {
EntitySyncState.mergeIdMap(iSyncMap.provenTx.idMap, this.syncMap.provenTx.idMap);
EntitySyncState.mergeIdMap(iSyncMap.outputBasket.idMap, this.syncMap.outputBasket.idMap);
EntitySyncState.mergeIdMap(iSyncMap.transaction.idMap, this.syncMap.transaction.idMap);
EntitySyncState.mergeIdMap(iSyncMap.provenTxReq.idMap, this.syncMap.provenTxReq.idMap);
EntitySyncState.mergeIdMap(iSyncMap.txLabel.idMap, this.syncMap.txLabel.idMap);
EntitySyncState.mergeIdMap(iSyncMap.output.idMap, this.syncMap.output.idMap);
EntitySyncState.mergeIdMap(iSyncMap.outputTag.idMap, this.syncMap.outputTag.idMap);
EntitySyncState.mergeIdMap(iSyncMap.certificate.idMap, this.syncMap.certificate.idMap);
EntitySyncState.mergeIdMap(iSyncMap.commission.idMap, this.syncMap.commission.idMap);
}
/**
* Eliminate any properties besides code and description
*/
errorToString(e) {
if (!e)
return undefined;
const es = {
code: e.code,
description: e.description,
stack: e.stack
};
return JSON.stringify(es);
}
equals(ei, syncMap) {
return false;
}
async mergeNew(storage, userId, syncMap, trx) { }
async mergeExisting(storage, since, ei, syncMap, trx) {
return false;
}
makeRequestSyncChunkArgs(forIdentityKey, forStorageIdentityKey, maxRoughSize, maxItems) {
const a = {
identityKey: forIdentityKey,
maxRoughSize: maxRoughSize || 10000000,
maxItems: maxItems || 1000,
offsets: [],
since: this.when,
fromStorageIdentityKey: this.storageIdentityKey,
toStorageIdentityKey: forStorageIdentityKey
};
for (const ess of [
this.syncMap.provenTx,
this.syncMap.outputBasket,
this.syncMap.outputTag,
this.syncMap.txLabel,
this.syncMap.transaction,
this.syncMap.output,
this.syncMap.txLabelMap,
this.syncMap.outputTagMap,
this.syncMap.certificate,
this.syncMap.certificateField,
this.syncMap.commission,
this.syncMap.provenTxReq
]) {
if (!ess || !ess.entityName)
debugger;
a.offsets.push({ name: ess.entityName, offset: ess.count });
}
return a;
}
static syncChunkSummary(c) {
let log = '';
log += `SYNC CHUNK SUMMARY
from storage: ${c.fromStorageIdentityKey}
to storage: ${c.toStorageIdentityKey}
for user: ${c.userIdentityKey}
`;
if (c.user)
log += ` USER activeStorage ${c.user.activeStorage}\n`;
if (!!c.provenTxs) {
log += ` PROVEN_TXS\n`;
for (const r of c.provenTxs) {
log += ` ${r.provenTxId} ${r.txid}\n`;
}
}
if (!!c.provenTxReqs) {
log += ` PROVEN_TX_REQS\n`;
for (const r of c.provenTxReqs) {
log += ` ${r.provenTxReqId} ${r.txid} ${r.status} ${r.provenTxId || ''}\n`;
}
}
if (!!c.transactions) {
log += ` TRANSACTIONS\n`;
for (const r of c.transactions) {
log += ` ${r.transactionId} ${r.txid} ${r.status} ${r.provenTxId || ''} sats:${r.satoshis}\n`;
}
}
if (!!c.outputs) {
log += ` OUTPUTS\n`;
for (const r of c.outputs) {
log += ` ${r.outputId} ${r.txid}.${r.vout} ${r.transactionId} ${r.spendable ? 'spendable' : ''} sats:${r.satoshis}\n`;
}
}
return log;
}
async processSyncChunk(writer, args, chunk) {
var _a;
const mes = [
new MergeEntity_1.MergeEntity(chunk.provenTxs, EntityProvenTx_1.EntityProvenTx.mergeFind, this.syncMap.provenTx),
new MergeEntity_1.MergeEntity(chunk.outputBaskets, EntityOutputBasket_1.EntityOutputBasket.mergeFind, this.syncMap.outputBasket),
new MergeEntity_1.MergeEntity(chunk.outputTags, EntityOutputTag_1.EntityOutputTag.mergeFind, this.syncMap.outputTag),
new MergeEntity_1.MergeEntity(chunk.txLabels, EntityTxLabel_1.EntityTxLabel.mergeFind, this.syncMap.txLabel),
new MergeEntity_1.MergeEntity(chunk.transactions, EntityTransaction_1.EntityTransaction.mergeFind, this.syncMap.transaction),
new MergeEntity_1.MergeEntity(chunk.outputs, EntityOutput_1.EntityOutput.mergeFind, this.syncMap.output),
new MergeEntity_1.MergeEntity(chunk.txLabelMaps, EntityTxLabelMap_1.EntityTxLabelMap.mergeFind, this.syncMap.txLabelMap),
new MergeEntity_1.MergeEntity(chunk.outputTagMaps, EntityOutputTagMap_1.EntityOutputTagMap.mergeFind, this.syncMap.outputTagMap),
new MergeEntity_1.MergeEntity(chunk.certificates, EntityCertificate_1.EntityCertificate.mergeFind, this.syncMap.certificate),
new MergeEntity_1.MergeEntity(chunk.certificateFields, EntityCertificateField_1.EntityCertificateField.mergeFind, this.syncMap.certificateField),
new MergeEntity_1.MergeEntity(chunk.commissions, EntityCommission_1.EntityCommission.mergeFind, this.syncMap.commission),
new MergeEntity_1.MergeEntity(chunk.provenTxReqs, EntityProvenTxReq_1.EntityProvenTxReq.mergeFind, this.syncMap.provenTxReq)
];
let updates = 0;
let inserts = 0;
let maxUpdated_at = undefined;
let done = true;
// Merge User
if (chunk.user) {
const ei = chunk.user;
const { found, eo } = await EntityUser_1.EntityUser.mergeFind(writer, this.userId, ei);
if (found) {
if (await eo.mergeExisting(writer, args.since, ei)) {
maxUpdated_at = (0, utilityHelpers_1.maxDate)(maxUpdated_at, ei.updated_at);
updates++;
}
}
}
// Merge everything else...
for (const me of mes) {
const r = await me.merge(args.since, writer, this.userId, this.syncMap);
// The counts become the offsets for the next chunk.
me.esm.count += ((_a = me.stateArray) === null || _a === void 0 ? void 0 : _a.length) || 0;
updates += r.updates;
inserts += r.inserts;
maxUpdated_at = (0, utilityHelpers_1.maxDate)(maxUpdated_at, me.esm.maxUpdated_at);
// If any entity type either did not report results or if there were at least one, then we aren't done.
if (me.stateArray === undefined || me.stateArray.length > 0)
done = false;
//if (me.stateArray !== undefined && me.stateArray.length > 0)
// console.log(`merged ${me.stateArray?.length} ${me.esm.entityName} ${r.inserts} inserted, ${r.updates} updated`);
}
if (done) {
// Next batch starts further in the future with offsets of zero.
this.when = maxUpdated_at;
for (const me of mes)
me.esm.count = 0;
}
await this.updateStorage(writer, false);
return { done, maxUpdated_at, updates, inserts };
}
}
exports.EntitySyncState = EntitySyncState;
//# sourceMappingURL=EntitySyncState.js.map