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