@weaveprotocol/cli 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/weave.mjs +75 -6
- package/package.json +1 -1
package/dist/weave.mjs
CHANGED
|
@@ -7527,14 +7527,18 @@ async function proveRead(access, label, provider) {
|
|
|
7527
7527
|
}
|
|
7528
7528
|
async function checkRead(access, peerDid, label, proof, provider) {
|
|
7529
7529
|
const current = access.current();
|
|
7530
|
+
const admit = (readKey2) => {
|
|
7531
|
+
access.admitted?.(peerDid, readKey2);
|
|
7532
|
+
return true;
|
|
7533
|
+
};
|
|
7530
7534
|
const claimed = typeof proof.readKey === "string" ? proof.readKey : current;
|
|
7531
7535
|
if (claimed.startsWith("did:key:") && await verifyBy(provider, claimed, proof.read, label)) {
|
|
7532
|
-
if (claimed === current) return
|
|
7533
|
-
if (access.admits && await access.admits(peerDid, claimed, proof.member)) return
|
|
7536
|
+
if (claimed === current) return admit(current);
|
|
7537
|
+
if (access.admits && await access.admits(peerDid, claimed, proof.member)) return admit(claimed);
|
|
7534
7538
|
}
|
|
7535
7539
|
const earlier = Array.isArray(proof.earlier) ? proof.earlier.slice(0, MAX_EARLIER_READ_KEYS) : [];
|
|
7536
7540
|
const ours = earlier.find((item) => item?.readKey === current);
|
|
7537
|
-
return ours !== void 0 && current.startsWith("did:key:") && verifyBy(provider, current, ours.read, label);
|
|
7541
|
+
return ours !== void 0 && current.startsWith("did:key:") && await verifyBy(provider, current, ours.read, label) && admit(current);
|
|
7538
7542
|
}
|
|
7539
7543
|
function peerNonce() {
|
|
7540
7544
|
return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(16)));
|
|
@@ -8016,6 +8020,12 @@ function createMesh(config) {
|
|
|
8016
8020
|
broadcast: (message) => {
|
|
8017
8021
|
for (const peer of room.peers.keys()) send2(peer, message);
|
|
8018
8022
|
},
|
|
8023
|
+
drop(peer) {
|
|
8024
|
+
if (rooms.get(name) !== room || !room.peers.has(peer)) return;
|
|
8025
|
+
sendFrame(peer, { room: name, type: LEAVE_MESSAGE });
|
|
8026
|
+
drop(room, peer);
|
|
8027
|
+
closeIfIdle(peer);
|
|
8028
|
+
},
|
|
8019
8029
|
getPeers: () => [...room.peers.values()],
|
|
8020
8030
|
on: room.events.on,
|
|
8021
8031
|
off: room.events.off,
|
|
@@ -8066,6 +8076,9 @@ function createNetworkManager(config) {
|
|
|
8066
8076
|
broadcast: (message) => {
|
|
8067
8077
|
for (const peerId of peers.keys()) send2(peerId, message);
|
|
8068
8078
|
},
|
|
8079
|
+
drop: (peerId) => {
|
|
8080
|
+
if (peers.has(peerId)) transport.close(peerId);
|
|
8081
|
+
},
|
|
8069
8082
|
getPeers: () => [...peers.values()],
|
|
8070
8083
|
on,
|
|
8071
8084
|
off,
|
|
@@ -9011,6 +9024,8 @@ var writeCapability = (spaceId) => ({
|
|
|
9011
9024
|
});
|
|
9012
9025
|
var noteCid = (encoded) => cidFromBytes(utf8Encode(encoded));
|
|
9013
9026
|
var NO_ACCOUNT = Object.freeze({ account: null, agent: false });
|
|
9027
|
+
var STALE_ADMISSION_MS = 6e4;
|
|
9028
|
+
var WHO_WAIT_MS = 1e4;
|
|
9014
9029
|
var WHO_MESSAGE = "who";
|
|
9015
9030
|
var LIVE_MESSAGE = "live";
|
|
9016
9031
|
var MAX_LIVE_BYTES = 64 * 1024;
|
|
@@ -9564,6 +9579,8 @@ async function openSpaceRuntime(deps) {
|
|
|
9564
9579
|
for (const mirror of mirrors) mirror.changed();
|
|
9565
9580
|
void refreshHolds().catch(() => {
|
|
9566
9581
|
});
|
|
9582
|
+
void letGoOfOutsiders().catch(() => {
|
|
9583
|
+
});
|
|
9567
9584
|
};
|
|
9568
9585
|
function currentKey2(state) {
|
|
9569
9586
|
const epoch = state.keys.at(-1);
|
|
@@ -9721,6 +9738,7 @@ async function openSpaceRuntime(deps) {
|
|
|
9721
9738
|
keysRunning = null;
|
|
9722
9739
|
});
|
|
9723
9740
|
}
|
|
9741
|
+
const admittedOn = /* @__PURE__ */ new Map();
|
|
9724
9742
|
const readAccess = space2.visibility !== "private" ? null : {
|
|
9725
9743
|
async key() {
|
|
9726
9744
|
const held = newestKey((await access()).history.current);
|
|
@@ -9744,6 +9762,9 @@ async function openSpaceRuntime(deps) {
|
|
|
9744
9762
|
if (!held) return false;
|
|
9745
9763
|
const { account } = await accountOf(peerDid, await openWith(held, membership, membershipContext(space2.id)));
|
|
9746
9764
|
return account !== null && readers(history.current).has(account);
|
|
9765
|
+
},
|
|
9766
|
+
admitted(peerDid, readKey2) {
|
|
9767
|
+
admittedOn.set(peerDid, { readKey: readKey2, at: Date.now() });
|
|
9747
9768
|
}
|
|
9748
9769
|
};
|
|
9749
9770
|
const mirrors = [];
|
|
@@ -9853,6 +9874,24 @@ async function openSpaceRuntime(deps) {
|
|
|
9853
9874
|
if (!routes.has(peer)) return;
|
|
9854
9875
|
emit({ type: "message", space: space2.id, from: account, peer, agent, message: payload });
|
|
9855
9876
|
}
|
|
9877
|
+
async function letGoOfOutsiders() {
|
|
9878
|
+
if (admittedOn.size === 0) return;
|
|
9879
|
+
const connected = new Set(connectedPeers());
|
|
9880
|
+
for (const [peer, { at }] of admittedOn) {
|
|
9881
|
+
if (!connected.has(peer) && Date.now() - at > STALE_ADMISSION_MS) admittedOn.delete(peer);
|
|
9882
|
+
}
|
|
9883
|
+
const allowed = readers((await access()).history.current);
|
|
9884
|
+
for (const peer of connected) {
|
|
9885
|
+
const admission = admittedOn.get(peer);
|
|
9886
|
+
if (!admission || admission.readKey === currentReadKey) continue;
|
|
9887
|
+
if (!peerAccounts.has(peer) && Date.now() - admission.at < WHO_WAIT_MS) continue;
|
|
9888
|
+
const { account } = await peerAccounts.get(peer) ?? NO_ACCOUNT;
|
|
9889
|
+
if (account !== null && allowed.has(account)) continue;
|
|
9890
|
+
if (admittedOn.get(peer) !== admission) continue;
|
|
9891
|
+
admittedOn.delete(peer);
|
|
9892
|
+
for (const network of networks) network.drop(peer);
|
|
9893
|
+
}
|
|
9894
|
+
}
|
|
9856
9895
|
for (const network of networks) {
|
|
9857
9896
|
network.on("message", (message) => {
|
|
9858
9897
|
if (message.type === "sync") void sync.handleMessage(message.from, message.payload);
|
|
@@ -9864,6 +9903,8 @@ async function openSpaceRuntime(deps) {
|
|
|
9864
9903
|
if (peerAccounts.get(message.from) !== checked) return;
|
|
9865
9904
|
knownAccounts.set(message.from, known);
|
|
9866
9905
|
emit({ type: "status", space: space2.id });
|
|
9906
|
+
void letGoOfOutsiders().catch(() => {
|
|
9907
|
+
});
|
|
9867
9908
|
});
|
|
9868
9909
|
} else if (message.type === LIVE_MESSAGE) void onLive(message.from, message.payload);
|
|
9869
9910
|
});
|
|
@@ -9873,6 +9914,8 @@ async function openSpaceRuntime(deps) {
|
|
|
9873
9914
|
sync.addPeer(info.did);
|
|
9874
9915
|
sync.notifyPeers([info.did]);
|
|
9875
9916
|
emit({ type: "status", space: space2.id });
|
|
9917
|
+
void letGoOfOutsiders().catch(() => {
|
|
9918
|
+
});
|
|
9876
9919
|
});
|
|
9877
9920
|
network.on("peer-disconnected", (info) => {
|
|
9878
9921
|
if (routes.get(info.did) === network) {
|
|
@@ -9882,6 +9925,7 @@ async function openSpaceRuntime(deps) {
|
|
|
9882
9925
|
knownAccounts.delete(info.did);
|
|
9883
9926
|
liveAllowance.delete(info.did);
|
|
9884
9927
|
}
|
|
9928
|
+
if (!connectedPeers().includes(info.did)) admittedOn.delete(info.did);
|
|
9885
9929
|
emit({ type: "status", space: space2.id });
|
|
9886
9930
|
});
|
|
9887
9931
|
network.on("error", () => {
|
|
@@ -12942,7 +12986,8 @@ var app = {
|
|
|
12942
12986
|
title: { type: "string", minLength: 1, maxLength: 100 },
|
|
12943
12987
|
description: { type: "string", maxLength: 1e3 },
|
|
12944
12988
|
needs: { type: "array", minItems: 1, maxItems: MAX_APP_COLLECTIONS, items: { type: "object" } },
|
|
12945
|
-
from: { type: "string", maxLength: 300 }
|
|
12989
|
+
from: { type: "string", maxLength: 300 },
|
|
12990
|
+
updates: { type: "string", minLength: 1, maxLength: 100 }
|
|
12946
12991
|
},
|
|
12947
12992
|
required: ["title", "needs"]
|
|
12948
12993
|
},
|
|
@@ -12956,6 +13001,9 @@ function checkApp(value) {
|
|
|
12956
13001
|
if (body.description !== void 0 && (typeof body.description !== "string" || body.description.length > 1e3)) {
|
|
12957
13002
|
return "An app's description is at most 1000 characters";
|
|
12958
13003
|
}
|
|
13004
|
+
if (body.updates !== void 0 && (typeof body.updates !== "string" || !body.updates || body.updates.length > 100)) {
|
|
13005
|
+
return `An app's "updates" is the key of the app it replaces`;
|
|
13006
|
+
}
|
|
12959
13007
|
if (!Array.isArray(body.needs) || body.needs.length === 0 || body.needs.length > MAX_APP_COLLECTIONS) {
|
|
12960
13008
|
return `An app needs 1\u2013${MAX_APP_COLLECTIONS} collections`;
|
|
12961
13009
|
}
|
|
@@ -13031,9 +13079,25 @@ function reviewApp(body, collections) {
|
|
|
13031
13079
|
});
|
|
13032
13080
|
return { needs, added: needs.every((need) => need.status === "same"), problem: null };
|
|
13033
13081
|
}
|
|
13082
|
+
function supersededApps(records, collections) {
|
|
13083
|
+
const replaces = /* @__PURE__ */ new Map();
|
|
13084
|
+
for (const record of records) {
|
|
13085
|
+
if (record.body?.updates && record.body.updates !== record.key) replaces.set(record.key, record.body.updates);
|
|
13086
|
+
}
|
|
13087
|
+
const superseded = /* @__PURE__ */ new Set();
|
|
13088
|
+
for (const record of records) {
|
|
13089
|
+
if (!record.body || !reviewApp(record.body, collections).added) continue;
|
|
13090
|
+
for (let key3 = replaces.get(record.key); key3 && !superseded.has(key3) && key3 !== record.key; key3 = replaces.get(key3)) superseded.add(key3);
|
|
13091
|
+
}
|
|
13092
|
+
return superseded;
|
|
13093
|
+
}
|
|
13034
13094
|
async function proposeApp(node, spaceId, body) {
|
|
13035
13095
|
const problem = checkApp(body);
|
|
13036
13096
|
if (problem) throw new Error(problem);
|
|
13097
|
+
if (body.updates) {
|
|
13098
|
+
const replaced = await node.records.get(spaceId, body.updates);
|
|
13099
|
+
if (!replaced || replaced.collection !== app.name) throw new Error(`"updates" names no app in this space: ${body.updates}`);
|
|
13100
|
+
}
|
|
13037
13101
|
return node.records.put(spaceId, app.name, body);
|
|
13038
13102
|
}
|
|
13039
13103
|
|
|
@@ -13280,7 +13344,7 @@ var NODE_ACTIONS = Object.freeze([
|
|
|
13280
13344
|
},
|
|
13281
13345
|
{
|
|
13282
13346
|
name: "apps_list",
|
|
13283
|
-
description: 'The apps proposed in a space: each with its title, who proposed it (viaAgent when an agent did), and for every collection it needs whether it is new, already there, or would change one \u2014 with what it allows, worked out from its rules. "added" is true once a person has added it.',
|
|
13347
|
+
description: 'The apps proposed in a space: each with its title, who proposed it (viaAgent when an agent did), and for every collection it needs whether it is new, already there, or would change one \u2014 with what it allows, worked out from its rules. "added" is true once a person has added it. "updates" names the app it is a new version of; "superseded" is true once a newer version of it has been added, so it is only history.',
|
|
13284
13348
|
input: { type: "object", properties: { space }, required: ["space"] },
|
|
13285
13349
|
readOnly: true,
|
|
13286
13350
|
peerContent: true,
|
|
@@ -13288,6 +13352,7 @@ var NODE_ACTIONS = Object.freeze([
|
|
|
13288
13352
|
const spaceId = str(input, "space");
|
|
13289
13353
|
const collections = await node.collections.list(spaceId);
|
|
13290
13354
|
const found = await node.records.list(spaceId, { collection: app.name });
|
|
13355
|
+
const superseded = supersededApps(found, collections);
|
|
13291
13356
|
return found.map((record) => {
|
|
13292
13357
|
const review = record.body ? reviewApp(record.body, collections) : null;
|
|
13293
13358
|
return {
|
|
@@ -13297,7 +13362,9 @@ var NODE_ACTIONS = Object.freeze([
|
|
|
13297
13362
|
proposedBy: record.createdBy,
|
|
13298
13363
|
...record.viaAgent ? { viaAgent: true } : {},
|
|
13299
13364
|
...record.body && appScreen(record.body) ? { screen: appScreen(record.body).collection } : {},
|
|
13365
|
+
...record.body?.updates ? { updates: record.body.updates } : {},
|
|
13300
13366
|
added: review?.added ?? false,
|
|
13367
|
+
superseded: superseded.has(record.key),
|
|
13301
13368
|
problem: review?.problem ?? (record.body ? null : "It could not be read"),
|
|
13302
13369
|
needs: review?.needs.map(({ definition, status, summary, changes }) => ({ name: definition.name, status, summary, changes })) ?? []
|
|
13303
13370
|
};
|
|
@@ -13313,13 +13380,14 @@ var NODE_ACTIONS = Object.freeze([
|
|
|
13313
13380
|
},
|
|
13314
13381
|
{
|
|
13315
13382
|
name: "apps_propose",
|
|
13316
|
-
description: `Propose an app in a space: a new way for its people to work together \u2014 a carpool, a sign-up sheet, a decision log. Give it a title, a line on what it is for, and the collections it needs, each exactly as collections_define takes it (name, title, description, schema, links, permissions, rules \u2014 no version). Nothing is defined yet: everyone in the space sees the proposal, with what it allows worked out from its rules, and a person who may define collections adds it. Read collections_list first and reuse what the space already has (std.poll, std.task\u2026) rather than inventing a twin. For anything plain lists and forms can't show \u2014 a game board, a calendar, a whiteboard \u2014 give the main collection a "screen": its own HTML UI (one document, inline scripts and styles, no network unless the collection names exact origins in "network"). Inside it, use exactly: weave.me ({ did, name }), await weave.list("<collection>", { where: { "link:<rel>": key } }), await weave.put("<collection>", body, { links: [{ rel, to: key }] }), await weave.update(key, body), await weave.remove(key), weave.onChange(redraw). Records are { key, body, links, createdBy, mine }. Read apps_screen_guide for the rest. Returns what each collection will allow; tell the person that, not your own description.`,
|
|
13383
|
+
description: `Propose an app in a space: a new way for its people to work together \u2014 a carpool, a sign-up sheet, a decision log. Give it a title, a line on what it is for, and the collections it needs, each exactly as collections_define takes it (name, title, description, schema, links, permissions, rules \u2014 no version). Nothing is defined yet: everyone in the space sees the proposal, with what it allows worked out from its rules, and a person who may define collections adds it. Read collections_list first and reuse what the space already has (std.poll, std.task\u2026) rather than inventing a twin. To change an app that is already here, read apps_list and pass the key of its newest version, the one not superseded, as "updates": the change then shows as an update to it, and once it is added the old version is not offered again. For anything plain lists and forms can't show \u2014 a game board, a calendar, a whiteboard \u2014 give the main collection a "screen": its own HTML UI (one document, inline scripts and styles, no network unless the collection names exact origins in "network"). Inside it, use exactly: weave.me ({ did, name }), await weave.list("<collection>", { where: { "link:<rel>": key } }), await weave.put("<collection>", body, { links: [{ rel, to: key }] }), await weave.update(key, body), await weave.remove(key), weave.onChange(redraw). Records are { key, body, links, createdBy, mine }. Read apps_screen_guide for the rest. Returns what each collection will allow; tell the person that, not your own description.`,
|
|
13317
13384
|
input: {
|
|
13318
13385
|
type: "object",
|
|
13319
13386
|
properties: {
|
|
13320
13387
|
space,
|
|
13321
13388
|
title: { type: "string", description: 'What people will call it, e.g. "Carpool"' },
|
|
13322
13389
|
description: { type: "string", description: "What it is for, in a sentence" },
|
|
13390
|
+
updates: { type: "string", description: "The key of the app this is a new version of, from apps_list" },
|
|
13323
13391
|
needs: {
|
|
13324
13392
|
type: "array",
|
|
13325
13393
|
description: "Collection definitions, as collections_define takes them \u2014 without version",
|
|
@@ -13334,6 +13402,7 @@ var NODE_ACTIONS = Object.freeze([
|
|
|
13334
13402
|
const body = {
|
|
13335
13403
|
title: str(input, "title"),
|
|
13336
13404
|
...typeof input.description === "string" ? { description: input.description } : {},
|
|
13405
|
+
...typeof input.updates === "string" ? { updates: input.updates } : {},
|
|
13337
13406
|
needs: input.needs
|
|
13338
13407
|
};
|
|
13339
13408
|
const problem = checkApp(body);
|