nervur 0.21.0 → 0.21.2
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 +14 -1
- package/dist/cli/command.js +11 -4
- package/dist/contract/index.js +2 -1
- package/dist/harbor/catalogue.js +11 -3
- package/dist/harbor/dock.d.ts +5 -1
- package/dist/harbor/dock.js +63 -10
- package/dist/harbor/harbor.d.ts +3 -1
- package/dist/harbor/harbor.js +78 -12
- package/dist/harbor/index.d.ts +1 -0
- package/dist/harbor/index.js +1 -0
- package/dist/harbor/package.d.ts +2 -1
- package/dist/harbor/package.js +12 -6
- package/dist/harbor/registry.js +6 -1
- package/dist/harbor/store.d.ts +13 -0
- package/dist/harbor/store.js +53 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/node/index.js +12 -4
- package/dist/proof/index.d.ts +1 -1
- package/dist/proof/index.js +4 -2
- package/dist/proof/law.d.ts +40 -2
- package/dist/proof/law.js +251 -5
- package/dist/ward/ward.js +18 -12
- package/package.json +1 -1
- package/src/cli/command.ts +11 -4
- package/src/contract/index.ts +2 -1
- package/src/harbor/catalogue.ts +11 -4
- package/src/harbor/dock.ts +63 -12
- package/src/harbor/harbor.ts +78 -14
- package/src/harbor/index.ts +1 -0
- package/src/harbor/package.ts +16 -6
- package/src/harbor/registry.ts +7 -1
- package/src/harbor/store.ts +69 -0
- package/src/index.ts +2 -0
- package/src/node/index.ts +11 -4
- package/src/proof/index.ts +4 -2
- package/src/proof/law.ts +244 -5
- package/src/ward/ward.ts +15 -10
package/README.md
CHANGED
|
@@ -212,13 +212,24 @@ catalogue, and the root's asks write the rest: `add` on the catalogue,
|
|
|
212
212
|
`open`, `host`, `route`, and `boot` on a ward. `host { ward, seed? }`
|
|
213
213
|
stands a ward under a seed you name, or a drawn one.
|
|
214
214
|
|
|
215
|
+
A ward's place is kept by the root memory unless you name a store.
|
|
216
|
+
`open { key: 'vault', class: 'org.nervur.ground-store' }` opens the
|
|
217
|
+
store every ground ships, and `host { ward, store: 'vault' }` keeps the
|
|
218
|
+
ward there. A class of yours extending `Store` keeps a ward in a bucket
|
|
219
|
+
or a database the same way, and `move { ward, store }` carries a ward
|
|
220
|
+
between keepers whole.
|
|
221
|
+
|
|
215
222
|
## What it holds
|
|
216
223
|
|
|
217
224
|
- **Being, Faculty.** What you extend. A faculty's contract is its class
|
|
218
225
|
and every class between it and `Faculty`.
|
|
219
226
|
- **Entropy, Clock, Custody, Memory, Carrier, Loader.** The contracts a
|
|
220
227
|
terrain fulfils. Each has one suite, and every body that fulfils it
|
|
221
|
-
passes it.
|
|
228
|
+
passes it. `nervur/proof` hands you those suites and the unpacking
|
|
229
|
+
law's as scenes, a title and a function each, so a body of your own,
|
|
230
|
+
a store in a cloud or a secret of your platform's, runs them under
|
|
231
|
+
your own runner: `for (const [title, scene] of memoryScenes('Mine',
|
|
232
|
+
make, expect)) test(title, scene)`. `@nervur-org/s3` is one such body.
|
|
222
233
|
- **Harbor, Terrain, Dock, Catalogue, Registry, WardBeing.** The onion.
|
|
223
234
|
- **World, PointerTerrain** and the pointer bodies, `HeldLoader` among
|
|
224
235
|
them. A whole world with this package alone.
|
|
@@ -240,6 +251,8 @@ stands a ward under a seed you name, or a drawn one.
|
|
|
240
251
|
- **WebDialer, webFaculty**: Quo over the web, a post to `https://` or a
|
|
241
252
|
held line to `wss://`, in any engine with `fetch` and WebSocket.
|
|
242
253
|
`webServe` and `webGround`, from `nervur/http`, listen for it on Node.
|
|
254
|
+
`answerPost(harbor, listen, request)` answers one post and nothing
|
|
255
|
+
more, for a function that opens a harbor per request and closes it.
|
|
243
256
|
|
|
244
257
|
- **The relay**, `org.nervur.relay` and `org.nervur.relay-client`, in
|
|
245
258
|
every harbor: a harbor nobody can dial is reached through one that
|
package/dist/cli/command.js
CHANGED
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
// dev, from this folder, at
|
|
29
29
|
// http://127.0.0.1:P/quo
|
|
30
30
|
// nervur open <key> <kind> a faculty stands in the box ward
|
|
31
|
-
// nervur host <ward> [--seed S]
|
|
31
|
+
// nervur host <ward> [--seed S] [--store K] a hosted ward stands, kept by the
|
|
32
|
+
// root memory or the store K
|
|
33
|
+
// nervur move <ward> <store | root> a hosted ward carried to a store,
|
|
34
|
+
// or back to the root memory
|
|
32
35
|
// nervur route <ward pk> <address... | none> where a ward is reached, in order
|
|
33
36
|
// nervur reach <address...> where this harbor is reached, as
|
|
34
37
|
// every invitation it gives says
|
|
@@ -59,8 +62,8 @@ import { edgeDev } from './edge.js';
|
|
|
59
62
|
import { carry, isServed, land, open, pack, request, serve } from './harbor.js';
|
|
60
63
|
// Where `edge dev` answers unless --port names another, wrangler's own.
|
|
61
64
|
const EDGE_PORT = 8787;
|
|
62
|
-
const USAGE = 'usage: nervur <init|serve|module|modules|census|sweep|open|host|route|reach|boot|unboot|public|invite|ask|own|disown|pilot|export|import|edge> [args] [--dir D] [--ward W] [--via V] [--seed S] [--with-seed] [--host H] [--port P]';
|
|
63
|
-
const FLAGS = new Set(['dir', 'ward', 'via', 'seed', 'host', 'port']);
|
|
65
|
+
const USAGE = 'usage: nervur <init|serve|module|modules|census|sweep|open|host|move|route|reach|boot|unboot|public|invite|ask|own|disown|pilot|export|import|edge> [args] [--dir D] [--ward W] [--via V] [--seed S] [--store K] [--with-seed] [--host H] [--port P]';
|
|
66
|
+
const FLAGS = new Set(['dir', 'ward', 'via', 'seed', 'store', 'host', 'port']);
|
|
64
67
|
// The one flag that stands alone, since what it names is a yes.
|
|
65
68
|
const ALONE = 'with-seed';
|
|
66
69
|
export const parse = (argv) => {
|
|
@@ -120,7 +123,11 @@ export const requestOf = (command, words, flags) => {
|
|
|
120
123
|
}
|
|
121
124
|
case 'host': {
|
|
122
125
|
const [ward] = need(1);
|
|
123
|
-
return { method: 'host', args: { ward: ward, ...(flags.seed === undefined ? {} : { seed: flags.seed }) } };
|
|
126
|
+
return { method: 'host', args: { ward: ward, ...(flags.seed === undefined ? {} : { seed: flags.seed }), ...(flags.store === undefined ? {} : { store: flags.store }) } };
|
|
127
|
+
}
|
|
128
|
+
case 'move': {
|
|
129
|
+
const [ward, store] = need(2);
|
|
130
|
+
return { method: 'move', args: { ward: ward, store: store === 'root' ? null : store } };
|
|
124
131
|
}
|
|
125
132
|
case 'route': {
|
|
126
133
|
const [ward, ...where] = words;
|
package/dist/contract/index.js
CHANGED
|
@@ -18,7 +18,8 @@ export class Memory {
|
|
|
18
18
|
export class Custody {
|
|
19
19
|
}
|
|
20
20
|
// Code, by the name a harbor keeps for it, or null where this loader has
|
|
21
|
-
// none. Code
|
|
21
|
+
// none. Code it has that does not load throws, saying why. Code never
|
|
22
|
+
// crosses a door and never lives in a cell, so it is the
|
|
22
23
|
// one thing beside the seed a terrain hands in: a map, an import, a bundle.
|
|
23
24
|
export class Loader {
|
|
24
25
|
}
|
package/dist/harbor/catalogue.js
CHANGED
|
@@ -42,8 +42,14 @@ export class Catalogue extends Faculty {
|
|
|
42
42
|
// it. The rest stay out, each with its reason.
|
|
43
43
|
async wake() {
|
|
44
44
|
for (const [module, row] of Object.entries(this.#rows)) {
|
|
45
|
-
const loaded = await this.#harbor.load(row.from).catch(() =>
|
|
46
|
-
const refused = loaded
|
|
45
|
+
const loaded = await this.#harbor.load(row.from).catch((e) => e);
|
|
46
|
+
const refused = loaded instanceof Error
|
|
47
|
+
? loaded.message
|
|
48
|
+
: loaded === null
|
|
49
|
+
? `not found at ${row.from}`
|
|
50
|
+
: loaded.module !== module
|
|
51
|
+
? `${row.from} is module ${loaded.module}`
|
|
52
|
+
: this.#harbor.check([...this.#running.values(), loaded]);
|
|
47
53
|
if (refused === null)
|
|
48
54
|
this.#running.set(module, loaded);
|
|
49
55
|
else
|
|
@@ -54,7 +60,9 @@ export class Catalogue extends Faculty {
|
|
|
54
60
|
async add(args) {
|
|
55
61
|
if (!text(args.from))
|
|
56
62
|
return { error: 'from is text' };
|
|
57
|
-
const loaded = await this.#harbor.load(args.from);
|
|
63
|
+
const loaded = await this.#harbor.load(args.from).catch((e) => e);
|
|
64
|
+
if (loaded instanceof Error)
|
|
65
|
+
return { error: loaded.message };
|
|
58
66
|
if (loaded === null)
|
|
59
67
|
return { error: 'no such module' };
|
|
60
68
|
const running = new Map(this.#running);
|
package/dist/harbor/dock.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type Asker, type AskSpec, type Blueprint, type Invitation, type JsonObject } from '../being/index.ts';
|
|
2
2
|
import { WardBeing, type Lender, type WardBeingStance } from '../ward/index.ts';
|
|
3
3
|
export interface Hosting {
|
|
4
|
-
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
4
|
+
host(name: string, invitation: Invitation, seed?: string, store?: string): Promise<string | null>;
|
|
5
|
+
move(name: string, seed: string, from: string | undefined, to: string | undefined): Promise<string | null>;
|
|
6
|
+
release(name: string, store: string | undefined): Promise<void>;
|
|
5
7
|
route(pk: string, at: readonly string[] | null): boolean;
|
|
6
8
|
keep(): Promise<boolean>;
|
|
7
9
|
failures(): Failures;
|
|
@@ -13,6 +15,7 @@ export interface Hosting {
|
|
|
13
15
|
export type Hosted = {
|
|
14
16
|
ward: string;
|
|
15
17
|
seed: string;
|
|
18
|
+
store?: string;
|
|
16
19
|
};
|
|
17
20
|
export type Failures = {
|
|
18
21
|
wards: Record<string, string>;
|
|
@@ -41,6 +44,7 @@ export declare class Dock extends WardBeing implements Lender {
|
|
|
41
44
|
open(args: JsonObject): Promise<JsonObject>;
|
|
42
45
|
sweep(): Promise<JsonObject>;
|
|
43
46
|
host(args: JsonObject): Promise<JsonObject>;
|
|
47
|
+
move(args: JsonObject): Promise<JsonObject>;
|
|
44
48
|
lend(args: JsonObject): Promise<JsonObject>;
|
|
45
49
|
retract(args: JsonObject): Promise<JsonObject>;
|
|
46
50
|
offer(contract: string): Promise<Invitation | null>;
|
package/dist/harbor/dock.js
CHANGED
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
//
|
|
6
6
|
// open { key, class } a faculty stands in the box ward, and the
|
|
7
7
|
// dock holds a standing on her under her key
|
|
8
|
-
// host { ward, seed
|
|
9
|
-
//
|
|
10
|
-
// dock; the dock keeps its name
|
|
8
|
+
// host { ward, seed?, a hosted ward stands under the seed named,
|
|
9
|
+
// store? } or a drawn one, holding a standing on the
|
|
10
|
+
// dock; the dock keeps its name, its seed and
|
|
11
|
+
// the store faculty that keeps its place, or
|
|
12
|
+
// none for the root memory
|
|
13
|
+
// move { ward, store } a hosted ward's place carried whole to
|
|
14
|
+
// another keeper, a store's key or null for
|
|
15
|
+
// the root memory, and forgotten where it was
|
|
11
16
|
// lend { contract } asked by a hosted ward: an offer from a
|
|
12
17
|
// faculty that fulfils the contract
|
|
13
18
|
// retract { contract, heir } an offer that was not taken
|
|
@@ -22,6 +27,7 @@
|
|
|
22
27
|
// route, where the root set none.
|
|
23
28
|
import { Faculty, KIT } from '../being/index.js';
|
|
24
29
|
import { CATALOGUE } from './catalogue.js';
|
|
30
|
+
import { STORE } from './store.js';
|
|
25
31
|
import { readAt, readInvitation } from '../quo/index.js';
|
|
26
32
|
import { ownerAsk, pilots, rootAsk, WardBeing } from '../ward/index.js';
|
|
27
33
|
export const BOX = 'box';
|
|
@@ -35,7 +41,8 @@ export class Dock extends WardBeing {
|
|
|
35
41
|
static asks = {
|
|
36
42
|
...WardBeing.asks,
|
|
37
43
|
open: ownerAsk('a faculty of this harbor', { required: ['key', 'class'] }),
|
|
38
|
-
host: ownerAsk('a ward this harbor hosts', { required: ['ward'] }),
|
|
44
|
+
host: ownerAsk('a ward this harbor hosts, kept by the root memory or by the store named', { required: ['ward'] }),
|
|
45
|
+
move: ownerAsk('a hosted ward carried to a store, or to the root memory with null', { required: ['ward', 'store'] }),
|
|
39
46
|
route: ownerAsk('where a ward is reached, addresses in order, or null', { required: ['ward', 'at'] }),
|
|
40
47
|
reach: ownerAsk('where this harbor is reached, the addresses every invitation carries', { required: ['at'] }),
|
|
41
48
|
sweep: rootAsk('every place of the memory this harbor does not name, gone'),
|
|
@@ -55,7 +62,11 @@ export class Dock extends WardBeing {
|
|
|
55
62
|
return blueprint;
|
|
56
63
|
}
|
|
57
64
|
get hosted() {
|
|
58
|
-
return this.cells.hosted.map(({ ward, seed }) => ({ ward, seed }));
|
|
65
|
+
return this.cells.hosted.map(({ ward, seed, store }) => ({ ward, seed, ...(store === undefined ? {} : { store }) }));
|
|
66
|
+
}
|
|
67
|
+
// Whether a key names a store faculty the dock opened.
|
|
68
|
+
#store(key) {
|
|
69
|
+
return text(key) && this.#fulfilling(STORE).includes(key);
|
|
59
70
|
}
|
|
60
71
|
get routes() {
|
|
61
72
|
return Object.fromEntries(Object.entries(this.cells.routes).map(([pk, at]) => [pk, [...at]]));
|
|
@@ -97,8 +108,11 @@ export class Dock extends WardBeing {
|
|
|
97
108
|
if ((await this.stance.boot(kind, CATALOGUE, CATALOGUE)) === null || !(await this.#hosting.keep()))
|
|
98
109
|
throw new Error('the catalogue did not stand');
|
|
99
110
|
}
|
|
100
|
-
// A faculty that goes stops listening first
|
|
111
|
+
// A faculty that goes stops listening first, and a store keeping a
|
|
112
|
+
// ward's place does not go.
|
|
101
113
|
async unboot(args) {
|
|
114
|
+
if (this.hosted.some((h) => h.store !== undefined && h.store === args.being))
|
|
115
|
+
return { error: 'a ward is kept by this store' };
|
|
102
116
|
if (typeof args.being === 'string')
|
|
103
117
|
await this.#hosting.lull(args.being);
|
|
104
118
|
return super.unboot(args);
|
|
@@ -126,21 +140,60 @@ export class Dock extends WardBeing {
|
|
|
126
140
|
}
|
|
127
141
|
async host(args) {
|
|
128
142
|
const name = args.ward;
|
|
129
|
-
if (!text(name)
|
|
130
|
-
return { error: '
|
|
143
|
+
if (!text(name))
|
|
144
|
+
return { error: 'ward is text' };
|
|
145
|
+
if (name === BOX)
|
|
146
|
+
return { error: 'box is the box ward' };
|
|
147
|
+
if (this.hosted.some((h) => h.ward === name))
|
|
148
|
+
return { error: `${name} is hosted already` };
|
|
131
149
|
if (args.seed !== undefined && !text(args.seed))
|
|
132
150
|
return { error: 'seed is text' };
|
|
151
|
+
const store = args.store ?? undefined;
|
|
152
|
+
if (store !== undefined && !this.#store(store))
|
|
153
|
+
return { error: 'no such store' };
|
|
133
154
|
const invitation = await this.stance.occupants.invite(name, { hosted: true });
|
|
134
155
|
if (invitation === null)
|
|
135
156
|
return { error: 'not hosted' };
|
|
136
|
-
const seed = await this.#hosting.host(name, invitation, args.seed);
|
|
157
|
+
const seed = await this.#hosting.host(name, invitation, args.seed, store);
|
|
137
158
|
if (seed === null) {
|
|
138
159
|
this.stance.occupants.remove(name);
|
|
139
160
|
return { error: 'not hosted' };
|
|
140
161
|
}
|
|
141
|
-
this.cells.hosted.push({ ward: name, seed });
|
|
162
|
+
this.cells.hosted.push({ ward: name, seed, ...(store === undefined ? {} : { store }) });
|
|
142
163
|
return { hosted: name };
|
|
143
164
|
}
|
|
165
|
+
// The place is written where it goes before the dock's cells say so, and
|
|
166
|
+
// forgotten where it was only once they are kept, so a cut at any step
|
|
167
|
+
// leaves the ward whole under the keeper the cells name.
|
|
168
|
+
async move(args) {
|
|
169
|
+
const rows = this.cells.hosted;
|
|
170
|
+
const row = rows.find((h) => h.ward === args.ward);
|
|
171
|
+
if (!row)
|
|
172
|
+
return { error: 'no such ward' };
|
|
173
|
+
const to = args.store ?? undefined;
|
|
174
|
+
if (to !== undefined && !this.#store(to))
|
|
175
|
+
return { error: 'no such store' };
|
|
176
|
+
const from = row.store;
|
|
177
|
+
if (from === to)
|
|
178
|
+
return { error: `${row.ward} is kept there already` };
|
|
179
|
+
const refused = await this.#hosting.move(row.ward, row.seed, from, to);
|
|
180
|
+
if (refused !== null)
|
|
181
|
+
return { error: refused };
|
|
182
|
+
const set = (store) => {
|
|
183
|
+
if (store === undefined)
|
|
184
|
+
delete row.store;
|
|
185
|
+
else
|
|
186
|
+
row.store = store;
|
|
187
|
+
};
|
|
188
|
+
set(to);
|
|
189
|
+
if (!(await this.#hosting.keep())) {
|
|
190
|
+
set(from);
|
|
191
|
+
await this.#hosting.move(row.ward, row.seed, to, from);
|
|
192
|
+
return { error: 'not kept' };
|
|
193
|
+
}
|
|
194
|
+
await this.#hosting.release(row.ward, from);
|
|
195
|
+
return { moved: row.ward, store: to ?? null };
|
|
196
|
+
}
|
|
144
197
|
async lend(args) {
|
|
145
198
|
if (!text(args.contract))
|
|
146
199
|
return { error: 'contract is text' };
|
package/dist/harbor/harbor.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Res
|
|
|
13
13
|
private constructor();
|
|
14
14
|
static open(from?: Terrain | Probing): Promise<Harbor>;
|
|
15
15
|
sweep(): Promise<string[]>;
|
|
16
|
+
move(name: string, seed: string, from: string | undefined, to: string | undefined): Promise<string | null>;
|
|
17
|
+
release(name: string, store: string | undefined): Promise<void>;
|
|
16
18
|
failures(): Failures;
|
|
17
19
|
get box(): Ward;
|
|
18
20
|
get dock(): Dock;
|
|
@@ -30,7 +32,7 @@ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Res
|
|
|
30
32
|
run(modules: readonly Loaded[]): string | null;
|
|
31
33
|
stands(kinds: readonly string[]): boolean;
|
|
32
34
|
kindsOf(module: string): string[];
|
|
33
|
-
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
35
|
+
host(name: string, invitation: Invitation, seed?: string, store?: string): Promise<string | null>;
|
|
34
36
|
keep(): Promise<boolean>;
|
|
35
37
|
route(pk: string, at: readonly string[] | null): boolean;
|
|
36
38
|
grounds(kind: string): boolean;
|
package/dist/harbor/harbor.js
CHANGED
|
@@ -6,6 +6,7 @@ import { Catalogue, CATALOGUE, catalogueFor } from './catalogue.js';
|
|
|
6
6
|
import { CarrierFaculty, UNDIALED } from './carrying.js';
|
|
7
7
|
import { BOX, Dock } from './dock.js';
|
|
8
8
|
import { Package } from './package.js';
|
|
9
|
+
import { groundStore, Store } from './store.js';
|
|
9
10
|
import { Registry } from './registry.js';
|
|
10
11
|
import { relayClient, relayFaculty } from './relay.js';
|
|
11
12
|
import { probeGround } from './probe.js';
|
|
@@ -22,7 +23,7 @@ export class Harbor extends Carrier {
|
|
|
22
23
|
super();
|
|
23
24
|
this.terrain = terrain;
|
|
24
25
|
this.#package = pack;
|
|
25
|
-
const faculties = [...terrain.faculties.map((make) => make(this)), relayFaculty(terrain.clock), relayClient(this, terrain.clock)];
|
|
26
|
+
const faculties = [...terrain.faculties.map((make) => make(this)), relayFaculty(terrain.clock), relayClient(this, terrain.clock), groundStore(terrain.memory)];
|
|
26
27
|
this.#grounds = new Set(faculties.map((C) => C.kind));
|
|
27
28
|
this.#kit = [catalogueFor(this), Pilot, ...faculties];
|
|
28
29
|
this.#registry = new Registry(this.#kit, []);
|
|
@@ -69,7 +70,7 @@ export class Harbor extends Carrier {
|
|
|
69
70
|
}
|
|
70
71
|
for (const hosted of harbor.dock.hosted) {
|
|
71
72
|
try {
|
|
72
|
-
await harbor.#unpack(hosted.ward, unhex(hosted.seed));
|
|
73
|
+
await harbor.#unpack(hosted.ward, unhex(hosted.seed), undefined, undefined, harbor.#keeper(hosted.store));
|
|
73
74
|
}
|
|
74
75
|
catch (e) {
|
|
75
76
|
harbor.#failed.wards[hosted.ward] = e instanceof Error ? e.message : String(e);
|
|
@@ -77,16 +78,81 @@ export class Harbor extends Carrier {
|
|
|
77
78
|
}
|
|
78
79
|
return harbor;
|
|
79
80
|
}
|
|
80
|
-
// Hosting: every place of the
|
|
81
|
-
// gone. A crash inside `host`
|
|
82
|
-
//
|
|
81
|
+
// Hosting: every place of the root memory and of each store that the
|
|
82
|
+
// dock does not name there, gone. A crash inside `host` or `move` leaves
|
|
83
|
+
// such a place, sealed under a seed nothing kept or kept elsewhere, and
|
|
84
|
+
// nothing else writes one. A place a store keeps in the root memory is
|
|
85
|
+
// the store's.
|
|
83
86
|
async sweep() {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const named = async (store) => {
|
|
88
|
+
const wards = this.dock.hosted.filter((h) => h.store === store).map((h) => h.ward);
|
|
89
|
+
return new Set(await Promise.all((store === undefined ? [BOX, ...wards] : wards).map((name) => this.#package.place(name))));
|
|
90
|
+
};
|
|
91
|
+
const stores = this.#stores();
|
|
92
|
+
const swept = [];
|
|
93
|
+
const root = await named(undefined);
|
|
94
|
+
for (const place of await this.terrain.memory.places()) {
|
|
95
|
+
if (root.has(place) || stores.some(([, s]) => s.occupies(place)))
|
|
96
|
+
continue;
|
|
87
97
|
await this.terrain.memory.forget(place);
|
|
98
|
+
swept.push(place);
|
|
99
|
+
}
|
|
100
|
+
for (const [key, store] of stores) {
|
|
101
|
+
const mine = await named(key);
|
|
102
|
+
for (const place of await store.places()) {
|
|
103
|
+
if (mine.has(place))
|
|
104
|
+
continue;
|
|
105
|
+
await store.forget(place);
|
|
106
|
+
swept.push(place);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
88
109
|
return swept;
|
|
89
110
|
}
|
|
111
|
+
// Hosting: a standing ward, carried whole to another keeper. The ward
|
|
112
|
+
// leaves the harbor's doors while it moves, so no arrival lands on the
|
|
113
|
+
// keeper it leaves.
|
|
114
|
+
async move(name, seed, from, to) {
|
|
115
|
+
const ward = this.#wards.get(name);
|
|
116
|
+
if (!ward)
|
|
117
|
+
return 'the ward does not stand';
|
|
118
|
+
const source = this.#keeper(from);
|
|
119
|
+
const target = this.#keeper(to);
|
|
120
|
+
this.#wards.delete(name);
|
|
121
|
+
try {
|
|
122
|
+
if (!(await ward.partition.kept()))
|
|
123
|
+
throw new Error('the ward was not kept');
|
|
124
|
+
const place = await this.#package.place(name);
|
|
125
|
+
const entries = new Map(await source.read(place));
|
|
126
|
+
for (const entry of (await target.read(place)).keys())
|
|
127
|
+
if (!entries.has(entry))
|
|
128
|
+
entries.set(entry, null);
|
|
129
|
+
await target.write(place, entries);
|
|
130
|
+
await this.#unpack(name, unhex(seed), undefined, undefined, target);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
this.#wards.set(name, ward);
|
|
135
|
+
return e instanceof Error ? e.message : String(e);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async release(name, store) {
|
|
139
|
+
await this.#keeper(store).forget(await this.#package.place(name));
|
|
140
|
+
}
|
|
141
|
+
// The root memory, or the store standing under the key named.
|
|
142
|
+
#keeper(store) {
|
|
143
|
+
if (store === undefined)
|
|
144
|
+
return this.terrain.memory;
|
|
145
|
+
const being = this.box.being(store);
|
|
146
|
+
if (being instanceof Store)
|
|
147
|
+
return being;
|
|
148
|
+
throw new Error(`the store ${store} does not stand`);
|
|
149
|
+
}
|
|
150
|
+
#stores() {
|
|
151
|
+
return Object.keys(this.box.partition.beings).flatMap((key) => {
|
|
152
|
+
const being = this.box.being(key);
|
|
153
|
+
return being instanceof Store ? [[key, being]] : [];
|
|
154
|
+
});
|
|
155
|
+
}
|
|
90
156
|
// Hosting: what did not stand when the harbor opened, each alone.
|
|
91
157
|
failures() {
|
|
92
158
|
return { wards: { ...this.#failed.wards }, routes: { ...this.#failed.routes } };
|
|
@@ -177,14 +243,14 @@ export class Harbor extends Carrier {
|
|
|
177
243
|
// Hosting: a ward the dock hosts stands now, under the seed named or a
|
|
178
244
|
// drawn one, and the seed it stands under comes back. A ward whose key
|
|
179
245
|
// another ward of this harbor already has does not stand.
|
|
180
|
-
async host(name, invitation, seed) {
|
|
246
|
+
async host(name, invitation, seed, store) {
|
|
181
247
|
if (this.#wards.has(name))
|
|
182
248
|
return null;
|
|
183
249
|
const bytes = seed === undefined ? this.terrain.entropy.draw(32) : isHex(seed, 32) ? unhex(seed) : null;
|
|
184
250
|
if (bytes === null || this.holds((await WardKey.from(bytes)).pk))
|
|
185
251
|
return null;
|
|
186
252
|
try {
|
|
187
|
-
await this.#unpack(name, bytes, invitation);
|
|
253
|
+
await this.#unpack(name, bytes, invitation, undefined, this.#keeper(store));
|
|
188
254
|
}
|
|
189
255
|
catch {
|
|
190
256
|
return null;
|
|
@@ -238,11 +304,11 @@ export class Harbor extends Carrier {
|
|
|
238
304
|
#dialable(at) {
|
|
239
305
|
return at.some((address) => this.carriers.some((carrier) => carrier.accepts(address)));
|
|
240
306
|
}
|
|
241
|
-
async #unpack(name, seed, dock, root) {
|
|
307
|
+
async #unpack(name, seed, dock, root, keeper) {
|
|
242
308
|
const { entropy, clock } = this.terrain;
|
|
243
309
|
const ward = await Ward.unpack({
|
|
244
310
|
seed,
|
|
245
|
-
memory: await this.#package.rows(name),
|
|
311
|
+
memory: await this.#package.rows(name, keeper),
|
|
246
312
|
catalogue: this,
|
|
247
313
|
entropy,
|
|
248
314
|
clock,
|
package/dist/harbor/index.d.ts
CHANGED
|
@@ -7,4 +7,5 @@ export { readRootRequest, rootLine, type RootRequest } from './root-line.ts';
|
|
|
7
7
|
export { addGround, grounds, probeGround, type GroundProbe, type Probing } from './probe.ts';
|
|
8
8
|
export { Registry } from './registry.ts';
|
|
9
9
|
export { Push, PUSH, RELAY, RELAY_CLIENT, RELAY_HOLD, RELAY_REST, RELAY_WAIT, RELAYED } from './relay.ts';
|
|
10
|
+
export { GROUND_STORE, groundStore, Store, STORE } from './store.ts';
|
|
10
11
|
export { Terrain, type Opening } from './terrain.ts';
|
package/dist/harbor/index.js
CHANGED
|
@@ -10,4 +10,5 @@ export { readRootRequest, rootLine } from './root-line.js';
|
|
|
10
10
|
export { addGround, grounds, probeGround } from './probe.js';
|
|
11
11
|
export { Registry } from './registry.js';
|
|
12
12
|
export { Push, PUSH, RELAY, RELAY_CLIENT, RELAY_HOLD, RELAY_REST, RELAY_WAIT, RELAYED } from './relay.js';
|
|
13
|
+
export { GROUND_STORE, groundStore, Store, STORE } from './store.js';
|
|
13
14
|
export { Terrain } from './terrain.js';
|
package/dist/harbor/package.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type Carried = {
|
|
|
8
8
|
export declare const readCarried: (value: unknown) => Carried | null;
|
|
9
9
|
export declare const carryPlaces: (memory: Memory) => Promise<Carried['places']>;
|
|
10
10
|
export declare const landPlaces: (memory: Memory, carried: Carried) => Promise<string[]>;
|
|
11
|
+
export type Keeper = Pick<Memory, 'read' | 'write' | 'places' | 'forget'>;
|
|
11
12
|
export declare class Package {
|
|
12
13
|
#private;
|
|
13
14
|
readonly memory: Memory;
|
|
@@ -16,5 +17,5 @@ export declare class Package {
|
|
|
16
17
|
static open(memory: Memory, entropy: Entropy, seed: Uint8Array): Promise<Package>;
|
|
17
18
|
derive(info: string[], length: number): Promise<Uint8Array>;
|
|
18
19
|
place(name: string): Promise<string>;
|
|
19
|
-
rows(name: string): Promise<Rows>;
|
|
20
|
+
rows(name: string, keeper?: Keeper): Promise<Rows>;
|
|
20
21
|
}
|
package/dist/harbor/package.js
CHANGED
|
@@ -99,12 +99,17 @@ class Ward {
|
|
|
99
99
|
#pack;
|
|
100
100
|
#name;
|
|
101
101
|
#seal;
|
|
102
|
+
#keeper;
|
|
102
103
|
place;
|
|
103
|
-
constructor(pack, name, seal, place) {
|
|
104
|
+
constructor(pack, name, seal, place, keeper) {
|
|
104
105
|
this.#pack = pack;
|
|
105
106
|
this.#name = name;
|
|
106
107
|
this.#seal = seal;
|
|
107
108
|
this.place = place;
|
|
109
|
+
this.#keeper = keeper;
|
|
110
|
+
}
|
|
111
|
+
read() {
|
|
112
|
+
return this.#keeper.read(this.place);
|
|
108
113
|
}
|
|
109
114
|
entry(row) {
|
|
110
115
|
return this.#pack.derive(['entry', this.#name, row], 16).then(hex);
|
|
@@ -123,7 +128,7 @@ class Ward {
|
|
|
123
128
|
return JSON.parse(new TextDecoder().decode(text));
|
|
124
129
|
}
|
|
125
130
|
write(entries) {
|
|
126
|
-
return this.#
|
|
131
|
+
return this.#keeper.write(this.place, entries);
|
|
127
132
|
}
|
|
128
133
|
}
|
|
129
134
|
export class Package {
|
|
@@ -145,13 +150,14 @@ export class Package {
|
|
|
145
150
|
place(name) {
|
|
146
151
|
return this.derive(['place', name], 16).then(hex);
|
|
147
152
|
}
|
|
148
|
-
// One ward's rows, as
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
// One ward's rows, as its keeper kept them: the root memory unless a
|
|
154
|
+
// store is named.
|
|
155
|
+
async rows(name, keeper = this.memory) {
|
|
156
|
+
const ward = new Ward(this, name, await this.derive(['seal', name], 32), await this.place(name), keeper);
|
|
151
157
|
const rows = {};
|
|
152
158
|
const beings = {};
|
|
153
159
|
let any = false;
|
|
154
|
-
for (const [entry, bytes] of await
|
|
160
|
+
for (const [entry, bytes] of await ward.read()) {
|
|
155
161
|
const { row, value } = await ward.open(entry, bytes);
|
|
156
162
|
any = true;
|
|
157
163
|
if (row === HEAD_ROW)
|
package/dist/harbor/registry.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// a class with no kind of its own, or one outside its module's domain
|
|
9
9
|
// two classes of one kind, or one kind in two modules
|
|
10
10
|
// a contract with no kind of its own, or one kind for two contracts
|
|
11
|
+
// a contract of the kit's kinds, save `org.nervur.store`
|
|
11
12
|
// a kind that is a class and a contract
|
|
12
13
|
//
|
|
13
14
|
// A module's domain is its name's first two segments: `com.acme.shop`
|
|
@@ -15,7 +16,11 @@
|
|
|
15
16
|
// so a faculty fulfils a contract another module declares. Two harbors hold
|
|
16
17
|
// two registries, and nothing between them names a class.
|
|
17
18
|
import { Faculty, isKind, KIT, kindOf } from '../being/index.js';
|
|
19
|
+
import { Store } from './store.js';
|
|
18
20
|
const domainOf = (name) => `${name.split('.').slice(0, 2).join('.')}.`;
|
|
21
|
+
// The kit's contracts a module's class may fulfil: a store keeps a ward's
|
|
22
|
+
// place wherever its author reaches.
|
|
23
|
+
const OPEN = [Store];
|
|
19
24
|
export class Registry {
|
|
20
25
|
#classes = new Map();
|
|
21
26
|
#modules = new Map();
|
|
@@ -54,7 +59,7 @@ export class Registry {
|
|
|
54
59
|
continue;
|
|
55
60
|
for (let c = Object.getPrototypeOf(C); c !== Faculty; c = Object.getPrototypeOf(c)) {
|
|
56
61
|
const contract = kindOf(c);
|
|
57
|
-
if (contract.startsWith(KIT))
|
|
62
|
+
if (contract.startsWith(KIT) && !OPEN.includes(c))
|
|
58
63
|
throw new Error(`kind ${contract} is the kit's`);
|
|
59
64
|
if (contracts.has(contract) && contracts.get(contract) !== c)
|
|
60
65
|
throw new Error(`two contracts are of kind ${contract}`);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Faculty, type BeingClass } from '../being/index.ts';
|
|
2
|
+
import type { Memory } from '../contract/index.ts';
|
|
3
|
+
export declare const STORE = "org.nervur.store";
|
|
4
|
+
export declare const GROUND_STORE = "org.nervur.ground-store";
|
|
5
|
+
export declare abstract class Store extends Faculty {
|
|
6
|
+
static readonly kind: string;
|
|
7
|
+
abstract read(place: string): Promise<Map<string, Uint8Array>>;
|
|
8
|
+
abstract write(place: string, entries: ReadonlyMap<string, Uint8Array | null>): Promise<void>;
|
|
9
|
+
abstract places(): Promise<string[]>;
|
|
10
|
+
abstract forget(place: string): Promise<void>;
|
|
11
|
+
occupies(_place: string): boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare const groundStore: (memory: Memory) => BeingClass;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The store: a faculty that keeps a hosted ward's place, where the root
|
|
3
|
+
// memory does not. It makes the same promise the Memory body makes, lent
|
|
4
|
+
// once the dock stands: sealed entries in places, each keep whole or not
|
|
5
|
+
// at all. The box ward never moves off the root memory, since the store
|
|
6
|
+
// itself stands from the box ward's cells.
|
|
7
|
+
//
|
|
8
|
+
// A ward is the unit a keeper holds: its head, its ward-being and every
|
|
9
|
+
// being of it are one place and one keep, so an answer's cells and the
|
|
10
|
+
// door's keys move together, whoever keeps them.
|
|
11
|
+
//
|
|
12
|
+
// The kit ships `org.nervur.ground-store` on every ground: places of the
|
|
13
|
+
// terrain's own memory under the faculty's key, so a store stands wherever
|
|
14
|
+
// a harbor does. A store of a cloud, a database or a ledger is a class
|
|
15
|
+
// fulfilling `org.nervur.store`, handed to the catalogue in a module.
|
|
16
|
+
import { Faculty } from '../being/index.js';
|
|
17
|
+
import { hex, utf8 } from '../crypto/index.js';
|
|
18
|
+
export const STORE = 'org.nervur.store';
|
|
19
|
+
export const GROUND_STORE = 'org.nervur.ground-store';
|
|
20
|
+
export class Store extends Faculty {
|
|
21
|
+
static kind = STORE;
|
|
22
|
+
// Whether a place of the root memory is this store's own, so the root's
|
|
23
|
+
// sweep leaves it.
|
|
24
|
+
occupies(_place) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// A place of the package is thirty-two hex digits.
|
|
29
|
+
const PLACE = 32;
|
|
30
|
+
// The store over the terrain's memory, made for one harbor. Its places are
|
|
31
|
+
// the package's, behind the hex of the faculty's key, so two stores and the
|
|
32
|
+
// root never meet in one place.
|
|
33
|
+
export const groundStore = (memory) => class GroundStore extends Store {
|
|
34
|
+
static kind = GROUND_STORE;
|
|
35
|
+
get #prefix() {
|
|
36
|
+
return hex(utf8(this.stance.key));
|
|
37
|
+
}
|
|
38
|
+
occupies(place) {
|
|
39
|
+
return place.length === this.#prefix.length + PLACE && place.startsWith(this.#prefix);
|
|
40
|
+
}
|
|
41
|
+
read(place) {
|
|
42
|
+
return memory.read(this.#prefix + place);
|
|
43
|
+
}
|
|
44
|
+
write(place, entries) {
|
|
45
|
+
return memory.write(this.#prefix + place, entries);
|
|
46
|
+
}
|
|
47
|
+
async places() {
|
|
48
|
+
return (await memory.places()).filter((place) => this.occupies(place)).map((place) => place.slice(this.#prefix.length));
|
|
49
|
+
}
|
|
50
|
+
forget(place) {
|
|
51
|
+
return memory.forget(this.#prefix + place);
|
|
52
|
+
}
|
|
53
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export type { Answer, Ask, Asker, AskSpec, BeingClass, BeingLike, Blueprint, Inv
|
|
|
3
3
|
export { readInvitation } from './quo/index.ts';
|
|
4
4
|
export { Carrier, Clock, Custody, Entropy, Loader, Memory, type Loaded, type Module, type Rows } from './contract/index.ts';
|
|
5
5
|
export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing, type Steward, type WardBeingStance } from './ward/index.ts';
|
|
6
|
-
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Terrain, UNDIALED, type Dialed, type Failures, type Ground, type GroundFaculty, type GroundProbe, type Opening, type Probing, type RootRequest, } from './harbor/index.ts';
|
|
6
|
+
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Store, STORE, Terrain, UNDIALED, type Dialed, type Failures, type Ground, type GroundFaculty, type GroundProbe, type Opening, type Probing, type RootRequest, } from './harbor/index.ts';
|
|
7
7
|
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World, type PointerParts } from './pointer/index.ts';
|
|
8
8
|
export { answerPost, NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush, type WebListen, type WebListener, type WebServe } from './line/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { Carrier, Clock, Custody, Entropy, Loader, Memory } from './contract/ind
|
|
|
12
12
|
// Wards and the ward-being.
|
|
13
13
|
export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing } from './ward/index.js';
|
|
14
14
|
// The onion, and the one line its root's asks cross from any front.
|
|
15
|
-
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Terrain, UNDIALED, } from './harbor/index.js';
|
|
15
|
+
export { BOX, CarrierFaculty, Catalogue, copyPackage, Dock, Harbor, addGround, grounds, readRootRequest, Registry, Push, PUSH, RELAY, RELAY_CLIENT, rootLine, Store, STORE, Terrain, UNDIALED, } from './harbor/index.js';
|
|
16
16
|
// The pointer dock: a whole world in one process.
|
|
17
17
|
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World } from './pointer/index.js';
|
|
18
18
|
export { answerPost, NEUTRAL, WEB, WEB_OPENING, WEB_PUSH, WebDialer, webFaculty, webPush } from './line/index.js';
|