nervur 0.19.2 → 0.20.0

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 (205) hide show
  1. package/README.md +66 -101
  2. package/dist/being/being.d.ts +7 -15
  3. package/dist/being/being.js +16 -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 +9 -0
  7. package/dist/being/faculty.js +68 -0
  8. package/dist/being/index.d.ts +4 -5
  9. package/dist/being/index.js +5 -5
  10. package/dist/being/types.d.ts +35 -62
  11. package/dist/being/types.js +3 -57
  12. package/dist/being/words.d.ts +13 -0
  13. package/dist/being/words.js +14 -0
  14. package/dist/contract/index.d.ts +26 -0
  15. package/dist/contract/index.js +19 -0
  16. package/dist/crypto/aes.d.ts +3 -0
  17. package/dist/crypto/aes.js +24 -0
  18. package/dist/crypto/bytes.d.ts +6 -0
  19. package/dist/crypto/bytes.js +33 -0
  20. package/dist/crypto/ed25519.d.ts +3 -0
  21. package/dist/crypto/ed25519.js +85 -0
  22. package/dist/crypto/hash.d.ts +2 -0
  23. package/dist/crypto/hash.js +11 -0
  24. package/dist/crypto/index.d.ts +7 -0
  25. package/dist/crypto/index.js +10 -0
  26. package/dist/crypto/json.d.ts +8 -0
  27. package/dist/crypto/json.js +250 -0
  28. package/dist/crypto/mlkem.d.ts +12 -0
  29. package/dist/crypto/mlkem.js +36 -0
  30. package/dist/crypto/subtle.d.ts +10 -0
  31. package/dist/crypto/subtle.js +29 -0
  32. package/dist/crypto/x25519.d.ts +2 -0
  33. package/dist/crypto/x25519.js +21 -0
  34. package/dist/folder/index.d.ts +13 -0
  35. package/dist/folder/index.js +125 -0
  36. package/dist/harbor/dock.d.ts +19 -0
  37. package/dist/harbor/dock.js +92 -0
  38. package/dist/harbor/harbor.d.ts +22 -0
  39. package/dist/harbor/harbor.js +84 -0
  40. package/dist/harbor/index.d.ts +4 -10
  41. package/dist/harbor/index.js +5 -18
  42. package/dist/harbor/registry.d.ts +7 -0
  43. package/dist/harbor/registry.js +13 -0
  44. package/dist/harbor/terrain.d.ts +8 -0
  45. package/dist/harbor/terrain.js +2 -0
  46. package/dist/index.d.ts +7 -0
  47. package/dist/index.js +14 -0
  48. package/dist/pointer/bodies.d.ts +20 -0
  49. package/dist/pointer/bodies.js +45 -0
  50. package/dist/pointer/index.d.ts +2 -0
  51. package/dist/pointer/index.js +4 -0
  52. package/dist/pointer/world.d.ts +34 -0
  53. package/dist/pointer/world.js +76 -0
  54. package/dist/quo/door.d.ts +34 -0
  55. package/dist/quo/door.js +172 -0
  56. package/dist/quo/index.d.ts +8 -0
  57. package/dist/quo/index.js +11 -0
  58. package/dist/quo/invitation.d.ts +7 -0
  59. package/dist/quo/invitation.js +15 -0
  60. package/dist/quo/keys.d.ts +40 -0
  61. package/dist/quo/keys.js +79 -0
  62. package/dist/quo/payload.d.ts +15 -0
  63. package/dist/quo/payload.js +55 -0
  64. package/dist/quo/relations.d.ts +40 -0
  65. package/dist/quo/relations.js +33 -0
  66. package/dist/quo/reply.d.ts +13 -0
  67. package/dist/quo/reply.js +35 -0
  68. package/dist/quo/seal.d.ts +42 -0
  69. package/dist/quo/seal.js +78 -0
  70. package/dist/quo/standing.d.ts +39 -0
  71. package/dist/quo/standing.js +90 -0
  72. package/dist/ward/allowance.d.ts +13 -9
  73. package/dist/ward/allowance.js +28 -67
  74. package/dist/ward/cells.d.ts +6 -6
  75. package/dist/ward/cells.js +67 -179
  76. package/dist/ward/index.d.ts +5 -10
  77. package/dist/ward/index.js +6 -13
  78. package/dist/ward/partition.d.ts +44 -60
  79. package/dist/ward/partition.js +56 -285
  80. package/dist/ward/stance.d.ts +29 -20
  81. package/dist/ward/stance.js +173 -406
  82. package/dist/ward/ward-being.d.ts +33 -0
  83. package/dist/ward/ward-being.js +76 -0
  84. package/dist/ward/ward.d.ts +43 -11
  85. package/dist/ward/ward.js +215 -363
  86. package/package.json +19 -33
  87. package/src/being/being.ts +31 -78
  88. package/src/being/digest.ts +7 -35
  89. package/src/being/faculty.ts +66 -0
  90. package/src/being/index.ts +5 -6
  91. package/src/being/types.ts +47 -145
  92. package/src/being/words.ts +31 -0
  93. package/src/contract/index.ts +44 -0
  94. package/src/crypto/aes.ts +26 -0
  95. package/src/crypto/bytes.ts +37 -0
  96. package/src/crypto/ed25519.ts +84 -0
  97. package/src/crypto/hash.ts +14 -0
  98. package/src/crypto/index.ts +10 -0
  99. package/src/crypto/json.ts +241 -0
  100. package/src/crypto/mlkem.ts +38 -0
  101. package/src/crypto/subtle.ts +33 -0
  102. package/src/crypto/x25519.ts +21 -0
  103. package/src/folder/index.ts +134 -0
  104. package/src/harbor/dock.ts +101 -0
  105. package/src/harbor/harbor.ts +105 -0
  106. package/src/harbor/index.ts +5 -19
  107. package/src/harbor/registry.ts +20 -0
  108. package/src/harbor/terrain.ts +13 -0
  109. package/src/index.ts +20 -0
  110. package/src/pointer/bodies.ts +47 -0
  111. package/src/pointer/index.ts +4 -0
  112. package/src/pointer/world.ts +91 -0
  113. package/src/quo/door.ts +178 -0
  114. package/src/quo/index.ts +11 -0
  115. package/src/quo/invitation.ts +15 -0
  116. package/src/quo/keys.ts +91 -0
  117. package/src/quo/payload.ts +61 -0
  118. package/src/quo/relations.ts +62 -0
  119. package/src/quo/reply.ts +38 -0
  120. package/src/quo/seal.ts +101 -0
  121. package/src/quo/standing.ts +111 -0
  122. package/src/stand/main.ts +19 -0
  123. package/src/stand/stand.ts +178 -0
  124. package/src/ward/allowance.ts +37 -75
  125. package/src/ward/cells.ts +63 -176
  126. package/src/ward/index.ts +6 -17
  127. package/src/ward/partition.ts +86 -326
  128. package/src/ward/stance.ts +185 -420
  129. package/src/ward/ward-being.ts +97 -0
  130. package/src/ward/ward.ts +229 -363
  131. package/dist/being/lent.d.ts +0 -32
  132. package/dist/being/lent.js +0 -72
  133. package/dist/being/silence.d.ts +0 -12
  134. package/dist/being/silence.js +0 -41
  135. package/dist/conformance/assert.d.ts +0 -11
  136. package/dist/conformance/assert.js +0 -106
  137. package/dist/conformance/beings.d.ts +0 -199
  138. package/dist/conformance/beings.js +0 -188
  139. package/dist/conformance/estate.d.ts +0 -5
  140. package/dist/conformance/estate.js +0 -388
  141. package/dist/conformance/index.d.ts +0 -80
  142. package/dist/conformance/index.js +0 -819
  143. package/dist/conformance/reach.d.ts +0 -10
  144. package/dist/conformance/reach.js +0 -72
  145. package/dist/conformance/store.d.ts +0 -5
  146. package/dist/conformance/store.js +0 -113
  147. package/dist/harbor/box.d.ts +0 -121
  148. package/dist/harbor/box.js +0 -121
  149. package/dist/harbor/core.d.ts +0 -55
  150. package/dist/harbor/core.js +0 -662
  151. package/dist/harbor/dial.d.ts +0 -9
  152. package/dist/harbor/dial.js +0 -81
  153. package/dist/harbor/memory.d.ts +0 -29
  154. package/dist/harbor/memory.js +0 -104
  155. package/dist/harbor/reach.d.ts +0 -36
  156. package/dist/harbor/reach.js +0 -199
  157. package/dist/harbor/store.d.ts +0 -35
  158. package/dist/harbor/store.js +0 -62
  159. package/dist/vector/cases.d.ts +0 -42
  160. package/dist/vector/cases.js +0 -223
  161. package/dist/vector/index.d.ts +0 -6
  162. package/dist/vector/index.js +0 -8
  163. package/dist/vector/stand.d.ts +0 -9
  164. package/dist/vector/stand.js +0 -77
  165. package/dist/vector/world.d.ts +0 -144
  166. package/dist/vector/world.js +0 -209
  167. package/dist/ward/arithmetic.d.ts +0 -31
  168. package/dist/ward/arithmetic.js +0 -249
  169. package/dist/ward/door.d.ts +0 -18
  170. package/dist/ward/door.js +0 -186
  171. package/dist/ward/ground.d.ts +0 -29
  172. package/dist/ward/ground.js +0 -56
  173. package/dist/ward/heirs.d.ts +0 -13
  174. package/dist/ward/heirs.js +0 -117
  175. package/dist/ward/json.d.ts +0 -2
  176. package/dist/ward/json.js +0 -163
  177. package/dist/ward/owner.d.ts +0 -13
  178. package/dist/ward/owner.js +0 -220
  179. package/dist/ward/seal.d.ts +0 -52
  180. package/dist/ward/seal.js +0 -150
  181. package/src/being/lent.ts +0 -72
  182. package/src/being/silence.ts +0 -46
  183. package/src/conformance/assert.ts +0 -100
  184. package/src/conformance/beings.ts +0 -188
  185. package/src/conformance/estate.ts +0 -412
  186. package/src/conformance/index.ts +0 -965
  187. package/src/conformance/reach.ts +0 -83
  188. package/src/conformance/store.ts +0 -125
  189. package/src/harbor/box.ts +0 -131
  190. package/src/harbor/core.ts +0 -699
  191. package/src/harbor/dial.ts +0 -112
  192. package/src/harbor/memory.ts +0 -123
  193. package/src/harbor/reach.ts +0 -221
  194. package/src/harbor/store.ts +0 -91
  195. package/src/vector/cases.ts +0 -257
  196. package/src/vector/index.ts +0 -11
  197. package/src/vector/stand.ts +0 -76
  198. package/src/vector/world.ts +0 -232
  199. package/src/ward/arithmetic.ts +0 -251
  200. package/src/ward/door.ts +0 -186
  201. package/src/ward/ground.ts +0 -142
  202. package/src/ward/heirs.ts +0 -116
  203. package/src/ward/json.ts +0 -144
  204. package/src/ward/owner.ts +0 -214
  205. package/src/ward/seal.ts +0 -178
