nervur 0.19.2 → 0.20.1

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.
Files changed (235) hide show
  1. package/README.md +156 -104
  2. package/dist/being/being.d.ts +8 -15
  3. package/dist/being/being.js +20 -67
  4. package/dist/being/digest.d.ts +1 -1
  5. package/dist/being/digest.js +7 -27
  6. package/dist/being/faculty.d.ts +10 -0
  7. package/dist/being/faculty.js +74 -0
  8. package/dist/being/index.d.ts +5 -5
  9. package/dist/being/index.js +6 -5
  10. package/dist/being/kind.d.ts +4 -0
  11. package/dist/being/kind.js +28 -0
  12. package/dist/being/types.d.ts +35 -63
  13. package/dist/being/types.js +3 -57
  14. package/dist/being/words.d.ts +13 -0
  15. package/dist/being/words.js +14 -0
  16. package/dist/cli/command.d.ts +13 -0
  17. package/dist/cli/command.js +194 -0
  18. package/dist/cli/harbor.d.ts +21 -0
  19. package/dist/cli/harbor.js +141 -0
  20. package/dist/cli/main.d.ts +2 -0
  21. package/dist/cli/main.js +14 -0
  22. package/dist/contract/index.d.ts +39 -0
  23. package/dist/contract/index.js +33 -0
  24. package/dist/crypto/aes.d.ts +3 -0
  25. package/dist/crypto/aes.js +24 -0
  26. package/dist/crypto/bytes.d.ts +6 -0
  27. package/dist/crypto/bytes.js +33 -0
  28. package/dist/crypto/ed25519.d.ts +3 -0
  29. package/dist/crypto/ed25519.js +85 -0
  30. package/dist/crypto/hash.d.ts +2 -0
  31. package/dist/crypto/hash.js +11 -0
  32. package/dist/crypto/index.d.ts +7 -0
  33. package/dist/crypto/index.js +10 -0
  34. package/dist/crypto/json.d.ts +8 -0
  35. package/dist/crypto/json.js +250 -0
  36. package/dist/crypto/mlkem.d.ts +12 -0
  37. package/dist/crypto/mlkem.js +36 -0
  38. package/dist/crypto/subtle.d.ts +10 -0
  39. package/dist/crypto/subtle.js +29 -0
  40. package/dist/crypto/x25519.d.ts +2 -0
  41. package/dist/crypto/x25519.js +21 -0
  42. package/dist/folder/index.d.ts +14 -0
  43. package/dist/folder/index.js +87 -0
  44. package/dist/harbor/catalogue.d.ts +25 -0
  45. package/dist/harbor/catalogue.js +93 -0
  46. package/dist/harbor/dock.d.ts +29 -0
  47. package/dist/harbor/dock.js +122 -0
  48. package/dist/harbor/harbor.d.ts +31 -0
  49. package/dist/harbor/harbor.js +146 -0
  50. package/dist/harbor/index.d.ts +7 -10
  51. package/dist/harbor/index.js +9 -18
  52. package/dist/harbor/package.d.ts +10 -0
  53. package/dist/harbor/package.js +115 -0
  54. package/dist/harbor/registry.d.ts +9 -0
  55. package/dist/harbor/registry.js +78 -0
  56. package/dist/harbor/root-line.d.ts +9 -0
  57. package/dist/harbor/root-line.js +36 -0
  58. package/dist/harbor/terrain.d.ts +9 -0
  59. package/dist/harbor/terrain.js +2 -0
  60. package/dist/index.d.ts +7 -0
  61. package/dist/index.js +14 -0
  62. package/dist/pointer/bodies.d.ts +26 -0
  63. package/dist/pointer/bodies.js +64 -0
  64. package/dist/pointer/index.d.ts +2 -0
  65. package/dist/pointer/index.js +4 -0
  66. package/dist/pointer/world.d.ts +35 -0
  67. package/dist/pointer/world.js +85 -0
  68. package/dist/quo/door.d.ts +34 -0
  69. package/dist/quo/door.js +172 -0
  70. package/dist/quo/index.d.ts +8 -0
  71. package/dist/quo/index.js +11 -0
  72. package/dist/quo/invitation.d.ts +7 -0
  73. package/dist/quo/invitation.js +15 -0
  74. package/dist/quo/keys.d.ts +40 -0
  75. package/dist/quo/keys.js +79 -0
  76. package/dist/quo/payload.d.ts +15 -0
  77. package/dist/quo/payload.js +55 -0
  78. package/dist/quo/relations.d.ts +40 -0
  79. package/dist/quo/relations.js +33 -0
  80. package/dist/quo/reply.d.ts +13 -0
  81. package/dist/quo/reply.js +35 -0
  82. package/dist/quo/seal.d.ts +42 -0
  83. package/dist/quo/seal.js +78 -0
  84. package/dist/quo/standing.d.ts +39 -0
  85. package/dist/quo/standing.js +90 -0
  86. package/dist/tcp/frame.d.ts +29 -0
  87. package/dist/tcp/frame.js +74 -0
  88. package/dist/tcp/index.d.ts +18 -0
  89. package/dist/tcp/index.js +164 -0
  90. package/dist/ward/allowance.d.ts +13 -9
  91. package/dist/ward/allowance.js +28 -67
  92. package/dist/ward/cells.d.ts +6 -6
  93. package/dist/ward/cells.js +67 -179
  94. package/dist/ward/index.d.ts +6 -10
  95. package/dist/ward/index.js +7 -13
  96. package/dist/ward/partition.d.ts +44 -60
  97. package/dist/ward/partition.js +56 -285
  98. package/dist/ward/pilot.d.ts +8 -0
  99. package/dist/ward/pilot.js +50 -0
  100. package/dist/ward/stance.d.ts +27 -20
  101. package/dist/ward/stance.js +174 -406
  102. package/dist/ward/ward-being.d.ts +35 -0
  103. package/dist/ward/ward-being.js +82 -0
  104. package/dist/ward/ward.d.ts +45 -11
  105. package/dist/ward/ward.js +239 -357
  106. package/package.json +24 -31
  107. package/src/being/being.ts +35 -78
  108. package/src/being/digest.ts +7 -35
  109. package/src/being/faculty.ts +73 -0
  110. package/src/being/index.ts +6 -6
  111. package/src/being/kind.ts +30 -0
  112. package/src/being/types.ts +50 -146
  113. package/src/being/words.ts +31 -0
  114. package/src/cli/command.ts +188 -0
  115. package/src/cli/harbor.ts +137 -0
  116. package/src/cli/main.ts +13 -0
  117. package/src/contract/index.ts +73 -0
  118. package/src/crypto/aes.ts +26 -0
  119. package/src/crypto/bytes.ts +37 -0
  120. package/src/crypto/ed25519.ts +84 -0
  121. package/src/crypto/hash.ts +14 -0
  122. package/src/crypto/index.ts +10 -0
  123. package/src/crypto/json.ts +241 -0
  124. package/src/crypto/mlkem.ts +38 -0
  125. package/src/crypto/subtle.ts +33 -0
  126. package/src/crypto/x25519.ts +21 -0
  127. package/src/folder/index.ts +92 -0
  128. package/src/harbor/catalogue.ts +110 -0
  129. package/src/harbor/dock.ts +137 -0
  130. package/src/harbor/harbor.ts +180 -0
  131. package/src/harbor/index.ts +9 -19
  132. package/src/harbor/package.ts +131 -0
  133. package/src/harbor/registry.ts +73 -0
  134. package/src/harbor/root-line.ts +36 -0
  135. package/src/harbor/terrain.ts +15 -0
  136. package/src/index.ts +20 -0
  137. package/src/pointer/bodies.ts +65 -0
  138. package/src/pointer/index.ts +4 -0
  139. package/src/pointer/world.ts +94 -0
  140. package/src/quo/door.ts +178 -0
  141. package/src/quo/index.ts +11 -0
  142. package/src/quo/invitation.ts +15 -0
  143. package/src/quo/keys.ts +91 -0
  144. package/src/quo/payload.ts +61 -0
  145. package/src/quo/relations.ts +62 -0
  146. package/src/quo/reply.ts +38 -0
  147. package/src/quo/seal.ts +101 -0
  148. package/src/quo/standing.ts +111 -0
  149. package/src/stand/main.ts +20 -0
  150. package/src/stand/stand.ts +220 -0
  151. package/src/tcp/frame.ts +78 -0
  152. package/src/tcp/index.ts +173 -0
  153. package/src/ward/allowance.ts +37 -75
  154. package/src/ward/cells.ts +63 -176
  155. package/src/ward/index.ts +7 -17
  156. package/src/ward/partition.ts +86 -326
  157. package/src/ward/pilot.ts +50 -0
  158. package/src/ward/stance.ts +186 -420
  159. package/src/ward/ward-being.ts +103 -0
  160. package/src/ward/ward.ts +256 -359
  161. package/dist/being/lent.d.ts +0 -32
  162. package/dist/being/lent.js +0 -72
  163. package/dist/being/silence.d.ts +0 -12
  164. package/dist/being/silence.js +0 -41
  165. package/dist/conformance/assert.d.ts +0 -11
  166. package/dist/conformance/assert.js +0 -106
  167. package/dist/conformance/beings.d.ts +0 -199
  168. package/dist/conformance/beings.js +0 -188
  169. package/dist/conformance/estate.d.ts +0 -5
  170. package/dist/conformance/estate.js +0 -388
  171. package/dist/conformance/index.d.ts +0 -80
  172. package/dist/conformance/index.js +0 -819
  173. package/dist/conformance/reach.d.ts +0 -10
  174. package/dist/conformance/reach.js +0 -72
  175. package/dist/conformance/store.d.ts +0 -5
  176. package/dist/conformance/store.js +0 -113
  177. package/dist/harbor/box.d.ts +0 -121
  178. package/dist/harbor/box.js +0 -121
  179. package/dist/harbor/core.d.ts +0 -55
  180. package/dist/harbor/core.js +0 -662
  181. package/dist/harbor/dial.d.ts +0 -9
  182. package/dist/harbor/dial.js +0 -81
  183. package/dist/harbor/memory.d.ts +0 -29
  184. package/dist/harbor/memory.js +0 -104
  185. package/dist/harbor/reach.d.ts +0 -36
  186. package/dist/harbor/reach.js +0 -199
  187. package/dist/harbor/store.d.ts +0 -35
  188. package/dist/harbor/store.js +0 -62
  189. package/dist/vector/cases.d.ts +0 -42
  190. package/dist/vector/cases.js +0 -223
  191. package/dist/vector/index.d.ts +0 -6
  192. package/dist/vector/index.js +0 -8
  193. package/dist/vector/stand.d.ts +0 -9
  194. package/dist/vector/stand.js +0 -77
  195. package/dist/vector/world.d.ts +0 -144
  196. package/dist/vector/world.js +0 -209
  197. package/dist/ward/arithmetic.d.ts +0 -31
  198. package/dist/ward/arithmetic.js +0 -249
  199. package/dist/ward/door.d.ts +0 -18
  200. package/dist/ward/door.js +0 -186
  201. package/dist/ward/ground.d.ts +0 -29
  202. package/dist/ward/ground.js +0 -56
  203. package/dist/ward/heirs.d.ts +0 -13
  204. package/dist/ward/heirs.js +0 -117
  205. package/dist/ward/json.d.ts +0 -2
  206. package/dist/ward/json.js +0 -163
  207. package/dist/ward/owner.d.ts +0 -13
  208. package/dist/ward/owner.js +0 -220
  209. package/dist/ward/seal.d.ts +0 -52
  210. package/dist/ward/seal.js +0 -150
  211. package/src/being/lent.ts +0 -72
  212. package/src/being/silence.ts +0 -46
  213. package/src/conformance/assert.ts +0 -100
  214. package/src/conformance/beings.ts +0 -188
  215. package/src/conformance/estate.ts +0 -412
  216. package/src/conformance/index.ts +0 -965
  217. package/src/conformance/reach.ts +0 -83
  218. package/src/conformance/store.ts +0 -125
  219. package/src/harbor/box.ts +0 -131
  220. package/src/harbor/core.ts +0 -699
  221. package/src/harbor/dial.ts +0 -112
  222. package/src/harbor/memory.ts +0 -123
  223. package/src/harbor/reach.ts +0 -221
  224. package/src/harbor/store.ts +0 -91
  225. package/src/vector/cases.ts +0 -257
  226. package/src/vector/index.ts +0 -11
  227. package/src/vector/stand.ts +0 -76
  228. package/src/vector/world.ts +0 -232
  229. package/src/ward/arithmetic.ts +0 -251
  230. package/src/ward/door.ts +0 -186
  231. package/src/ward/ground.ts +0 -142
  232. package/src/ward/heirs.ts +0 -116
  233. package/src/ward/json.ts +0 -144
  234. package/src/ward/owner.ts +0 -214
  235. package/src/ward/seal.ts +0 -178
