add JSDoc typescript sigh

This commit is contained in:
Andre Staltz 2023-09-14 12:23:21 +03:00
parent ceea5b91c1
commit de9618667d
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
2 changed files with 169 additions and 30 deletions

View File

@ -2,12 +2,35 @@ const MsgV3 = require('ppppp-db/msg-v3')
const PREFIX = 'record_v1__' const PREFIX = 'record_v1__'
/**
* @typedef {import('ppppp-db').Msg} Msg
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
* @typedef {import('ppppp-db').RecPresent} RecPresent
* @typedef {{
* hook: (
* cb: (
* this: any,
* fn: (this: any, ...a: Array<any>) => any,
* args: Array<any>
* ) => void
* ) => void
* }} ClosableHook
*/
/** /**
* @typedef {string} Subdomain * @typedef {string} Subdomain
* @typedef {string} MsgID * @typedef {string} MsgID
* @typedef {`${Subdomain}.${string}`} SubdomainField * @typedef {`${Subdomain}.${string}`} SubdomainField
*/ */
/**
* @template T
* @typedef {T extends void ?
* (...args: [Error] | []) => void :
* (...args: [Error] | [null, T]) => void
* } CB
*/
/** /**
* @param {string} domain * @param {string} domain
* @returns {Subdomain} * @returns {Subdomain}
@ -24,43 +47,86 @@ function fromSubdomain(subdomain) {
return PREFIX + subdomain return PREFIX + subdomain
} }
/**
* @param {{
* db: PPPPPDB | null,
* close: ClosableHook,
* }} peer
* @returns {asserts peer is { db: PPPPPDB, close: ClosableHook }}
*/
function assertDBExists(peer) {
if (!peer.db) throw new Error('record plugin requires ppppp-db plugin')
}
module.exports = { module.exports = {
name: 'record', name: 'record',
manifest: {}, manifest: {},
/**
* @param {{
* db: PPPPPDB | null,
* close: ClosableHook,
* }} peer
* @param {any} config
*/
init(peer, config) { init(peer, config) {
assertDBExists(peer)
//#region state //#region state
let accountID = /** @type {string | null} */ (null) let accountID = /** @type {string | null} */ (null)
let cancelListeningToRecordAdded = null let cancelOnRecordAdded = /** @type {CallableFunction | null} */ (null)
let loadPromise = /** @type {Promise<void> | null} */ (null) let loadPromise = /** @type {Promise<void> | null} */ (null)
const tangles = /** @type {Map<Subdomain, unknown>} */ (new Map()) const tangles = /** @type {Map<Subdomain, MsgV3.Tangle>} */ (new Map())
const fieldRoots = { const fieldRoots = {
/** @type {Map<SubdomainField, Set<MsgID>} */ _map: /** @type {Map<SubdomainField, Set<MsgID>>} */ (new Map()),
_map: new Map(), /**
* @param {string} subdomain
* @param {string} field
* @returns {SubdomainField}
*/
_getKey(subdomain, field) { _getKey(subdomain, field) {
return subdomain + '.' + field return `${subdomain}.${field}`
}, },
get(subdomain, field = null) { /**
if (field) { * @param {string} subdomain
const key = this._getKey(subdomain, field) * @returns {Record<string, Array<MsgID>>}
return this._map.get(key) */
} else { getAll(subdomain) {
const out = {} const out = /** @type {Record<string, Array<MsgID>>} */ ({})
for (const [key, value] of this._map.entries()) { for (const [key, value] of this._map.entries()) {
if (key.startsWith(subdomain + '.')) { if (key.startsWith(subdomain + '.')) {
const field = key.slice(subdomain.length + 1) const field = key.slice(subdomain.length + 1)
out[field] = [...value] out[field] = [...value]
}
} }
return out
} }
return out
}, },
/**
* @param {string} subdomain
* @param {string} field
* @returns {Set<MsgID> | undefined}
*/
get(subdomain, field) {
const key = this._getKey(subdomain, field)
return this._map.get(key)
},
/**
* @param {string} subdomain
* @param {string} field
* @param {MsgID} msgID
*/
add(subdomain, field, msgID) { add(subdomain, field, msgID) {
const key = this._getKey(subdomain, field) const key = this._getKey(subdomain, field)
const set = this._map.get(key) ?? new Set() const set = this._map.get(key) ?? new Set()
set.add(msgID) set.add(msgID)
return this._map.set(key, set) return this._map.set(key, set)
}, },
/**
* @param {string} subdomain
* @param {string} field
* @param {MsgID} msgID
*/
del(subdomain, field, msgID) { del(subdomain, field, msgID) {
const key = this._getKey(subdomain, field) const key = this._getKey(subdomain, field)
const set = this._map.get(key) const set = this._map.get(key)
@ -77,13 +143,18 @@ module.exports = {
//#region active processes //#region active processes
peer.close.hook(function (fn, args) { peer.close.hook(function (fn, args) {
cancelListeningToRecordAdded() cancelOnRecordAdded?.()
fn.apply(this, args) fn.apply(this, args)
}) })
//#endregion //#endregion
//#region internal methods //#region internal methods
function isValidRecordRootMsg(msg) { /**
* @private
* @param {Msg | null | undefined} msg
* @returns {msg is Msg}
*/
function isValidRecordMoot(msg) {
if (!msg) return false if (!msg) return false
if (msg.metadata.account !== accountID) return false if (msg.metadata.account !== accountID) return false
const domain = msg.metadata.domain const domain = msg.metadata.domain
@ -91,6 +162,11 @@ module.exports = {
return MsgV3.isMoot(msg, accountID, domain) return MsgV3.isMoot(msg, accountID, domain)
} }
/**
* @private
* @param {Msg | null | undefined} msg
* @returns {msg is Msg}
*/
function isValidRecordMsg(msg) { function isValidRecordMsg(msg) {
if (!msg) return false if (!msg) return false
if (!msg.data) return false if (!msg.data) return false
@ -103,6 +179,11 @@ module.exports = {
return true return true
} }
/**
* @private
* @param {string} rootID
* @param {Msg} root
*/
function learnRecordRoot(rootID, root) { function learnRecordRoot(rootID, root) {
const subdomain = toSubdomain(root.metadata.domain) const subdomain = toSubdomain(root.metadata.domain)
const tangle = tangles.get(subdomain) ?? new MsgV3.Tangle(rootID) const tangle = tangles.get(subdomain) ?? new MsgV3.Tangle(rootID)
@ -110,6 +191,11 @@ module.exports = {
tangles.set(subdomain, tangle) tangles.set(subdomain, tangle)
} }
/**
* @private
* @param {string} msgID
* @param {Msg} msg
*/
function learnRecordUpdate(msgID, msg) { function learnRecordUpdate(msgID, msg) {
const { account, domain } = msg.metadata const { account, domain } = msg.metadata
const rootID = MsgV3.getMootID(account, domain) const rootID = MsgV3.getMootID(account, domain)
@ -135,9 +221,14 @@ module.exports = {
} }
} }
/**
* @private
* @param {string} msgID
* @param {Msg} msg
*/
function maybeLearnAboutRecord(msgID, msg) { function maybeLearnAboutRecord(msgID, msg) {
if (msg.metadata.account !== accountID) return if (msg.metadata.account !== accountID) return
if (isValidRecordRootMsg(msg)) { if (isValidRecordMoot(msg)) {
learnRecordRoot(msgID, msg) learnRecordRoot(msgID, msg)
return return
} }
@ -147,12 +238,24 @@ module.exports = {
} }
} }
/**
* @private
* @param {CB<void>} cb
* @returns
*/
function loaded(cb) { function loaded(cb) {
if (cb === void 0) return loadPromise if (cb === void 0) return loadPromise
else loadPromise.then(() => cb(null), cb) else loadPromise?.then(() => cb(), cb)
} }
/**
* @private
* @param {string} subdomain
* @returns {number}
*/
function _squeezePotential(subdomain) { function _squeezePotential(subdomain) {
assertDBExists(peer)
if (!accountID) throw new Error('Cannot squeeze potential before loading')
// TODO: improve this so that the squeezePotential is the size of the // TODO: improve this so that the squeezePotential is the size of the
// tangle suffix built as a slice from the fieldRoots // tangle suffix built as a slice from the fieldRoots
const mootID = MsgV3.getMootID(accountID, fromSubdomain(subdomain)) const mootID = MsgV3.getMootID(accountID, fromSubdomain(subdomain))
@ -169,7 +272,14 @@ module.exports = {
return maxDepth - minDepth return maxDepth - minDepth
} }
/**
* @param {string} subdomain
* @param {Record<string, any>} update
* @param {CB<boolean>} cb
*/
function forceUpdate(subdomain, update, cb) { function forceUpdate(subdomain, update, cb) {
assertDBExists(peer)
if (!accountID) throw new Error('Cannot force update before loading')
const domain = fromSubdomain(subdomain) const domain = fromSubdomain(subdomain)
// Populate supersedes // Populate supersedes
@ -184,6 +294,7 @@ module.exports = {
(err, rec) => { (err, rec) => {
// prettier-ignore // prettier-ignore
if (err) return cb(new Error('Failed to create msg when force updating Record', { cause: err })) if (err) return cb(new Error('Failed to create msg when force updating Record', { cause: err }))
// @ts-ignore
cb(null, true) cb(null, true)
} }
) )
@ -194,34 +305,49 @@ module.exports = {
/** /**
* @param {string} id * @param {string} id
* @param {CB<void>} cb
*/ */
function load(id, cb) { function load(id, cb) {
assertDBExists(peer)
accountID = id accountID = id
loadPromise = new Promise((resolve, reject) => { loadPromise = new Promise((resolve, reject) => {
for (const { id, msg } of peer.db.records()) { for (const rec of peer.db.records()) {
maybeLearnAboutRecord(id, msg) if (!rec.msg) continue
maybeLearnAboutRecord(rec.id, rec.msg)
} }
cancelListeningToRecordAdded = peer.db.onRecordAdded(({ id, msg }) => { cancelOnRecordAdded = peer.db.onRecordAdded(
maybeLearnAboutRecord(id, msg) (/** @type {RecPresent} */ rec) => {
if (!rec.msg) return
maybeLearnAboutRecord(rec.id, rec.msg)
}) })
resolve() resolve()
cb() cb()
}) })
} }
/**
* @param {string} id
* @param {string} subdomain
*/
function getFieldRoots(id, subdomain) { function getFieldRoots(id, subdomain) {
// prettier-ignore // prettier-ignore
if (id !== accountID) return cb(new Error(`Cannot getFieldRoots for another user's record. Given ID was "${id}"`)) if (id !== accountID) throw new Error(`Cannot getFieldRoots for another user's record. Given ID was "${id}"`)
return fieldRoots.get(subdomain) return fieldRoots.getAll(subdomain)
} }
/**
* @public
* @param {string} id
* @param {string} subdomain
*/
function get(id, subdomain) { function get(id, subdomain) {
assertDBExists(peer)
const domain = fromSubdomain(subdomain) const domain = fromSubdomain(subdomain)
const mootID = MsgV3.getMootID(id, domain) const mootID = MsgV3.getMootID(id, domain)
const tangle = peer.db.getTangle(mootID) const tangle = peer.db.getTangle(mootID)
if (!tangle || tangle.size === 0) return {} if (!tangle || tangle.size === 0) return {}
const msgIDs = tangle.topoSort() const msgIDs = tangle.topoSort()
const record = {} const record = /** @type {Record<string, any>}*/ ({})
for (const msgID of msgIDs) { for (const msgID of msgIDs) {
const msg = peer.db.get(msgID) const msg = peer.db.get(msgID)
if (isValidRecordMsg(msg)) { if (isValidRecordMsg(msg)) {
@ -232,6 +358,13 @@ module.exports = {
return record return record
} }
/**
* @public
* @param {string} id
* @param {string} subdomain
* @param {Record<string, any>} update
* @param {CB<boolean>} cb
*/
function update(id, subdomain, update, cb) { function update(id, subdomain, update, cb) {
// prettier-ignore // prettier-ignore
if (id !== accountID) return cb(new Error(`Cannot update another user's record. Given ID was "${id}"`)) if (id !== accountID) return cb(new Error(`Cannot update another user's record. Given ID was "${id}"`))
@ -251,6 +384,11 @@ module.exports = {
}) })
} }
/**
* @param {string} id
* @param {string} subdomain
* @param {CB<boolean>} cb
*/
function squeeze(id, subdomain, cb) { function squeeze(id, subdomain, cb) {
// prettier-ignore // prettier-ignore
if (id !== accountID) return cb(new Error(`Cannot squeeze another user's record. Given ID was "${id}"`)) if (id !== accountID) return cb(new Error(`Cannot squeeze another user's record. Given ID was "${id}"`))
@ -259,9 +397,10 @@ module.exports = {
loaded(() => { loaded(() => {
const record = get(id, subdomain) const record = get(id, subdomain)
forceUpdate(subdomain, record, (err) => { forceUpdate(subdomain, record, (err, _forceUpdated) => {
// prettier-ignore // prettier-ignore
if (err) return cb(new Error('Failed to force update when squeezing Record', { cause: err })) if (err) return cb(new Error('Failed to force update when squeezing Record', { cause: err }))
// @ts-ignore
cb(null, true) cb(null, true)
}) })
}) })

View File

@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"bs58": "^5.0.0", "bs58": "^5.0.0",
"c8": "7", "c8": "7",
"ppppp-db": "github:staltz/ppppp-db", "ppppp-db": "file:../db",
"ppppp-caps": "github:staltz/ppppp-caps", "ppppp-caps": "github:staltz/ppppp-caps",
"ppppp-keypair": "github:staltz/ppppp-keypair", "ppppp-keypair": "github:staltz/ppppp-keypair",
"rimraf": "^4.4.0", "rimraf": "^4.4.0",