mirror of https://codeberg.org/pzp/pzp-goals.git
restructure index.js for better exported types
This commit is contained in:
parent
037aec208c
commit
d8a0f841fe
|
@ -4,3 +4,4 @@ pnpm-lock.yaml
|
||||||
package-lock.json
|
package-lock.json
|
||||||
coverage
|
coverage
|
||||||
*~
|
*~
|
||||||
|
lib/*.d.ts
|
251
lib/index.js
251
lib/index.js
|
@ -26,9 +26,7 @@ const Obz = require('obz')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @param {{ db: PPPPPDB | null }} peer
|
||||||
* db: PPPPPDB | null,
|
|
||||||
* }} peer
|
|
||||||
* @returns {asserts peer is { db: PPPPPDB }}
|
* @returns {asserts peer is { db: PPPPPDB }}
|
||||||
*/
|
*/
|
||||||
function assertDBExists(peer) {
|
function assertDBExists(peer) {
|
||||||
|
@ -110,141 +108,136 @@ class GoalImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
/**
|
||||||
name: 'goals',
|
* @param {{ db: PPPPPDB | null }} peer
|
||||||
manifest: {},
|
* @param {unknown} config
|
||||||
permissions: {
|
*/
|
||||||
anonymous: {},
|
function initGoals(peer, config) {
|
||||||
},
|
assertDBExists(peer)
|
||||||
|
// Constants:
|
||||||
|
const EMPTY_RANGE = /** @type {Range} */ ([1, 0])
|
||||||
|
|
||||||
|
// State:
|
||||||
|
const goals = /** @type {Map<string, Goal>} */ (new Map())
|
||||||
|
const listen = Obz()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{ db: PPPPPDB | null, close: ClosableHook }} peer
|
* @private
|
||||||
* @param {{ path: string; keypair: Keypair; }} config
|
* @param {Goal} goal
|
||||||
|
* @param {Tangle} tangle
|
||||||
|
* @returns {Range}
|
||||||
*/
|
*/
|
||||||
init(peer, config) {
|
function crossGoalWithTangle(goal, tangle) {
|
||||||
|
const maxDepth = tangle.maxDepth
|
||||||
|
switch (goal.type) {
|
||||||
|
case 'none':
|
||||||
|
return EMPTY_RANGE
|
||||||
|
case 'all':
|
||||||
|
case 'set':
|
||||||
|
case 'record':
|
||||||
|
return [0, maxDepth]
|
||||||
|
case 'newest':
|
||||||
|
const start = Math.max(0, maxDepth - goal.count + 1)
|
||||||
|
return [start, maxDepth]
|
||||||
|
case 'oldest':
|
||||||
|
const end = Math.min(maxDepth, goal.count - 1)
|
||||||
|
return [0, end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @param {string} tangleID
|
||||||
|
* @param {GoalDSL} goalDSL
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
function set(tangleID, goalDSL) {
|
||||||
|
const goal = new GoalImpl(tangleID, goalDSL)
|
||||||
|
goals.set(tangleID, goal)
|
||||||
|
listen.set(goal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @param {string} tangleID
|
||||||
|
* @returns {Goal | null}
|
||||||
|
*/
|
||||||
|
function get(tangleID) {
|
||||||
|
return goals.get(tangleID) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @param {RecPresent} rec
|
||||||
|
* @returns {Purpose}
|
||||||
|
*/
|
||||||
|
function getRecordPurpose(rec) {
|
||||||
assertDBExists(peer)
|
assertDBExists(peer)
|
||||||
// Constants:
|
let servesAsTrail = false
|
||||||
const EMPTY_RANGE = /** @type {Range} */ ([1, 0])
|
|
||||||
|
|
||||||
// State:
|
// Check whether this record is a goalful root of some tangle:
|
||||||
const goals = /** @type {Map<string, Goal>} */ (new Map())
|
asRoot: if (goals.has(rec.id)) {
|
||||||
const listen = Obz()
|
const goal = /** @type {GoalImpl} */ (goals.get(rec.id))
|
||||||
|
if (goal.type === 'none') break asRoot
|
||||||
/**
|
const tangle = peer.db.getTangle(rec.id)
|
||||||
* @private
|
if (!tangle) break asRoot
|
||||||
* @param {Goal} goal
|
const [min, max] = crossGoalWithTangle(goal, tangle)
|
||||||
* @param {Tangle} tangle
|
if (min > max) break asRoot
|
||||||
* @returns {Range}
|
if (min === 0) return 'goal'
|
||||||
*/
|
if (min > 0) servesAsTrail = true
|
||||||
function crossGoalWithTangle(goal, tangle) {
|
|
||||||
const maxDepth = tangle.maxDepth
|
|
||||||
switch (goal.type) {
|
|
||||||
case 'none':
|
|
||||||
return EMPTY_RANGE
|
|
||||||
case 'all':
|
|
||||||
case 'set':
|
|
||||||
case 'record':
|
|
||||||
return [0, maxDepth]
|
|
||||||
case 'newest':
|
|
||||||
const start = Math.max(0, maxDepth - goal.count + 1)
|
|
||||||
return [start, maxDepth]
|
|
||||||
case 'oldest':
|
|
||||||
const end = Math.min(maxDepth, goal.count - 1)
|
|
||||||
return [0, end]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Check whether this record is a goalful affix of some tangle:
|
||||||
* @public
|
const validTangles =
|
||||||
* @param {string} tangleID
|
/** @type {Array<[DBTangle, number, number, number]>} */ ([])
|
||||||
* @param {GoalDSL} goalDSL
|
asAffix: for (const tangleID in rec.msg.metadata.tangles) {
|
||||||
* @returns {void}
|
if (!goals.has(tangleID)) continue asAffix
|
||||||
*/
|
const goal = /** @type {GoalImpl} */ (goals.get(tangleID))
|
||||||
function set(tangleID, goalDSL) {
|
if (goal.type === 'none') continue asAffix
|
||||||
const goal = new GoalImpl(tangleID, goalDSL)
|
const tangle = peer.db.getTangle(tangleID)
|
||||||
goals.set(tangleID, goal)
|
if (!tangle) continue asAffix
|
||||||
listen.set(goal)
|
const [min, max] = crossGoalWithTangle(goal, tangle)
|
||||||
|
if (min > max) continue asAffix
|
||||||
|
const recDepth = tangle.getDepth(rec.id)
|
||||||
|
if (recDepth < 0) continue asAffix
|
||||||
|
validTangles.push([tangle, min, max, recDepth])
|
||||||
|
}
|
||||||
|
// (Loop over once without heavy computations and maybe return early:)
|
||||||
|
for (const [, min, max, recDepth] of validTangles) {
|
||||||
|
if (min <= recDepth && recDepth <= max) return 'goal'
|
||||||
|
}
|
||||||
|
// At this point we know that the record *cannot* serve as 'goal',
|
||||||
|
// so if it serves as trail, that'll do:
|
||||||
|
if (servesAsTrail) return 'trail'
|
||||||
|
// Check whether this record is a trail affix of some tangle:
|
||||||
|
// (Loop again with heavy computations now that it's inevitable:)
|
||||||
|
for (const [tangle, min] of validTangles) {
|
||||||
|
const minMsgIDs = tangle
|
||||||
|
.topoSort()
|
||||||
|
.filter((msgID) => tangle.getDepth(msgID) === min)
|
||||||
|
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
|
||||||
|
if (erasables.has(rec.id)) return 'trail'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return 'none'
|
||||||
* @public
|
}
|
||||||
* @param {string} tangleID
|
|
||||||
* @returns {Goal | null}
|
|
||||||
*/
|
|
||||||
function get(tangleID) {
|
|
||||||
return goals.get(tangleID) ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {RecPresent} rec
|
* @returns {IterableIterator<Goal>}
|
||||||
* @returns {Purpose}
|
*/
|
||||||
*/
|
function list() {
|
||||||
function getRecordPurpose(rec) {
|
return goals.values()
|
||||||
assertDBExists(peer)
|
}
|
||||||
let servesAsTrail = false
|
|
||||||
|
|
||||||
// Check whether this record is a goalful root of some tangle:
|
return {
|
||||||
asRoot: if (goals.has(rec.id)) {
|
set,
|
||||||
const goal = /** @type {GoalImpl} */ (goals.get(rec.id))
|
get,
|
||||||
if (goal.type === 'none') break asRoot
|
getRecordPurpose,
|
||||||
const tangle = peer.db.getTangle(rec.id)
|
list,
|
||||||
if (!tangle) break asRoot
|
listen,
|
||||||
const [min, max] = crossGoalWithTangle(goal, tangle)
|
}
|
||||||
if (min > max) break asRoot
|
|
||||||
if (min === 0) return 'goal'
|
|
||||||
if (min > 0) servesAsTrail = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether this record is a goalful affix of some tangle:
|
|
||||||
const validTangles =
|
|
||||||
/** @type {Array<[DBTangle, number, number, number]>} */ ([])
|
|
||||||
asAffix: for (const tangleID in rec.msg.metadata.tangles) {
|
|
||||||
if (!goals.has(tangleID)) continue asAffix
|
|
||||||
const goal = /** @type {GoalImpl} */ (goals.get(tangleID))
|
|
||||||
if (goal.type === 'none') continue asAffix
|
|
||||||
const tangle = peer.db.getTangle(tangleID)
|
|
||||||
if (!tangle) continue asAffix
|
|
||||||
const [min, max] = crossGoalWithTangle(goal, tangle)
|
|
||||||
if (min > max) continue asAffix
|
|
||||||
const recDepth = tangle.getDepth(rec.id)
|
|
||||||
if (recDepth < 0) continue asAffix
|
|
||||||
validTangles.push([tangle, min, max, recDepth])
|
|
||||||
}
|
|
||||||
// (Loop over once without heavy computations and maybe return early:)
|
|
||||||
for (const [, min, max, recDepth] of validTangles) {
|
|
||||||
if (min <= recDepth && recDepth <= max) return 'goal'
|
|
||||||
}
|
|
||||||
// At this point we know that the record *cannot* serve as 'goal',
|
|
||||||
// so if it serves as trail, that'll do:
|
|
||||||
if (servesAsTrail) return 'trail'
|
|
||||||
// Check whether this record is a trail affix of some tangle:
|
|
||||||
// (Loop again with heavy computations now that it's inevitable:)
|
|
||||||
for (const [tangle, min] of validTangles) {
|
|
||||||
const minMsgIDs = tangle
|
|
||||||
.topoSort()
|
|
||||||
.filter((msgID) => tangle.getDepth(msgID) === min)
|
|
||||||
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
|
|
||||||
if (erasables.has(rec.id)) return 'trail'
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'none'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @returns {IterableIterator<Goal>}
|
|
||||||
*/
|
|
||||||
function list() {
|
|
||||||
return goals.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
set,
|
|
||||||
get,
|
|
||||||
getRecordPurpose,
|
|
||||||
list,
|
|
||||||
listen,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.name = 'goals'
|
||||||
|
exports.init = initGoals
|
||||||
|
|
Loading…
Reference in New Issue