mirror of https://codeberg.org/pzp/pzp-goals.git
getRecordPurpose can return ghost
This commit is contained in:
parent
3f73dada5b
commit
ca8f2ceed1
54
lib/index.js
54
lib/index.js
|
@ -1,5 +1,7 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const Obz = require('obz')
|
const Obz = require('obz')
|
||||||
|
// @ts-ignore
|
||||||
|
const multicb = require('multicb')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
|
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
|
||||||
|
@ -7,23 +9,38 @@ const Obz = require('obz')
|
||||||
* @typedef {import('ppppp-db').RecPresent} RecPresent
|
* @typedef {import('ppppp-db').RecPresent} RecPresent
|
||||||
* @typedef {import('ppppp-db').Tangle} Tangle
|
* @typedef {import('ppppp-db').Tangle} Tangle
|
||||||
* @typedef {ReturnType<PPPPPDB['getTangle']>} DBTangle
|
* @typedef {ReturnType<PPPPPDB['getTangle']>} DBTangle
|
||||||
|
* @typedef {string} MsgID
|
||||||
* @typedef {'none'|'all'|`newest-${number}`|'record'|'set'} GoalDSL
|
* @typedef {'none'|'all'|`newest-${number}`|'record'|'set'} GoalDSL
|
||||||
* @typedef {'none'|'all'|'newest'|'record'|'set'} GoalType
|
* @typedef {'none'|'all'|'newest'|'record'|'set'} GoalType
|
||||||
* @typedef {[number, number]} Range
|
* @typedef {[number, number]} Range
|
||||||
* @typedef {{ id: string, type: GoalType, count: number }} Goal
|
* @typedef {{ id: string, type: GoalType, count: number }} Goal
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(...args: [Error] | [null, T]) => void } CB
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @typedef {(args?: CB<Array<T>>) => any} Multicb
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A *purpose* is a tag that explains why a msg exists in the database.
|
* A *purpose* is a tag that explains why a msg exists in the database.
|
||||||
* - "none" means the msg has no purpose, and should not exist in the database.
|
* - "none" means the msg has no purpose, and should not exist in the database.
|
||||||
|
* - "ghost" means the msg has no purpose, should not exist in the database, but
|
||||||
|
* we should still register it as a ghost so that we don't accidentally
|
||||||
|
* re-request it during replication.
|
||||||
* - "trail" means the msg does not meet any goal, but it is required to be in
|
* - "trail" means the msg does not meet any goal, but it is required to be in
|
||||||
* the database because it is along the path of goalful msgs to the root of the
|
* the database because it is along the path of goalful msgs to the root of the
|
||||||
* tangle. See "Lipmaa certificate pool" concept from Bamboo.
|
* tangle. See "Lipmaa certificate pool" concept from Bamboo.
|
||||||
* - "goal" means the msg perfectly meets the requirements of some goal.
|
* - "goal" means the msg perfectly meets the requirements of some goal.
|
||||||
*
|
*
|
||||||
* These tags are ordered, "none" < "trail" < "goal", meaning that a msg with
|
* These tags are ordered, "none" < "ghost" < "trail" < "goal", meaning that a
|
||||||
* purpose "goal" may *also* fulfill the purpose of "trail".
|
* msg with purpose "goal" may *also* fulfill the purpose of "trail", and a
|
||||||
* @typedef {'none' | 'trail' | 'goal'} Purpose
|
* "trail" also prevents accidental re-request like "ghost" does.
|
||||||
|
* @typedef {'none' | 'ghost' | 'trail' | 'goal'} Purpose
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,9 +188,9 @@ function initGoals(peer, config) {
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {RecPresent} rec
|
* @param {RecPresent} rec
|
||||||
* @returns {Purpose}
|
* @param {CB<Purpose>} cb
|
||||||
*/
|
*/
|
||||||
function getRecordPurpose(rec) {
|
function getRecordPurpose(rec, cb) {
|
||||||
assertDBPlugin(peer)
|
assertDBPlugin(peer)
|
||||||
let servesAsTrail = false
|
let servesAsTrail = false
|
||||||
|
|
||||||
|
@ -185,7 +202,7 @@ function initGoals(peer, config) {
|
||||||
if (!tangle) break asRoot
|
if (!tangle) break asRoot
|
||||||
const [min, max] = crossGoalWithTangle(goal, tangle)
|
const [min, max] = crossGoalWithTangle(goal, tangle)
|
||||||
if (min > max) break asRoot
|
if (min > max) break asRoot
|
||||||
if (min === 0) return 'goal'
|
if (min === 0) return cb(null, 'goal')
|
||||||
if (min > 0) servesAsTrail = true
|
if (min > 0) servesAsTrail = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,11 +223,11 @@ function initGoals(peer, config) {
|
||||||
}
|
}
|
||||||
// (Loop over once without heavy computations and maybe return early:)
|
// (Loop over once without heavy computations and maybe return early:)
|
||||||
for (const [, min, max, recDepth] of validTangles) {
|
for (const [, min, max, recDepth] of validTangles) {
|
||||||
if (min <= recDepth && recDepth <= max) return 'goal'
|
if (min <= recDepth && recDepth <= max) return cb(null, 'goal')
|
||||||
}
|
}
|
||||||
// At this point we know that the record *cannot* serve as 'goal',
|
// At this point we know that the record *cannot* serve as 'goal',
|
||||||
// so if it serves as trail, that'll do:
|
// so if it serves as trail, that'll do:
|
||||||
if (servesAsTrail) return 'trail'
|
if (servesAsTrail) return cb(null, 'trail')
|
||||||
// Check whether this record is a trail affix of some tangle:
|
// Check whether this record is a trail affix of some tangle:
|
||||||
// (Loop again with heavy computations now that it's inevitable:)
|
// (Loop again with heavy computations now that it's inevitable:)
|
||||||
for (const [tangle, min] of validTangles) {
|
for (const [tangle, min] of validTangles) {
|
||||||
|
@ -218,10 +235,27 @@ function initGoals(peer, config) {
|
||||||
.topoSort()
|
.topoSort()
|
||||||
.filter((msgID) => tangle.getDepth(msgID) === min)
|
.filter((msgID) => tangle.getDepth(msgID) === min)
|
||||||
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
|
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
|
||||||
if (erasables.has(rec.id)) return 'trail'
|
if (erasables.has(rec.id)) return cb(null, 'trail')
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'none'
|
// Check whether this record is a ghost affix of some tangle:
|
||||||
|
if (validTangles.length > 0) {
|
||||||
|
const done = /** @type {Multicb<Array<MsgID>>} */ (multicb({ pluck: 1 }))
|
||||||
|
for (const [tangle] of validTangles) {
|
||||||
|
peer.db.ghosts.get(tangle.id, done())
|
||||||
|
}
|
||||||
|
done((err, allGhosts) => {
|
||||||
|
// prettier-ignore
|
||||||
|
if (err) return cb(new Error('getRecordPurpose() failed to get ghosts', {cause: err}))
|
||||||
|
for (const ghosts of allGhosts) {
|
||||||
|
if (ghosts.includes(rec.id)) return cb(null, 'ghost')
|
||||||
|
}
|
||||||
|
cb(null, 'none')
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, 'none')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"obz": "~1.1.0"
|
"obz": "~1.1.0",
|
||||||
|
"multicb": "~1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bs58": "^5.0.0",
|
"bs58": "^5.0.0",
|
||||||
|
|
|
@ -34,7 +34,7 @@ test('set, getByID, list, listen', async (t) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const purpose = alice.goals.getRecordPurpose(aliceAccountRoot)
|
const purpose = await p(alice.goals.getRecordPurpose)(aliceAccountRoot)
|
||||||
assert.equal(purpose, 'goal', 'rec purpose is "goal"')
|
assert.equal(purpose, 'goal', 'rec purpose is "goal"')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,13 +91,17 @@ test('getRecordPurpose', async (t) => {
|
||||||
const gottenGoal = alice.goals.get(feedID)
|
const gottenGoal = alice.goals.get(feedID)
|
||||||
assert.strictEqual(gottenGoal.id, feedID, 'gotten goal id is correct')
|
assert.strictEqual(gottenGoal.id, feedID, 'gotten goal id is correct')
|
||||||
|
|
||||||
const purpose = alice.goals.getRecordPurpose(post2)
|
const purpose = await p(alice.goals.getRecordPurpose)(post2)
|
||||||
assert.equal(purpose, 'goal', 'purpose is "goal"')
|
assert.equal(purpose, 'goal', 'purpose is "goal"')
|
||||||
|
|
||||||
alice.goals.set(feedID, 'newest-1')
|
alice.goals.set(feedID, 'newest-1')
|
||||||
assert('set goal to newest-1')
|
assert('set goal to newest-1')
|
||||||
const purpose2 = alice.goals.getRecordPurpose(post2)
|
const purpose2 = await p(alice.goals.getRecordPurpose)(post2)
|
||||||
assert.equal(purpose2, 'none', 'purpose2 is "none"')
|
assert.equal(purpose2, 'none', 'purpose2 is "none"')
|
||||||
|
|
||||||
|
await p(alice.db.ghosts.add)({ msg: post2.id, tangle: feedID, max: 5 })
|
||||||
|
const purpose3 = await p(alice.goals.getRecordPurpose)(post2)
|
||||||
|
assert.equal(purpose3, 'ghost', 'purpose3 is "ghost"')
|
||||||
|
|
||||||
await p(alice.close)(true)
|
await p(alice.close)(true)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue