mirror of https://codeberg.org/pzp/pzp-db.git
new API tangle.slice()
This commit is contained in:
parent
f523df3a0e
commit
99c1520415
58
lib/index.js
58
lib/index.js
|
@ -86,12 +86,17 @@ function assertValidConfig(config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class DBTangle extends MsgV3.Tangle {
|
class DBTangle extends MsgV3.Tangle {
|
||||||
|
/** @type {(msgID: MsgID) => Msg | undefined} */
|
||||||
|
#getMsg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MsgID} rootID
|
* @param {MsgID} rootID
|
||||||
* @param {Iterable<Rec>} recordsIter
|
* @param {Iterable<Rec>} recordsIter
|
||||||
|
* @param {(msgID: MsgID) => Msg | undefined} getMsg
|
||||||
*/
|
*/
|
||||||
constructor(rootID, recordsIter) {
|
constructor(rootID, recordsIter, getMsg) {
|
||||||
super(rootID)
|
super(rootID)
|
||||||
|
this.#getMsg = getMsg
|
||||||
for (const rec of recordsIter) {
|
for (const rec of recordsIter) {
|
||||||
if (!rec.msg) continue
|
if (!rec.msg) continue
|
||||||
this.add(rec.id, rec.msg)
|
this.add(rec.id, rec.msg)
|
||||||
|
@ -133,6 +138,42 @@ class DBTangle extends MsgV3.Tangle {
|
||||||
|
|
||||||
return { deletables, erasables }
|
return { deletables, erasables }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array<string>} minSet
|
||||||
|
* @param {Array<string>} maxSet
|
||||||
|
* @returns {Array<Msg>}
|
||||||
|
*/
|
||||||
|
slice(minSet, maxSet = []) {
|
||||||
|
const minSetGood = minSet.filter((msgID) => this.has(msgID))
|
||||||
|
const maxSetGood = maxSet.filter((msgID) => this.has(msgID))
|
||||||
|
const minSetTight = this.getMinimumAmong(minSetGood)
|
||||||
|
|
||||||
|
const trail = new Set()
|
||||||
|
for (const msgID of minSetTight) {
|
||||||
|
const path = this.shortestPathToRoot(msgID)
|
||||||
|
for (const msgID of path) {
|
||||||
|
trail.add(msgID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgs = /**@type {Array<Msg>}*/ ([])
|
||||||
|
for (const msgID of this.topoSort()) {
|
||||||
|
if (trail.has(msgID)) {
|
||||||
|
const msg = this.#getMsg(msgID)
|
||||||
|
if (msg) msgs.push({ ...msg, data: null })
|
||||||
|
}
|
||||||
|
const isMin = minSetGood.includes(msgID)
|
||||||
|
const isMax = maxSetGood.includes(msgID)
|
||||||
|
const isBeforeMin = minSetGood.some((min) => this.precedes(msgID, min))
|
||||||
|
const isAfterMax = maxSetGood.some((max) => this.precedes(max, msgID))
|
||||||
|
if (!isMin && isBeforeMin) continue
|
||||||
|
if (!isMax && isAfterMax) continue
|
||||||
|
const msg = this.#getMsg(msgID)
|
||||||
|
if (msg) msgs.push(msg)
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -288,7 +329,7 @@ function initDB(peer, config) {
|
||||||
/** @type {Record<MsgID, DBTangle>} */
|
/** @type {Record<MsgID, DBTangle>} */
|
||||||
const tangles = {}
|
const tangles = {}
|
||||||
for (const tangleID of tangleIDs) {
|
for (const tangleID of tangleIDs) {
|
||||||
tangles[tangleID] ??= new DBTangle(tangleID, records())
|
tangles[tangleID] ??= new DBTangle(tangleID, records(), get)
|
||||||
}
|
}
|
||||||
return tangles
|
return tangles
|
||||||
}
|
}
|
||||||
|
@ -301,7 +342,7 @@ function initDB(peer, config) {
|
||||||
const accountID = getAccountID(rec)
|
const accountID = getAccountID(rec)
|
||||||
let accountTangle = /** @type {Tangle | null} */ (null)
|
let accountTangle = /** @type {Tangle | null} */ (null)
|
||||||
if (accountID) {
|
if (accountID) {
|
||||||
accountTangle = new DBTangle(accountID, records())
|
accountTangle = new DBTangle(accountID, records(), get)
|
||||||
if (rec.id === accountID) {
|
if (rec.id === accountID) {
|
||||||
accountTangle.add(rec.id, rec.msg)
|
accountTangle.add(rec.id, rec.msg)
|
||||||
}
|
}
|
||||||
|
@ -359,7 +400,7 @@ function initDB(peer, config) {
|
||||||
function verifyRec(rec, tangleID) {
|
function verifyRec(rec, tangleID) {
|
||||||
// TODO: optimize this. This may be slow if you're adding many msgs in a
|
// TODO: optimize this. This may be slow if you're adding many msgs in a
|
||||||
// row, because it creates a new Map() each time. Perhaps with QuickLRU
|
// row, because it creates a new Map() each time. Perhaps with QuickLRU
|
||||||
const tangle = new DBTangle(tangleID, records())
|
const tangle = new DBTangle(tangleID, records(), get)
|
||||||
if (rec.id === tangleID) {
|
if (rec.id === tangleID) {
|
||||||
tangle.add(rec.id, rec.msg)
|
tangle.add(rec.id, rec.msg)
|
||||||
}
|
}
|
||||||
|
@ -560,7 +601,7 @@ function initDB(peer, config) {
|
||||||
function accountHas(opts) {
|
function accountHas(opts) {
|
||||||
const keypair = opts?.keypair ?? config.keypair
|
const keypair = opts?.keypair ?? config.keypair
|
||||||
|
|
||||||
const accountTangle = new DBTangle(opts.account, records())
|
const accountTangle = new DBTangle(opts.account, records(), get)
|
||||||
for (const msgID of accountTangle.topoSort()) {
|
for (const msgID of accountTangle.topoSort()) {
|
||||||
const msg = get(msgID)
|
const msg = get(msgID)
|
||||||
if (!msg?.data) continue
|
if (!msg?.data) continue
|
||||||
|
@ -731,7 +772,7 @@ function initDB(peer, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify powers of the signingKeypair:
|
// Verify powers of the signingKeypair:
|
||||||
const accountTangle = new DBTangle(opts.account, records())
|
const accountTangle = new DBTangle(opts.account, records(), get)
|
||||||
if (obeying) {
|
if (obeying) {
|
||||||
const signingPowers = getAccountPowers(accountTangle, signingKeypair)
|
const signingPowers = getAccountPowers(accountTangle, signingKeypair)
|
||||||
if (!signingPowers.has('add')) {
|
if (!signingPowers.has('add')) {
|
||||||
|
@ -856,7 +897,7 @@ function initDB(peer, config) {
|
||||||
const tangleTemplates = opts.tangles ?? []
|
const tangleTemplates = opts.tangles ?? []
|
||||||
tangleTemplates.push(mootID)
|
tangleTemplates.push(mootID)
|
||||||
const tangles = populateTangles(tangleTemplates)
|
const tangles = populateTangles(tangleTemplates)
|
||||||
const accountTangle = new DBTangle(opts.account, records())
|
const accountTangle = new DBTangle(opts.account, records(), get)
|
||||||
const accountTips = [...accountTangle.tips]
|
const accountTips = [...accountTangle.tips]
|
||||||
const fullOpts = { ...opts, tangles, accountTips, keypair }
|
const fullOpts = { ...opts, tangles, accountTips, keypair }
|
||||||
|
|
||||||
|
@ -946,6 +987,7 @@ function initDB(peer, config) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MsgID} msgID
|
* @param {MsgID} msgID
|
||||||
|
* @returns {Msg | undefined}
|
||||||
*/
|
*/
|
||||||
function get(msgID) {
|
function get(msgID) {
|
||||||
return getRecord(msgID)?.msg
|
return getRecord(msgID)?.msg
|
||||||
|
@ -1066,7 +1108,7 @@ function initDB(peer, config) {
|
||||||
* @returns {DBTangle}
|
* @returns {DBTangle}
|
||||||
*/
|
*/
|
||||||
function getTangle(tangleID) {
|
function getTangle(tangleID) {
|
||||||
return new DBTangle(tangleID, records())
|
return new DBTangle(tangleID, records(), get)
|
||||||
}
|
}
|
||||||
|
|
||||||
function* msgs() {
|
function* msgs() {
|
||||||
|
|
|
@ -11,9 +11,15 @@ const Keypair = require('ppppp-keypair')
|
||||||
const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle')
|
const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle')
|
||||||
rimraf.sync(DIR)
|
rimraf.sync(DIR)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* /–-reply1Hi <-\ /--reply3Hi
|
||||||
|
* root <-< >-reply2 <-<
|
||||||
|
* \--reply1Lo <-/ \--reply3Lo
|
||||||
|
*/
|
||||||
test('getTangle()', async (t) => {
|
test('getTangle()', async (t) => {
|
||||||
let peer
|
let peer
|
||||||
let rootPost, reply1Lo, reply1Hi, reply2, reply3Lo, reply3Hi
|
let rootPost, reply1Lo, reply1Hi, reply2, reply3Lo, reply3Hi
|
||||||
|
let reply1LoText, reply1HiText, reply3LoText, reply3HiText
|
||||||
let tangle
|
let tangle
|
||||||
|
|
||||||
// Setup
|
// Setup
|
||||||
|
@ -29,7 +35,10 @@ test('getTangle()', async (t) => {
|
||||||
|
|
||||||
await peer.db.loaded()
|
await peer.db.loaded()
|
||||||
|
|
||||||
const id = await p(peer.db.account.create)({ subdomain: 'person' })
|
const id = await p(peer.db.account.create)({
|
||||||
|
subdomain: 'person',
|
||||||
|
_nonce: 'alice',
|
||||||
|
})
|
||||||
|
|
||||||
// Slow down append so that we can trigger msg creation in parallel
|
// Slow down append so that we can trigger msg creation in parallel
|
||||||
const originalAppend = peer.db._getLog().append
|
const originalAppend = peer.db._getLog().append
|
||||||
|
@ -64,6 +73,8 @@ test('getTangle()', async (t) => {
|
||||||
])
|
])
|
||||||
reply1Lo = reply1B.localeCompare(reply1C) < 0 ? reply1B : reply1C
|
reply1Lo = reply1B.localeCompare(reply1C) < 0 ? reply1B : reply1C
|
||||||
reply1Hi = reply1B.localeCompare(reply1C) < 0 ? reply1C : reply1B
|
reply1Hi = reply1B.localeCompare(reply1C) < 0 ? reply1C : reply1B
|
||||||
|
reply1LoText = reply1B.localeCompare(reply1C) < 0 ? 'reply 1B' : 'reply 1C'
|
||||||
|
reply1HiText = reply1B.localeCompare(reply1C) < 0 ? 'reply 1C' : 'reply 1B'
|
||||||
|
|
||||||
reply2 = (
|
reply2 = (
|
||||||
await p(peer.db.feed.publish)({
|
await p(peer.db.feed.publish)({
|
||||||
|
@ -93,6 +104,8 @@ test('getTangle()', async (t) => {
|
||||||
])
|
])
|
||||||
reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C
|
reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C
|
||||||
reply3Hi = reply3B.localeCompare(reply3C) < 0 ? reply3C : reply3B
|
reply3Hi = reply3B.localeCompare(reply3C) < 0 ? reply3C : reply3B
|
||||||
|
reply3LoText = reply3B.localeCompare(reply3C) < 0 ? 'reply 3B' : 'reply 3C'
|
||||||
|
reply3HiText = reply3B.localeCompare(reply3C) < 0 ? 'reply 3C' : 'reply 3B'
|
||||||
|
|
||||||
tangle = peer.db.getTangle(rootPost)
|
tangle = peer.db.getTangle(rootPost)
|
||||||
}
|
}
|
||||||
|
@ -264,6 +277,47 @@ test('getTangle()', async (t) => {
|
||||||
assert.deepEqual(actual4, expected4)
|
assert.deepEqual(actual4, expected4)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await t.test('Tangle.slice', (t) => {
|
||||||
|
{
|
||||||
|
const msgs = tangle.slice([], [reply2])
|
||||||
|
const texts = msgs.map((msg) => msg.data?.text)
|
||||||
|
assert.deepEqual(texts, ['root', reply1LoText, reply1HiText, 'reply 2'])
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const msgs = tangle.slice([reply2], [])
|
||||||
|
const texts = msgs.map((msg) => msg.data?.text)
|
||||||
|
assert.deepEqual(texts, [
|
||||||
|
undefined, // root
|
||||||
|
undefined, // reply1Lo (no need to have a trail from reply1Hi)
|
||||||
|
'reply 2',
|
||||||
|
reply3LoText,
|
||||||
|
reply3HiText,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const msgs = tangle.slice([reply2], [reply2])
|
||||||
|
const texts = msgs.map((msg) => msg.data?.text)
|
||||||
|
assert.deepEqual(texts, [
|
||||||
|
undefined, // root
|
||||||
|
undefined, // reply1Lo (no need to have a trail from reply1Hi)
|
||||||
|
'reply 2',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const msgs = tangle.slice([reply2], [reply2, reply3Lo])
|
||||||
|
const texts = msgs.map((msg) => msg.data?.text)
|
||||||
|
assert.deepEqual(texts, [
|
||||||
|
undefined, // root
|
||||||
|
undefined, // reply1Lo (no need to have a trail from reply1Hi)
|
||||||
|
'reply 2',
|
||||||
|
reply3LoText,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await t.test('Tangle.topoSort after some deletes and erases', async (t) => {
|
await t.test('Tangle.topoSort after some deletes and erases', async (t) => {
|
||||||
const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo)
|
const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo)
|
||||||
for (const msgID of deletables) {
|
for (const msgID of deletables) {
|
||||||
|
|
Loading…
Reference in New Issue