mirror of https://codeberg.org/pzp/pzp-goals.git
Rename to pzp
This commit is contained in:
parent
46a8d8889c
commit
94b3ca3a3f
|
@ -1,25 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm test
|
|
@ -0,0 +1,13 @@
|
|||
matrix:
|
||||
NODE_VERSION:
|
||||
- 18
|
||||
- 20
|
||||
|
||||
steps:
|
||||
test:
|
||||
when:
|
||||
event: [push]
|
||||
image: node:${NODE_VERSION}
|
||||
commands:
|
||||
- npm install
|
||||
- npm test
|
|
@ -1,9 +1,7 @@
|
|||
**Work in progress**
|
||||
# pzp-goals
|
||||
|
||||
## Installation
|
||||
|
||||
We're not on npm yet. In your package.json, include this as
|
||||
|
||||
```js
|
||||
"ppppp-goals": "github:staltz/ppppp-goals"
|
||||
```
|
||||
npm install pzp-goals
|
||||
```
|
||||
|
|
185
lib/index.js
185
lib/index.js
|
@ -1,14 +1,24 @@
|
|||
// @ts-ignore
|
||||
const Obz = require('obz')
|
||||
// @ts-ignore
|
||||
const p = (fn) => (...args) => {
|
||||
return new Promise((res, rej) => {
|
||||
// @ts-ignore
|
||||
fn(...args, (err, val) => {
|
||||
if (err) return rej(err)
|
||||
return res(val)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {ReturnType<import('ppppp-db').init>} PPPPPDB
|
||||
* @typedef {ReturnType<import('ppppp-dict').init>} PPPPPDict
|
||||
* @typedef {ReturnType<import('ppppp-set').init>} PPPPPSet
|
||||
* @typedef {import('ppppp-db').RecPresent} RecPresent
|
||||
* @typedef {import('ppppp-db').Tangle} Tangle
|
||||
* @typedef {import('ppppp-db').Msg} Msg
|
||||
* @typedef {NonNullable<ReturnType<PPPPPDB['getTangle']>>} DBTangle
|
||||
* @typedef {ReturnType<import('pzp-db').init>} PZPDB
|
||||
* @typedef {ReturnType<import('pzp-dict').init>} PZPDict
|
||||
* @typedef {ReturnType<import('pzp-set').init>} PZPSet
|
||||
* @typedef {import('pzp-db').RecPresent} RecPresent
|
||||
* @typedef {import('pzp-db').Tangle} Tangle
|
||||
* @typedef {import('pzp-db').Msg} Msg
|
||||
* @typedef {NonNullable<ReturnType<PZPDB['getTangle']>>} DBTangle
|
||||
* @typedef {string} MsgID
|
||||
* @typedef {'none'|'all'|`newest-${number}`|'dict'|'set'} GoalDSL
|
||||
* @typedef {'none'|'all'|'newest'|'dict'|'set'} GoalType
|
||||
|
@ -19,7 +29,10 @@ const Obz = require('obz')
|
|||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {(...args: [Error] | [null, T]) => void } CB
|
||||
* @typedef {[T] extends [void] ?
|
||||
* (...args: [Error] | []) => void :
|
||||
* (...args: [Error] | [null, T]) => void
|
||||
* } CB
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -117,7 +130,7 @@ class GoalImpl {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {{ db: PPPPPDB, dict: PPPPPDict, set: PPPPPSet }} peer
|
||||
* @param {{ db: PZPDB, dict: PZPDict, set: PZPSet }} peer
|
||||
* @param {unknown} config
|
||||
*/
|
||||
function initGoals(peer, config) {
|
||||
|
@ -142,31 +155,35 @@ function initGoals(peer, config) {
|
|||
* @private
|
||||
* @param {Goal} goal
|
||||
* @param {Tangle} tangle
|
||||
* @returns {Range}
|
||||
* @param {CB<Range>} cb
|
||||
*/
|
||||
function crossGoalWithTangle(goal, tangle) {
|
||||
function crossGoalWithTangle(goal, tangle, cb) {
|
||||
const maxDepth = tangle.maxDepth
|
||||
switch (goal.type) {
|
||||
case 'newest':
|
||||
const start = Math.max(0, maxDepth - goal.count + 1)
|
||||
return [start, maxDepth]
|
||||
return cb(null, [start, maxDepth])
|
||||
|
||||
case 'all':
|
||||
return [0, maxDepth]
|
||||
return cb(null, [0, maxDepth])
|
||||
|
||||
case 'set':
|
||||
const minSetDepth = peer.set.minRequiredDepth(goal.id)
|
||||
return [minSetDepth, maxDepth]
|
||||
return peer.set.minRequiredDepth(goal.id, (err, minSetDepth) => {
|
||||
if (err) return cb(err)
|
||||
return cb(null, [minSetDepth, maxDepth])
|
||||
})
|
||||
|
||||
case 'dict':
|
||||
const minDictDepth = peer.dict.minRequiredDepth(goal.id)
|
||||
return [minDictDepth, maxDepth]
|
||||
return peer.dict.minRequiredDepth(goal.id, (err, minDictDepth) => {
|
||||
if (err) return cb(err)
|
||||
return cb(null, [minDictDepth, maxDepth])
|
||||
})
|
||||
|
||||
case 'none':
|
||||
return EMPTY_RANGE
|
||||
return cb(null, EMPTY_RANGE)
|
||||
|
||||
default:
|
||||
throw new Error(`Unrecognized goal type: ${goal.type}`)
|
||||
return cb(Error(`Unrecognized goal type: ${goal.type}`))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,73 +242,87 @@ function initGoals(peer, config) {
|
|||
* @public
|
||||
* @param {MsgID} msgID
|
||||
* @param {Msg} msg
|
||||
* @returns {PurposeWithDetails}
|
||||
* @param {CB<PurposeWithDetails>} cb
|
||||
*/
|
||||
function getMsgPurpose(msgID, msg) {
|
||||
function getMsgPurpose(msgID, msg, cb) {
|
||||
let servesAsTrail = false
|
||||
|
||||
// Check whether this msg is a goalful root of some tangle:
|
||||
asRoot: if (goals.has(msgID)) {
|
||||
const goal = /** @type {GoalImpl} */ (goals.get(msgID))
|
||||
if (goal.type === 'none') break asRoot
|
||||
const tangle = peer.db.getTangle(msgID)
|
||||
if (!tangle) break asRoot
|
||||
const range = crossGoalWithTangle(goal, tangle)
|
||||
if (isEmptyRange(range)) break asRoot
|
||||
const [min] = range
|
||||
if (min === 0) return ['goal']
|
||||
if (min > 0) servesAsTrail = true
|
||||
}
|
||||
/** @return {Promise<PurposeWithDetails>} */
|
||||
async function getPurpose() {
|
||||
// Check whether this msg is a goalful root of some tangle:
|
||||
asRoot: if (goals.has(msgID)) {
|
||||
const goal = /** @type {GoalImpl} */ (goals.get(msgID))
|
||||
if (goal.type === 'none') break asRoot
|
||||
const tangle = await p(peer.db.getTangle)(msgID)
|
||||
if (!tangle) break asRoot
|
||||
const range = await p(crossGoalWithTangle)(goal, tangle)
|
||||
if (isEmptyRange(range)) break asRoot
|
||||
const [min] = range
|
||||
if (min === 0) return ['goal']
|
||||
if (min > 0) servesAsTrail = true
|
||||
}
|
||||
|
||||
// Check whether this msg is a goalful affix of some tangle:
|
||||
const validTangles =
|
||||
/** @type {Array<[DBTangle, number, number, number, GoalType]>} */ ([])
|
||||
asAffix: for (const tangleID in 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(msgID)
|
||||
if (recDepth < 0) continue asAffix
|
||||
validTangles.push([tangle, min, max, recDepth, goal.type])
|
||||
}
|
||||
// (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 msg *cannot* serve as 'goal',
|
||||
// so if it serves as trail, that'll do:
|
||||
if (servesAsTrail) return ['trail']
|
||||
// Check whether this msg 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(msgID)) return ['trail']
|
||||
}
|
||||
// Check whether this msg is a goalful affix of some tangle:
|
||||
const validTangles =
|
||||
/** @type {Array<[DBTangle, number, number, number, GoalType]>} */ ([])
|
||||
asAffix: for (const tangleID in msg.metadata.tangles) {
|
||||
if (!goals.has(tangleID)) continue asAffix
|
||||
const goal = /** @type {GoalImpl} */ (goals.get(tangleID))
|
||||
if (goal.type === 'none') continue asAffix
|
||||
const tangle = await p(peer.db.getTangle)(tangleID)
|
||||
if (!tangle) continue asAffix
|
||||
const [min, max] = await p(crossGoalWithTangle)(goal, tangle)
|
||||
if (min > max) continue asAffix
|
||||
const recDepth = tangle.getDepth(msgID)
|
||||
if (recDepth < 0) continue asAffix
|
||||
validTangles.push([tangle, min, max, recDepth, goal.type])
|
||||
}
|
||||
// (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 msg *cannot* serve as 'goal',
|
||||
// so if it serves as trail, that'll do:
|
||||
if (servesAsTrail) return ['trail']
|
||||
// Check whether this msg 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
|
||||
// @ts-ignore
|
||||
.topoSort()
|
||||
// @ts-ignore
|
||||
.filter((msgID) => tangle.getDepth(msgID) === min)
|
||||
// @ts-ignore
|
||||
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
|
||||
if (erasables.has(msgID)) return ['trail']
|
||||
}
|
||||
|
||||
// Check whether this msg is a ghost affix of some tangle:
|
||||
for (const [tangle, min, max, recDepth, goalType] of validTangles) {
|
||||
if (goalType === 'dict') {
|
||||
const span = peer.dict.getGhostSpan()
|
||||
if (peer.dict.isGhostable(msgID, tangle.id)) {
|
||||
return ['ghost', { tangleID: tangle.id, span }]
|
||||
}
|
||||
}
|
||||
if (goalType === 'set') {
|
||||
const span = peer.set.getGhostSpan()
|
||||
if (peer.set.isGhostable(msgID, tangle.id)) {
|
||||
return ['ghost', { tangleID: tangle.id, span }]
|
||||
// Check whether this msg is a ghost affix of some tangle:
|
||||
for (const [tangle, min, max, recDepth, goalType] of validTangles) {
|
||||
if (goalType === 'dict') {
|
||||
const span = peer.dict.getGhostSpan()
|
||||
// @ts-ignore
|
||||
if (await p(peer.dict.isGhostable)(msgID, tangle.id)) {
|
||||
// @ts-ignore
|
||||
return ['ghost', { tangleID: tangle.id, span }]
|
||||
}
|
||||
}
|
||||
if (goalType === 'set') {
|
||||
const span = peer.set.getGhostSpan()
|
||||
// @ts-ignore
|
||||
if (await p(peer.set.isGhostable)(msgID, tangle.id)) {
|
||||
// @ts-ignore
|
||||
return ['ghost', { tangleID: tangle.id, span }]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['none']
|
||||
}
|
||||
|
||||
return ['none']
|
||||
getPurpose()
|
||||
.then(purpose => cb(null, purpose))
|
||||
.catch(err => cb(err))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
20
package.json
20
package.json
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"name": "ppppp-goals",
|
||||
"version": "1.0.0",
|
||||
"description": "PPPPP tracker of replication goals",
|
||||
"name": "pzp-goals",
|
||||
"version": "0.0.1",
|
||||
"description": "PZP tracker of replication goals",
|
||||
"author": "Andre Staltz <contact@staltz.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/staltz/ppppp-goals",
|
||||
"homepage": "https://codeberg.org/pzp/pzp-goals",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:staltz/ppppp-goals.git"
|
||||
"url": "git@codeberg.org:pzp/pzp-goals.git"
|
||||
},
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
|
@ -32,11 +32,11 @@
|
|||
"devDependencies": {
|
||||
"bs58": "^5.0.0",
|
||||
"c8": "7",
|
||||
"ppppp-caps": "github:staltz/ppppp-caps#93fa810b9a40b78aef4872d4c2a8412cccb52929",
|
||||
"ppppp-db": "github:staltz/ppppp-db#667b33779d98aff12a9b0cd2d7c80469a95cd04e",
|
||||
"ppppp-dict": "github:staltz/ppppp-dict#6f0ff4e3383a8c18b766949f6db9b51460ecb640",
|
||||
"ppppp-keypair": "github:staltz/ppppp-keypair#61ef4420578f450dc2cc7b1efc1c5a691a871c74",
|
||||
"ppppp-set": "github:staltz/ppppp-set#8983ba29f03db95a76b4bd9a55aa4392b350fdbb",
|
||||
"pzp-caps": "^1.0.0",
|
||||
"pzp-db": "^1.0.1",
|
||||
"pzp-dict": "^1.0.0",
|
||||
"pzp-keypair": "^1.0.0",
|
||||
"pzp-set": "^1.0.0",
|
||||
"prettier": "^2.6.2",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rimraf": "^4.4.0",
|
||||
|
|
|
@ -31,7 +31,7 @@ test('set, getByID, list, watch', async (t) => {
|
|||
subdomain: 'account',
|
||||
_nonce: 'alice',
|
||||
})
|
||||
const aliceAccountRoot = alice.db.getRecord(aliceID)
|
||||
const aliceAccountRoot = await p(alice.db.getRecord)(aliceID)
|
||||
|
||||
const watched = []
|
||||
const stopListening = alice.goals.watch((goal) => {
|
||||
|
@ -53,7 +53,7 @@ test('set, getByID, list, watch', async (t) => {
|
|||
}
|
||||
|
||||
{
|
||||
const [purpose] = alice.goals.getMsgPurpose(
|
||||
const [purpose] = await p(alice.goals.getMsgPurpose)(
|
||||
aliceAccountRoot.id,
|
||||
aliceAccountRoot.msg
|
||||
)
|
||||
|
@ -113,12 +113,12 @@ test('getMsgPurpose', async (t) => {
|
|||
const gottenGoal = alice.goals.get(feedID)
|
||||
assert.strictEqual(gottenGoal.id, feedID, 'gotten goal id is correct')
|
||||
|
||||
const [purpose] = alice.goals.getMsgPurpose(post2.id, post2.msg)
|
||||
const [purpose] = await p(alice.goals.getMsgPurpose)(post2.id, post2.msg)
|
||||
assert.equal(purpose, 'goal', 'purpose is "goal"')
|
||||
|
||||
alice.goals.set(feedID, 'newest-1')
|
||||
assert('set goal to newest-1')
|
||||
const [purpose2] = alice.goals.getMsgPurpose(post2.id, post2.msg)
|
||||
const [purpose2] = await p(alice.goals.getMsgPurpose)(post2.id, post2.msg)
|
||||
assert.equal(purpose2, 'none', 'purpose2 is "none"')
|
||||
|
||||
await p(alice.close)(true)
|
||||
|
@ -141,20 +141,20 @@ test('getMsgPurpose ghost', async (t) => {
|
|||
await p(alice.dict.update)('profile', { name: 'ALICIAA' })
|
||||
|
||||
const feedID = alice.dict.getFeedID('profile')
|
||||
const tangle = alice.db.getTangle(feedID)
|
||||
const tangle = await p(alice.db.getTangle)(feedID)
|
||||
|
||||
const msgIDs = tangle.topoSort()
|
||||
assert.equal(msgIDs.length, 6, 'tangle has root+5 messages')
|
||||
const recs = msgIDs.map((id) => alice.db.getRecord(id))
|
||||
const recs = await Promise.all(msgIDs.map((id) =>p(alice.db.getRecord)(id)))
|
||||
|
||||
alice.goals.set(feedID, 'dict')
|
||||
assert.equal(alice.goals.getMsgPurpose(recs[1].id, recs[1].msg)[0], 'none')
|
||||
assert.equal(alice.goals.getMsgPurpose(recs[2].id, recs[2].msg)[0], 'ghost')
|
||||
assert.equal(alice.goals.getMsgPurpose(recs[3].id, recs[3].msg)[0], 'trail')
|
||||
assert.equal(alice.goals.getMsgPurpose(recs[4].id, recs[4].msg)[0], 'trail')
|
||||
assert.equal(alice.goals.getMsgPurpose(recs[5].id, recs[5].msg)[0], 'goal')
|
||||
assert.equal((await p(alice.goals.getMsgPurpose)(recs[1].id, recs[1].msg))[0], 'none')
|
||||
assert.equal((await p(alice.goals.getMsgPurpose)(recs[2].id, recs[2].msg))[0], 'ghost')
|
||||
assert.equal((await p(alice.goals.getMsgPurpose)(recs[3].id, recs[3].msg))[0], 'trail')
|
||||
assert.equal((await p(alice.goals.getMsgPurpose)(recs[4].id, recs[4].msg))[0], 'trail')
|
||||
assert.equal((await p(alice.goals.getMsgPurpose)(recs[5].id, recs[5].msg))[0], 'goal')
|
||||
|
||||
const [purpose, details] = alice.goals.getMsgPurpose(recs[2].id, recs[2].msg)
|
||||
const [purpose, details] = await p(alice.goals.getMsgPurpose)(recs[2].id, recs[2].msg)
|
||||
assert.equal(purpose, 'ghost')
|
||||
assert.deepEqual(details, { tangleID: feedID, span: 3 })
|
||||
|
||||
|
|
12
test/util.js
12
test/util.js
|
@ -1,15 +1,15 @@
|
|||
const OS = require('node:os')
|
||||
const Path = require('node:path')
|
||||
const rimraf = require('rimraf')
|
||||
const caps = require('ppppp-caps')
|
||||
const Keypair = require('ppppp-keypair')
|
||||
const caps = require('pzp-caps')
|
||||
const Keypair = require('pzp-keypair')
|
||||
|
||||
function createPeer(config) {
|
||||
if (config.name) {
|
||||
const name = config.name
|
||||
const tmp = OS.tmpdir()
|
||||
config.global ??= {}
|
||||
config.global.path ??= Path.join(tmp, `ppppp-goals-${name}-${Date.now()}`)
|
||||
config.global.path ??= Path.join(tmp, `pzp-goals-${name}-${Date.now()}`)
|
||||
config.global.keypair ??= Keypair.generate('ed25519', name)
|
||||
delete config.name
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ function createPeer(config) {
|
|||
return require('secret-stack/bare')()
|
||||
.use(require('secret-stack/plugins/net'))
|
||||
.use(require('secret-handshake-ext/secret-stack'))
|
||||
.use(require('ppppp-db'))
|
||||
.use(require('ppppp-dict'))
|
||||
.use(require('ppppp-set'))
|
||||
.use(require('pzp-db'))
|
||||
.use(require('pzp-dict'))
|
||||
.use(require('pzp-set'))
|
||||
.use(require('ssb-box'))
|
||||
.use(require('../lib'))
|
||||
.call(null, {
|
||||
|
|
Loading…
Reference in New Issue