nervur 0.20.0 → 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 +229 -20
- package/dist/being/being.d.ts +1 -0
- package/dist/being/being.js +5 -1
- package/dist/being/faculty.d.ts +1 -0
- package/dist/being/faculty.js +13 -7
- package/dist/being/index.d.ts +1 -0
- package/dist/being/index.js +1 -0
- package/dist/being/kind.d.ts +4 -0
- package/dist/being/kind.js +28 -0
- package/dist/being/types.d.ts +3 -4
- 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 +13 -0
- package/dist/cli/command.js +237 -0
- package/dist/cli/harbor.d.ts +19 -0
- package/dist/cli/harbor.js +99 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +14 -0
- package/dist/contract/index.d.ts +16 -2
- package/dist/contract/index.js +13 -3
- 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 +9 -4
- package/dist/folder/index.js +89 -84
- package/dist/harbor/carrying.d.ts +17 -0
- package/dist/harbor/carrying.js +38 -0
- package/dist/harbor/catalogue.d.ts +27 -0
- package/dist/harbor/catalogue.js +101 -0
- package/dist/harbor/dock.d.ts +32 -3
- package/dist/harbor/dock.js +98 -12
- package/dist/harbor/harbor.d.ts +27 -8
- package/dist/harbor/harbor.js +217 -26
- package/dist/harbor/index.d.ts +8 -2
- package/dist/harbor/index.js +8 -1
- package/dist/harbor/package.d.ts +12 -0
- package/dist/harbor/package.js +130 -0
- package/dist/harbor/probe.d.ts +18 -0
- package/dist/harbor/probe.js +15 -0
- package/dist/harbor/registry.d.ts +6 -3
- package/dist/harbor/registry.js +83 -7
- package/dist/harbor/relay.d.ts +15 -0
- package/dist/harbor/relay.js +387 -0
- package/dist/harbor/root-line.d.ts +9 -0
- package/dist/harbor/root-line.js +38 -0
- package/dist/harbor/terrain.d.ts +14 -1
- 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 +5 -4
- package/dist/index.js +12 -6
- 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 +11 -3
- package/dist/pointer/bodies.js +47 -18
- package/dist/pointer/index.d.ts +1 -1
- package/dist/pointer/index.js +1 -1
- package/dist/pointer/world.d.ts +12 -5
- package/dist/pointer/world.js +29 -14
- 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 +6 -4
- 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/quo/keys.js +1 -1
- package/dist/tcp/frame.d.ts +25 -0
- package/dist/tcp/frame.js +64 -0
- package/dist/tcp/index.d.ts +24 -0
- package/dist/tcp/index.js +260 -0
- package/dist/ward/index.d.ts +3 -2
- package/dist/ward/index.js +2 -1
- package/dist/ward/pilot.d.ts +8 -0
- package/dist/ward/pilot.js +50 -0
- package/dist/ward/stance.d.ts +4 -4
- package/dist/ward/stance.js +10 -7
- package/dist/ward/ward-being.d.ts +4 -1
- package/dist/ward/ward-being.js +8 -2
- package/dist/ward/ward.d.ts +11 -2
- package/dist/ward/ward.js +41 -5
- package/package.json +28 -2
- package/src/being/being.ts +5 -1
- package/src/being/faculty.ts +14 -7
- package/src/being/index.ts +1 -0
- package/src/being/kind.ts +30 -0
- package/src/being/types.ts +8 -6
- 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 +224 -0
- package/src/cli/harbor.ts +103 -0
- package/src/cli/main.ts +13 -0
- package/src/contract/index.ts +42 -10
- package/src/edge/index.ts +359 -0
- package/src/edge.ts +10 -0
- package/src/folder/index.ts +87 -84
- package/src/harbor/carrying.ts +56 -0
- package/src/harbor/catalogue.ts +125 -0
- package/src/harbor/dock.ts +122 -15
- package/src/harbor/harbor.ts +240 -36
- package/src/harbor/index.ts +10 -3
- package/src/harbor/package.ts +147 -0
- package/src/harbor/probe.ts +34 -0
- package/src/harbor/registry.ts +73 -9
- package/src/harbor/relay.ts +399 -0
- package/src/harbor/root-line.ts +39 -0
- package/src/harbor/terrain.ts +31 -3
- package/src/http/index.ts +150 -0
- package/src/http/websocket.ts +124 -0
- package/src/index.ts +39 -6
- 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 +45 -16
- package/src/pointer/index.ts +1 -1
- package/src/pointer/world.ts +38 -19
- package/src/quo/address.ts +87 -0
- package/src/quo/door.ts +6 -4
- package/src/quo/index.ts +1 -0
- package/src/quo/invitation.ts +10 -6
- package/src/quo/keys.ts +1 -1
- package/src/stand/main.ts +1 -0
- package/src/stand/stand.ts +87 -6
- package/src/tcp/index.ts +284 -0
- package/src/ward/index.ts +3 -2
- package/src/ward/pilot.ts +50 -0
- package/src/ward/stance.ts +14 -8
- package/src/ward/ward-being.ts +9 -3
- package/src/ward/ward.ts +53 -6
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The catalogue: the dock's first faculty, the code a harbor runs. Her
|
|
3
|
+
// cells name each module by the name it declares, its version, and where
|
|
4
|
+
// the terrain's loader finds it. The harbor wakes her before any other
|
|
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.
|
|
8
|
+
//
|
|
9
|
+
// add { from } the module the loader finds there, run from now
|
|
10
|
+
// on; the same module again takes its new version
|
|
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
|
|
15
|
+
//
|
|
16
|
+
// She is the kit's, of kind `org.nervur.catalogue`, and answers the root
|
|
17
|
+
// and an owner alone.
|
|
18
|
+
import { Faculty } from '../being/index.js';
|
|
19
|
+
import { ownerAsk } from '../ward/index.js';
|
|
20
|
+
export const CATALOGUE = 'catalogue';
|
|
21
|
+
const text = (v) => typeof v === 'string';
|
|
22
|
+
export class Catalogue extends Faculty {
|
|
23
|
+
static kind = 'org.nervur.catalogue';
|
|
24
|
+
static cells = { modules: {} };
|
|
25
|
+
static asks = {
|
|
26
|
+
add: ownerAsk('a module run from now on', { required: ['from'] }),
|
|
27
|
+
remove: ownerAsk('a module run no more', { required: ['module'] }),
|
|
28
|
+
modules: ownerAsk('the modules this harbor runs'),
|
|
29
|
+
};
|
|
30
|
+
#harbor;
|
|
31
|
+
#running = new Map();
|
|
32
|
+
#failed = new Map();
|
|
33
|
+
constructor(stance, harbor) {
|
|
34
|
+
super(stance);
|
|
35
|
+
this.#harbor = harbor;
|
|
36
|
+
}
|
|
37
|
+
get #rows() {
|
|
38
|
+
return this.cells.modules;
|
|
39
|
+
}
|
|
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.
|
|
43
|
+
async wake() {
|
|
44
|
+
for (const [module, row] of Object.entries(this.#rows)) {
|
|
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);
|
|
51
|
+
}
|
|
52
|
+
this.#harbor.run([...this.#running.values()]);
|
|
53
|
+
}
|
|
54
|
+
async add(args) {
|
|
55
|
+
if (!text(args.from))
|
|
56
|
+
return { error: 'from is text' };
|
|
57
|
+
const loaded = await this.#harbor.load(args.from);
|
|
58
|
+
if (loaded === null)
|
|
59
|
+
return { error: 'no such module' };
|
|
60
|
+
const running = new Map(this.#running);
|
|
61
|
+
running.set(loaded.module, loaded);
|
|
62
|
+
const refused = this.#harbor.run([...running.values()]);
|
|
63
|
+
if (refused !== null)
|
|
64
|
+
return { error: refused };
|
|
65
|
+
this.#running.set(loaded.module, loaded);
|
|
66
|
+
this.#failed.delete(loaded.module);
|
|
67
|
+
this.#rows[loaded.module] = { version: loaded.version, from: loaded.from, kinds: this.#harbor.kindsOf(loaded.module) };
|
|
68
|
+
return { added: loaded.module, version: loaded.version };
|
|
69
|
+
}
|
|
70
|
+
remove(args) {
|
|
71
|
+
const module = args.module;
|
|
72
|
+
if (!text(module) || !Object.hasOwn(this.#rows, module))
|
|
73
|
+
return { error: 'no such module' };
|
|
74
|
+
const kinds = new Set([...this.#rows[module].kinds, ...this.#harbor.kindsOf(module)]);
|
|
75
|
+
if (this.#harbor.stands([...kinds]))
|
|
76
|
+
return { error: 'a being stands on this module' };
|
|
77
|
+
const running = new Map(this.#running);
|
|
78
|
+
running.delete(module);
|
|
79
|
+
this.#harbor.run([...running.values()]);
|
|
80
|
+
this.#running.delete(module);
|
|
81
|
+
this.#failed.delete(module);
|
|
82
|
+
delete this.#rows[module];
|
|
83
|
+
return { removed: module };
|
|
84
|
+
}
|
|
85
|
+
modules() {
|
|
86
|
+
const out = {};
|
|
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
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// The catalogue class one harbor stands: a being is born of her class and
|
|
95
|
+
// her stance alone, so the class holds its harbor.
|
|
96
|
+
export const catalogueFor = (harbor) => class extends Catalogue {
|
|
97
|
+
static kind = Catalogue.kind;
|
|
98
|
+
constructor(stance) {
|
|
99
|
+
super(stance, harbor);
|
|
100
|
+
}
|
|
101
|
+
};
|
package/dist/harbor/dock.d.ts
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
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
|
-
host(name: string, invitation: Invitation): Promise<
|
|
4
|
+
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
5
|
+
route(pk: string, at: readonly string[] | null): boolean;
|
|
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>;
|
|
5
12
|
}
|
|
13
|
+
export type Hosted = {
|
|
14
|
+
ward: string;
|
|
15
|
+
seed: string;
|
|
16
|
+
};
|
|
17
|
+
export type Failures = {
|
|
18
|
+
wards: Record<string, string>;
|
|
19
|
+
routes: Record<string, string>;
|
|
20
|
+
};
|
|
6
21
|
export declare const BOX = "box";
|
|
7
22
|
export declare class Dock extends WardBeing implements Lender {
|
|
8
23
|
#private;
|
|
24
|
+
static readonly kind: string;
|
|
9
25
|
static cells: JsonObject;
|
|
10
26
|
static asks: Record<string, AskSpec>;
|
|
11
27
|
constructor(stance: WardBeingStance, hosting: Hosting);
|
|
12
|
-
|
|
28
|
+
describe(asker: Asker): Blueprint;
|
|
29
|
+
get hosted(): Hosted[];
|
|
30
|
+
get routes(): Record<string, string[]>;
|
|
31
|
+
get addresses(): string[];
|
|
32
|
+
route(args: JsonObject): JsonObject;
|
|
33
|
+
reach(args: JsonObject): JsonObject;
|
|
34
|
+
learn(ward: string, at: readonly string[]): Promise<void>;
|
|
35
|
+
standCatalogue(): Promise<void>;
|
|
36
|
+
unboot(args: JsonObject): Promise<JsonObject>;
|
|
37
|
+
standCarriers(opens: readonly {
|
|
38
|
+
key: string;
|
|
39
|
+
kind: string;
|
|
40
|
+
}[]): Promise<void>;
|
|
13
41
|
open(args: JsonObject): Promise<JsonObject>;
|
|
42
|
+
sweep(): Promise<JsonObject>;
|
|
14
43
|
host(args: JsonObject): Promise<JsonObject>;
|
|
15
44
|
lend(args: JsonObject): Promise<JsonObject>;
|
|
16
45
|
retract(args: JsonObject): Promise<JsonObject>;
|
package/dist/harbor/dock.js
CHANGED
|
@@ -5,23 +5,40 @@
|
|
|
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 }
|
|
9
|
-
// the
|
|
8
|
+
// host { ward, seed? } a hosted ward stands under the seed named,
|
|
9
|
+
// or a drawn one, holding a standing on the
|
|
10
|
+
// dock; the dock keeps its name and seed
|
|
10
11
|
// lend { contract } asked by a hosted ward: an offer from a
|
|
11
12
|
// faculty that fulfils the contract
|
|
12
13
|
// retract { contract, heir } an offer that was not taken
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
23
|
+
import { Faculty, KIT } from '../being/index.js';
|
|
24
|
+
import { CATALOGUE } from './catalogue.js';
|
|
25
|
+
import { readAt, readInvitation } from '../quo/index.js';
|
|
26
|
+
import { ownerAsk, pilots, rootAsk, WardBeing } from '../ward/index.js';
|
|
16
27
|
export const BOX = 'box';
|
|
17
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;
|
|
18
31
|
const hosted = (_, notes) => notes?.hosted === true;
|
|
19
32
|
export class Dock extends WardBeing {
|
|
20
|
-
static
|
|
33
|
+
static kind = 'org.nervur.dock';
|
|
34
|
+
static cells = { hosted: [], routes: {}, reach: [] };
|
|
21
35
|
static asks = {
|
|
22
36
|
...WardBeing.asks,
|
|
23
37
|
open: ownerAsk('a faculty of this harbor', { required: ['key', 'class'] }),
|
|
24
38
|
host: ownerAsk('a ward this harbor hosts', { required: ['ward'] }),
|
|
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'),
|
|
25
42
|
lend: { description: 'an offer from a faculty fulfilling a contract', input: { type: 'object', required: ['contract'] }, for: hosted },
|
|
26
43
|
retract: { description: 'an offer that was not taken', input: { type: 'object', required: ['contract', 'heir'] }, for: hosted },
|
|
27
44
|
};
|
|
@@ -30,29 +47,98 @@ export class Dock extends WardBeing {
|
|
|
30
47
|
super(stance);
|
|
31
48
|
this.#hosting = hosting;
|
|
32
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
|
+
}
|
|
33
57
|
get hosted() {
|
|
34
|
-
return
|
|
58
|
+
return this.cells.hosted.map(({ ward, seed }) => ({ ward, seed }));
|
|
59
|
+
}
|
|
60
|
+
get routes() {
|
|
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];
|
|
66
|
+
}
|
|
67
|
+
route(args) {
|
|
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))
|
|
71
|
+
return { error: 'not routed' };
|
|
72
|
+
const routes = this.cells.routes;
|
|
73
|
+
if (at === null)
|
|
74
|
+
delete routes[ward];
|
|
75
|
+
else
|
|
76
|
+
routes[ward] = [...at];
|
|
77
|
+
return { routed: ward };
|
|
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
|
+
}
|
|
94
|
+
// Genesis: the catalogue stands first, and is kept at once.
|
|
95
|
+
async standCatalogue() {
|
|
96
|
+
const kind = 'org.nervur.catalogue';
|
|
97
|
+
if ((await this.stance.boot(kind, CATALOGUE, CATALOGUE)) === null || !(await this.#hosting.keep()))
|
|
98
|
+
throw new Error('the catalogue did not stand');
|
|
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();
|
|
35
111
|
}
|
|
36
112
|
async open(args) {
|
|
37
113
|
if (!text(args.key) || !text(args.class))
|
|
38
114
|
return { error: 'key and class are text' };
|
|
39
|
-
|
|
115
|
+
const mine = args.class.startsWith(KIT) ? this.#hosting.grounds(args.class) : true;
|
|
116
|
+
if (!mine || !Faculty.fulfils(this.steward.classOf(args.class)))
|
|
40
117
|
return { error: 'no such faculty' };
|
|
41
118
|
const opened = await this.stance.boot(args.class, args.key, args.key);
|
|
42
|
-
|
|
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() };
|
|
43
126
|
}
|
|
44
127
|
async host(args) {
|
|
45
128
|
const name = args.ward;
|
|
46
|
-
if (!text(name) || name === BOX || this.hosted.
|
|
129
|
+
if (!text(name) || name === BOX || this.hosted.some((h) => h.ward === name))
|
|
47
130
|
return { error: 'no such ward to host' };
|
|
131
|
+
if (args.seed !== undefined && !text(args.seed))
|
|
132
|
+
return { error: 'seed is text' };
|
|
48
133
|
const invitation = await this.stance.occupants.invite(name, { hosted: true });
|
|
49
134
|
if (invitation === null)
|
|
50
135
|
return { error: 'not hosted' };
|
|
51
|
-
|
|
136
|
+
const seed = await this.#hosting.host(name, invitation, args.seed);
|
|
137
|
+
if (seed === null) {
|
|
52
138
|
this.stance.occupants.remove(name);
|
|
53
139
|
return { error: 'not hosted' };
|
|
54
140
|
}
|
|
55
|
-
this.cells.hosted.push(name);
|
|
141
|
+
this.cells.hosted.push({ ward: name, seed });
|
|
56
142
|
return { hosted: name };
|
|
57
143
|
}
|
|
58
144
|
async lend(args) {
|
package/dist/harbor/harbor.d.ts
CHANGED
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
import type { Answer, BeingClass, Invitation, JsonObject } from '../being/index.ts';
|
|
2
|
-
import { Carrier } from '../contract/index.ts';
|
|
3
|
-
import { Ward } from '../ward/index.ts';
|
|
4
|
-
import {
|
|
2
|
+
import { Carrier, type Loaded } from '../contract/index.ts';
|
|
3
|
+
import { Ward, type Catalogue as Resolving } from '../ward/index.ts';
|
|
4
|
+
import { Catalogue, type Cataloguing } from './catalogue.ts';
|
|
5
|
+
import { CarrierFaculty } from './carrying.ts';
|
|
6
|
+
import { Dock, type Failures, type Hosting } from './dock.ts';
|
|
5
7
|
import { Registry } from './registry.ts';
|
|
6
|
-
import type
|
|
7
|
-
|
|
8
|
+
import { type Probing } from './probe.ts';
|
|
9
|
+
import { Terrain } from './terrain.ts';
|
|
10
|
+
export declare class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
|
|
8
11
|
#private;
|
|
9
12
|
readonly terrain: Terrain;
|
|
10
|
-
readonly registry: Registry;
|
|
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;
|
|
19
|
+
get catalogue(): Catalogue;
|
|
20
|
+
get registry(): Registry;
|
|
15
21
|
ward(name: string): Ward | undefined;
|
|
16
22
|
get names(): string[];
|
|
17
23
|
root(method?: string, args?: JsonObject): Promise<Answer>;
|
|
18
24
|
holds(pk: string): boolean;
|
|
19
25
|
arrive(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
20
26
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
21
|
-
|
|
27
|
+
classOf(kind: string): BeingClass | undefined;
|
|
28
|
+
load(from: string): Promise<Loaded | null>;
|
|
29
|
+
check(modules: readonly Loaded[]): string | null;
|
|
30
|
+
run(modules: readonly Loaded[]): string | null;
|
|
31
|
+
stands(kinds: readonly string[]): boolean;
|
|
32
|
+
kindsOf(module: string): string[];
|
|
33
|
+
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
34
|
+
keep(): Promise<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>;
|
|
22
41
|
}
|
package/dist/harbor/harbor.js
CHANGED
|
@@ -1,29 +1,108 @@
|
|
|
1
1
|
import { Carrier } from '../contract/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { hex, isHex, unhex } from '../crypto/index.js';
|
|
3
|
+
import { unspecified, WardKey } from '../quo/index.js';
|
|
4
|
+
import { DOCK, Pilot, Ward } from '../ward/index.js';
|
|
5
|
+
import { Catalogue, CATALOGUE, catalogueFor } from './catalogue.js';
|
|
6
|
+
import { CarrierFaculty, UNDIALED } from './carrying.js';
|
|
3
7
|
import { BOX, Dock } from './dock.js';
|
|
8
|
+
import { Package } from './package.js';
|
|
4
9
|
import { Registry } from './registry.js';
|
|
10
|
+
import { relayClient, relayFaculty } from './relay.js';
|
|
11
|
+
import { probeGround } from './probe.js';
|
|
12
|
+
import { Terrain } from './terrain.js';
|
|
5
13
|
export class Harbor extends Carrier {
|
|
6
14
|
terrain;
|
|
7
|
-
|
|
15
|
+
#package;
|
|
8
16
|
#wards = new Map();
|
|
9
|
-
|
|
17
|
+
#kit;
|
|
18
|
+
#grounds;
|
|
19
|
+
#registry;
|
|
20
|
+
#failed = { wards: {}, routes: {} };
|
|
21
|
+
constructor(terrain, pack) {
|
|
10
22
|
super();
|
|
11
23
|
this.terrain = terrain;
|
|
12
|
-
this
|
|
24
|
+
this.#package = pack;
|
|
25
|
+
const faculties = [...terrain.faculties.map((make) => make(this)), relayFaculty(terrain.clock), relayClient(this, terrain.clock)];
|
|
26
|
+
this.#grounds = new Set(faculties.map((C) => C.kind));
|
|
27
|
+
this.#kit = [catalogueFor(this), Pilot, ...faculties];
|
|
28
|
+
this.#registry = new Registry(this.#kit, []);
|
|
13
29
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
// A harbor on the terrain handed in, or, with none, on the ground the
|
|
31
|
+
// probe finds here. A ground another run holds refuses, and what the
|
|
32
|
+
// ground serves beside the harbor stands once it is whole.
|
|
33
|
+
static async open(from = {}) {
|
|
34
|
+
const terrain = from instanceof Terrain ? from : probeGround(from).terrain;
|
|
35
|
+
await terrain.claim();
|
|
36
|
+
let harbor;
|
|
37
|
+
try {
|
|
38
|
+
harbor = await Harbor.#stand(terrain);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
await terrain.release();
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await terrain.stand(harbor);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
await harbor.close();
|
|
49
|
+
throw e;
|
|
50
|
+
}
|
|
51
|
+
return harbor;
|
|
52
|
+
}
|
|
53
|
+
static async #stand(terrain) {
|
|
54
|
+
const seed = await terrain.custody.seed();
|
|
55
|
+
const harbor = new Harbor(terrain, await Package.open(terrain.memory, terrain.entropy, seed));
|
|
56
|
+
await harbor.#unpack(BOX, seed, undefined, (stance) => new Dock(stance, harbor));
|
|
57
|
+
const genesis = !harbor.box.partition.being(CATALOGUE);
|
|
58
|
+
if (genesis)
|
|
59
|
+
await harbor.dock.standCatalogue();
|
|
60
|
+
await harbor.catalogue.wake();
|
|
61
|
+
if (genesis)
|
|
62
|
+
await harbor.dock.standCarriers(terrain.opens);
|
|
63
|
+
else if (terrain.wakes)
|
|
64
|
+
for (const carrier of harbor.carriers)
|
|
65
|
+
await carrier.awake();
|
|
66
|
+
for (const [pk, at] of Object.entries(harbor.dock.routes)) {
|
|
67
|
+
if (!harbor.#dialable(at))
|
|
68
|
+
harbor.#failed.routes[pk] = 'no carrier dials it';
|
|
69
|
+
}
|
|
70
|
+
for (const hosted of harbor.dock.hosted) {
|
|
71
|
+
try {
|
|
72
|
+
await harbor.#unpack(hosted.ward, unhex(hosted.seed));
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
harbor.#failed.wards[hosted.ward] = e instanceof Error ? e.message : String(e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
19
78
|
return harbor;
|
|
20
79
|
}
|
|
80
|
+
// Hosting: every place of the terrain's memory the dock does not name,
|
|
81
|
+
// gone. A crash inside `host` leaves such a place, sealed under a seed
|
|
82
|
+
// nothing kept, and nothing else writes one.
|
|
83
|
+
async sweep() {
|
|
84
|
+
const mine = new Set(await Promise.all([BOX, ...this.dock.hosted.map((h) => h.ward)].map((name) => this.#package.place(name))));
|
|
85
|
+
const swept = (await this.terrain.memory.places()).filter((place) => !mine.has(place));
|
|
86
|
+
for (const place of swept)
|
|
87
|
+
await this.terrain.memory.forget(place);
|
|
88
|
+
return swept;
|
|
89
|
+
}
|
|
90
|
+
// Hosting: what did not stand when the harbor opened, each alone.
|
|
91
|
+
failures() {
|
|
92
|
+
return { wards: { ...this.#failed.wards }, routes: { ...this.#failed.routes } };
|
|
93
|
+
}
|
|
21
94
|
get box() {
|
|
22
95
|
return this.#wards.get(BOX);
|
|
23
96
|
}
|
|
24
97
|
get dock() {
|
|
25
98
|
return this.box.wardBeing;
|
|
26
99
|
}
|
|
100
|
+
get catalogue() {
|
|
101
|
+
return this.box.being(CATALOGUE);
|
|
102
|
+
}
|
|
103
|
+
get registry() {
|
|
104
|
+
return this.#registry;
|
|
105
|
+
}
|
|
27
106
|
ward(name) {
|
|
28
107
|
return this.#wards.get(name);
|
|
29
108
|
}
|
|
@@ -37,44 +116,156 @@ export class Harbor extends Carrier {
|
|
|
37
116
|
holds(pk) {
|
|
38
117
|
return this.#door(pk) !== undefined;
|
|
39
118
|
}
|
|
40
|
-
// Bytes for one of this harbor's doors, or
|
|
119
|
+
// Bytes for one of this harbor's doors, or for a ward a relay of this
|
|
120
|
+
// harbor answers for, or nothing.
|
|
41
121
|
async arrive(pk, bytes) {
|
|
42
122
|
const ward = this.#door(pk);
|
|
43
|
-
|
|
123
|
+
if (ward)
|
|
124
|
+
return (await ward.door.arrive(bytes)).bytes;
|
|
125
|
+
return (await this.#relaying(pk)?.relay(pk, bytes)) ?? null;
|
|
44
126
|
}
|
|
45
|
-
// Carrier: this harbor's own doors first, then
|
|
46
|
-
|
|
47
|
-
|
|
127
|
+
// Carrier: this harbor's own doors and relays first, then a route's
|
|
128
|
+
// addresses in order, each to the carrier that dials it, and with no
|
|
129
|
+
// route the terrain's carrier.
|
|
130
|
+
async carry(pk, bytes) {
|
|
131
|
+
if (this.holds(pk) || this.#relaying(pk))
|
|
132
|
+
return this.arrive(pk, bytes);
|
|
133
|
+
const at = this.dock.routes[pk];
|
|
134
|
+
if (at === undefined)
|
|
135
|
+
return this.terrain.carrier.carry(pk, bytes);
|
|
136
|
+
for (const address of at) {
|
|
137
|
+
const carrier = this.carriers.find((c) => c.accepts(address));
|
|
138
|
+
const out = carrier ? await carrier.dial(address, pk, bytes) : UNDIALED;
|
|
139
|
+
if (out !== UNDIALED)
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
48
143
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
144
|
+
// Resolving: a kind, by the registry the catalogue runs.
|
|
145
|
+
classOf(kind) {
|
|
146
|
+
return this.#registry.classOf(kind);
|
|
147
|
+
}
|
|
148
|
+
// Cataloguing: code through the terrain, and the modules run.
|
|
149
|
+
load(from) {
|
|
150
|
+
return this.terrain.loader.load(from);
|
|
151
|
+
}
|
|
152
|
+
check(modules) {
|
|
153
|
+
return Registry.refusal(this.#kit, modules);
|
|
154
|
+
}
|
|
155
|
+
run(modules) {
|
|
156
|
+
try {
|
|
157
|
+
this.#registry = new Registry(this.#kit, modules);
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
return e.message;
|
|
161
|
+
}
|
|
162
|
+
for (const ward of this.#wards.values())
|
|
163
|
+
ward.bearAnew();
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
stands(kinds) {
|
|
167
|
+
for (const ward of this.#wards.values()) {
|
|
168
|
+
for (const row of Object.values(ward.partition.beings))
|
|
169
|
+
if (kinds.includes(row.class))
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
kindsOf(module) {
|
|
175
|
+
return this.#registry.kindsOf(module);
|
|
176
|
+
}
|
|
177
|
+
// Hosting: a ward the dock hosts stands now, under the seed named or a
|
|
178
|
+
// drawn one, and the seed it stands under comes back. A ward whose key
|
|
179
|
+
// another ward of this harbor already has does not stand.
|
|
180
|
+
async host(name, invitation, seed) {
|
|
51
181
|
if (this.#wards.has(name))
|
|
52
|
-
return
|
|
182
|
+
return null;
|
|
183
|
+
const bytes = seed === undefined ? this.terrain.entropy.draw(32) : isHex(seed, 32) ? unhex(seed) : null;
|
|
184
|
+
if (bytes === null || this.holds((await WardKey.from(bytes)).pk))
|
|
185
|
+
return null;
|
|
53
186
|
try {
|
|
54
|
-
await this.#unpack(name, invitation);
|
|
187
|
+
await this.#unpack(name, bytes, invitation);
|
|
55
188
|
}
|
|
56
189
|
catch {
|
|
57
|
-
return
|
|
190
|
+
return null;
|
|
58
191
|
}
|
|
59
192
|
if (this.#wards.get(name).wardBeing.stance.standings.get(DOCK))
|
|
60
|
-
return
|
|
193
|
+
return hex(bytes);
|
|
61
194
|
this.#wards.delete(name);
|
|
62
|
-
return
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
keep() {
|
|
198
|
+
return this.box.partition.kept();
|
|
199
|
+
}
|
|
200
|
+
// Hosting: a route is taken where a carrier of this harbor dials one of
|
|
201
|
+
// its addresses; forgetting one always is.
|
|
202
|
+
route(pk, at) {
|
|
203
|
+
const taken = isHex(pk, 64) && (at === null || this.#dialable(at));
|
|
204
|
+
if (taken)
|
|
205
|
+
delete this.#failed.routes[pk];
|
|
206
|
+
return taken;
|
|
207
|
+
}
|
|
208
|
+
// Hosting: the carriers this ground ships, and their sleep and waking.
|
|
209
|
+
grounds(kind) {
|
|
210
|
+
return this.#grounds.has(kind);
|
|
211
|
+
}
|
|
212
|
+
async awaken(key) {
|
|
213
|
+
const being = this.box.being(key);
|
|
214
|
+
if (being instanceof CarrierFaculty)
|
|
215
|
+
await being.awake();
|
|
216
|
+
}
|
|
217
|
+
async lull(key) {
|
|
218
|
+
const being = this.box.being(key);
|
|
219
|
+
if (being instanceof CarrierFaculty)
|
|
220
|
+
await being.sleep();
|
|
221
|
+
}
|
|
222
|
+
// The carrier faculties standing in the box ward.
|
|
223
|
+
get carriers() {
|
|
224
|
+
return Object.keys(this.box.partition.beings)
|
|
225
|
+
.map((key) => this.box.being(key))
|
|
226
|
+
.filter((being) => being instanceof CarrierFaculty);
|
|
227
|
+
}
|
|
228
|
+
// Every carrier asleep, the last opened first: nothing listens and no
|
|
229
|
+
// line stays open.
|
|
230
|
+
async close() {
|
|
231
|
+
for (const carrier of this.carriers.reverse())
|
|
232
|
+
await carrier.sleep();
|
|
233
|
+
await this.terrain.release();
|
|
234
|
+
}
|
|
235
|
+
#relaying(pk) {
|
|
236
|
+
return this.carriers.find((carrier) => carrier.relays(pk));
|
|
237
|
+
}
|
|
238
|
+
#dialable(at) {
|
|
239
|
+
return at.some((address) => this.carriers.some((carrier) => carrier.accepts(address)));
|
|
63
240
|
}
|
|
64
|
-
async #unpack(name, dock, root) {
|
|
65
|
-
const { entropy, clock
|
|
241
|
+
async #unpack(name, seed, dock, root) {
|
|
242
|
+
const { entropy, clock } = this.terrain;
|
|
66
243
|
const ward = await Ward.unpack({
|
|
67
|
-
seed
|
|
68
|
-
memory: await
|
|
69
|
-
catalogue: this
|
|
244
|
+
seed,
|
|
245
|
+
memory: await this.#package.rows(name),
|
|
246
|
+
catalogue: this,
|
|
70
247
|
entropy,
|
|
71
248
|
clock,
|
|
72
249
|
carrier: this,
|
|
250
|
+
routes: this.#routes,
|
|
73
251
|
...(dock ? { dock } : {}),
|
|
74
252
|
...(root ? { root } : {}),
|
|
75
253
|
});
|
|
76
254
|
this.#wards.set(name, ward);
|
|
77
255
|
}
|
|
256
|
+
// Every ward is reached where the harbor is: where the root's `reach`
|
|
257
|
+
// says, and where it said nothing, where its carriers listen, save the
|
|
258
|
+
// unspecified host. What a ward learns from an invitation's `at` is the
|
|
259
|
+
// dock's to keep. A ward of this harbor needs no route.
|
|
260
|
+
#routes = {
|
|
261
|
+
reach: () => {
|
|
262
|
+
if (!this.#wards.has(BOX))
|
|
263
|
+
return [];
|
|
264
|
+
const set = this.dock.addresses;
|
|
265
|
+
return set.length > 0 ? set : this.carriers.flatMap((carrier) => carrier.listening()).filter((address) => !unspecified(address));
|
|
266
|
+
},
|
|
267
|
+
learn: (ward, at) => (this.holds(ward) ? Promise.resolve() : this.dock.learn(ward, at)),
|
|
268
|
+
};
|
|
78
269
|
#door(pk) {
|
|
79
270
|
for (const ward of this.#wards.values())
|
|
80
271
|
if (ward.pk === pk)
|
package/dist/harbor/index.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Catalogue, CATALOGUE, type Cataloguing, type ModuleRow } from './catalogue.ts';
|
|
2
|
+
export { BOX, Dock, type Failures, type Hosted, type Hosting } from './dock.ts';
|
|
3
|
+
export { CarrierFaculty, UNDIALED, type Dialed, type Ground, type GroundFaculty } from './carrying.ts';
|
|
2
4
|
export { Harbor } from './harbor.ts';
|
|
5
|
+
export { copyPackage, Package } from './package.ts';
|
|
6
|
+
export { readRootRequest, rootLine, type RootRequest } from './root-line.ts';
|
|
7
|
+
export { addGround, grounds, probeGround, type GroundProbe, type Probing } from './probe.ts';
|
|
3
8
|
export { Registry } from './registry.ts';
|
|
4
|
-
export {
|
|
9
|
+
export { Push, PUSH, RELAY, RELAY_CLIENT, RELAY_HOLD, RELAY_REST, RELAY_WAIT, RELAYED } from './relay.ts';
|
|
10
|
+
export { Terrain, type Opening } from './terrain.ts';
|