@@ -1,81 +0,0 @@
1
- import { REFUSED, Socket } from './reach.js';
2
- export function dial(harbor, url) {
3
- const d = { url, socket: null, wake: () => { }, close: () => { } };
4
- let stopped = false, wait = 1000, timer, line, // the line in hand, open or still opening: close() closes it either way
5
- held; // the one this dialer owns now, and how it gives back what the line took
6
- const connect = () => {
7
- timer = undefined; // no wait pending: this is the dial it was waiting for
8
- if (stopped)
9
- return;
10
- line = new WebSocket(url.replace(/\/$/, '').replace(/^http/, 'ws'));
11
- const s = new Socket(line, (pk, bytes) => harbor.deliver(pk, bytes), (far) => {
12
- // A line that opened is not a line that works. The wait goes back to
13
- // a second here, where the far side has answered and been taken, and
14
- // not at the handshake: a listener that opens and then refuses every
15
- // time would otherwise be dialed at a fixed two seconds for good.
16
- wait = 1000;
17
- void harbor.bind(far, s, false); // the listener's claims, each proven at its door: reached through this socket, not held for others
18
- }, (why) => {
19
- shed();
20
- // A line this dialer already put down. Its close arrives whenever the
21
- // system gets round to noticing, which on a socket that died under a
22
- // sleeping device is minutes; by then the line dialed in its place is
23
- // the one in hand, so this one gives back what it took and dials
24
- // nothing. Only the line the dialer still holds dials again.
25
- if (held?.socket !== s)
26
- return;
27
- held = undefined;
28
- // A suite this listener will not speak is not a line that dropped. It
29
- // will not become speakable by asking again sooner, so the wait goes
30
- // straight to the ceiling and stays there until it changes its mind.
31
- if (why.code === REFUSED)
32
- wait = 30000;
33
- if (!stopped)
34
- timer = setTimeout(connect, wait);
35
- wait = Math.min(wait * 2, 30000);
36
- });
37
- // Everything this line took from the harbor, given back. Said when it
38
- // closes, and when the dialer puts it down without waiting for that.
39
- const shed = () => {
40
- harbor.announcers.delete(say);
41
- harbor.unbind(s);
42
- if (d.socket === s)
43
- d.socket = null;
44
- const at = harbor.fallbacks.indexOf(s);
45
- if (at !== -1)
46
- harbor.fallbacks.splice(at, 1); // this line only: another dialer's stays
47
- };
48
- held = { socket: s, shed };
49
- harbor.fallbacks.push(s); // from the moment it is dialed: an ask made before the handshake waits on the socket, and is not unreached
50
- // What this harbor holds, said when the line opens and again whenever it
51
- // changes. A ward booted or adopted after the handshake is announced on
52
- // the line already in hand, and a dropped one stops being claimed at once.
53
- const say = () => s.announce([...harbor.doors.keys()]);
54
- line.addEventListener('open', () => {
55
- if (stopped)
56
- return s.close(); // closed while it was opening: it opens into nothing
57
- d.socket = s;
58
- harbor.announcers.add(say);
59
- say();
60
- });
61
- };
62
- d.wake = () => {
63
- if (stopped)
64
- return;
65
- clearTimeout(timer);
66
- timer = undefined;
67
- wait = 1000; // the device is back, and the wait it froze belongs to the sleep, not to this line
68
- const old = held;
69
- held = undefined; // put down before the new one is dialed, so a fallback list never holds the dead line ahead of the live one
70
- old?.shed();
71
- old?.socket.close();
72
- connect();
73
- };
74
- d.close = () => {
75
- stopped = true;
76
- clearTimeout(timer);
77
- line?.close(); // open or opening: a line closed while connecting fires close and never open
78
- };
79
- connect();
80
- return d;
81
- }
@@ -1,29 +0,0 @@
1
- import type { BeingClass, BeingLike, Invitation } from '../being/types.ts';
2
- import { type Ground, type WardPointers } from '../ward/ground.ts';
3
- export type Booted = WardPointers & {
4
- pk: string;
5
- };
6
- export type WardFactory = (ground: Ground) => Promise<WardPointers>;
7
- export declare class MemoryHarbor {
8
- readonly doors: Map<string, (bytes: Uint8Array) => Promise<{
9
- bytes: Uint8Array;
10
- heard: boolean;
11
- }>>;
12
- readonly down: Set<string>;
13
- readonly wire: string[];
14
- readonly refused: Map<string, number>;
15
- readonly peers: Set<MemoryHarbor>;
16
- cut: boolean;
17
- readonly partitions: Map<string, Record<string, unknown>>;
18
- readonly wards: Map<string, Booted>;
19
- readonly objects: WeakMap<object, BeingLike>;
20
- random: Ground['random'];
21
- route(farPk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
22
- link(harbor: MemoryHarbor): void;
23
- boot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>, box?: Invitation): Promise<Booted>;
24
- reboot(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>, box?: Invitation): Promise<Booted>;
25
- up(Ward: WardFactory, lent?: Record<string, BeingClass>): Promise<Booted>;
26
- get box(): Booted | undefined;
27
- host(seed: string, Ward: WardFactory, classes: Record<string, BeingClass>): Promise<Booted>;
28
- hosted(Ward: WardFactory, classes: Record<string, BeingClass>): Promise<string[]>;
29
- }
@@ -1,104 +0,0 @@
1
- import { maker, entropy, learnPk, volatile } from '../ward/ground.js';
2
- import { hex } from '../ward/arithmetic.js';
3
- import { Box, BOX_SEED, stand, offerTo, roll } from './box.js';
4
- export class MemoryHarbor {
5
- doors = new Map(); // ward pk -> door
6
- down = new Set(); // ward pks this harbor cannot reach right now
7
- wire = []; // every byte string that ever crossed, as hex. for inspection
8
- refused = new Map(); // ward pk -> arrivals its door would not admit: strangers, counted
9
- peers = new Set();
10
- cut = false;
11
- partitions = new Map(); // seed -> memory. the harbor keeps it and reads nothing
12
- wards = new Map(); // seed -> pointers
13
- objects = new WeakMap(); // cells -> being object. what instantiate constructed. a hand for tests, never the ward's; it follows the cells out when she is unbooted
14
- // Where the wards this harbor boots draw their entropy. The device's own
15
- // by default. A harbor may hand a fixed stream instead, which is what pins
16
- // bytes: every key a ward mints and every nonce it seals under comes from
17
- // here, so a ward on a known seed with a known stream answers the same
18
- // bytes every run, and a corpus of them is a corpus and not a sample.
19
- random = entropy;
20
- // the directory: its own doors, else a peer it is linked to. Quo says nothing about how.
21
- async route(farPk, bytes) {
22
- if (this.down.has(farPk))
23
- return undefined;
24
- const door = this.doors.get(farPk);
25
- if (door) {
26
- const r = await door(bytes);
27
- if (!r.heard)
28
- this.refused.set(farPk, (this.refused.get(farPk) ?? 0) + 1);
29
- return r.bytes;
30
- }
31
- const peer = [...this.peers].find((h) => h.doors.has(farPk) && !h.down.has(farPk));
32
- if (!peer || this.cut)
33
- return undefined; // nothing. the harbor has no word silence
34
- return peer.route(farPk, bytes);
35
- }
36
- link(harbor) {
37
- this.peers.add(harbor);
38
- harbor.peers.add(this);
39
- }
40
- // boot by seed. the harbor gets a door and an ask, and learns the pk by
41
- // asking. `box` is the invitation on this device's box being, for this
42
- // ward: left out, the ward holds no box and every lend is null.
43
- async boot(seed, Ward, classes, box) {
44
- this.partitions.set(seed, {});
45
- return this.reboot(seed, Ward, classes, box);
46
- }
47
- // a restart: same seed, same partition, fresh ward. the ward's files are the harbor's to keep.
48
- async reboot(seed, Ward, classes, box) {
49
- const rows = this.partitions.get(seed);
50
- if (!rows)
51
- throw new Error(`no partition for ${seed}`);
52
- const ground = {
53
- seed,
54
- memory: volatile(rows),
55
- ...(box ? { box } : {}),
56
- // One objects map for the whole harbor, not one per ward: every ward
57
- // here is in this process and a test reaches any being of any of them.
58
- instantiate: maker(this.objects, classes),
59
- // bytes cross, copied, never a reference. both ways.
60
- carry: async (farPk, bytes) => {
61
- this.wire.push(hex(bytes));
62
- const back = await this.route(farPk, new Uint8Array(bytes));
63
- return back === undefined ? undefined : new Uint8Array(back);
64
- },
65
- random: (n) => this.random(n),
66
- };
67
- const w = await Ward(ground);
68
- const booted = { ...w, pk: await learnPk(w) };
69
- this.doors.set(booted.pk, booted.door);
70
- this.wards.set(seed, booted);
71
- return booted;
72
- }
73
- // Act one. The box's own ward, the box's being and the lent beings named,
74
- // each booted under its name and held by her under it. Run again on a
75
- // harbor whose box ward stands, it changes nothing.
76
- async up(Ward, lent = {}) {
77
- const classes = { Box };
78
- for (const C of Object.values(lent))
79
- classes[C.name] = C;
80
- const box = this.wards.get(BOX_SEED) ?? (await this.boot(BOX_SEED, Ward, classes));
81
- await stand(box, lent);
82
- return box;
83
- }
84
- get box() {
85
- return this.wards.get(BOX_SEED);
86
- }
87
- // Act two, one ward: on the roll and handed one invitation on the box's being.
88
- // A harbor with no box ward up hosts as `boot` does, with nothing.
89
- async host(seed, Ward, classes) {
90
- const box = this.box;
91
- const inv = box ? await offerTo(box, seed) : undefined;
92
- return this.partitions.has(seed) ? this.reboot(seed, Ward, classes, inv) : this.boot(seed, Ward, classes, inv);
93
- }
94
- // Act two, whole: the box's being says who is hosted, and each comes up.
95
- async hosted(Ward, classes) {
96
- const box = this.box;
97
- if (!box)
98
- return [];
99
- const seeds = await roll(box);
100
- for (const seed of seeds)
101
- await this.host(seed, Ward, classes);
102
- return seeds;
103
- }
104
- }
@@ -1,36 +0,0 @@
1
- export type Carry = (pk: string, bytes: Uint8Array) => Promise<Uint8Array | undefined>;
2
- export type Reach = {
3
- carry: Carry;
4
- close(): void;
5
- };
6
- export declare function request(url: string): Reach;
7
- export declare const SUITE = 1;
8
- export declare const REFUSED = 4001;
9
- export type Line = {
10
- readyState: number;
11
- binaryType: string;
12
- send(data: string | Uint8Array): void;
13
- close(code?: number, reason?: string): void;
14
- addEventListener(type: 'open' | 'error', fn: () => void): void;
15
- addEventListener(type: 'close', fn: (e?: {
16
- code?: number;
17
- reason?: string;
18
- }) => void): void;
19
- addEventListener(type: 'message', fn: (e: {
20
- data: unknown;
21
- }) => void): void;
22
- };
23
- export type Announce = (pks: string[]) => void;
24
- export declare class Socket implements Reach {
25
- #private;
26
- readonly line: Line;
27
- readonly pending: Map<number, (bytes: Uint8Array | undefined) => void>;
28
- far: string[];
29
- constructor(line: Line, deliver: Carry, onAnnounce: Announce, onClose: (why: {
30
- code?: number;
31
- reason?: string;
32
- }) => void);
33
- announce(pks: string[]): void;
34
- carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | undefined>;
35
- close(): void;
36
- }
@@ -1,199 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Reach: carrying bytes to a pk off the device. One interface, two
3
- // implementations:
4
- //
5
- // request a URL. POST the bytes to `<url>/<pk>`, get bytes back.
6
- // Listener to listener.
7
- // socket a held WebSocket at `<url>`, opened by whichever side can
8
- // dial, used in both directions with a frame id. Each side
9
- // announces the ward pks it holds when the socket opens, and the
10
- // other binds them.
11
- //
12
- // Both are written on what every terrain has: fetch, and the standard
13
- // WebSocket surface, which a browser, Node, an edge worker's pair and the
14
- // `ws` server all offer. The listener half of a socket is the terrain's
15
- // own and stays outside this tree; what is here is the framing, either end
16
- // of a line, and the request. Nothing here names a runtime, and
17
- // `src/conformance/reach.ts` is what every reach passes. A reach carries and returns; it reads
18
- // nothing. `undefined` is a promise that nothing was delivered: no such pk
19
- // at the far end, a connection that would not open, a socket that is gone.
20
- // A reach that sent the bytes and lost the line afterwards answers nothing
21
- // at all, and the ward's own bound ends the ask.
22
- import { concat, hex, unhex } from '../ward/arithmetic.js';
23
- import { readJson } from '../ward/json.js';
24
- const never = () => new Promise(() => { }); // sent, and no word since: the ward's bound decides
25
- // A request reach to one listener.
26
- export function request(url) {
27
- const base = url.replace(/\/$/, '');
28
- return {
29
- close: () => { },
30
- carry: async (pk, bytes) => {
31
- let res;
32
- // A body of its own, not the caller's view: fetch reads it after this
33
- // line returns, and a buffer somebody else may write is not a body. The
34
- // copy is its own ArrayBuffer, which is what a body is; the bytes handed
35
- // in are typed as backed by anything, shared memory included.
36
- const body = new Uint8Array(bytes.length);
37
- body.set(bytes);
38
- try {
39
- res = await fetch(`${base}/${pk}`, { method: 'POST', headers: { 'content-type': 'application/octet-stream', 'quo-suite': String(SUITE) }, body });
40
- }
41
- catch {
42
- return undefined; // the connection never opened: nothing was delivered
43
- }
44
- // A status that says the listener did not take the bytes is nothing
45
- // delivered: no reach for that pk, a request it refused on its face, a
46
- // gateway with nobody behind it. Any other failure is a listener that
47
- // took them and failed after, which is sent and no word since.
48
- if (res.status === 404 || (res.status >= 400 && res.status < 500) || res.status === 502 || res.status === 503 || res.status === 504)
49
- return undefined;
50
- if (!res.ok)
51
- return never();
52
- return new Uint8Array(await res.arrayBuffer());
53
- },
54
- };
55
- }
56
- // The frames on a socket. Binary, one byte of kind first.
57
- // ask [0][id: 4 bytes][pk: 64 bytes][bytes]
58
- // reply [1][id: 4 bytes][bytes]
59
- // none [2][id: 4 bytes] nothing was delivered
60
- // And one text frame, JSON: { announce: [pk, ...], suite }, the ward pks this
61
- // side holds and the wire suite it speaks, sent when the socket opens and
62
- // whenever the pks change.
63
- const ASK = 0, REPLY = 1, NONE = 2;
64
- // The wire suite: which frames these are and which five algorithms seal what
65
- // they carry. One value, and it is not negotiated. It rides in the handshake
66
- // and never on an ask, so an ask stays bytes nobody can tell from noise, and
67
- // a line still learns before it carries anything whether the far side speaks
68
- // what this side speaks. A side meeting a suite it does not know carries
69
- // nothing for it and closes: the alternative is bytes that will never open,
70
- // answered by a silence that says nothing about why, forever. Nothing
71
- // changes this number before 1.0.0. After it, this is the one place a second
72
- // suite can be told from a first without guessing.
73
- export const SUITE = 1;
74
- // The close a side sends when it refuses a suite, in the range a protocol
75
- // may spend on itself. A line that just closes tells the far side nothing,
76
- // and a dialer that learns nothing dials again on its backoff for good: the
77
- // whole reason this number exists is that a kit which cannot be understood
78
- // should be told so instead of meeting a silence that names no reason. So
79
- // the refusal names itself on the way out, and the far side may read it.
80
- export const REFUSED = 4001;
81
- const OPEN = 1; // WebSocket.OPEN, on every terrain
82
- // How many asks a socket keeps a resolver for. An ask the far side never
83
- // answers is ended by the ward's bound, and its resolver stays until the
84
- // line closes; a far side that answers nothing for long enough would grow
85
- // the map for as long as this side asks, so the oldest goes when it is full.
86
- const PENDING = 4096;
87
- // One held socket, either side of it. `deliver` is what this side does with
88
- // an ask that arrives for a pk: its own door, or a socket it holds for that
89
- // pk, and nothing else. `onAnnounce` learns the far side's pks; `onClose`
90
- // forgets them.
91
- export class Socket {
92
- line;
93
- pending = new Map();
94
- far = []; // the pks the far side announced
95
- #id = 1;
96
- #open;
97
- constructor(line, deliver, onAnnounce, onClose) {
98
- this.line = line;
99
- line.binaryType = 'arraybuffer';
100
- // A line that opens is a reach; one that errors, or closes before it
101
- // opened, is a reach that failed, and an ask waiting on the handshake is
102
- // nothing delivered.
103
- this.#open =
104
- line.readyState === OPEN
105
- ? Promise.resolve()
106
- : new Promise((ok, no) => {
107
- line.addEventListener('open', ok);
108
- line.addEventListener('error', no);
109
- line.addEventListener('close', () => no(new Error('closed before it opened')));
110
- });
111
- this.#open.catch(() => { }); // a handshake refused is a reach that failed, not a process that dies
112
- line.addEventListener('message', (e) => {
113
- if (typeof e.data === 'string') {
114
- try {
115
- const m = readJson(e.data);
116
- if (m === null || typeof m !== 'object')
117
- return;
118
- // Absent is this suite: a side that says nothing says one.
119
- if (m.suite !== undefined && m.suite !== SUITE) {
120
- this.far = [];
121
- line.close(REFUSED, 'suite');
122
- return;
123
- }
124
- if (Array.isArray(m.announce)) {
125
- this.far = m.announce.filter((x) => typeof x === 'string');
126
- onAnnounce(this.far);
127
- }
128
- }
129
- catch {
130
- /* not for us */
131
- }
132
- return;
133
- }
134
- const buf = e.data instanceof ArrayBuffer ? new Uint8Array(e.data) : ArrayBuffer.isView(e.data) ? new Uint8Array(e.data.buffer, e.data.byteOffset, e.data.byteLength) : undefined;
135
- // A frame that is none of the three is not a frame: an unknown kind, an
136
- // ask too short to hold a pk, a nothing with bytes after its id. It is
137
- // dropped, and tells no ask waiting on its id that nothing was delivered.
138
- if (!buf || buf.length < 5)
139
- return;
140
- const kind = buf[0], id = new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(1);
141
- if (kind > NONE || (kind === ASK && buf.length < 69) || (kind === NONE && buf.length !== 5))
142
- return;
143
- if (kind === ASK) {
144
- const pk = hex(buf.subarray(5, 69));
145
- // A deliver that rejects has delivered nothing this side can vouch
146
- // for, and a listener does not die of it: the far side hears none.
147
- void deliver(pk, new Uint8Array(buf.subarray(69)))
148
- .then((back) => back, () => undefined)
149
- .then((back) => {
150
- const head = new Uint8Array(5);
151
- head[0] = back === undefined ? NONE : REPLY;
152
- new DataView(head.buffer).setUint32(1, id);
153
- if (line.readyState === OPEN)
154
- line.send(back === undefined ? head : concat([head, back]));
155
- });
156
- return;
157
- }
158
- const waiting = this.pending.get(id);
159
- if (!waiting)
160
- return;
161
- this.pending.delete(id);
162
- waiting(kind === REPLY ? new Uint8Array(buf.subarray(5)) : undefined);
163
- });
164
- line.addEventListener('error', () => { });
165
- line.addEventListener('close', (e) => {
166
- this.pending.clear(); // in flight and no word since: nothing is answered
167
- onClose({ code: e?.code, reason: e?.reason });
168
- });
169
- }
170
- // Say which ward pks this side holds.
171
- announce(pks) {
172
- if (this.line.readyState === OPEN)
173
- this.line.send(JSON.stringify({ announce: pks, suite: SUITE }));
174
- }
175
- async carry(pk, bytes) {
176
- try {
177
- await this.#open;
178
- }
179
- catch {
180
- return undefined;
181
- }
182
- if (this.line.readyState !== OPEN)
183
- return undefined;
184
- const id = this.#id++;
185
- const head = new Uint8Array(69);
186
- head[0] = ASK;
187
- new DataView(head.buffer).setUint32(1, id);
188
- head.set(unhex(pk), 5);
189
- return new Promise((ok) => {
190
- if (this.pending.size >= PENDING)
191
- this.pending.delete(this.pending.keys().next().value); // the oldest, which the ward's bound ended long ago
192
- this.pending.set(id, ok);
193
- this.line.send(concat([head, bytes]));
194
- });
195
- }
196
- close() {
197
- this.line.close();
198
- }
199
- }
@@ -1,35 +0,0 @@
1
- export type WardRecord = {
2
- pk: string;
3
- code: string;
4
- user: string;
5
- };
6
- export type Kept = {
7
- seed: Uint8Array;
8
- partition: Record<string, unknown>;
9
- record: WardRecord;
10
- };
11
- export type Store = {
12
- list(): Promise<string[]>;
13
- load(name: string): Promise<Kept | undefined>;
14
- put(name: string, kept: Kept): Promise<void>;
15
- save(name: string, partition: Record<string, unknown>, rows: readonly string[]): Promise<void>;
16
- record(name: string, record: WardRecord): Promise<void>;
17
- take(name: string): Promise<Kept | undefined>;
18
- };
19
- export declare const values: (p: Record<string, unknown>) => Record<string, unknown>;
20
- export declare const rowsOf: (partition: Record<string, unknown>, rows: readonly string[]) => [string, Record<string, unknown> | undefined][];
21
- type Row = {
22
- seed: Uint8Array;
23
- parts: Map<string, Record<string, unknown>>;
24
- record: WardRecord;
25
- };
26
- export declare class MemoryStore implements Store {
27
- readonly rows: Map<string, Row>;
28
- list(): Promise<string[]>;
29
- load(name: string): Promise<Kept | undefined>;
30
- put(name: string, kept: Kept): Promise<void>;
31
- save(name: string, partition: Record<string, unknown>, rows: readonly string[]): Promise<void>;
32
- record(name: string, record: WardRecord): Promise<void>;
33
- take(name: string): Promise<Kept | undefined>;
34
- }
35
- export {};
@@ -1,62 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The store: keeping wards by name. A ward is three things and the store
3
- // keeps all three under one name: the seed, the partition and the ward
4
- // record, which says where the class bodies come from and which being is
5
- // the user's. The store reads nothing it keeps; the partition is
6
- // values the ward wrote, and the seed is bytes only the ward derives from.
7
- // Every terrain has one: files on a disk, IndexedDB in a tab, an object's
8
- // storage on the edge; the memory store below is the library's own, for a
9
- // harbor core with no device under it. `src/conformance/store.ts` is what
10
- // every one of them passes.
11
- import { fromRows, rowOf, rowsIn } from '../ward/partition.js';
12
- // The partition is values only, and the ward hands it out through a guard,
13
- // so a store takes a copy of the values the way a file would: through JSON.
14
- export const values = (p) => JSON.parse(JSON.stringify(p));
15
- // The rows a store was told about, as values, ready to be written down. A row
16
- // whose key names nobody is a being who left, and comes back undefined, which
17
- // is the store dropping what it holds for her.
18
- export const rowsOf = (partition, rows) => rows.map((row) => {
19
- const value = rowOf(partition, row);
20
- return [row, value === undefined ? undefined : (values(value))];
21
- });
22
- // One store's copy, brought up to date with the rows it was told about.
23
- const keep = (row, partition, rows) => {
24
- for (const [id, value] of rowsOf(partition, rows)) {
25
- if (value === undefined)
26
- row.parts.delete(id);
27
- else
28
- row.parts.set(id, value);
29
- }
30
- };
31
- export class MemoryStore {
32
- rows = new Map();
33
- async list() {
34
- return [...this.rows.keys()];
35
- }
36
- async load(name) {
37
- const row = this.rows.get(name);
38
- return row && { seed: new Uint8Array(row.seed), partition: fromRows(Object.fromEntries(row.parts)), record: { ...row.record } };
39
- }
40
- async put(name, kept) {
41
- if (this.rows.has(name))
42
- throw new Error(`ward ${name} already exists here`);
43
- const row = { seed: new Uint8Array(kept.seed), parts: new Map(), record: { ...kept.record } };
44
- this.rows.set(name, row);
45
- keep(row, kept.partition, rowsIn(kept.partition));
46
- }
47
- async save(name, partition, rows) {
48
- const row = this.rows.get(name);
49
- if (row)
50
- keep(row, partition, rows);
51
- }
52
- async record(name, record) {
53
- const row = this.rows.get(name);
54
- if (row)
55
- row.record = { ...record };
56
- }
57
- async take(name) {
58
- const kept = await this.load(name);
59
- this.rows.delete(name);
60
- return kept;
61
- }
62
- }
@@ -1,42 +0,0 @@
1
- import type { Json, JsonObject } from '../being/types.ts';
2
- import type { Booted } from '../harbor/memory.ts';
3
- import type { World, Sealed } from './world.ts';
4
- export type DoorRecord = {
5
- case: string;
6
- name: string;
7
- state: string;
8
- ward: string;
9
- kind: 'silence' | 'word' | 'object';
10
- heard: boolean;
11
- ask: string;
12
- reply: string;
13
- opens: Json | null;
14
- draws: number;
15
- blueprint?: JsonObject;
16
- wrote: boolean;
17
- };
18
- export type Pin = () => Promise<void>;
19
- export type Arrival = {
20
- kind: DoorRecord['kind'];
21
- door: Booted;
22
- ask: Sealed | {
23
- bytes: Uint8Array;
24
- open: null;
25
- };
26
- };
27
- export type Case = {
28
- case: string;
29
- name: string;
30
- state: string;
31
- ward: string;
32
- arrive: (w: World, pin: Pin) => Promise<Arrival>;
33
- };
34
- export declare function stand(c: Case): Promise<{
35
- w: World;
36
- before: string;
37
- draws: number;
38
- arrival: Arrival;
39
- }>;
40
- export declare function doorCases(): Promise<DoorRecord[]>;
41
- export declare const caseNamed: (kase: string, name: string) => Case | undefined;
42
- export declare const cases: Case[];