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