Merge pull request 'Rename to pzp, get ready for publish, and use async db functions' (#1) from async-db into master

Reviewed-on: https://codeberg.org/pzp/pzp-goals/pulls/1
This commit is contained in:
Powersource 2024-04-30 14:39:28 +00:00
commit 82d6f94a81
7 changed files with 152 additions and 135 deletions

View File

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

13
.woodpecker.yaml Normal file
View File

@ -0,0 +1,13 @@
matrix:
NODE_VERSION:
- 18
- 20
steps:
test:
when:
event: [push]
image: node:${NODE_VERSION}
commands:
- npm install
- npm test

View File

@ -1,9 +1,7 @@
**Work in progress** # pzp-goals
## Installation ## Installation
We're not on npm yet. In your package.json, include this as ```
npm install pzp-goals
```js
"ppppp-goals": "github:staltz/ppppp-goals"
``` ```

View File

@ -1,14 +1,24 @@
// @ts-ignore // @ts-ignore
const Obz = require('obz') 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('pzp-db').init>} PZPDB
* @typedef {ReturnType<import('ppppp-dict').init>} PPPPPDict * @typedef {ReturnType<import('pzp-dict').init>} PZPDict
* @typedef {ReturnType<import('ppppp-set').init>} PPPPPSet * @typedef {ReturnType<import('pzp-set').init>} PZPSet
* @typedef {import('ppppp-db').RecPresent} RecPresent * @typedef {import('pzp-db').RecPresent} RecPresent
* @typedef {import('ppppp-db').Tangle} Tangle * @typedef {import('pzp-db').Tangle} Tangle
* @typedef {import('ppppp-db').Msg} Msg * @typedef {import('pzp-db').Msg} Msg
* @typedef {NonNullable<ReturnType<PPPPPDB['getTangle']>>} DBTangle * @typedef {NonNullable<ReturnType<PZPDB['getTangle']>>} DBTangle
* @typedef {string} MsgID * @typedef {string} MsgID
* @typedef {'none'|'all'|`newest-${number}`|'dict'|'set'} GoalDSL * @typedef {'none'|'all'|`newest-${number}`|'dict'|'set'} GoalDSL
* @typedef {'none'|'all'|'newest'|'dict'|'set'} GoalType * @typedef {'none'|'all'|'newest'|'dict'|'set'} GoalType
@ -19,7 +29,10 @@ const Obz = require('obz')
/** /**
* @template T * @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 * @param {unknown} config
*/ */
function initGoals(peer, config) { function initGoals(peer, config) {
@ -142,31 +155,35 @@ function initGoals(peer, config) {
* @private * @private
* @param {Goal} goal * @param {Goal} goal
* @param {Tangle} tangle * @param {Tangle} tangle
* @returns {Range} * @param {CB<Range>} cb
*/ */
function crossGoalWithTangle(goal, tangle) { function crossGoalWithTangle(goal, tangle, cb) {
const maxDepth = tangle.maxDepth const maxDepth = tangle.maxDepth
switch (goal.type) { switch (goal.type) {
case 'newest': case 'newest':
const start = Math.max(0, maxDepth - goal.count + 1) const start = Math.max(0, maxDepth - goal.count + 1)
return [start, maxDepth] return cb(null, [start, maxDepth])
case 'all': case 'all':
return [0, maxDepth] return cb(null, [0, maxDepth])
case 'set': case 'set':
const minSetDepth = peer.set.minRequiredDepth(goal.id) return peer.set.minRequiredDepth(goal.id, (err, minSetDepth) => {
return [minSetDepth, maxDepth] if (err) return cb(err)
return cb(null, [minSetDepth, maxDepth])
})
case 'dict': case 'dict':
const minDictDepth = peer.dict.minRequiredDepth(goal.id) return peer.dict.minRequiredDepth(goal.id, (err, minDictDepth) => {
return [minDictDepth, maxDepth] if (err) return cb(err)
return cb(null, [minDictDepth, maxDepth])
})
case 'none': case 'none':
return EMPTY_RANGE return cb(null, EMPTY_RANGE)
default: default:
throw new Error(`Unrecognized goal type: ${goal.type}`) return cb(Error(`Unrecognized goal type: ${goal.type}`))
} }
} }
@ -225,18 +242,20 @@ function initGoals(peer, config) {
* @public * @public
* @param {MsgID} msgID * @param {MsgID} msgID
* @param {Msg} msg * @param {Msg} msg
* @returns {PurposeWithDetails} * @param {CB<PurposeWithDetails>} cb
*/ */
function getMsgPurpose(msgID, msg) { function getMsgPurpose(msgID, msg, cb) {
let servesAsTrail = false let servesAsTrail = false
/** @return {Promise<PurposeWithDetails>} */
async function getPurpose() {
// Check whether this msg is a goalful root of some tangle: // Check whether this msg is a goalful root of some tangle:
asRoot: if (goals.has(msgID)) { asRoot: if (goals.has(msgID)) {
const goal = /** @type {GoalImpl} */ (goals.get(msgID)) const goal = /** @type {GoalImpl} */ (goals.get(msgID))
if (goal.type === 'none') break asRoot if (goal.type === 'none') break asRoot
const tangle = peer.db.getTangle(msgID) const tangle = await p(peer.db.getTangle)(msgID)
if (!tangle) break asRoot if (!tangle) break asRoot
const range = crossGoalWithTangle(goal, tangle) const range = await p(crossGoalWithTangle)(goal, tangle)
if (isEmptyRange(range)) break asRoot if (isEmptyRange(range)) break asRoot
const [min] = range const [min] = range
if (min === 0) return ['goal'] if (min === 0) return ['goal']
@ -250,9 +269,9 @@ function initGoals(peer, config) {
if (!goals.has(tangleID)) continue asAffix if (!goals.has(tangleID)) continue asAffix
const goal = /** @type {GoalImpl} */ (goals.get(tangleID)) const goal = /** @type {GoalImpl} */ (goals.get(tangleID))
if (goal.type === 'none') continue asAffix if (goal.type === 'none') continue asAffix
const tangle = peer.db.getTangle(tangleID) const tangle = await p(peer.db.getTangle)(tangleID)
if (!tangle) continue asAffix if (!tangle) continue asAffix
const [min, max] = crossGoalWithTangle(goal, tangle) const [min, max] = await p(crossGoalWithTangle)(goal, tangle)
if (min > max) continue asAffix if (min > max) continue asAffix
const recDepth = tangle.getDepth(msgID) const recDepth = tangle.getDepth(msgID)
if (recDepth < 0) continue asAffix if (recDepth < 0) continue asAffix
@ -267,10 +286,13 @@ function initGoals(peer, config) {
if (servesAsTrail) return ['trail'] if (servesAsTrail) return ['trail']
// Check whether this msg is a trail affix of some tangle: // Check whether this msg 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) {
const minMsgIDs = tangle const minMsgIDs = tangle
// @ts-ignore
.topoSort() .topoSort()
// @ts-ignore
.filter((msgID) => tangle.getDepth(msgID) === min) .filter((msgID) => tangle.getDepth(msgID) === min)
// @ts-ignore
const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs) const { erasables } = tangle.getDeletablesAndErasables(...minMsgIDs)
if (erasables.has(msgID)) return ['trail'] if (erasables.has(msgID)) return ['trail']
} }
@ -279,13 +301,17 @@ function initGoals(peer, config) {
for (const [tangle, min, max, recDepth, goalType] of validTangles) { for (const [tangle, min, max, recDepth, goalType] of validTangles) {
if (goalType === 'dict') { if (goalType === 'dict') {
const span = peer.dict.getGhostSpan() const span = peer.dict.getGhostSpan()
if (peer.dict.isGhostable(msgID, tangle.id)) { // @ts-ignore
if (await p(peer.dict.isGhostable)(msgID, tangle.id)) {
// @ts-ignore
return ['ghost', { tangleID: tangle.id, span }] return ['ghost', { tangleID: tangle.id, span }]
} }
} }
if (goalType === 'set') { if (goalType === 'set') {
const span = peer.set.getGhostSpan() const span = peer.set.getGhostSpan()
if (peer.set.isGhostable(msgID, tangle.id)) { // @ts-ignore
if (await p(peer.set.isGhostable)(msgID, tangle.id)) {
// @ts-ignore
return ['ghost', { tangleID: tangle.id, span }] return ['ghost', { tangleID: tangle.id, span }]
} }
} }
@ -294,6 +320,11 @@ function initGoals(peer, config) {
return ['none'] return ['none']
} }
getPurpose()
.then(purpose => cb(null, purpose))
.catch(err => cb(err))
}
/** /**
* @public * @public
* @returns {IterableIterator<Goal>} * @returns {IterableIterator<Goal>}

View File

@ -1,13 +1,13 @@
{ {
"name": "ppppp-goals", "name": "pzp-goals",
"version": "1.0.0", "version": "0.0.1",
"description": "PPPPP tracker of replication goals", "description": "PZP tracker of replication goals",
"author": "Andre Staltz <contact@staltz.com>", "author": "Andre Staltz <contact@staltz.com>",
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/staltz/ppppp-goals", "homepage": "https://codeberg.org/pzp/pzp-goals",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@github.com:staltz/ppppp-goals.git" "url": "git@codeberg.org:pzp/pzp-goals.git"
}, },
"main": "index.js", "main": "index.js",
"files": [ "files": [
@ -32,11 +32,11 @@
"devDependencies": { "devDependencies": {
"bs58": "^5.0.0", "bs58": "^5.0.0",
"c8": "7", "c8": "7",
"ppppp-caps": "github:staltz/ppppp-caps#93fa810b9a40b78aef4872d4c2a8412cccb52929", "pzp-caps": "^1.0.0",
"ppppp-db": "github:staltz/ppppp-db#667b33779d98aff12a9b0cd2d7c80469a95cd04e", "pzp-db": "^1.0.1",
"ppppp-dict": "github:staltz/ppppp-dict#6f0ff4e3383a8c18b766949f6db9b51460ecb640", "pzp-dict": "^1.0.0",
"ppppp-keypair": "github:staltz/ppppp-keypair#61ef4420578f450dc2cc7b1efc1c5a691a871c74", "pzp-keypair": "^1.0.0",
"ppppp-set": "github:staltz/ppppp-set#8983ba29f03db95a76b4bd9a55aa4392b350fdbb", "pzp-set": "^1.0.0",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.1.3",
"rimraf": "^4.4.0", "rimraf": "^4.4.0",

View File

@ -31,7 +31,7 @@ test('set, getByID, list, watch', async (t) => {
subdomain: 'account', subdomain: 'account',
_nonce: 'alice', _nonce: 'alice',
}) })
const aliceAccountRoot = alice.db.getRecord(aliceID) const aliceAccountRoot = await p(alice.db.getRecord)(aliceID)
const watched = [] const watched = []
const stopListening = alice.goals.watch((goal) => { 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.id,
aliceAccountRoot.msg aliceAccountRoot.msg
) )
@ -113,12 +113,12 @@ test('getMsgPurpose', 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.getMsgPurpose(post2.id, post2.msg) const [purpose] = await p(alice.goals.getMsgPurpose)(post2.id, post2.msg)
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.getMsgPurpose(post2.id, post2.msg) const [purpose2] = await p(alice.goals.getMsgPurpose)(post2.id, post2.msg)
assert.equal(purpose2, 'none', 'purpose2 is "none"') assert.equal(purpose2, 'none', 'purpose2 is "none"')
await p(alice.close)(true) await p(alice.close)(true)
@ -141,20 +141,20 @@ test('getMsgPurpose ghost', async (t) => {
await p(alice.dict.update)('profile', { name: 'ALICIAA' }) await p(alice.dict.update)('profile', { name: 'ALICIAA' })
const feedID = alice.dict.getFeedID('profile') const feedID = alice.dict.getFeedID('profile')
const tangle = alice.db.getTangle(feedID) const tangle = await p(alice.db.getTangle)(feedID)
const msgIDs = tangle.topoSort() const msgIDs = tangle.topoSort()
assert.equal(msgIDs.length, 6, 'tangle has root+5 messages') 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') alice.goals.set(feedID, 'dict')
assert.equal(alice.goals.getMsgPurpose(recs[1].id, recs[1].msg)[0], 'none') assert.equal((await p(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((await p(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((await p(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((await p(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[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.equal(purpose, 'ghost')
assert.deepEqual(details, { tangleID: feedID, span: 3 }) assert.deepEqual(details, { tangleID: feedID, span: 3 })

View File

@ -1,15 +1,15 @@
const OS = require('node:os') const OS = require('node:os')
const Path = require('node:path') const Path = require('node:path')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const caps = require('ppppp-caps') const caps = require('pzp-caps')
const Keypair = require('ppppp-keypair') const Keypair = require('pzp-keypair')
function createPeer(config) { function createPeer(config) {
if (config.name) { if (config.name) {
const name = config.name const name = config.name
const tmp = OS.tmpdir() const tmp = OS.tmpdir()
config.global ??= {} 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) config.global.keypair ??= Keypair.generate('ed25519', name)
delete config.name delete config.name
} }
@ -27,9 +27,9 @@ function createPeer(config) {
return require('secret-stack/bare')() return require('secret-stack/bare')()
.use(require('secret-stack/plugins/net')) .use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack')) .use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-db')) .use(require('pzp-db'))
.use(require('ppppp-dict')) .use(require('pzp-dict'))
.use(require('ppppp-set')) .use(require('pzp-set'))
.use(require('ssb-box')) .use(require('ssb-box'))
.use(require('../lib')) .use(require('../lib'))
.call(null, { .call(null, {