add types in JSDoc

This commit is contained in:
Andre Staltz 2023-09-28 11:25:14 +03:00
parent 889d003be4
commit 597f3a3bce
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
3 changed files with 204 additions and 55 deletions

View File

@ -1,68 +1,200 @@
const makeDebug = require('debug')
// @ts-ignore
const multicb = require('multicb')
const makeDebug = require('debug')
module.exports = {
name: 'gc',
manifest: {},
permissions: {
anonymous: {},
},
/**
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
* @typedef {ReturnType<import('ppppp-goals').init>} PPPPPGoal
* @typedef {{
* gc: {
* maxLogBytes: number
* compactionInterval?: number
* }
* }} ExpectedConfig
* @typedef {{gc?: Partial<ExpectedConfig['gc']>}} Config
*/
/**
* @template T
* @typedef {T extends void ?
* (...args: [Error] | []) => void :
* (...args: [Error] | [null, T]) => void
* } CB
*/
/**
* @param {{ db: PPPPPDB | null }} peer
* @returns {asserts peer is { db: PPPPPDB }}
*/
function assertDBExists(peer) {
if (!peer.db) throw new Error('gc plugin requires ppppp-db plugin')
}
/**
* @param {{ goals: PPPPPGoal | null }} peer
* @returns {asserts peer is { goals: PPPPPGoal }}
*/
function assertGoalsExists(peer) {
if (!peer.goals) throw new Error('gc plugin requires ppppp-goals plugin')
}
/**
* @param {Config} config
* @returns {asserts config is ExpectedConfig}
*/
function assertValidConfig(config) {
if (typeof config.gc?.maxLogBytes !== 'number') {
throw new Error('gc requires config.gc.maxLogBytes')
}
}
/**
* @param {{ db: PPPPPDB | null, goals: PPPPPGoal | null }} peer
* @param {Config} config
*/
function initGC(peer, config) {
// Assertions
assertDBExists(peer)
assertGoalsExists(peer)
assertValidConfig(config)
// Constants
const COMPACTION_INTERVAL = config.gc?.compactionInterval ?? 120e3
// State
const debug = makeDebug('ppppp:gc')
let lastCompacted = Date.now()
let stopMonitoringLogSize = /** @type {CallableFunction | null} */ (null)
let hasCompactionScheduled = false
let hasCleanupScheduled = false
/**
* @param {any} peer
* @param {{
* gc?: {
* maxLogBytes?: number
* }
* }} config
* Deletes messages that don't correspond to any goal.
* @private
* @param {CB<void>} cb
*/
init(peer, config) {
// Assertions
if (!peer.goals) throw new Error('gc requires the goals plugin')
if (typeof config.gc?.maxLogBytes !== 'number') {
throw new Error('gc requires config.gc.maxLogBytes')
}
// State
const debug = makeDebug('ppppp:gc')
/**
* Deletes messages that don't correspond to any goal.
* @private
*/
function cleanup(cb) {
debug('cleanup goalless started')
const done = multicb({ pluck: 1 })
let waiting = false
for (const rec of peer.db.records()) {
if (!rec.msg) continue
const purpose = peer.goals.getRecordPurpose(rec)
if (purpose === 'none') {
peer.db.del(rec.id, done())
waiting = true
} else if (purpose === 'trail') {
peer.db.erase(rec.id, done())
waiting = true
}
function cleanup(cb) {
assertDBExists(peer)
assertGoalsExists(peer)
debug('cleanup goalless started')
const done = multicb({ pluck: 1 })
let waiting = false
for (const rec of peer.db.records()) {
if (!rec.msg) continue
const purpose = peer.goals.getRecordPurpose(rec)
if (purpose === 'none') {
peer.db.del(rec.id, done())
waiting = true
} else if (purpose === 'trail') {
peer.db.erase(rec.id, done())
waiting = true
}
function whenEnded(err) {
// prettier-ignore
if (err) debug('cleanup goalless ended with an error %s', err.message ?? err)
}
/** @param {Error=} err */
function whenEnded(err) {
// prettier-ignore
if (err) debug('cleanup goalless ended with an error %s', err.message ?? err)
else debug('cleanup goalless ended')
cb()
cb()
}
if (waiting) done(whenEnded)
else whenEnded()
}
/**
* Compact the log (remove deleted records by filling in all the blanks).
* @private
* @param {number} waitPeriod
* @param {CB<void>} cb
*/
function compact(waitPeriod, cb) {
assertDBExists(peer)
const log = peer.db._getLog() // TODO: use public API?
debug('compaction started')
/** @param {Error=} err */
function whenEnded(err) {
if (err) debug('compaction ended with an error %s', err.message ?? err)
else debug('compaction ended')
cb()
}
if (waitPeriod > 0) {
setTimeout(log.compact, waitPeriod, whenEnded)
} else {
log.compact(whenEnded)
}
}
/**
* Monitor the log size and schedule compaction and/or cleanup.
*/
function monitorLogSize() {
assertDBExists(peer)
function checkLogSize() {
assertDBExists(peer)
assertValidConfig(config)
peer.db.logStats((err, stats) => {
if (err) return
const percentUsed = (stats.totalBytes / config.gc.maxLogBytes) * 100
const needsCompaction = stats.deletedBytes > 0
// Schedule compaction
if (needsCompaction && !hasCompactionScheduled) {
const nextCompacted = lastCompacted + COMPACTION_INTERVAL
const waitPeriod = Math.max(0, nextCompacted - Date.now())
hasCompactionScheduled = true
compact(waitPeriod, () => {
hasCompactionScheduled = false
})
}
// Schedule clean up
if (percentUsed > 80 && !hasCleanupScheduled) {
hasCleanupScheduled = true
cleanup(() => {
hasCleanupScheduled = false
})
}
})
}
let count = 0
stopMonitoringLogSize = peer.db.onRecordAdded(() => {
count += 1
if (count >= 1000) {
count = 0
checkLogSize()
}
if (waiting) done(whenEnded)
else whenEnded()
}
})
checkLogSize()
}
function forceImmediately(cb) {
debug('force immediately')
cleanup(cb)
function start() {
if (!stopMonitoringLogSize) {
monitorLogSize()
}
}
return {
initiate,
forceImmediately,
function stop() {
if (stopMonitoringLogSize) {
stopMonitoringLogSize()
stopMonitoringLogSize = null
}
},
}
/**
* @param {CB<void>} cb
*/
function forceImmediately(cb) {
debug('force immediately')
cleanup(cb)
}
return {
start,
stop,
forceImmediately,
}
}
exports.name = 'gc'
exports.init = initGC

View File

@ -28,6 +28,7 @@
"multicb": "^1.2.2"
},
"devDependencies": {
"@types/debug": "4.1.9",
"bs58": "^5.0.0",
"c8": "7",
"ppppp-caps": "github:staltz/ppppp-caps",

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"
}
}