From 94b3ca3a3f49cbc9d2696ad09db7d3d38552f12d Mon Sep 17 00:00:00 2001 From: Jacob Karlsson Date: Tue, 30 Apr 2024 16:37:37 +0200 Subject: [PATCH] Rename to pzp --- .github/workflows/node.js.yml | 25 ----- .woodpecker.yaml | 13 +++ README.md | 8 +- lib/index.js | 185 ++++++++++++++++++++-------------- package.json | 20 ++-- test/goals.test.js | 24 ++--- test/util.js | 12 +-- 7 files changed, 152 insertions(+), 135 deletions(-) delete mode 100644 .github/workflows/node.js.yml create mode 100644 .woodpecker.yaml diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index b3558e2..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -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 diff --git a/.woodpecker.yaml b/.woodpecker.yaml new file mode 100644 index 0000000..d254f8d --- /dev/null +++ b/.woodpecker.yaml @@ -0,0 +1,13 @@ +matrix: + NODE_VERSION: + - 18 + - 20 + +steps: + test: + when: + event: [push] + image: node:${NODE_VERSION} + commands: + - npm install + - npm test \ No newline at end of file diff --git a/README.md b/README.md index 3f8d48f..f68b602 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/lib/index.js b/lib/index.js index d229d68..5a9e9c5 100644 --- a/lib/index.js +++ b/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} PPPPPDB - * @typedef {ReturnType} PPPPPDict - * @typedef {ReturnType} PPPPPSet - * @typedef {import('ppppp-db').RecPresent} RecPresent - * @typedef {import('ppppp-db').Tangle} Tangle - * @typedef {import('ppppp-db').Msg} Msg - * @typedef {NonNullable>} DBTangle + * @typedef {ReturnType} PZPDB + * @typedef {ReturnType} PZPDict + * @typedef {ReturnType} PZPSet + * @typedef {import('pzp-db').RecPresent} RecPresent + * @typedef {import('pzp-db').Tangle} Tangle + * @typedef {import('pzp-db').Msg} Msg + * @typedef {NonNullable>} 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} 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} 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} */ + 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)) } /** diff --git a/package.json b/package.json index 4ee290f..d36d575 100644 --- a/package.json +++ b/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 ", "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", diff --git a/test/goals.test.js b/test/goals.test.js index f8b3b24..8ead028 100644 --- a/test/goals.test.js +++ b/test/goals.test.js @@ -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 }) diff --git a/test/util.js b/test/util.js index a6ca52a..46bd92c 100644 --- a/test/util.js +++ b/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, {