@@ -1,662 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // The harbor core: what every harbor on a device is. It boots wards from a
3
- // store, keeps the map of ward pk to door for its own wards and pk to reach
4
- // for foreign ones, carries bytes to a pk, and hands bytes from the wire to
5
- // one door. It names no terrain: the store, the class loader and the lease
6
- // are the terrain's, handed in or laid over it. A disk harbor is this plus
7
- // files and a pid; a browser harbor is this plus IndexedDB and a lock; an
8
- // edge harbor is this over an object's storage. None of them is in this
9
- // tree, and every one of them passes the conformance suite untouched.
10
- //
11
- // The directory is filled four ways, in this order: the harbor's own
12
- // doors; a socket a dialer holds to it, bound once the door behind the
13
- // dialer's claim has proved it holds the key, and unbound at close; the
14
- // claims of a listener this harbor dialed, proved the same way and reached
15
- // through that line; and a hint, a pk at a URL. A hint never displaces a
16
- // reach proved at a door. The directory lives in memory and is kept by no
17
- // store: whoever learns a hint keeps it where it keeps everything, in a
18
- // being's cells, and hands it back at every boot. And one fallback: a dialer with nothing in its
19
- // directory for a pk sends down the socket it holds, because the listener
20
- // it dialed is the rendezvous and may hold that pk on another socket. Bytes
21
- // that arrive from the wire go to an own door or a held socket and never
22
- // onward by request or by fallback; that one rule is the rendezvous.
23
- import { Ward } from '../ward/ward.js';
24
- import { maker, entropy as random, learnPk } from '../ward/ground.js';
25
- import { silence } from '../being/silence.js';
26
- import { request } from './reach.js';
27
- import { isWardPk, openReply, wardSignPk } from '../ward/seal.js';
28
- import { concat, sealingPair, KEY } from '../ward/arithmetic.js';
29
- import { within, LATE } from '../ward/allowance.js';
30
- import { restoreRow, rowsIn } from '../ward/partition.js';
31
- import { rowsOf } from './store.js';
32
- export const DEFAULT_CODE = 'classes/index.ts';
33
- // The device's entropy, spelled once. A harbor draws it for a ward's seed at
34
- // birth, for the lid of every probe it sends, and it hands the same function
35
- // to every ward it boots as the ground's `random`.
36
- // How long a probe waits for the door behind a claim. A claim that answers
37
- // nothing in this time is not bound; the next announce is another chance.
38
- const PROBE = 10_000;
39
- // How many pks one announce may claim, and how many are proven at once.
40
- const ANNOUNCE = 64;
41
- const PROVING = 8;
42
- // How many times each reach has announced: a proof from an older announce
43
- // binds nothing when it lands.
44
- const announces = new WeakMap();
45
- // The reaches that are hints, so a hint can be replaced and put down without
46
- // touching a reach a door proved.
47
- const hinted = new WeakSet();
48
- export class Harbor {
49
- store;
50
- loader;
51
- wards = new Map(); // name -> pointers
52
- doors = new Map(); // ward pk -> door
53
- reaches = new Map(); // the directory: foreign ward pk -> reach
54
- down = new Set(); // pks this harbor will not reach right now: weather, for a test
55
- refused = new Map(); // own ward pk -> arrivals its door would not admit. what a terrain does with it is its own
56
- faults = new Map(); // ward name -> saves the store refused. what a terrain does with it is its own
57
- // Told every time a save is refused, with the ward's name and how many of
58
- // its saves the store has now refused. The refusal is already the answer of
59
- // the ask that caused it, so nobody is waiting on this; it is for the box's
60
- // own record, a journal line or a counter, and a terrain that keeps none
61
- // leaves it alone.
62
- onFault = () => { };
63
- unbooted = []; // the wards the store keeps that would not host this run, by name
64
- classes = {}; // classes handed in-process, beside the loader's
65
- #saving = new Map();
66
- // The dialers' sockets, in the order they were dialed: where a pk nobody
67
- // here knows is sent, because the listener a harbor dialed is a rendezvous
68
- // and may hold that pk for someone else. It is a list because a harbor may
69
- // dial more than one, and a rendezvous is a listener and nothing more, so
70
- // there is never only one of them. A pk is tried down the list until one
71
- // carries it: with a single slot, a harbor holding two lines answered
72
- // unreached for a pk the other line could have reached.
73
- fallbacks = [];
74
- // How a dialer says what this harbor holds now. An announce is a claim and
75
- // a claim is worth nothing until the door behind it answers a probe, so
76
- // saying it again costs a proof and buys nothing to an impostor. What is
77
- // not free is never saying it again: a ward booted, adopted or dropped
78
- // after a line opened would be announced only by the next line, and a line
79
- // that does not drop never opens again, so it would be unreachable through
80
- // that listener for as long as the socket stayed healthy.
81
- announcers = new Set();
82
- #announce() {
83
- for (const say of this.announcers)
84
- say();
85
- }
86
- // The box, as one invitation per ward: what this device hands a ward it
87
- // boots so that the ward takes the box's being as a standing. Set once
88
- // the harbor's own ward is standing, which is the usual order: a terrain
89
- // puts that ward up first, then boots the rest on a ground that can reach
90
- // it. A terrain says which ward is handed one by overriding `boxFor`, and
91
- // a stranger's ward is handed none.
92
- offering;
93
- constructor(store, loader = async () => ({})) {
94
- this.store = store;
95
- this.loader = loader;
96
- }
97
- boxFor(name, _record) {
98
- return this.offering?.(name) ?? Promise.resolve(undefined);
99
- }
100
- // Boot every ward the store keeps. One ward that will not host, a
101
- // partition this kit cannot read or a loader that throws, is named in
102
- // `unbooted` and takes nobody else down with it.
103
- async boot() {
104
- for (const name of await this.store.list()) {
105
- // A ward this harbor already hosts is not booted twice. A terrain that
106
- // keeps a ward of its own puts it up before this runs, so that a being
107
- // born in any other ward can lend from her first line.
108
- if (this.wards.has(name))
109
- continue;
110
- try {
111
- const kept = await this.store.load(name);
112
- if (kept)
113
- await this.host(name, kept);
114
- }
115
- catch {
116
- this.unbooted.push(name);
117
- }
118
- }
119
- }
120
- // The object a ward writes into, by the name it is kept under: the
121
- // partition the harbor was handed at boot and keeps so it can save it.
122
- // Nothing in the harbor reads it, and it is not on `Hosted`, because a
123
- // side that holds a ward would then hold every seed in it and reach any
124
- // being past every door. It is here for one reader, the conformance
125
- // suite, whose census is ward state read straight and whose forgeries are
126
- // ward state written straight, the way `MemoryHarbor.partitions` is. A
127
- // side asks the ward.
128
- partitionOf(name) {
129
- return this.#saving.get(name)?.memory;
130
- }
131
- // ---- saving
132
- // Every ward's writes kept and none waiting: no row named, no save armed
133
- // and none in flight. A write can name a row while another ward is being
134
- // saved, so this goes round until a pass finds nothing. A process about to
135
- // exit waits here, since its exit is not a turn the timer is given.
136
- async quiet() {
137
- for (;;) {
138
- const busy = [...this.#saving].filter(([, s]) => !s.gone && (s.dirty.size > 0 || s.timer !== undefined || s.running !== null));
139
- if (busy.length === 0)
140
- return;
141
- for (const [name, s] of busy)
142
- await (s.running ?? this.#flush(name));
143
- }
144
- }
145
- // The ward wrote one row. Its partition is saved once the line is free, and
146
- // once for every burst of writes, with every row named in that burst.
147
- #wrote(name, row) {
148
- const s = this.#saving.get(name);
149
- if (!s || s.gone)
150
- return;
151
- s.dirty.add(row);
152
- if (s.timer === undefined)
153
- s.timer = setTimeout(() => void this.#flush(name), 0);
154
- }
155
- // The rows a store refused, put back in the ward's memory to what the
156
- // store holds for them. A being whose row was refused would otherwise go
157
- // on answering out of a memory that runs ahead of the device: her next
158
- // write is a row past the same ceiling, refused again, and everything she
159
- // was told between now and the reload is gone with it. Standing her at
160
- // what is kept costs her the writes that were never kept, which is what
161
- // the asker was told, and leaves a small write after it one the store
162
- // takes.
163
- //
164
- // The shape is read where the shape lives. The harbor hands each row and
165
- // the copy it kept to the partition, which knows which part of itself a row
166
- // is; nothing here reads a being, a bind table or a head. And nothing here
167
- // asks the store: what it took is what this harbor handed it, which is
168
- // `kept`, and a row nobody has handed it since this ward was hosted is a
169
- // row the ward has not moved, so there is nothing to put back.
170
- // Every row together, and never the refused ones alone. A relation's state
171
- // is spread over rows, the head for the door's heirs and a being's own row
172
- // for the keys she speaks under: put one back and the door stands ahead of
173
- // the knocker and the relation never speaks again. So the ward is put back
174
- // to the whole point, which is what the store held at the last save taken
175
- // while no call was in flight. A row that point never knew is left where it
176
- // is and stays named: no save has ever taken it, so there is nothing to put
177
- // it back to. The rows that moved come back, so the store is put back with
178
- // them and a reload finds no half-done row.
179
- #standAtKept(s) {
180
- if (s.gone)
181
- return [];
182
- const moved = [];
183
- for (const row of s.kept.keys()) {
184
- const was = s.kept.get(row);
185
- const now = rowsOf(s.memory, [row])[0][1];
186
- if (JSON.stringify(was) === JSON.stringify(now))
187
- continue;
188
- restoreRow(s.memory, row, was);
189
- moved.push(row);
190
- }
191
- for (const row of moved)
192
- s.dirty.delete(row);
193
- return moved;
194
- }
195
- // The ward back to the point, and the store with it. The store is written
196
- // because a reload reads it and not the memory: a refusal that put only the
197
- // memory back would leave the rows the store did take standing ahead of the
198
- // ward until the next restart, and one half of a relation ahead of the
199
- // other. What goes out is what the store already took once, so it is a
200
- // write the store has no reason to refuse; one that refuses it anyway is
201
- // counted like any other, and there is nothing further to put back to.
202
- async #putBack(name, s) {
203
- // What the store took under the refused call is not a point to stand at.
204
- // It is also what has to be written over: a row the ward never moved
205
- // after that save comes back unchanged in memory and is still ahead of
206
- // the point in the store, so the rows that go out are the ones the memory
207
- // moved and the ones the store took under the call, together.
208
- const under = [...s.under.keys()];
209
- s.under.clear();
210
- const moved = this.#standAtKept(s);
211
- const rows = [...new Set([...moved, ...under])];
212
- if (rows.length === 0 || s.gone)
213
- return;
214
- try {
215
- await this.store.save(name, s.memory, rows);
216
- for (const row of rows)
217
- s.dirty.delete(row);
218
- }
219
- catch {
220
- // A store that will not take back what it took once is a store this
221
- // ward cannot be put back in. The count says so, the memory stands at
222
- // the point all the same, and the rows stay named for the next save.
223
- this.#fault(name);
224
- }
225
- }
226
- // The call ended and the ward is whole, so the point catches up with it. A
227
- // row the store took under the call and nobody has touched since is where
228
- // the ward stands, so it joins the point as it is; a row that moved after
229
- // that save is named for one more, and that save is quiet and moves the
230
- // point itself. Nothing is written twice for a ward whose call left its
231
- // rows where the store already has them.
232
- #settle(name, s) {
233
- for (const [row, value] of s.under) {
234
- const now = rowsOf(s.memory, [row])[0][1];
235
- if (JSON.stringify(value) === JSON.stringify(now))
236
- s.kept.set(row, value);
237
- else
238
- s.dirty.add(row);
239
- }
240
- s.under.clear();
241
- if (s.dirty.size > 0)
242
- void this.#flush(name);
243
- }
244
- // One refusal, counted and told. What a terrain does with the count is its
245
- // own; the arrival that caused it is told by the answer, not by this.
246
- #fault(name) {
247
- const faults = (this.faults.get(name) ?? 0) + 1;
248
- this.faults.set(name, faults);
249
- try {
250
- this.onFault(name, faults);
251
- }
252
- catch {
253
- /* a box that cannot write its own journal is not a ward's problem */
254
- }
255
- }
256
- // Answers whether everything it wrote was kept. False is a store that
257
- // refused, and it is the ward's answer to the arrival that caused it, not
258
- // a number in a map nobody reads. A flush that had nothing to write kept
259
- // everything it was given, which is true and is why a read is never
260
- // refused.
261
- async #flush(name) {
262
- const s = this.#saving.get(name);
263
- if (!s)
264
- return true;
265
- if (s.timer !== undefined) {
266
- clearTimeout(s.timer);
267
- s.timer = undefined;
268
- }
269
- // A flush already in flight is the one that will write these rows, and
270
- // its verdict is theirs: two arrivals whose writes went out together are
271
- // both refused when that write was refused, because neither of them is
272
- // in the store.
273
- if (s.running)
274
- return s.running;
275
- s.running = (async () => {
276
- // A put back that was owed to a refusal under a call is done here, the
277
- // first moment the ward is whole again. Nothing is written over it
278
- // until it is done, because what is in memory is half of a call the
279
- // store already said no to.
280
- // The pen is held from the refusal to the put back that answers it.
281
- // What a save would write between the two is about to be put back
282
- // anyway, so writing it buys nothing and costs the one thing that
283
- // matters: a store written there holds rows of two moments, one side of
284
- // a call kept and the other about to be undone, and a box that goes
285
- // down before the put back leaves it torn with nobody left to mend it.
286
- if (s.back) {
287
- if (s.calls > 0)
288
- return false; // the call that owed it is still in flight, and a put back inside one would roll back rows it is still writing
289
- s.back = false;
290
- await this.#putBack(name, s);
291
- return false; // the verdict of the refusal that owed it, and nothing is written over a put back in the same save
292
- }
293
- while (s.dirty.size > 0 && !s.gone) {
294
- // Taken before the write, so a row named while this one is in flight
295
- // is written by the next turn of the loop and not lost with it.
296
- const rows = [...s.dirty];
297
- s.dirty.clear();
298
- // The copy of what is about to be written, taken before the store is
299
- // asked and kept only if the store takes it. It is the same values
300
- // the store is about to write down, so it costs one copy of the rows
301
- // of this save and nothing of the rows it does not name.
302
- const writing = rowsOf(s.memory, rows);
303
- try {
304
- const quiet = s.calls === 0; // read before the store is asked: the point is where the ward stood when this save was taken
305
- await this.store.save(name, s.memory, rows);
306
- // The store took it, and a restart finds it. Whether it is a point
307
- // a refused ward may be put back to is another question: a save
308
- // taken under a call holds half of a relation, so it is written and
309
- // is not somewhere to stand.
310
- for (const [row, value] of writing)
311
- (quiet ? s.kept : s.under).set(row, value);
312
- }
313
- catch {
314
- this.#fault(name);
315
- // A row the store would not take is a row that is not kept, so it
316
- // stays named and the verdict is no. The loop ends here rather than
317
- // trying again: what the next attempt writes is the same bytes to
318
- // the same store, and it is the next arrival that asks for one, not
319
- // this one in a circle. Whoever wrote it is told, which is the
320
- // whole point, and the row waits where a reader can still see it.
321
- for (const row of rows)
322
- s.dirty.add(row);
323
- // The refusal waits for an arrival to hear it: a save taken by a
324
- // timer answers nobody, and a ward whose row was refused between
325
- // two arrivals would otherwise have the refusal counted and the
326
- // next door go on saying yes. And the ward goes back to the point
327
- // now, unless a call is in flight, in which case the put back is
328
- // owed until that call ends.
329
- s.fault = true;
330
- if (s.calls > 0) {
331
- s.back = true;
332
- return false;
333
- }
334
- await this.#putBack(name, s);
335
- return false;
336
- }
337
- }
338
- return true;
339
- })();
340
- try {
341
- return await s.running;
342
- }
343
- finally {
344
- s.running = null;
345
- }
346
- }
347
- // ---- the directory
348
- // Bytes to one of this harbor's own doors, and the one bit the door says
349
- // beside its reply, counted.
350
- async #own(pk, door, bytes) {
351
- const r = await door(bytes);
352
- if (!r.heard)
353
- this.refused.set(pk, (this.refused.get(pk) ?? 0) + 1);
354
- return new Uint8Array(r.bytes);
355
- }
356
- async carry(pk, bytes) {
357
- if (this.down.has(pk))
358
- return undefined;
359
- const door = this.doors.get(pk);
360
- if (door)
361
- return this.#own(pk, door, bytes);
362
- const bound = this.reaches.get(pk);
363
- if (bound)
364
- return bound.reach.carry(pk, bytes);
365
- for (const reach of this.fallbacks) {
366
- const back = await reach.carry(pk, bytes);
367
- if (back !== undefined)
368
- return back;
369
- }
370
- return undefined; // nobody here knows it, and no listener we dialed holds it
371
- }
372
- async deliver(pk, bytes) {
373
- if (this.down.has(pk))
374
- return undefined;
375
- const door = this.doors.get(pk);
376
- if (door)
377
- return this.#own(pk, door, bytes);
378
- const bound = this.reaches.get(pk);
379
- return bound?.held ? bound.reach.carry(pk, bytes) : undefined;
380
- }
381
- // A hint, taken on the word of whoever gives it. A hint already standing
382
- // for the pk is replaced, since a URL is the one part of a ward that moves;
383
- // a reach proven at a door is never displaced by one.
384
- hint(pk, url) {
385
- const standing = this.reaches.get(pk);
386
- if (standing && !hinted.has(standing.reach))
387
- return; // a reach proven at a door beats a hint, held or reached through a dialed line
388
- const reach = request(url);
389
- hinted.add(reach);
390
- this.reaches.set(pk, { reach, held: false });
391
- }
392
- // A hint kept only once the door behind it has proven it holds the pk's
393
- // key, the same probe an announce is proven by. A URL that answers for
394
- // another ward, or for nobody, is refused and nothing is kept: whoever
395
- // hands a harbor a hint could otherwise point one ward's asks at a place
396
- // that drops them. True is kept.
397
- async learn(pk, url) {
398
- const standing = this.reaches.get(pk);
399
- if (standing && !hinted.has(standing.reach))
400
- return true; // already reached, and proven at a door
401
- const reach = request(url);
402
- if (!(await this.prove(pk, reach)))
403
- return false;
404
- this.hint(pk, url);
405
- return true;
406
- }
407
- // A hint put down. A reach proven at a door is the line's, and goes when
408
- // the line does, never by a word from here.
409
- forget(pk) {
410
- const standing = this.reaches.get(pk);
411
- if (standing && hinted.has(standing.reach))
412
- this.reaches.delete(pk);
413
- }
414
- // An announce is a claim, and a claim binds nothing until proven: anyone
415
- // who can reach a listener could otherwise name a pk that is not theirs and
416
- // take its reachability. The proof is the door itself, as it already is. A
417
- // lid this harbor minted and noise after it is a box that does not open,
418
- // and the door answers one such box with silence sealed to the lid and
419
- // signed by the ward key. Only the holder of that ward's seed can write
420
- // that reply, the lid is fresh so nothing replays, and a box that does not
421
- // open writes nothing at the ward. This is the one box a harbor ever opens,
422
- // the one it sealed itself, and it reads nothing from it but that it is
423
- // the silence a door owes such a box, signed by the claimed key.
424
- async prove(pk, reach) {
425
- if (!isWardPk(pk))
426
- return false;
427
- try {
428
- const lid = await sealingPair(random(KEY));
429
- const back = await within(PROBE, reach.carry(pk, concat([lid.pk, random(KEY)])));
430
- if (back === LATE || back === undefined)
431
- return false;
432
- const reply = await openReply(back, lid.secret, wardSignPk(pk));
433
- return reply !== null && 'silence' in reply;
434
- }
435
- catch {
436
- return false;
437
- }
438
- }
439
- // Bind what the far side claims, each pk proven at its door first. Both
440
- // sides bind this way: the listener the dialer's claims, the dialer the
441
- // listener's, since a claim is a claim whichever end made it. An announce
442
- // names at most this many pks, and they are proven a few at a time: a
443
- // side that announced a thousand would otherwise cost a thousand keys, a
444
- // thousand frames and a thousand waits at once, on its word alone.
445
- async bind(pks, reach, held) {
446
- // An announce is the whole of what that side holds now, so a pk this
447
- // reach was bound for and does not claim now is unbound at once: a ward
448
- // that left a dialer is not reachable through it, proof or no proof.
449
- // And it is the newest word: a proof still in flight from an earlier
450
- // announce on this reach binds nothing when it lands, since the side
451
- // has spoken again since.
452
- const said = (announces.get(reach) ?? 0) + 1;
453
- announces.set(reach, said);
454
- for (const [pk, b] of this.reaches)
455
- if (b.reach === reach && !pks.includes(pk))
456
- this.reaches.delete(pk);
457
- const claimed = [...new Set(pks)].slice(0, ANNOUNCE);
458
- const prove = async (pk) => {
459
- if (this.doors.has(pk))
460
- return;
461
- // A line already held for this pk is not displaced by another line that
462
- // proves the same pk. A proof says only that the ward is reachable that
463
- // way, and a relay that forwards to the real ward proves as well as the
464
- // ward's own line does: taking the binding would add a hop the relay
465
- // chooses, and can drop. The first held line keeps it until it closes,
466
- // which unbind says.
467
- const standing = this.reaches.get(pk);
468
- if (standing?.held && standing.reach !== reach)
469
- return;
470
- if (!(await this.prove(pk, reach)))
471
- return;
472
- if (announces.get(reach) !== said)
473
- return; // a newer announce has spoken since
474
- if (this.reaches.get(pk)?.held && this.reaches.get(pk).reach !== reach)
475
- return; // one took it while this was proving
476
- this.reaches.set(pk, { reach, held });
477
- };
478
- for (let i = 0; i < claimed.length; i += PROVING)
479
- await Promise.all(claimed.slice(i, i + PROVING).map(prove));
480
- }
481
- unbind(reach) {
482
- for (const [pk, b] of this.reaches)
483
- if (b.reach === reach)
484
- this.reaches.delete(pk);
485
- }
486
- // ---- wards
487
- // Mint a seed and boot an empty ward under a name: a world born here.
488
- async create(name, user = '', code = DEFAULT_CODE) {
489
- const kept = { seed: random(KEY), partition: {}, record: { pk: '', code, user } };
490
- await this.store.put(name, kept);
491
- const hosted = await this.host(name, kept);
492
- await this.store.record(name, hosted.record);
493
- return hosted;
494
- }
495
- // Stop serving a ward and take it out of the store: what was kept comes
496
- // back, for another harbor to put. The partition is written first, and
497
- // never again under this name: whoever still holds the old pointers holds
498
- // a ward that saves nothing and answers its owner silence.
499
- async drop(name) {
500
- const h = this.wards.get(name);
501
- if (h) {
502
- await h.save();
503
- // Gone for whoever still holds the old pointers, whose save closure has
504
- // this row in hand, and out of the map, which now says what this harbor
505
- // serves and nothing else.
506
- const s = this.#saving.get(name);
507
- if (s)
508
- s.gone = true;
509
- this.#saving.delete(name);
510
- this.wards.delete(name);
511
- this.doors.delete(h.pk);
512
- this.#announce(); // this harbor no longer claims it: a listener unbinds a pk an announce stops naming
513
- }
514
- return this.store.take(name);
515
- }
516
- // Put what another harbor dropped, and boot it: same seed, same pk. A ward
517
- // that will not boot is not adopted. This is the one path where a partition
518
- // written somewhere else arrives, so it is the one place the ward's own
519
- // shape check is met, and the name is refused for as long as it is kept:
520
- // a partition put and left would hold the name against every later try,
521
- // with a copy of it in a store that can never serve it. It goes back out,
522
- // and the only copy is the one the caller is still holding.
523
- async adopt(name, kept) {
524
- await this.store.put(name, kept);
525
- try {
526
- return await this.host(name, kept);
527
- }
528
- catch (e) {
529
- await this.store.take(name);
530
- throw e;
531
- }
532
- }
533
- // A ward from what the store keeps: the ground, once, and two pointers.
534
- async host(name, kept) {
535
- const { seed, partition: memory } = kept;
536
- const classes = await this.loader(kept.record);
537
- const objects = new WeakMap(); // cells -> the object instantiate made. a side's hand, never the ward's; it follows the cells out when she is unbooted
538
- const ground = {
539
- seed,
540
- // This ward's own classes first, then the harbor's: a ward that names a
541
- // class of its own is answered with hers.
542
- instantiate: maker(objects, classes, this.classes),
543
- carry: (pk, bytes) => this.carry(pk, new Uint8Array(bytes)),
544
- random,
545
- memory: {
546
- rows: memory,
547
- told: (row) => this.#wrote(name, row),
548
- // What the ward wrote is in the store when this resolves, and it says
549
- // whether the store took it. The ward calls it at the end of every
550
- // arrival, before it seals, so a call through a pointer has already
551
- // waited for the store when it comes back: a restart right after one
552
- // finds everything, and a refusal is that arrival's own answer rather
553
- // than a number counted behind it. A ward that wrote nothing has
554
- // nothing to save, so asking it what it holds is free, which is what
555
- // makes the ask pointer the way to find out rather than an expensive
556
- // one.
557
- //
558
- // A ward on its way out keeps nothing and refuses nothing: an arrival
559
- // racing an unhost is not a store saying no, and saying so would be a
560
- // lie in the one direction that matters.
561
- kept: async () => {
562
- const s = this.#saving.get(name);
563
- if (!s || s.gone)
564
- return true;
565
- const ok = await this.#flush(name);
566
- // A refusal is heard once, by the first arrival to ask after it: the
567
- // save that met it may have been a timer's, which had nobody to tell.
568
- // Under a call it is left standing, so the call that carried the
569
- // refused write is refused too, and the outermost arrival hears it.
570
- const no = !ok || s.fault;
571
- s.fault = false; // heard. the next arrival is answered by the store and not by what happened before it
572
- return !no;
573
- },
574
- // The ward says a call is in flight, and says when it ends. The harbor
575
- // goes on writing under it; what it does not do is take a save made
576
- // there as the point a refused ward is put back to, and a refusal that
577
- // lands under a call waits here for the call to end before it puts the
578
- // ward back. The ward is the one that knows, because a being reaching
579
- // another being of her own ward goes to that ward's door directly and
580
- // the harbor never sees that call at all.
581
- during: () => {
582
- const s = this.#saving.get(name);
583
- if (!s)
584
- return () => { };
585
- s.calls += 1;
586
- let lowered = false;
587
- return () => {
588
- if (lowered)
589
- return;
590
- lowered = true;
591
- s.calls -= 1;
592
- if (s.calls > 0 || s.gone)
593
- return;
594
- // The moment the ward is whole again: the point catches up with the
595
- // call that just ended, or a put back that was owed is done now, so
596
- // a being stands at what is kept before her next word rather than
597
- // at the next save.
598
- if (s.back)
599
- void this.#flush(name);
600
- else
601
- this.#settle(name, s);
602
- };
603
- },
604
- },
605
- };
606
- const box = await this.boxFor(name, kept.record);
607
- if (box)
608
- ground.box = box;
609
- // What the store holds for this ward at birth is what it was just handed
610
- // or just read, row by row. It is copied once here so that a being whose
611
- // very first write of this run is refused is put back too, rather than
612
- // waiting for a save of hers to succeed before the harbor knows what she
613
- // was. One copy of a partition per hosted ward, paid at boot and never
614
- // again: after this, a row's copy is replaced by the save that kept it.
615
- const saving = { memory, dirty: new Set(), kept: new Map(rowsOf(memory, rowsIn(memory))), running: null, timer: undefined, gone: false, calls: 0, fault: false, back: false, under: new Map() };
616
- this.#saving.set(name, saving);
617
- // A ward that will not be born leaves nothing behind: a partition of a
618
- // shape this kit cannot read throws here, and a name the harbor does not
619
- // serve must not have a saving row saying it does. Learning the pk is
620
- // inside this, because a ward that will not say its pk is a ward this
621
- // harbor cannot route to and is no more born than one that threw.
622
- let w, pk;
623
- try {
624
- w = await Ward(ground);
625
- pk = await learnPk(w);
626
- }
627
- catch (e) {
628
- saving.gone = true;
629
- this.#saving.delete(name);
630
- throw e;
631
- }
632
- // The same keep, for a caller that holds the ward rather than asks it:
633
- // a side that wrote through `being` and wants the store current says so
634
- // here. It answers nothing, because a caller inside the process reads
635
- // the fault off the harbor and an arrival is the only thing that needs a
636
- // verdict of its own.
637
- const save = async () => void (await ground.memory.kept());
638
- const door = (bytes) => w.door(bytes);
639
- const ask = async (method, args) => {
640
- if (saving.gone)
641
- return silence;
642
- return w.ask(method, args);
643
- };
644
- // The objects map is keyed by the cells the ward handed instantiate, so
645
- // the way back to a being is her cells; the partition is read for that
646
- // one lookup and for nothing else, and a key with no object is a being
647
- // who is not here this run. `keys` is what the map holds, so a caller
648
- // never needs the beings table to find out what to ask for.
649
- const beings = memory.beings;
650
- const being = (key) => {
651
- const cells = beings && Object.hasOwn(beings, key) ? beings[key] : undefined;
652
- return cells ? objects.get(cells) : undefined;
653
- };
654
- const keys = () => Object.keys(beings ?? {}).filter((k) => being(k) !== undefined);
655
- const hosted = { door, ask, pk, name, record: { ...kept.record, pk }, being, keys, save };
656
- this.wards.set(name, hosted);
657
- this.doors.set(hosted.pk, door);
658
- await save();
659
- this.#announce(); // reachable through every line this harbor already holds, not only the next one
660
- return hosted;
661
- }
662
- }
@@ -1,9 +0,0 @@
1
- import type { Harbor } from './core.ts';
2
- import { Socket } from './reach.ts';
3
- export type Dialer = {
4
- url: string;
5
- socket: Socket | null;
6
- wake(): void;
7
- close(): void;
8
- };
9
- export declare function dial(harbor: Harbor, url: string): Dialer;