mirror of https://codeberg.org/pzp/pzp-gc.git
support deleting plus ghosting
This commit is contained in:
parent
597f3a3bce
commit
c7dc78b859
24
lib/index.js
24
lib/index.js
|
@ -76,25 +76,37 @@ function initGC(peer, config) {
|
|||
function cleanup(cb) {
|
||||
assertDBExists(peer)
|
||||
assertGoalsExists(peer)
|
||||
debug('cleanup goalless started')
|
||||
debug('cleanup-per-purpose 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)
|
||||
const { id: msgID, msg } = rec
|
||||
const [purpose, details] = peer.goals.getMsgPurpose(msgID, msg)
|
||||
if (purpose === 'none') {
|
||||
peer.db.del(rec.id, done())
|
||||
peer.db.del(msgID, done())
|
||||
waiting = true
|
||||
} else if (purpose === 'ghost') {
|
||||
const { tangleID, span } = details
|
||||
const cb = done()
|
||||
// TODO: Could one msg be a ghostable in MANY tangles? Or just one?
|
||||
peer.db.ghosts.add({ tangleID, msgID, span }, (err) => {
|
||||
// prettier-ignore
|
||||
if (err) return cb(new Error('gc failed to add ghost', { cause: err }))
|
||||
peer.db.del(msgID, cb)
|
||||
})
|
||||
waiting = true
|
||||
} else if (purpose === 'trail') {
|
||||
peer.db.erase(rec.id, done())
|
||||
peer.db.erase(msgID, done())
|
||||
waiting = true
|
||||
}
|
||||
}
|
||||
/** @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')
|
||||
if (err) debug('cleanup-per-purpose ended with an error %s', err.message ?? err)
|
||||
else debug('cleanup-per-purpose ended')
|
||||
cb()
|
||||
}
|
||||
if (waiting) done(whenEnded)
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
"ppppp-db": "github:staltz/ppppp-db",
|
||||
"ppppp-goals": "github:staltz/ppppp-goals",
|
||||
"ppppp-keypair": "github:staltz/ppppp-keypair",
|
||||
"ppppp-record": "github:staltz/ppppp-record",
|
||||
"prettier": "^2.6.2",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^4.4.0",
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
const test = require('node:test')
|
||||
const assert = require('node:assert')
|
||||
const p = require('node:util').promisify
|
||||
const { createPeer } = require('./util')
|
||||
|
||||
function getFields(msgs) {
|
||||
return msgs
|
||||
.map((msg) => msg.data?.update)
|
||||
.filter((x) => !!x)
|
||||
.map((x) => x.age ?? x.name)
|
||||
}
|
||||
|
||||
function isErased(msg) {
|
||||
return !msg.data
|
||||
}
|
||||
|
||||
function isDeleted(msg) {
|
||||
return !msg
|
||||
}
|
||||
|
||||
function isPresent(msg) {
|
||||
return !!msg.data.update
|
||||
}
|
||||
|
||||
test('record ghosts', async (t) => {
|
||||
const alice = createPeer({
|
||||
name: 'alice',
|
||||
gc: { maxLogBytes: 100 * 1024 * 1024 },
|
||||
record: { ghostSpan: 2 },
|
||||
})
|
||||
|
||||
await alice.db.loaded()
|
||||
|
||||
// Alice creates her own account
|
||||
const aliceID = await p(alice.db.account.create)({
|
||||
domain: 'account',
|
||||
_nonce: 'alice',
|
||||
})
|
||||
|
||||
// Alice constructs a record
|
||||
await p(alice.record.load)(aliceID)
|
||||
await p(alice.record.update)('profile', { name: 'alice' })
|
||||
await p(alice.record.update)('profile', { age: 24 })
|
||||
await p(alice.record.update)('profile', { name: 'Alice' })
|
||||
await p(alice.record.update)('profile', { age: 25 })
|
||||
await p(alice.record.update)('profile', { name: 'ALICE' })
|
||||
const recordID = alice.record.getFeedID('profile')
|
||||
|
||||
let mootID
|
||||
let msgID1
|
||||
let msgID2
|
||||
let msgID3
|
||||
let msgID4
|
||||
let msgID5
|
||||
for (const rec of alice.db.records()) {
|
||||
if (rec.msg.metadata.dataSize === 0) mootID = rec.id
|
||||
if (rec.msg.data?.update?.name === 'alice') msgID1 = rec.id
|
||||
if (rec.msg.data?.update?.age === 24) msgID2 = rec.id
|
||||
if (rec.msg.data?.update?.name === 'Alice') msgID3 = rec.id
|
||||
if (rec.msg.data?.update?.age === 25) msgID4 = rec.id
|
||||
if (rec.msg.data?.update?.name === 'ALICE') msgID5 = rec.id
|
||||
}
|
||||
|
||||
// Assert situation before GC
|
||||
assert.deepEqual(
|
||||
getFields([...alice.db.msgs()]),
|
||||
['alice', 24, 'Alice', 25, 'ALICE'],
|
||||
'has all record msgs'
|
||||
)
|
||||
assert.ok(isErased(alice.db.get(mootID)), 'moot by def erased')
|
||||
assert.ok(isPresent(alice.db.get(msgID1)), 'msg1 exists')
|
||||
assert.ok(isPresent(alice.db.get(msgID2)), 'msg2 exists')
|
||||
assert.ok(isPresent(alice.db.get(msgID3)), 'msg3 exists')
|
||||
assert.ok(isPresent(alice.db.get(msgID4)), 'msg4 exists')
|
||||
assert.ok(isPresent(alice.db.get(msgID5)), 'msg5 exists')
|
||||
|
||||
// Perform garbage collection
|
||||
alice.goals.set(aliceID, 'all')
|
||||
alice.goals.set(recordID, 'record')
|
||||
await p(alice.gc.forceImmediately)()
|
||||
|
||||
// Assert situation after GC
|
||||
assert.deepEqual(
|
||||
getFields([...alice.db.msgs()]),
|
||||
[25, 'ALICE'],
|
||||
'alice has only field root msgs'
|
||||
)
|
||||
|
||||
assert.ok(isErased(alice.db.get(mootID)), 'moot by def erased')
|
||||
assert.ok(isDeleted(alice.db.get(msgID1)), 'msg1 deleted')
|
||||
assert.ok(isDeleted(alice.db.get(msgID2)), 'msg2 deleted') // ghost!
|
||||
assert.ok(isErased(alice.db.get(msgID3)), 'msg3 erased')
|
||||
assert.ok(isPresent(alice.db.get(msgID4)), 'msg4 exists')
|
||||
assert.ok(isPresent(alice.db.get(msgID5)), 'msg5 exists')
|
||||
|
||||
assert.deepEqual(alice.db.ghosts.get(recordID), [msgID2])
|
||||
|
||||
await p(alice.close)(true)
|
||||
})
|
|
@ -18,6 +18,7 @@ function createPeer(opts) {
|
|||
.use(require('secret-stack/plugins/net'))
|
||||
.use(require('secret-handshake-ext/secret-stack'))
|
||||
.use(require('ppppp-db'))
|
||||
.use(require('ppppp-record'))
|
||||
.use(require('ppppp-goals'))
|
||||
.use(require('ssb-box'))
|
||||
.use(require('../lib'))
|
||||
|
|
Loading…
Reference in New Issue