nervur 0.20.1 → 0.20.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 +130 -8
- package/dist/browser/index.d.ts +35 -0
- package/dist/browser/index.js +188 -0
- package/dist/browser/worker.d.ts +12 -0
- package/dist/browser/worker.js +40 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +7 -0
- package/dist/cli/command.d.ts +1 -1
- package/dist/cli/command.js +59 -16
- package/dist/cli/harbor.d.ts +9 -11
- package/dist/cli/harbor.js +81 -123
- package/dist/contract/index.d.ts +2 -1
- package/dist/contract/index.js +3 -7
- package/dist/edge/index.d.ts +57 -0
- package/dist/edge/index.js +306 -0
- package/dist/edge.d.ts +1 -0
- package/dist/edge.js +8 -0
- package/dist/folder/index.d.ts +4 -0
- package/dist/folder/index.js +57 -14
- package/dist/harbor/carrying.d.ts +17 -0
- package/dist/harbor/carrying.js +38 -0
- package/dist/harbor/catalogue.d.ts +2 -0
- package/dist/harbor/catalogue.js +28 -20
- package/dist/harbor/dock.d.ts +22 -3
- package/dist/harbor/dock.js +69 -13
- package/dist/harbor/harbor.d.ts +14 -4
- package/dist/harbor/harbor.js +144 -15
- package/dist/harbor/index.d.ts +6 -3
- package/dist/harbor/index.js +4 -1
- package/dist/harbor/package.d.ts +2 -0
- package/dist/harbor/package.js +15 -0
- package/dist/harbor/probe.d.ts +18 -0
- package/dist/harbor/probe.js +15 -0
- package/dist/harbor/registry.d.ts +1 -0
- package/dist/harbor/registry.js +11 -0
- package/dist/harbor/relay.d.ts +15 -0
- package/dist/harbor/relay.js +387 -0
- package/dist/harbor/root-line.js +4 -2
- package/dist/harbor/terrain.d.ts +12 -0
- package/dist/harbor/terrain.js +19 -0
- package/dist/http/index.d.ts +4 -0
- package/dist/http/index.js +156 -0
- package/dist/http/websocket.d.ts +29 -0
- package/dist/http/websocket.js +133 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8 -2
- package/dist/line/answer.d.ts +8 -0
- package/dist/line/answer.js +60 -0
- package/dist/line/frame.d.ts +34 -0
- package/dist/line/frame.js +81 -0
- package/dist/line/ground.d.ts +3 -0
- package/dist/line/ground.js +9 -0
- package/dist/line/index.d.ts +5 -0
- package/dist/line/index.js +9 -0
- package/dist/line/push.d.ts +7 -0
- package/dist/line/push.js +92 -0
- package/dist/line/web.d.ts +30 -0
- package/dist/line/web.js +181 -0
- package/dist/node/index.d.ts +32 -0
- package/dist/node/index.js +164 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +7 -0
- package/dist/pointer/bodies.d.ts +2 -0
- package/dist/pointer/bodies.js +10 -0
- package/dist/pointer/world.d.ts +7 -1
- package/dist/pointer/world.js +6 -0
- package/dist/quo/address.d.ts +19 -0
- package/dist/quo/address.js +84 -0
- package/dist/quo/door.d.ts +1 -1
- package/dist/quo/door.js +4 -2
- package/dist/quo/index.d.ts +1 -0
- package/dist/quo/index.js +1 -0
- package/dist/quo/invitation.d.ts +1 -0
- package/dist/quo/invitation.js +9 -5
- package/dist/tcp/frame.d.ts +0 -4
- package/dist/tcp/frame.js +0 -10
- package/dist/tcp/index.d.ts +8 -2
- package/dist/tcp/index.js +123 -27
- package/dist/ward/index.d.ts +2 -2
- package/dist/ward/index.js +1 -1
- package/dist/ward/stance.d.ts +2 -0
- package/dist/ward/stance.js +3 -1
- package/dist/ward/ward-being.d.ts +2 -1
- package/dist/ward/ward-being.js +1 -1
- package/dist/ward/ward.d.ts +7 -0
- package/dist/ward/ward.js +6 -0
- package/package.json +21 -2
- package/src/browser/index.ts +207 -0
- package/src/browser/worker.ts +66 -0
- package/src/browser.ts +9 -0
- package/src/cli/command.ts +53 -17
- package/src/cli/harbor.ts +82 -116
- package/src/contract/index.ts +11 -8
- package/src/edge/index.ts +359 -0
- package/src/edge.ts +10 -0
- package/src/folder/index.ts +57 -12
- package/src/harbor/carrying.ts +56 -0
- package/src/harbor/catalogue.ts +32 -17
- package/src/harbor/dock.ts +88 -17
- package/src/harbor/harbor.ts +146 -17
- package/src/harbor/index.ts +6 -3
- package/src/harbor/package.ts +16 -0
- package/src/harbor/probe.ts +34 -0
- package/src/harbor/registry.ts +11 -0
- package/src/harbor/relay.ts +399 -0
- package/src/harbor/root-line.ts +4 -1
- package/src/harbor/terrain.ts +28 -2
- package/src/http/index.ts +150 -0
- package/src/http/websocket.ts +124 -0
- package/src/index.ts +35 -2
- package/src/line/answer.ts +59 -0
- package/src/line/frame.ts +88 -0
- package/src/line/ground.ts +19 -0
- package/src/line/index.ts +9 -0
- package/src/line/push.ts +112 -0
- package/src/line/web.ts +227 -0
- package/src/node/index.ts +170 -0
- package/src/node.ts +9 -0
- package/src/pointer/bodies.ts +11 -0
- package/src/pointer/world.ts +18 -2
- package/src/quo/address.ts +87 -0
- package/src/quo/door.ts +4 -2
- package/src/quo/index.ts +1 -0
- package/src/quo/invitation.ts +10 -6
- package/src/stand/stand.ts +60 -21
- package/src/tcp/index.ts +142 -31
- package/src/ward/index.ts +2 -2
- package/src/ward/stance.ts +6 -1
- package/src/ward/ward-being.ts +2 -2
- package/src/ward/ward.ts +16 -0
- package/src/tcp/frame.ts +0 -78
package/dist/folder/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { Custody, Memory, type Entropy } from '../contract/index.ts';
|
|
2
|
+
export type Keep = Record<string, string | null>;
|
|
2
3
|
export declare class FolderMemory extends Memory {
|
|
3
4
|
#private;
|
|
4
5
|
readonly root: string;
|
|
5
6
|
constructor(root: string);
|
|
6
7
|
read(place: string): Promise<Map<string, Uint8Array>>;
|
|
7
8
|
write(place: string, entries: ReadonlyMap<string, Uint8Array | null>): Promise<void>;
|
|
9
|
+
places(): Promise<string[]>;
|
|
10
|
+
forget(place: string): Promise<void>;
|
|
11
|
+
protected apply(dir: string, keep?: Keep): Promise<void>;
|
|
8
12
|
}
|
|
9
13
|
export declare class FolderCustody extends Custody {
|
|
10
14
|
#private;
|
package/dist/folder/index.js
CHANGED
|
@@ -4,17 +4,26 @@
|
|
|
4
4
|
//
|
|
5
5
|
// <root>/seed the harbor's seed, as hex
|
|
6
6
|
// <root>/<place>/<entry> one sealed entry of the harbor's memory
|
|
7
|
+
// <root>/<place>/keep a keep of that place, until it is applied
|
|
7
8
|
//
|
|
8
9
|
// Places and entries are the hex names the kit chose, so no name reaches
|
|
9
10
|
// outside its folder, and the disk shows no ward's name. A file is written
|
|
10
|
-
// whole, beside itself and then renamed over
|
|
11
|
-
//
|
|
12
|
-
//
|
|
11
|
+
// whole, beside itself and then renamed over.
|
|
12
|
+
//
|
|
13
|
+
// A keep is whole, by its own file: the whole keep is written under `keep`
|
|
14
|
+
// and renamed over, then its entries are written and removed one by one,
|
|
15
|
+
// then `keep` goes. A read applies a `keep` it finds first, and the same
|
|
16
|
+
// bytes written twice leave the same place, so a keep that a crash cut
|
|
17
|
+
// short finishes at the next read. A place read before its `keep` landed
|
|
18
|
+
// has none of it. `keep` is no hex name, so it is never an entry.
|
|
19
|
+
//
|
|
20
|
+
// The seed is a plain file: custody that seals it is a terrain's.
|
|
13
21
|
import { mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
14
22
|
import { join } from 'node:path';
|
|
15
23
|
import { hex, isHex, unhex } from '../crypto/index.js';
|
|
16
24
|
import { Custody, Memory } from '../contract/index.js';
|
|
17
25
|
const missing = (e) => e.code === 'ENOENT';
|
|
26
|
+
const KEEP = 'keep';
|
|
18
27
|
const named = (name) => {
|
|
19
28
|
if (!isHex(name) || name === '')
|
|
20
29
|
throw new Error(`${name} is not a name the kit chose`);
|
|
@@ -31,10 +40,12 @@ export class FolderMemory extends Memory {
|
|
|
31
40
|
super();
|
|
32
41
|
this.root = root;
|
|
33
42
|
}
|
|
34
|
-
// The entries as the disk holds them now
|
|
35
|
-
// one still being written among them, is
|
|
43
|
+
// The entries as the disk holds them now, a keep left unapplied first.
|
|
44
|
+
// A file that is not an entry, one still being written among them, is
|
|
45
|
+
// not read.
|
|
36
46
|
async read(place) {
|
|
37
47
|
const dir = join(this.root, named(place));
|
|
48
|
+
await this.apply(dir);
|
|
38
49
|
const out = new Map();
|
|
39
50
|
const files = await readdir(dir, { withFileTypes: true }).catch((e) => (missing(e) ? [] : Promise.reject(e)));
|
|
40
51
|
for (const file of files) {
|
|
@@ -44,22 +55,46 @@ export class FolderMemory extends Memory {
|
|
|
44
55
|
}
|
|
45
56
|
return out;
|
|
46
57
|
}
|
|
47
|
-
// One keep at a time,
|
|
58
|
+
// One keep at a time, written whole under `keep` before any entry moves.
|
|
48
59
|
async write(place, entries) {
|
|
49
60
|
const dir = join(this.root, named(place));
|
|
61
|
+
const keep = {};
|
|
62
|
+
for (const [name, bytes] of entries)
|
|
63
|
+
keep[named(name)] = bytes === null ? null : hex(bytes);
|
|
50
64
|
const kept = this.#line.then(async () => {
|
|
51
65
|
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (bytes === null)
|
|
55
|
-
await rm(path, { force: true });
|
|
56
|
-
else
|
|
57
|
-
await writeWhole(path, bytes);
|
|
58
|
-
}
|
|
66
|
+
await writeWhole(join(dir, KEEP), JSON.stringify(keep));
|
|
67
|
+
await this.apply(dir, keep);
|
|
59
68
|
});
|
|
60
69
|
this.#line = kept.catch(() => undefined);
|
|
61
70
|
await kept;
|
|
62
71
|
}
|
|
72
|
+
// Every folder of the root that is a place.
|
|
73
|
+
async places() {
|
|
74
|
+
const files = await readdir(this.root, { withFileTypes: true }).catch((e) => (missing(e) ? [] : Promise.reject(e)));
|
|
75
|
+
return files.filter((f) => f.isDirectory() && isHex(f.name) && f.name !== '').map((f) => f.name);
|
|
76
|
+
}
|
|
77
|
+
forget(place) {
|
|
78
|
+
return rm(join(this.root, named(place)), { recursive: true, force: true });
|
|
79
|
+
}
|
|
80
|
+
// The keep a folder holds, applied and then gone. Writing the same bytes
|
|
81
|
+
// again leaves the same place, so applying twice is applying once.
|
|
82
|
+
async apply(dir, keep) {
|
|
83
|
+
const found = keep ?? (await readFile(join(dir, KEEP), 'utf8').then((text) => JSON.parse(text), () => null));
|
|
84
|
+
if (found === null)
|
|
85
|
+
return;
|
|
86
|
+
for (const [name, written] of Object.entries(found)) {
|
|
87
|
+
const path = join(dir, named(name));
|
|
88
|
+
if (written === null)
|
|
89
|
+
await rm(path, { force: true });
|
|
90
|
+
else
|
|
91
|
+
await writeWhole(path, unhex(written));
|
|
92
|
+
}
|
|
93
|
+
await rm(join(dir, KEEP), { force: true });
|
|
94
|
+
const left = await readdir(dir).catch((e) => (missing(e) ? [] : Promise.reject(e)));
|
|
95
|
+
if (left.length === 0)
|
|
96
|
+
await rm(dir, { recursive: true, force: true });
|
|
97
|
+
}
|
|
63
98
|
}
|
|
64
99
|
export class FolderCustody extends Custody {
|
|
65
100
|
root;
|
|
@@ -70,6 +105,7 @@ export class FolderCustody extends Custody {
|
|
|
70
105
|
this.#entropy = entropy;
|
|
71
106
|
}
|
|
72
107
|
// The seed the folder holds, or a drawn one written there the first time.
|
|
108
|
+
// Two first asks at once keep the one written first.
|
|
73
109
|
async seed() {
|
|
74
110
|
const path = join(this.root, 'seed');
|
|
75
111
|
const kept = await readFile(path, 'utf8').catch((e) => (missing(e) ? undefined : Promise.reject(e)));
|
|
@@ -81,7 +117,14 @@ export class FolderCustody extends Custody {
|
|
|
81
117
|
}
|
|
82
118
|
const seed = this.#entropy.draw(32);
|
|
83
119
|
await mkdir(this.root, { recursive: true, mode: 0o700 });
|
|
84
|
-
|
|
120
|
+
try {
|
|
121
|
+
await writeFile(path, `${hex(seed)}\n`, { mode: 0o600, flag: 'wx' });
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
if (e.code === 'EEXIST')
|
|
125
|
+
return this.seed();
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
85
128
|
return seed;
|
|
86
129
|
}
|
|
87
130
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Faculty, type BeingClass } from '../being/index.ts';
|
|
2
|
+
export interface Ground {
|
|
3
|
+
arrive(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
4
|
+
}
|
|
5
|
+
export type GroundFaculty = (ground: Ground) => BeingClass;
|
|
6
|
+
export declare const UNDIALED: unique symbol;
|
|
7
|
+
export type Dialed = Uint8Array | null | typeof UNDIALED;
|
|
8
|
+
export declare abstract class CarrierFaculty extends Faculty {
|
|
9
|
+
static readonly kind: string;
|
|
10
|
+
abstract accepts(address: string): boolean;
|
|
11
|
+
abstract dial(address: string, pk: string, bytes: Uint8Array): Promise<Dialed>;
|
|
12
|
+
listening(): string[];
|
|
13
|
+
relays(_pk: string): boolean;
|
|
14
|
+
relay(_pk: string, _bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
15
|
+
awake(): Promise<void>;
|
|
16
|
+
sleep(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A carrier is a faculty. It speaks the schemes of the addresses it dials,
|
|
3
|
+
// dials an address when the harbor carries a box there, and listens where
|
|
4
|
+
// its cells say, handing each box it hears to the harbor. The harbor keeps
|
|
5
|
+
// every route and walks it in order, handing each address to the carrier
|
|
6
|
+
// that speaks its scheme.
|
|
7
|
+
//
|
|
8
|
+
// A ground ships its carriers as classes the terrain names, each made for
|
|
9
|
+
// one harbor, so the class holds the harbor it hands boxes to. They are
|
|
10
|
+
// the kit's, of kinds under `org.nervur.`, and the dock opens them as
|
|
11
|
+
// faculties, at genesis where the terrain says, and on the root's `open`.
|
|
12
|
+
import { Faculty } from '../being/index.js';
|
|
13
|
+
// Beside a reply and nothing, a dial answers `undialed` when the address
|
|
14
|
+
// could not be reached at all, so the harbor tries the next one. A box a
|
|
15
|
+
// line took may have been heard, so after it no other address is tried.
|
|
16
|
+
export const UNDIALED = Symbol('undialed');
|
|
17
|
+
export class CarrierFaculty extends Faculty {
|
|
18
|
+
static kind = 'org.nervur.carrier';
|
|
19
|
+
// The addresses it listens at now.
|
|
20
|
+
listening() {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
// Whether it answers for a ward pk no door of this harbor holds, as a
|
|
24
|
+
// relay does, and its answer.
|
|
25
|
+
relays(_pk) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
relay(_pk, _bytes) {
|
|
29
|
+
return Promise.resolve(null);
|
|
30
|
+
}
|
|
31
|
+
// Once the harbor has unpacked, and once before it closes.
|
|
32
|
+
awake() {
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
sleep() {
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -4,9 +4,11 @@ export declare const CATALOGUE = "catalogue";
|
|
|
4
4
|
export type ModuleRow = {
|
|
5
5
|
version: string;
|
|
6
6
|
from: string;
|
|
7
|
+
kinds: string[];
|
|
7
8
|
};
|
|
8
9
|
export interface Cataloguing {
|
|
9
10
|
load(from: string): Promise<Loaded | null>;
|
|
11
|
+
check(modules: readonly Loaded[]): string | null;
|
|
10
12
|
run(modules: readonly Loaded[]): string | null;
|
|
11
13
|
stands(kinds: readonly string[]): boolean;
|
|
12
14
|
kindsOf(module: string): string[];
|
package/dist/harbor/catalogue.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
// The catalogue: the dock's first faculty, the code a harbor runs. Her
|
|
3
3
|
// cells name each module by the name it declares, its version, and where
|
|
4
4
|
// the terrain's loader finds it. The harbor wakes her before any other
|
|
5
|
-
// faculty,
|
|
6
|
-
//
|
|
5
|
+
// faculty, and she runs every module she names that is found and splits
|
|
6
|
+
// no kind. One that is not stays out alone, with its reason, and the
|
|
7
|
+
// beings of its kinds stand absent.
|
|
7
8
|
//
|
|
8
9
|
// add { from } the module the loader finds there, run from now
|
|
9
10
|
// on; the same module again takes its new version
|
|
10
|
-
// remove { module } a module no being stands on,
|
|
11
|
-
//
|
|
11
|
+
// remove { module } a module no being stands on, by the kinds kept
|
|
12
|
+
// for it, run no more
|
|
13
|
+
// modules what she names, each with the version running,
|
|
14
|
+
// and why one does not run
|
|
12
15
|
//
|
|
13
16
|
// She is the kit's, of kind `org.nervur.catalogue`, and answers the root
|
|
14
17
|
// and an owner alone.
|
|
@@ -26,6 +29,7 @@ export class Catalogue extends Faculty {
|
|
|
26
29
|
};
|
|
27
30
|
#harbor;
|
|
28
31
|
#running = new Map();
|
|
32
|
+
#failed = new Map();
|
|
29
33
|
constructor(stance, harbor) {
|
|
30
34
|
super(stance);
|
|
31
35
|
this.#harbor = harbor;
|
|
@@ -33,20 +37,19 @@ export class Catalogue extends Faculty {
|
|
|
33
37
|
get #rows() {
|
|
34
38
|
return this.cells.modules;
|
|
35
39
|
}
|
|
36
|
-
// Every module her cells name,
|
|
37
|
-
//
|
|
40
|
+
// Every module her cells name, in their order, loaded and run where it
|
|
41
|
+
// is found, is the module named, and splits no kind with those before
|
|
42
|
+
// it. The rest stay out, each with its reason.
|
|
38
43
|
async wake() {
|
|
39
44
|
for (const [module, row] of Object.entries(this.#rows)) {
|
|
40
|
-
const loaded = await this.#harbor.load(row.from);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const loaded = await this.#harbor.load(row.from).catch(() => null);
|
|
46
|
+
const refused = loaded === null ? `not found at ${row.from}` : loaded.module !== module ? `${row.from} is module ${loaded.module}` : this.#harbor.check([...this.#running.values(), loaded]);
|
|
47
|
+
if (refused === null)
|
|
48
|
+
this.#running.set(module, loaded);
|
|
49
|
+
else
|
|
50
|
+
this.#failed.set(module, refused);
|
|
46
51
|
}
|
|
47
|
-
|
|
48
|
-
if (refused !== null)
|
|
49
|
-
throw new Error(refused);
|
|
52
|
+
this.#harbor.run([...this.#running.values()]);
|
|
50
53
|
}
|
|
51
54
|
async add(args) {
|
|
52
55
|
if (!text(args.from))
|
|
@@ -60,26 +63,31 @@ export class Catalogue extends Faculty {
|
|
|
60
63
|
if (refused !== null)
|
|
61
64
|
return { error: refused };
|
|
62
65
|
this.#running.set(loaded.module, loaded);
|
|
63
|
-
this.#
|
|
66
|
+
this.#failed.delete(loaded.module);
|
|
67
|
+
this.#rows[loaded.module] = { version: loaded.version, from: loaded.from, kinds: this.#harbor.kindsOf(loaded.module) };
|
|
64
68
|
return { added: loaded.module, version: loaded.version };
|
|
65
69
|
}
|
|
66
70
|
remove(args) {
|
|
67
71
|
const module = args.module;
|
|
68
|
-
if (!text(module) || !this.#
|
|
72
|
+
if (!text(module) || !Object.hasOwn(this.#rows, module))
|
|
69
73
|
return { error: 'no such module' };
|
|
70
|
-
|
|
74
|
+
const kinds = new Set([...this.#rows[module].kinds, ...this.#harbor.kindsOf(module)]);
|
|
75
|
+
if (this.#harbor.stands([...kinds]))
|
|
71
76
|
return { error: 'a being stands on this module' };
|
|
72
77
|
const running = new Map(this.#running);
|
|
73
78
|
running.delete(module);
|
|
74
79
|
this.#harbor.run([...running.values()]);
|
|
75
80
|
this.#running.delete(module);
|
|
81
|
+
this.#failed.delete(module);
|
|
76
82
|
delete this.#rows[module];
|
|
77
83
|
return { removed: module };
|
|
78
84
|
}
|
|
79
85
|
modules() {
|
|
80
86
|
const out = {};
|
|
81
|
-
for (const [module, row] of Object.entries(this.#rows))
|
|
82
|
-
|
|
87
|
+
for (const [module, row] of Object.entries(this.#rows)) {
|
|
88
|
+
const failed = this.#failed.get(module);
|
|
89
|
+
out[module] = { version: row.version, running: this.#running.get(module)?.version ?? null, from: row.from, ...(failed === undefined ? {} : { failed }) };
|
|
90
|
+
}
|
|
83
91
|
return out;
|
|
84
92
|
}
|
|
85
93
|
}
|
package/dist/harbor/dock.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import { type AskSpec, type Invitation, type JsonObject } from '../being/index.ts';
|
|
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
4
|
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
5
|
-
route(pk: string, at: string | null): boolean;
|
|
5
|
+
route(pk: string, at: readonly string[] | null): boolean;
|
|
6
6
|
keep(): Promise<boolean>;
|
|
7
|
+
failures(): Failures;
|
|
8
|
+
sweep(): Promise<string[]>;
|
|
9
|
+
grounds(kind: string): boolean;
|
|
10
|
+
awaken(key: string): Promise<void>;
|
|
11
|
+
lull(key: string): Promise<void>;
|
|
7
12
|
}
|
|
8
13
|
export type Hosted = {
|
|
9
14
|
ward: string;
|
|
10
15
|
seed: string;
|
|
11
16
|
};
|
|
17
|
+
export type Failures = {
|
|
18
|
+
wards: Record<string, string>;
|
|
19
|
+
routes: Record<string, string>;
|
|
20
|
+
};
|
|
12
21
|
export declare const BOX = "box";
|
|
13
22
|
export declare class Dock extends WardBeing implements Lender {
|
|
14
23
|
#private;
|
|
@@ -16,11 +25,21 @@ export declare class Dock extends WardBeing implements Lender {
|
|
|
16
25
|
static cells: JsonObject;
|
|
17
26
|
static asks: Record<string, AskSpec>;
|
|
18
27
|
constructor(stance: WardBeingStance, hosting: Hosting);
|
|
28
|
+
describe(asker: Asker): Blueprint;
|
|
19
29
|
get hosted(): Hosted[];
|
|
20
|
-
get routes(): Record<string, string>;
|
|
30
|
+
get routes(): Record<string, string[]>;
|
|
31
|
+
get addresses(): string[];
|
|
21
32
|
route(args: JsonObject): JsonObject;
|
|
33
|
+
reach(args: JsonObject): JsonObject;
|
|
34
|
+
learn(ward: string, at: readonly string[]): Promise<void>;
|
|
22
35
|
standCatalogue(): Promise<void>;
|
|
36
|
+
unboot(args: JsonObject): Promise<JsonObject>;
|
|
37
|
+
standCarriers(opens: readonly {
|
|
38
|
+
key: string;
|
|
39
|
+
kind: string;
|
|
40
|
+
}[]): Promise<void>;
|
|
23
41
|
open(args: JsonObject): Promise<JsonObject>;
|
|
42
|
+
sweep(): Promise<JsonObject>;
|
|
24
43
|
host(args: JsonObject): Promise<JsonObject>;
|
|
25
44
|
lend(args: JsonObject): Promise<JsonObject>;
|
|
26
45
|
retract(args: JsonObject): Promise<JsonObject>;
|
package/dist/harbor/dock.js
CHANGED
|
@@ -11,24 +11,34 @@
|
|
|
11
11
|
// lend { contract } asked by a hosted ward: an offer from a
|
|
12
12
|
// faculty that fulfils the contract
|
|
13
13
|
// retract { contract, heir } an offer that was not taken
|
|
14
|
-
// route { ward, at } where a ward pk is reached,
|
|
15
|
-
//
|
|
16
|
-
// the
|
|
14
|
+
// route { ward, at } where a ward pk is reached, addresses in the
|
|
15
|
+
// order they are tried, or null to forget
|
|
16
|
+
// it; the dock keeps it and the harbor hands
|
|
17
|
+
// it to its carrier
|
|
18
|
+
// reach { at } where this harbor is reached, the
|
|
19
|
+
// addresses every invitation it gives carries
|
|
20
|
+
//
|
|
21
|
+
// A take of an invitation that carries `at` teaches the dock that ward's
|
|
22
|
+
// route, where the root set none.
|
|
17
23
|
import { Faculty, KIT } from '../being/index.js';
|
|
18
24
|
import { CATALOGUE } from './catalogue.js';
|
|
19
|
-
import { readInvitation } from '../quo/index.js';
|
|
20
|
-
import { ownerAsk, WardBeing } from '../ward/index.js';
|
|
25
|
+
import { readAt, readInvitation } from '../quo/index.js';
|
|
26
|
+
import { ownerAsk, pilots, rootAsk, WardBeing } from '../ward/index.js';
|
|
21
27
|
export const BOX = 'box';
|
|
22
28
|
const text = (v) => typeof v === 'string';
|
|
29
|
+
// A list of addresses, each a URI with a scheme, and at least one.
|
|
30
|
+
const addresses = (v) => Array.isArray(v) && v.length > 0 && readAt(v).length === v.length;
|
|
23
31
|
const hosted = (_, notes) => notes?.hosted === true;
|
|
24
32
|
export class Dock extends WardBeing {
|
|
25
33
|
static kind = 'org.nervur.dock';
|
|
26
|
-
static cells = { hosted: [], routes: {} };
|
|
34
|
+
static cells = { hosted: [], routes: {}, reach: [] };
|
|
27
35
|
static asks = {
|
|
28
36
|
...WardBeing.asks,
|
|
29
37
|
open: ownerAsk('a faculty of this harbor', { required: ['key', 'class'] }),
|
|
30
38
|
host: ownerAsk('a ward this harbor hosts', { required: ['ward'] }),
|
|
31
|
-
route: ownerAsk('where a ward is reached, or null', { required: ['ward', 'at'] }),
|
|
39
|
+
route: ownerAsk('where a ward is reached, addresses in order, or null', { required: ['ward', 'at'] }),
|
|
40
|
+
reach: ownerAsk('where this harbor is reached, the addresses every invitation carries', { required: ['at'] }),
|
|
41
|
+
sweep: rootAsk('every place of the memory this harbor does not name, gone'),
|
|
32
42
|
lend: { description: 'an offer from a faculty fulfilling a contract', input: { type: 'object', required: ['contract'] }, for: hosted },
|
|
33
43
|
retract: { description: 'an offer that was not taken', input: { type: 'object', required: ['contract', 'heir'] }, for: hosted },
|
|
34
44
|
};
|
|
@@ -37,36 +47,82 @@ export class Dock extends WardBeing {
|
|
|
37
47
|
super(stance);
|
|
38
48
|
this.#hosting = hosting;
|
|
39
49
|
}
|
|
50
|
+
// The census adds what did not stand, for whoever pilots.
|
|
51
|
+
describe(asker) {
|
|
52
|
+
const blueprint = super.describe(asker);
|
|
53
|
+
if (pilots(asker, this.notes(asker)))
|
|
54
|
+
blueprint.notes.failed = this.#hosting.failures();
|
|
55
|
+
return blueprint;
|
|
56
|
+
}
|
|
40
57
|
get hosted() {
|
|
41
58
|
return this.cells.hosted.map(({ ward, seed }) => ({ ward, seed }));
|
|
42
59
|
}
|
|
43
60
|
get routes() {
|
|
44
|
-
return
|
|
61
|
+
return Object.fromEntries(Object.entries(this.cells.routes).map(([pk, at]) => [pk, [...at]]));
|
|
62
|
+
}
|
|
63
|
+
// Where this harbor is reached, in the order a caller tries it.
|
|
64
|
+
get addresses() {
|
|
65
|
+
return [...this.cells.reach];
|
|
45
66
|
}
|
|
46
67
|
route(args) {
|
|
47
|
-
const { ward
|
|
48
|
-
|
|
68
|
+
const { ward } = args;
|
|
69
|
+
const at = typeof args.at === 'string' ? [args.at] : args.at;
|
|
70
|
+
if (!text(ward) || (at !== null && !addresses(at)) || !this.#hosting.route(ward, at))
|
|
49
71
|
return { error: 'not routed' };
|
|
50
72
|
const routes = this.cells.routes;
|
|
51
73
|
if (at === null)
|
|
52
74
|
delete routes[ward];
|
|
53
75
|
else
|
|
54
|
-
routes[ward] = at;
|
|
76
|
+
routes[ward] = [...at];
|
|
55
77
|
return { routed: ward };
|
|
56
78
|
}
|
|
79
|
+
reach(args) {
|
|
80
|
+
if (!addresses(args.at))
|
|
81
|
+
return { error: 'at is a list of addresses' };
|
|
82
|
+
this.cells.reach = [...args.at];
|
|
83
|
+
return { reach: [...args.at] };
|
|
84
|
+
}
|
|
85
|
+
// A route learned from an invitation's `at`, where the root set none,
|
|
86
|
+
// and where the carrier dials one of its addresses.
|
|
87
|
+
async learn(ward, at) {
|
|
88
|
+
const routes = this.cells.routes;
|
|
89
|
+
if (Object.hasOwn(routes, ward) || !this.#hosting.route(ward, at))
|
|
90
|
+
return;
|
|
91
|
+
routes[ward] = [...at];
|
|
92
|
+
await this.#hosting.keep();
|
|
93
|
+
}
|
|
57
94
|
// Genesis: the catalogue stands first, and is kept at once.
|
|
58
95
|
async standCatalogue() {
|
|
59
96
|
const kind = 'org.nervur.catalogue';
|
|
60
97
|
if ((await this.stance.boot(kind, CATALOGUE, CATALOGUE)) === null || !(await this.#hosting.keep()))
|
|
61
98
|
throw new Error('the catalogue did not stand');
|
|
62
99
|
}
|
|
100
|
+
// A faculty that goes stops listening first.
|
|
101
|
+
async unboot(args) {
|
|
102
|
+
if (typeof args.being === 'string')
|
|
103
|
+
await this.#hosting.lull(args.being);
|
|
104
|
+
return super.unboot(args);
|
|
105
|
+
}
|
|
106
|
+
// Genesis: the carriers the terrain names stand next, each kept at once.
|
|
107
|
+
async standCarriers(opens) {
|
|
108
|
+
for (const { key, kind } of opens)
|
|
109
|
+
await this.open({ key, class: kind });
|
|
110
|
+
await this.#hosting.keep();
|
|
111
|
+
}
|
|
63
112
|
async open(args) {
|
|
64
113
|
if (!text(args.key) || !text(args.class))
|
|
65
114
|
return { error: 'key and class are text' };
|
|
66
|
-
|
|
115
|
+
const mine = args.class.startsWith(KIT) ? this.#hosting.grounds(args.class) : true;
|
|
116
|
+
if (!mine || !Faculty.fulfils(this.steward.classOf(args.class)))
|
|
67
117
|
return { error: 'no such faculty' };
|
|
68
118
|
const opened = await this.stance.boot(args.class, args.key, args.key);
|
|
69
|
-
|
|
119
|
+
if (opened === null)
|
|
120
|
+
return { error: 'not opened' };
|
|
121
|
+
await this.#hosting.awaken(opened);
|
|
122
|
+
return { opened };
|
|
123
|
+
}
|
|
124
|
+
async sweep() {
|
|
125
|
+
return { swept: await this.#hosting.sweep() };
|
|
70
126
|
}
|
|
71
127
|
async host(args) {
|
|
72
128
|
const name = args.ward;
|
package/dist/harbor/harbor.d.ts
CHANGED
|
@@ -2,14 +2,18 @@ import type { Answer, BeingClass, Invitation, JsonObject } from '../being/index.
|
|
|
2
2
|
import { Carrier, type Loaded } from '../contract/index.ts';
|
|
3
3
|
import { Ward, type Catalogue as Resolving } from '../ward/index.ts';
|
|
4
4
|
import { Catalogue, type Cataloguing } from './catalogue.ts';
|
|
5
|
-
import {
|
|
5
|
+
import { CarrierFaculty } from './carrying.ts';
|
|
6
|
+
import { Dock, type Failures, type Hosting } from './dock.ts';
|
|
6
7
|
import { Registry } from './registry.ts';
|
|
7
|
-
import type
|
|
8
|
+
import { type Probing } from './probe.ts';
|
|
9
|
+
import { Terrain } from './terrain.ts';
|
|
8
10
|
export declare class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
|
|
9
11
|
#private;
|
|
10
12
|
readonly terrain: Terrain;
|
|
11
13
|
private constructor();
|
|
12
|
-
static open(
|
|
14
|
+
static open(from?: Terrain | Probing): Promise<Harbor>;
|
|
15
|
+
sweep(): Promise<string[]>;
|
|
16
|
+
failures(): Failures;
|
|
13
17
|
get box(): Ward;
|
|
14
18
|
get dock(): Dock;
|
|
15
19
|
get catalogue(): Catalogue;
|
|
@@ -22,10 +26,16 @@ export declare class Harbor extends Carrier implements Hosting, Cataloguing, Res
|
|
|
22
26
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
23
27
|
classOf(kind: string): BeingClass | undefined;
|
|
24
28
|
load(from: string): Promise<Loaded | null>;
|
|
29
|
+
check(modules: readonly Loaded[]): string | null;
|
|
25
30
|
run(modules: readonly Loaded[]): string | null;
|
|
26
31
|
stands(kinds: readonly string[]): boolean;
|
|
27
32
|
kindsOf(module: string): string[];
|
|
28
33
|
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
29
34
|
keep(): Promise<boolean>;
|
|
30
|
-
route(pk: string, at: string | null): boolean;
|
|
35
|
+
route(pk: string, at: readonly string[] | null): boolean;
|
|
36
|
+
grounds(kind: string): boolean;
|
|
37
|
+
awaken(key: string): Promise<void>;
|
|
38
|
+
lull(key: string): Promise<void>;
|
|
39
|
+
get carriers(): CarrierFaculty[];
|
|
40
|
+
close(): Promise<void>;
|
|
31
41
|
}
|