use TypeScript in JSDoc

This commit is contained in:
Andre Staltz 2023-06-25 18:48:24 +03:00
parent aab707f5da
commit 674e2ba66c
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
13 changed files with 362 additions and 86 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ node_modules
pnpm-lock.yaml
package-lock.json
coverage
**/*.d.ts
*~
# For misc scripts and experiments:

View File

@ -3,10 +3,23 @@ const b4a = require('b4a')
const MsgV3 = require('./msg-v3')
/**
* @typedef {import('./index').Rec} Rec
* @typedef {import('./index').RecPresent} RecPresent
* @typedef {import('ppppp-keypair').Keypair} Keypair
*
* @typedef {Buffer | Uint8Array} B4A
*
* @typedef {{
* name: string;
* setup?: (config: any, cb: any) => void;
* onReady?: (cb: any) => void;
* encrypt: (plaintext: B4A, opts: any) => B4A;
* decrypt: (ciphertext: B4A, opts: any) => B4A | null;
* }} EncryptionFormat
*/
/**
* @param {string} str
*/
function ciphertextStrToBuffer(str) {
const dot = str.indexOf('.')
return b4a.from(str.slice(0, dot), 'base64')
@ -17,21 +30,21 @@ function ciphertextStrToBuffer(str) {
* @param {Keypair} keypair
*/
function keypairToSSBKeys(keypair) {
const public = b4a.from(base58.decode(keypair.public)).toString('base64')
const private = b4a.from(base58.decode(keypair.private)).toString('base64')
const _public = b4a.from(base58.decode(keypair.public)).toString('base64')
const _private = b4a.from(base58.decode(keypair.private)).toString('base64')
return {
id: `@${public}.ed25519`,
id: `@${_public}.ed25519`,
curve: keypair.curve,
public,
private,
public: _public,
private: _private,
}
}
/**
* @param {Rec} rec
* @param {RecPresent} rec
* @param {any} peer
* @param {any} config
* @returns {Rec}
* @returns {RecPresent}
*/
function decrypt(rec, peer, config) {
const msgEncrypted = rec.msg
@ -63,6 +76,9 @@ function decrypt(rec, peer, config) {
}
}
/**
* @param {RecPresent} rec
*/
function reEncrypt(rec) {
return {
hash: rec.hash,

View File

@ -1,47 +1,63 @@
const path = require('node:path')
// @ts-ignore
const push = require('push-stream')
// @ts-ignore
const AAOL = require('async-append-only-log')
const promisify = require('promisify-4loc')
const b4a = require('b4a')
const base58 = require('bs58')
// @ts-ignore
const Obz = require('obz')
const MsgV2 = require('./msg-v3')
const { ReadyGate } = require('./utils')
const { decrypt } = require('./encryption')
/**
* @typedef {import('ppppp-keypair').Keypair} Keypair
* @typedef {import('./msg-v3').Msg} Msg
* @typedef {import('./encryption').EncryptionFormat} EncryptionFormat
*
* @typedef {Buffer | Uint8Array} B4A
*/
/**
* @typedef {Object} RecDeleted
* @property {never} hash
* @property {never} msg
* @property {never} received
* @property {Object} misc
* @property {number} misc.offset
* @property {number} misc.size
* @property {number} misc.seq
*/
/**
* @typedef {Object} RecPresent
* @property {string} hash
* @property {Msg} msg
* @property {number} received
* @property {Object} misc
* @property {number} misc.offset
* @property {number} misc.size
* @property {number} misc.seq
* @property {boolean=} misc.private
* @property {Object=} misc.originalData
* @property {string=} misc.encryptionFormat
*/
/**
* @typedef {{
* hash?: never;
* msg?: never;
* received?: never;
* misc: {
* offset: number;
* size: number;
* seq: number;
* };
* }} RecDeleted
*
* @typedef {{
* hash: string;
* msg: Msg;
* received: number;
* misc: {
* offset: number;
* size: number;
* seq: number;
* private?: boolean;
* originalData?: any;
* encryptionFormat?: string;
* }
* }} RecPresent
*
* @typedef {RecPresent | RecDeleted} Rec
*/
/**
* @template T
* @typedef {(...args: [Error] | [null, T]) => void} CB
*/
/**
* @typedef {(...args: [Error] | []) => void} CBVoid
*/
class DBTangle extends MsgV2.Tangle {
/**
* @param {string} rootHash
@ -55,6 +71,9 @@ class DBTangle extends MsgV2.Tangle {
}
}
/**
* @param {string} msgHash
*/
getDeletablesAndErasables(msgHash) {
const erasables = this.shortestPathToRoot(msgHash)
const sorted = this.topoSort()
@ -66,28 +85,42 @@ class DBTangle extends MsgV2.Tangle {
}
}
exports.name = 'db'
exports.init = function initDB(peer, config) {
/**
* @param {any} peer
* @param {{ path: string; keypair: Keypair; }} config
*/
function initDB(peer, config) {
/** @type {Array<Rec>} */
const recs = []
/** @type {Map<string, EncryptionFormat>} */
const encryptionFormats = new Map()
const onRecordAdded = Obz()
const log = AAOL(path.join(config.path, 'db.bin'), {
cacheSize: 1,
blockSize: 64 * 1024,
codec: {
/**
* @param {Msg} msg
*/
encode(msg) {
return b4a.from(JSON.stringify(msg), 'utf8')
},
/**
* @param {B4A} buf
*/
decode(buf) {
return JSON.parse(buf.toString('utf8'))
return JSON.parse(b4a.toString(buf, 'utf8'))
},
},
/**
* @param {B4A} buf
*/
validateRecord(buf) {
try {
JSON.parse(buf.toString('utf8'))
JSON.parse(b4a.toString(buf, 'utf8'))
return true
} catch {
return false
@ -95,8 +128,9 @@ exports.init = function initDB(peer, config) {
},
})
peer.close.hook(function (fn, args) {
peer.close.hook(function (/** @type {any} */ fn, /** @type {any} */ args) {
log.close(() => {
// @ts-ignore
fn.apply(this, args)
})
})
@ -107,24 +141,27 @@ exports.init = function initDB(peer, config) {
let i = -1
log.stream({ offsets: true, values: true, sizes: true }).pipe(
push.drain(
// @ts-ignore
function drainEach({ offset, value, size }) {
i += 1
if (!value) {
// deleted record
recs.push({ misc: { offset, size, seq: i } })
/** @type {RecDeleted} */
const rec = { misc: { offset, size, seq: i } }
recs.push(rec)
return
}
// TODO: for performance, dont decrypt on startup, instead decrypt on
// demand, or decrypt in the background. Or then store the log with
// decrypted msgs and only encrypt when moving it to the network.
const rec = decrypt(value, peer, config)
rec.misc ??= {}
rec.misc ??= /** @type {Rec['misc']} */ ({})
rec.misc.offset = offset
rec.misc.size = size
rec.misc.seq = i
recs.push(rec)
},
function drainEnd(err) {
function drainEnd(/** @type {any} */ err) {
// prettier-ignore
if (err) throw new Error('Failed to initially scan the log', { cause: err });
scannedLog.setReady()
@ -133,13 +170,26 @@ exports.init = function initDB(peer, config) {
)
})
/**
* @param {string} hash
* @param {Msg} msg
* @param {CB<Rec>} cb
*/
function logAppend(hash, msg, cb) {
/** @type {RecPresent} */
const rec = {
hash,
msg,
received: Date.now(),
misc: {
offset: 0,
size: 0,
seq: 0,
},
}
log.append(rec, (err, newOffset) => {
log.append(
rec,
(/** @type {any} */ err, /** @type {number} */ newOffset) => {
if (err) return cb(new Error('logAppend failed', { cause: err }))
const offset = newOffset // latestOffset
const size = b4a.from(JSON.stringify(rec), 'utf8').length
@ -148,13 +198,17 @@ exports.init = function initDB(peer, config) {
rec.misc = recExposed.misc = { offset, size, seq }
recs.push(recExposed)
cb(null, rec)
})
}
)
}
/**
* @param {EncryptionFormat} encryptionFormat
*/
function installEncryptionFormat(encryptionFormat) {
if (encryptionFormat.setup) {
const loaded = new ReadyGate()
encryptionFormat.setup(config, (err) => {
encryptionFormat.setup(config, (/** @type {any} */ err) => {
// prettier-ignore
if (err) throw new Error(`Failed to install encryption format "${encryptionFormat.name}"`, {cause: err});
loaded.setReady()
@ -164,15 +218,27 @@ exports.init = function initDB(peer, config) {
encryptionFormats.set(encryptionFormat.name, encryptionFormat)
}
/**
* @param {string} ciphertextJS
*/
function findEncryptionFormatFor(ciphertextJS) {
if (!ciphertextJS) return null
if (typeof ciphertextJS !== 'string') return null
const suffix = ciphertextJS.split('.').pop()
if (!suffix) {
// prettier-ignore
console.warn('findEncryptionFormatFor() failed to find suffix\n\n' + ciphertextJS)
return null
}
const encryptionFormat = encryptionFormats.get(suffix) ?? null
return encryptionFormat
}
/**
* @param {Array<string>} tangleIds
*/
function populateTangles(tangleIds) {
/** @type {Record<string, DBTangle>} */
const tangles = {}
for (const tangleId of tangleIds) {
tangles[tangleId] ??= new DBTangle(tangleId, records())
@ -180,11 +246,19 @@ exports.init = function initDB(peer, config) {
return tangles
}
/**
* @param {CB<void>} cb
*/
function loaded(cb) {
if (cb === void 0) return promisify(loaded)()
scannedLog.onReady(cb)
}
/**
* @param {Msg} msg
* @param {string} tangleRootHash
* @param {CB<Rec>} cb
*/
function add(msg, tangleRootHash, cb) {
const msgHash = MsgV2.getMsgHash(msg)
@ -223,6 +297,10 @@ exports.init = function initDB(peer, config) {
})
}
/**
* @param {{ keypair?: any; identity: string; domain: string; }} opts
* @param {CB<string>} cb
*/
function initializeFeed(opts, cb) {
const keypair = opts.keypair ?? config.keypair
const { identity, domain } = opts
@ -234,10 +312,15 @@ exports.init = function initDB(peer, config) {
add(feedRoot, MsgV2.getMsgHash(feedRoot), (err, rec) => {
// prettier-ignore
if (err) return cb(new Error('initializeFeed() failed to add root', { cause: err }));
cb(null, rec.hash)
const recHash = /** @type {string} */ (rec.hash)
cb(null, recHash)
})
}
/**
* @param {{keypair?: Keypair, _nonce?: string} | null} opts
* @param {CB<Rec>} cb
*/
function createIdentity(opts, cb) {
const keypair = opts?.keypair ?? config.keypair
@ -257,9 +340,15 @@ exports.init = function initDB(peer, config) {
})
}
/**
* @param {{ keypair: Keypair; identity: string; }} opts
* @param {CB<Rec>} cb
*/
function addToIdentity(opts, cb) {
if (!opts?.keypair) return cb(new Error('identity.add() requires a `keypair`'))
if (!opts?.identity) return cb(new Error('identity.add() requires a `identity`'))
if (!opts?.keypair)
return cb(new Error('identity.add() requires a `keypair`'))
if (!opts?.identity)
return cb(new Error('identity.add() requires a `identity`'))
const addedKeypair = opts.keypair
const signingKeypair = config.keypair
@ -291,20 +380,31 @@ exports.init = function initDB(peer, config) {
})
}
/**
* @param {{
* keypair?: Keypair;
* encryptionFormat?: string;
* data: any;
* domain: string;
* identity: string;
* tangles?: Array<string>;
* }} opts
* @param {CB<Rec>} cb
*/
function publishToFeed(opts, cb) {
if (!opts) return cb(new Error('feed.publish() requires an `opts`'))
const keypair = opts.keypair ?? config.keypair
const encryptionFormat = encryptionFormats.get(opts.encryptionFormat)
if (opts.data.recps) {
if (!encryptionFormat) {
if (!encryptionFormats.has(opts.encryptionFormat ?? '')) {
// prettier-ignore
return cb(new Error(`feed.publish() does not support encryption format "${opts.encryptionFormat}"`))
}
}
if (!opts.data) return cb(new Error('feed.publish() requires a `data`'))
if (!opts.domain) return cb(new Error('feed.publish() requires a `domain`'))
if (!opts.identity) return cb(new Error('feed.publish() requires a `identity`'))
if (!opts.identity)
return cb(new Error('feed.publish() requires a `identity`'))
initializeFeed(opts, (err, feedRootHash) => {
// prettier-ignore
@ -330,6 +430,9 @@ exports.init = function initDB(peer, config) {
`@${b4a.from(base58.decode(recp)).toString('base64')}.ed25519`
),
}
const encryptionFormat = /** @type {EncryptionFormat} */ (
encryptionFormats.get(opts.encryptionFormat ?? '')
)
let ciphertextBuf
try {
ciphertextBuf = encryptionFormat.encrypt(plaintext, encryptOpts)
@ -367,31 +470,47 @@ exports.init = function initDB(peer, config) {
})
}
/**
* @param {string} id
* @param {string} findDomain
*/
function getFeedId(id, findDomain) {
const findIdentity = MsgV2.stripIdentity(id)
for (const rec of records()) {
if (MsgV2.isFeedRoot(rec.msg, findIdentity, findDomain)) return rec.hash
if (rec.msg && MsgV2.isFeedRoot(rec.msg, findIdentity, findDomain)) {
return rec.hash
}
}
return null
}
// TODO: improve performance of this when getting many messages, the argument
// could be an array of hashes, so we can do a single pass over the records.
/**
* @param {string} msgId
*/
function getRecord(msgId) {
// TODO: improve performance of this when getting many messages, the arg
// could be an array of hashes, so we can do a single pass over the records.
const isUri = msgId.startsWith('ppppp:')
for (let i = 0; i < recs.length; i++) {
const rec = recs[i]
if (!rec) continue
if (isUri && msgId.endsWith(rec.hash)) return rec
if (isUri && rec.hash && msgId.endsWith(rec.hash)) return rec
else if (!isUri && rec.hash === msgId) return rec
}
return null
}
/**
* @param {string} msgId
*/
function get(msgId) {
return getRecord(msgId)?.msg
}
/**
* @param {string} msgId
* @param {CBVoid} cb
*/
function del(msgId, cb) {
const rec = getRecord(msgId)
if (!rec) return cb()
@ -403,6 +522,10 @@ exports.init = function initDB(peer, config) {
})
}
/**
* @param {string} msgId
* @param {CBVoid} cb
*/
function erase(msgId, cb) {
const rec = getRecord(msgId)
if (!rec) return cb()
@ -413,6 +536,10 @@ exports.init = function initDB(peer, config) {
cb()
}
/**
* @param {string} tangleId
* @returns {DBTangle}
*/
function getTangle(tangleId) {
return new DBTangle(tangleId, records())
}
@ -460,3 +587,6 @@ exports.init = function initDB(peer, config) {
_getLog: () => log,
}
}
exports.name = 'db'
exports.init = initDB

View File

@ -1,6 +1,7 @@
const b4a = require('b4a')
const blake3 = require('blake3')
const base58 = require('bs58')
// @ts-ignore
const stringify = require('json-canon')
/**
@ -14,7 +15,8 @@ const stringify = require('json-canon')
*/
function getMsgHashBuf(msg) {
const metadataBuf = b4a.from(stringify(msg.metadata), 'utf8')
return blake3.hash(metadataBuf).subarray(0, 16)
const longHash = b4a.from(blake3.hash(metadataBuf))
return longHash.subarray(0, 16)
}
/**

View File

@ -1,8 +1,10 @@
const crypto = require('node:crypto')
const base58 = require('bs58')
const b4a = require('b4a')
// @ts-ignore
const stringify = require('json-canon')
const Keypair = require('ppppp-keypair')
// @ts-ignore
const union = require('set.prototype.union')
const { stripIdentity } = require('./strip')
const isFeedRoot = require('./is-feed-root')
@ -12,7 +14,6 @@ const {
validateDomain,
validateData,
validate,
validateBatch,
validateMsgHash,
} = require('./validation')
const Tangle = require('./tangle')
@ -56,6 +57,11 @@ const Tangle = require('./tangle')
* }} CreateOpts
*/
/**
* @param {string} id
* @param {string} domain
* @returns {string}
*/
function getFeedRootHash(id, domain) {
/** @type {Msg} */
const msg = {
@ -76,6 +82,10 @@ function getFeedRootHash(id, domain) {
return getMsgHash(msg)
}
/**
* @param {Pick<CreateOpts, 'data'>} opts
* @returns {B4A}
*/
function toPlaintextBuffer(opts) {
return b4a.from(stringify(opts.data), 'utf8')
}
@ -93,7 +103,7 @@ function create(opts) {
const identity = opts.identity ? stripIdentity(opts.identity) : null
const identityTips = opts.identityTips ? opts.identityTips.sort() : null
const tangles = {}
const tangles = /** @type {Msg['metadata']['tangles']} */ ({})
if (opts.tangles) {
for (const rootId in opts.tangles) {
if ((err = validateMsgHash(rootId))) throw err
@ -168,7 +178,7 @@ function createRoot(id, domain, keypair) {
/**
* @param {Keypair} keypair
* @param {string} nonce
* @param {string | (() => string)} nonce
* @returns {Msg}
*/
function createIdentity(
@ -217,5 +227,4 @@ module.exports = {
fromPlaintextBuffer,
Tangle,
validate,
validateBatch,
}

View File

@ -1,5 +1,12 @@
const { stripIdentity } = require('./strip')
/**
* @typedef {import('.').Msg} Msg
*/
/**
* @param {any} obj
*/
function isEmptyObject(obj) {
for (const _key in obj) {
return false
@ -7,6 +14,11 @@ function isEmptyObject(obj) {
return true
}
/**
* @param {Msg} msg
* @param {string | 0} id
* @param {string | 0} findDomain
*/
function isFeedRoot(msg, id = 0, findDomain = 0) {
const { dataHash, dataSize, identity, identityTips, tangles, domain } = msg.metadata
if (dataHash !== null) return false

View File

@ -1,15 +1,21 @@
const blake3 = require('blake3')
const b4a = require('b4a')
const base58 = require('bs58')
// @ts-ignore
const stringify = require('json-canon')
/**
* @typedef {Buffer | Uint8Array} B4A
*/
/**
* @param {any} data
* @returns {[string, number]}
*/
function representData(data) {
const dataBuf = b4a.from(stringify(data), 'utf8')
const dataHash = base58.encode(blake3.hash(dataBuf).subarray(0, 16))
const fullHash = /** @type {B4A} */ (blake3.hash(dataBuf))
const dataHash = base58.encode(fullHash.subarray(0, 16))
const dataSize = dataBuf.length
return [dataHash, dataSize]
}

View File

@ -1,5 +1,12 @@
const { getMsgHash } = require('./get-msg-id')
/**
* @typedef {import('.').Msg} Msg
*/
/**
* @param {any} msgKey
*/
function stripMsgKey(msgKey) {
if (typeof msgKey === 'object') {
if (msgKey.key) return stripMsgKey(msgKey.key)

View File

@ -2,6 +2,9 @@
* @typedef {import("./index").Msg} Msg
*/
/**
* @param {number} n
*/
function lipmaa(n) {
let m = 1
let po3 = 3
@ -46,7 +49,7 @@ class Tangle {
#rootHash
/**
* @type {Msg}
* @type {Msg | undefined}
*/
#rootMsg
@ -77,13 +80,16 @@ class Tangle {
/**
* @param {string} rootHash
* @param {Iterable<Msg>} msgsIter
*/
constructor(rootHash) {
this.#rootHash = rootHash
this.#maxDepth = 0
}
/**
* @param {string} msgHash
* @param {Msg} msg
*/
add(msgHash, msg) {
if (msgHash === this.#rootHash && !this.#rootMsg) {
this.#tips.add(msgHash)
@ -195,10 +201,17 @@ class Tangle {
getFeed() {
if (!this.isFeed()) return null
if (!this.#rootMsg) {
console.trace('Tangle is missing root message')
return null
}
const { identity, domain } = this.#rootMsg.metadata
return { identity, domain }
}
/**
* @param {string} msgHash
*/
shortestPathToRoot(msgHash) {
if (!this.#rootMsg) {
console.trace('Tangle is missing root message')
@ -209,10 +222,10 @@ class Tangle {
while (true) {
const prev = this.#prev.get(current)
if (!prev) break
let minDepth = this.#depth.get(current)
let minDepth = /** @type {number} */ (this.#depth.get(current))
let min = current
for (const p of prev) {
const d = this.#depth.get(p)
const d = /** @type {number} */ (this.#depth.get(p))
if (d < minDepth) {
minDepth = d
min = p
@ -226,18 +239,22 @@ class Tangle {
return path
}
precedes(a, b) {
/**
* @param {string} msgHashA
* @param {string} msgHashB
*/
precedes(msgHashA, msgHashB) {
if (!this.#rootMsg) {
console.trace('Tangle is missing root message')
return false
}
if (a === b) return false
if (b === this.#rootHash) return false
let toCheck = [b]
if (msgHashA === msgHashB) return false
if (msgHashB === this.#rootHash) return false
let toCheck = [msgHashB]
while (toCheck.length > 0) {
const prev = this.#prev.get(toCheck.shift())
const prev = this.#prev.get(/** @type {string} */ (toCheck.shift()))
if (!prev) continue
if (prev.includes(a)) return true
if (prev.includes(msgHashA)) return true
toCheck.push(...prev)
}
return false

View File

@ -1,11 +1,20 @@
const b4a = require('b4a')
const base58 = require('bs58')
const Keypair = require('ppppp-keypair')
// @ts-ignore
const stringify = require('json-canon')
const Tangle = require('./tangle')
const representData = require('./represent-data')
const isFeedRoot = require('./is-feed-root')
/**
* @typedef {import('.').Msg} Msg
*/
/**
* @param {Msg} msg
* @returns {string | undefined}
*/
function validateShape(msg) {
if (!msg || typeof msg !== 'object') {
return 'invalid message: not an object\n' + JSON.stringify(msg)
@ -25,7 +34,9 @@ function validateShape(msg) {
return 'invalid message: must have metadata.dataSize\n' + JSON.stringify(msg)
}
if (!('identity' in msg.metadata)) {
return 'invalid message: must have metadata.identity\n' + JSON.stringify(msg)
return (
'invalid message: must have metadata.identity\n' + JSON.stringify(msg)
)
}
if (!('identityTips' in msg.metadata)) {
// prettier-ignore
@ -45,6 +56,10 @@ function validateShape(msg) {
}
}
/**
* @param {Msg} msg
* @returns {string | undefined}
*/
function validatePubkey(msg) {
const { pubkey } = msg
if (typeof pubkey !== 'string') {
@ -63,6 +78,12 @@ function validatePubkey(msg) {
}
}
/**
*
* @param {Msg} msg
* @param {Set<string>} pubkeys
* @returns {string | undefined}
*/
function validateIdentityPubkey(msg, pubkeys) {
// Unusual case: if the msg is a feed root, ignore the identity and pubkey
if (isFeedRoot(msg)) return
@ -73,6 +94,10 @@ function validateIdentityPubkey(msg, pubkeys) {
}
}
/**
* @param {string} str
* @returns {string | undefined}
*/
function validateMsgHash(str) {
try {
const hashBuf = b4a.from(base58.decode(str))
@ -85,6 +110,10 @@ function validateMsgHash(str) {
}
}
/**
* @param {Msg} msg
* @returns {string | undefined}
*/
function validateDataSize(msg) {
const { dataSize } = msg.metadata
if (!Number.isSafeInteger(dataSize) || dataSize < 0) {
@ -93,6 +122,10 @@ function validateDataSize(msg) {
}
}
/**
* @param {Msg} msg
* @returns {string | undefined}
*/
function validateSignature(msg) {
const { sig } = msg
if (typeof sig !== 'string') {
@ -122,10 +155,13 @@ function validateSignature(msg) {
}
/**
*
* @param {any} msg
* @typedef {NonNullable<ReturnType<Tangle['getFeed']>>} FeedDetails
*/
/**
* @param {Msg} msg
* @param {Tangle} tangle
* @param {*} tangleId
* @param {string} tangleId
* @returns
*/
function validateTangle(msg, tangle, tangleId) {
@ -143,7 +179,7 @@ function validateTangle(msg, tangle, tangleId) {
return `invalid message: depth "${depth}" should have been a positive integer\n` + JSON.stringify(msg)
}
if (tangle.isFeed()) {
const { identity, domain } = tangle.getFeed()
const { identity, domain } = /** @type {FeedDetails} */ (tangle.getFeed())
if (domain !== msg.metadata.domain) {
// prettier-ignore
return `invalid message: domain "${msg.metadata.domain}" should have been feed domain "${domain}"\n` + JSON.stringify(msg)
@ -202,6 +238,11 @@ function validateTangle(msg, tangle, tangleId) {
}
}
/**
* @param {Msg} msg
* @param {string} msgHash
* @param {string} tangleId
*/
function validateTangleRoot(msg, msgHash, tangleId) {
if (msgHash !== tangleId) {
// prettier-ignore
@ -213,6 +254,9 @@ function validateTangleRoot(msg, msgHash, tangleId) {
}
}
/**
* @param {string} domain
*/
function validateDomain(domain) {
if (!domain || typeof domain !== 'string') {
// prettier-ignore
@ -232,6 +276,9 @@ function validateDomain(domain) {
}
}
/**
* @param {Msg} msg
*/
function validateData(msg) {
const { data } = msg
if (data === null) {
@ -260,6 +307,13 @@ function validateData(msg) {
}
}
/**
* @param {Msg} msg
* @param {Tangle} tangle
* @param {Set<string>} pubkeys
* @param {string} msgHash
* @param {string} rootHash
*/
function validate(msg, tangle, pubkeys, msgHash, rootHash) {
let err
if ((err = validateShape(msg))) return err

View File

@ -6,6 +6,9 @@ class ReadyGate {
this.#ready = false
}
/**
* @param {() => void} cb
*/
onReady(cb) {
if (this.#ready) cb()
else this.#waiting.add(cb)

View File

@ -14,6 +14,7 @@
"files": [
"lib/**/*"
],
"types": "types/index.d.ts",
"engines": {
"node": ">=16"
},
@ -46,9 +47,11 @@
"pretty-quick": "^3.1.3",
"rimraf": "^4.4.0",
"secret-stack": "^6.4.2",
"ssb-box": "^1.0.1"
"ssb-box": "^1.0.1",
"typescript": "^5.1.3"
},
"scripts": {
"build": "tsc --build --clean && tsc --build",
"test": "node --test",
"format-code": "prettier --write \"(lib|test)/**/*.js\"",
"format-code-staged": "pretty-quick --staged --pattern \"(lib|test)/**/*.js\"",

16
tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"include": ["lib/**/*.js"],
"exclude": ["coverage/", "node_modules/", "test/"],
"compilerOptions": {
"checkJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es2022", "dom"],
"module": "node16",
"skipLibCheck": true,
"strict": true,
"target": "es2021"
}
}