Skip to content

Commit

Permalink
new API tangle.slice()
Browse files Browse the repository at this point in the history
  • Loading branch information
staltz committed Dec 15, 2023
1 parent f523df3 commit 99c1520
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 9 deletions.
58 changes: 50 additions & 8 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,17 @@ function assertValidConfig(config) {
}

class DBTangle extends MsgV3.Tangle {
/** @type {(msgID: MsgID) => Msg | undefined} */
#getMsg

/**
* @param {MsgID} rootID
* @param {Iterable<Rec>} recordsIter
* @param {(msgID: MsgID) => Msg | undefined} getMsg
*/
constructor(rootID, recordsIter) {
constructor(rootID, recordsIter, getMsg) {
super(rootID)
this.#getMsg = getMsg
for (const rec of recordsIter) {
if (!rec.msg) continue
this.add(rec.id, rec.msg)
Expand Down Expand Up @@ -133,6 +138,42 @@ class DBTangle extends MsgV3.Tangle {

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

/**
Expand Down Expand Up @@ -288,7 +329,7 @@ function initDB(peer, config) {
/** @type {Record<MsgID, DBTangle>} */
const tangles = {}
for (const tangleID of tangleIDs) {
tangles[tangleID] ??= new DBTangle(tangleID, records())
tangles[tangleID] ??= new DBTangle(tangleID, records(), get)
}
return tangles
}
Expand All @@ -301,7 +342,7 @@ function initDB(peer, config) {
const accountID = getAccountID(rec)
let accountTangle = /** @type {Tangle | null} */ (null)
if (accountID) {
accountTangle = new DBTangle(accountID, records())
accountTangle = new DBTangle(accountID, records(), get)
if (rec.id === accountID) {
accountTangle.add(rec.id, rec.msg)
}
Expand Down Expand Up @@ -359,7 +400,7 @@ function initDB(peer, config) {
function verifyRec(rec, tangleID) {
// 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
const tangle = new DBTangle(tangleID, records())
const tangle = new DBTangle(tangleID, records(), get)
if (rec.id === tangleID) {
tangle.add(rec.id, rec.msg)
}
Expand Down Expand Up @@ -560,7 +601,7 @@ function initDB(peer, config) {
function accountHas(opts) {
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()) {
const msg = get(msgID)
if (!msg?.data) continue
Expand Down Expand Up @@ -731,7 +772,7 @@ function initDB(peer, config) {
}

// Verify powers of the signingKeypair:
const accountTangle = new DBTangle(opts.account, records())
const accountTangle = new DBTangle(opts.account, records(), get)
if (obeying) {
const signingPowers = getAccountPowers(accountTangle, signingKeypair)
if (!signingPowers.has('add')) {
Expand Down Expand Up @@ -856,7 +897,7 @@ function initDB(peer, config) {
const tangleTemplates = opts.tangles ?? []
tangleTemplates.push(mootID)
const tangles = populateTangles(tangleTemplates)
const accountTangle = new DBTangle(opts.account, records())
const accountTangle = new DBTangle(opts.account, records(), get)
const accountTips = [...accountTangle.tips]
const fullOpts = { ...opts, tangles, accountTips, keypair }

Expand Down Expand Up @@ -946,6 +987,7 @@ function initDB(peer, config) {

/**
* @param {MsgID} msgID
* @returns {Msg | undefined}
*/
function get(msgID) {
return getRecord(msgID)?.msg
Expand Down Expand Up @@ -1066,7 +1108,7 @@ function initDB(peer, config) {
* @returns {DBTangle}
*/
function getTangle(tangleID) {
return new DBTangle(tangleID, records())
return new DBTangle(tangleID, records(), get)
}

function* msgs() {
Expand Down
56 changes: 55 additions & 1 deletion test/getTangle.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ const Keypair = require('ppppp-keypair')
const DIR = path.join(os.tmpdir(), 'ppppp-db-tangle')
rimraf.sync(DIR)

/**
* /–-reply1Hi <-\ /--reply3Hi
* root <-< >-reply2 <-<
* \--reply1Lo <-/ \--reply3Lo
*/
test('getTangle()', async (t) => {
let peer
let rootPost, reply1Lo, reply1Hi, reply2, reply3Lo, reply3Hi
let reply1LoText, reply1HiText, reply3LoText, reply3HiText
let tangle

// Setup
Expand All @@ -29,7 +35,10 @@ test('getTangle()', async (t) => {

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
const originalAppend = peer.db._getLog().append
Expand Down Expand Up @@ -64,6 +73,8 @@ test('getTangle()', async (t) => {
])
reply1Lo = reply1B.localeCompare(reply1C) < 0 ? reply1B : reply1C
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 = (
await p(peer.db.feed.publish)({
Expand Down Expand Up @@ -93,6 +104,8 @@ test('getTangle()', async (t) => {
])
reply3Lo = reply3B.localeCompare(reply3C) < 0 ? reply3B : reply3C
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)
}
Expand Down Expand Up @@ -264,6 +277,47 @@ test('getTangle()', async (t) => {
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) => {
const { deletables, erasables } = tangle.getDeletablesAndErasables(reply3Lo)
for (const msgID of deletables) {
Expand Down

0 comments on commit 99c1520

Please sign in to comment.