nervur 0.20.0 → 0.20.1
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 +107 -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/cli/command.d.ts +13 -0
- package/dist/cli/command.js +194 -0
- package/dist/cli/harbor.d.ts +21 -0
- package/dist/cli/harbor.js +141 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +14 -0
- package/dist/contract/index.d.ts +15 -2
- package/dist/contract/index.js +16 -2
- package/dist/folder/index.d.ts +5 -4
- package/dist/folder/index.js +45 -83
- package/dist/harbor/catalogue.d.ts +25 -0
- package/dist/harbor/catalogue.js +93 -0
- package/dist/harbor/dock.d.ts +12 -2
- package/dist/harbor/dock.js +39 -9
- package/dist/harbor/harbor.d.ts +15 -6
- package/dist/harbor/harbor.js +83 -21
- package/dist/harbor/index.d.ts +4 -1
- package/dist/harbor/index.js +5 -1
- package/dist/harbor/package.d.ts +10 -0
- package/dist/harbor/package.js +115 -0
- package/dist/harbor/registry.d.ts +5 -3
- package/dist/harbor/registry.js +72 -7
- package/dist/harbor/root-line.d.ts +9 -0
- package/dist/harbor/root-line.js +36 -0
- package/dist/harbor/terrain.d.ts +2 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/pointer/bodies.d.ts +9 -3
- package/dist/pointer/bodies.js +37 -18
- package/dist/pointer/index.d.ts +1 -1
- package/dist/pointer/index.js +1 -1
- package/dist/pointer/world.d.ts +5 -4
- package/dist/pointer/world.js +23 -14
- package/dist/quo/door.js +2 -2
- package/dist/quo/keys.js +1 -1
- package/dist/tcp/frame.d.ts +29 -0
- package/dist/tcp/frame.js +74 -0
- package/dist/tcp/index.d.ts +18 -0
- package/dist/tcp/index.js +164 -0
- package/dist/ward/index.d.ts +1 -0
- package/dist/ward/index.js +1 -0
- package/dist/ward/pilot.d.ts +8 -0
- package/dist/ward/pilot.js +50 -0
- package/dist/ward/stance.d.ts +2 -4
- package/dist/ward/stance.js +7 -6
- package/dist/ward/ward-being.d.ts +2 -0
- package/dist/ward/ward-being.js +7 -1
- package/dist/ward/ward.d.ts +4 -2
- package/dist/ward/ward.js +35 -5
- package/package.json +8 -1
- 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/cli/command.ts +188 -0
- package/src/cli/harbor.ts +137 -0
- package/src/cli/main.ts +13 -0
- package/src/contract/index.ts +38 -9
- package/src/folder/index.ts +44 -86
- package/src/harbor/catalogue.ts +110 -0
- package/src/harbor/dock.ts +48 -12
- package/src/harbor/harbor.ts +104 -29
- package/src/harbor/index.ts +6 -2
- package/src/harbor/package.ts +131 -0
- package/src/harbor/registry.ts +62 -9
- package/src/harbor/root-line.ts +36 -0
- package/src/harbor/terrain.ts +3 -1
- package/src/index.ts +5 -5
- package/src/pointer/bodies.ts +34 -16
- package/src/pointer/index.ts +1 -1
- package/src/pointer/world.ts +21 -18
- package/src/quo/door.ts +2 -2
- package/src/quo/keys.ts +1 -1
- package/src/stand/main.ts +1 -0
- package/src/stand/stand.ts +46 -4
- package/src/tcp/frame.ts +78 -0
- package/src/tcp/index.ts +173 -0
- package/src/ward/index.ts +1 -0
- package/src/ward/pilot.ts +50 -0
- package/src/ward/stance.ts +8 -7
- package/src/ward/ward-being.ts +7 -1
- package/src/ward/ward.ts +37 -6
package/dist/harbor/harbor.d.ts
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
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';
|
|
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';
|
|
4
5
|
import { Dock, type Hosting } from './dock.ts';
|
|
5
6
|
import { Registry } from './registry.ts';
|
|
6
7
|
import type { Terrain } from './terrain.ts';
|
|
7
|
-
export declare class Harbor extends Carrier implements Hosting {
|
|
8
|
+
export declare class Harbor extends Carrier implements Hosting, Cataloguing, Resolving {
|
|
8
9
|
#private;
|
|
9
10
|
readonly terrain: Terrain;
|
|
10
|
-
readonly registry: Registry;
|
|
11
11
|
private constructor();
|
|
12
|
-
static open(terrain: Terrain
|
|
12
|
+
static open(terrain: Terrain): Promise<Harbor>;
|
|
13
13
|
get box(): Ward;
|
|
14
14
|
get dock(): Dock;
|
|
15
|
+
get catalogue(): Catalogue;
|
|
16
|
+
get registry(): Registry;
|
|
15
17
|
ward(name: string): Ward | undefined;
|
|
16
18
|
get names(): string[];
|
|
17
19
|
root(method?: string, args?: JsonObject): Promise<Answer>;
|
|
18
20
|
holds(pk: string): boolean;
|
|
19
21
|
arrive(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
20
22
|
carry(pk: string, bytes: Uint8Array): Promise<Uint8Array | null>;
|
|
21
|
-
|
|
23
|
+
classOf(kind: string): BeingClass | undefined;
|
|
24
|
+
load(from: string): Promise<Loaded | null>;
|
|
25
|
+
run(modules: readonly Loaded[]): string | null;
|
|
26
|
+
stands(kinds: readonly string[]): boolean;
|
|
27
|
+
kindsOf(module: string): string[];
|
|
28
|
+
host(name: string, invitation: Invitation, seed?: string): Promise<string | null>;
|
|
29
|
+
keep(): Promise<boolean>;
|
|
30
|
+
route(pk: string, at: string | null): boolean;
|
|
22
31
|
}
|
package/dist/harbor/harbor.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
import { Carrier } from '../contract/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { hex, isHex, unhex } from '../crypto/index.js';
|
|
3
|
+
import { WardKey } from '../quo/index.js';
|
|
4
|
+
import { DOCK, Pilot, Ward } from '../ward/index.js';
|
|
5
|
+
import { Catalogue, CATALOGUE, catalogueFor } from './catalogue.js';
|
|
3
6
|
import { BOX, Dock } from './dock.js';
|
|
7
|
+
import { Package } from './package.js';
|
|
4
8
|
import { Registry } from './registry.js';
|
|
5
9
|
export class Harbor extends Carrier {
|
|
6
10
|
terrain;
|
|
7
|
-
|
|
11
|
+
#package;
|
|
8
12
|
#wards = new Map();
|
|
9
|
-
|
|
13
|
+
#kit;
|
|
14
|
+
#registry;
|
|
15
|
+
constructor(terrain, pack) {
|
|
10
16
|
super();
|
|
11
17
|
this.terrain = terrain;
|
|
12
|
-
this
|
|
18
|
+
this.#package = pack;
|
|
19
|
+
this.#kit = [catalogueFor(this), Pilot];
|
|
20
|
+
this.#registry = new Registry(this.#kit, []);
|
|
13
21
|
}
|
|
14
|
-
static async open(terrain
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
static async open(terrain) {
|
|
23
|
+
const seed = await terrain.custody.seed();
|
|
24
|
+
const harbor = new Harbor(terrain, await Package.open(terrain.memory, terrain.entropy, seed));
|
|
25
|
+
await harbor.#unpack(BOX, seed, undefined, (stance) => new Dock(stance, harbor));
|
|
26
|
+
if (!harbor.box.partition.being(CATALOGUE))
|
|
27
|
+
await harbor.dock.standCatalogue();
|
|
28
|
+
await harbor.catalogue.wake();
|
|
29
|
+
for (const [pk, at] of Object.entries(harbor.dock.routes))
|
|
30
|
+
terrain.carrier.route(pk, at);
|
|
31
|
+
for (const hosted of harbor.dock.hosted)
|
|
32
|
+
await harbor.#unpack(hosted.ward, unhex(hosted.seed));
|
|
19
33
|
return harbor;
|
|
20
34
|
}
|
|
21
35
|
get box() {
|
|
@@ -24,6 +38,12 @@ export class Harbor extends Carrier {
|
|
|
24
38
|
get dock() {
|
|
25
39
|
return this.box.wardBeing;
|
|
26
40
|
}
|
|
41
|
+
get catalogue() {
|
|
42
|
+
return this.box.being(CATALOGUE);
|
|
43
|
+
}
|
|
44
|
+
get registry() {
|
|
45
|
+
return this.#registry;
|
|
46
|
+
}
|
|
27
47
|
ward(name) {
|
|
28
48
|
return this.#wards.get(name);
|
|
29
49
|
}
|
|
@@ -46,27 +66,69 @@ export class Harbor extends Carrier {
|
|
|
46
66
|
carry(pk, bytes) {
|
|
47
67
|
return this.holds(pk) ? this.arrive(pk, bytes) : this.terrain.carrier.carry(pk, bytes);
|
|
48
68
|
}
|
|
49
|
-
//
|
|
50
|
-
|
|
69
|
+
// Resolving: a kind, by the registry the catalogue runs.
|
|
70
|
+
classOf(kind) {
|
|
71
|
+
return this.#registry.classOf(kind);
|
|
72
|
+
}
|
|
73
|
+
// Cataloguing: code through the terrain, and the modules run.
|
|
74
|
+
load(from) {
|
|
75
|
+
return this.terrain.loader.load(from);
|
|
76
|
+
}
|
|
77
|
+
run(modules) {
|
|
78
|
+
try {
|
|
79
|
+
this.#registry = new Registry(this.#kit, modules);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
return e.message;
|
|
83
|
+
}
|
|
84
|
+
for (const ward of this.#wards.values())
|
|
85
|
+
ward.bearAnew();
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
stands(kinds) {
|
|
89
|
+
for (const ward of this.#wards.values()) {
|
|
90
|
+
for (const row of Object.values(ward.partition.beings))
|
|
91
|
+
if (kinds.includes(row.class))
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
kindsOf(module) {
|
|
97
|
+
return this.#registry.kindsOf(module);
|
|
98
|
+
}
|
|
99
|
+
// Hosting: a ward the dock hosts stands now, under the seed named or a
|
|
100
|
+
// drawn one, and the seed it stands under comes back. A ward whose key
|
|
101
|
+
// another ward of this harbor already has does not stand.
|
|
102
|
+
async host(name, invitation, seed) {
|
|
51
103
|
if (this.#wards.has(name))
|
|
52
|
-
return
|
|
104
|
+
return null;
|
|
105
|
+
const bytes = seed === undefined ? this.terrain.entropy.draw(32) : isHex(seed, 32) ? unhex(seed) : null;
|
|
106
|
+
if (bytes === null || this.holds((await WardKey.from(bytes)).pk))
|
|
107
|
+
return null;
|
|
53
108
|
try {
|
|
54
|
-
await this.#unpack(name, invitation);
|
|
109
|
+
await this.#unpack(name, bytes, invitation);
|
|
55
110
|
}
|
|
56
111
|
catch {
|
|
57
|
-
return
|
|
112
|
+
return null;
|
|
58
113
|
}
|
|
59
114
|
if (this.#wards.get(name).wardBeing.stance.standings.get(DOCK))
|
|
60
|
-
return
|
|
115
|
+
return hex(bytes);
|
|
61
116
|
this.#wards.delete(name);
|
|
62
|
-
return
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
keep() {
|
|
120
|
+
return this.box.partition.kept();
|
|
121
|
+
}
|
|
122
|
+
// Hosting: the terrain's carrier takes a route, or does not dial.
|
|
123
|
+
route(pk, at) {
|
|
124
|
+
return this.terrain.carrier.route(pk, at);
|
|
63
125
|
}
|
|
64
|
-
async #unpack(name, dock, root) {
|
|
65
|
-
const { entropy, clock
|
|
126
|
+
async #unpack(name, seed, dock, root) {
|
|
127
|
+
const { entropy, clock } = this.terrain;
|
|
66
128
|
const ward = await Ward.unpack({
|
|
67
|
-
seed
|
|
68
|
-
memory: await
|
|
69
|
-
catalogue: this
|
|
129
|
+
seed,
|
|
130
|
+
memory: await this.#package.rows(name),
|
|
131
|
+
catalogue: this,
|
|
70
132
|
entropy,
|
|
71
133
|
clock,
|
|
72
134
|
carrier: this,
|
package/dist/harbor/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { Catalogue, CATALOGUE, type Cataloguing, type ModuleRow } from './catalogue.ts';
|
|
2
|
+
export { BOX, Dock, type Hosted, type Hosting } from './dock.ts';
|
|
2
3
|
export { Harbor } from './harbor.ts';
|
|
4
|
+
export { Package } from './package.ts';
|
|
5
|
+
export { readRootRequest, rootLine, type RootRequest } from './root-line.ts';
|
|
3
6
|
export { Registry } from './registry.ts';
|
|
4
7
|
export { Terrain } from './terrain.ts';
|
package/dist/harbor/index.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
// The onion: a terrain,
|
|
2
|
+
// The onion: a terrain, the package, the dock, the catalogue and its
|
|
3
|
+
// registry, and the harbor.
|
|
4
|
+
export { Catalogue, CATALOGUE } from './catalogue.js';
|
|
3
5
|
export { BOX, Dock } from './dock.js';
|
|
4
6
|
export { Harbor } from './harbor.js';
|
|
7
|
+
export { Package } from './package.js';
|
|
8
|
+
export { readRootRequest, rootLine } from './root-line.js';
|
|
5
9
|
export { Registry } from './registry.js';
|
|
6
10
|
export { Terrain } from './terrain.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Entropy, Memory, Rows } from '../contract/index.ts';
|
|
2
|
+
export declare class Package {
|
|
3
|
+
#private;
|
|
4
|
+
readonly memory: Memory;
|
|
5
|
+
readonly entropy: Entropy;
|
|
6
|
+
private constructor();
|
|
7
|
+
static open(memory: Memory, entropy: Entropy, seed: Uint8Array): Promise<Package>;
|
|
8
|
+
derive(info: string[], length: number): Promise<Uint8Array>;
|
|
9
|
+
rows(name: string): Promise<Rows>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The package: a harbor's memory, sealed under the harbor's seed. The
|
|
3
|
+
// terrain keeps its bytes and reads neither a ward's name, nor a being's
|
|
4
|
+
// key, nor anything a row holds.
|
|
5
|
+
//
|
|
6
|
+
// key HKDF-SHA-256 of the seed under `nervur-package`
|
|
7
|
+
// place 16 bytes under ["place", ward], as hex
|
|
8
|
+
// entry 16 bytes under ["entry", ward, row], as hex
|
|
9
|
+
// seal 32 bytes under ["seal", ward]
|
|
10
|
+
// entry bytes nonce (12) || AES-256-GCM( JSON { row, value } ),
|
|
11
|
+
// the entry's name as additional data
|
|
12
|
+
//
|
|
13
|
+
// A row is the head, under the empty name, or one being, under her key. An
|
|
14
|
+
// entry that does not open refuses the whole ward, so nothing unpacks from
|
|
15
|
+
// bytes the package did not seal.
|
|
16
|
+
import { concat, decrypt, encrypt, hex, hkdf, utf8 } from '../crypto/index.js';
|
|
17
|
+
import { HEAD_ROW } from '../ward/index.js';
|
|
18
|
+
const NONCE = 12;
|
|
19
|
+
const LABEL = utf8('nervur-package');
|
|
20
|
+
class PackageRows {
|
|
21
|
+
rows;
|
|
22
|
+
#ward;
|
|
23
|
+
#told = new Set();
|
|
24
|
+
#line = Promise.resolve(true);
|
|
25
|
+
constructor(ward, rows) {
|
|
26
|
+
this.#ward = ward;
|
|
27
|
+
this.rows = rows;
|
|
28
|
+
}
|
|
29
|
+
told(row) {
|
|
30
|
+
this.#told.add(row);
|
|
31
|
+
}
|
|
32
|
+
// Every row told since the last keep, sealed and written as one keep, one
|
|
33
|
+
// keep at a time.
|
|
34
|
+
kept() {
|
|
35
|
+
const rows = [...this.#told];
|
|
36
|
+
this.#told.clear();
|
|
37
|
+
this.#line = this.#line.then(() => this.#write(rows)).catch(() => false);
|
|
38
|
+
return this.#line;
|
|
39
|
+
}
|
|
40
|
+
async #write(told) {
|
|
41
|
+
const beings = (this.rows.beings ?? {});
|
|
42
|
+
const entries = new Map();
|
|
43
|
+
for (const row of told) {
|
|
44
|
+
const value = row === HEAD_ROW ? (this.rows.ward ?? null) : Object.hasOwn(beings, row) ? beings[row] : undefined;
|
|
45
|
+
entries.set(await this.#ward.entry(row), value === undefined ? null : await this.#ward.seal(row, value));
|
|
46
|
+
}
|
|
47
|
+
await this.#ward.write(entries);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
class Ward {
|
|
52
|
+
#pack;
|
|
53
|
+
#name;
|
|
54
|
+
#seal;
|
|
55
|
+
place;
|
|
56
|
+
constructor(pack, name, seal, place) {
|
|
57
|
+
this.#pack = pack;
|
|
58
|
+
this.#name = name;
|
|
59
|
+
this.#seal = seal;
|
|
60
|
+
this.place = place;
|
|
61
|
+
}
|
|
62
|
+
entry(row) {
|
|
63
|
+
return this.#pack.derive(['entry', this.#name, row], 16).then(hex);
|
|
64
|
+
}
|
|
65
|
+
async seal(row, value) {
|
|
66
|
+
const nonce = this.#pack.entropy.draw(NONCE);
|
|
67
|
+
const text = utf8(JSON.stringify({ row, value }));
|
|
68
|
+
return concat(nonce, await encrypt(this.#seal, nonce, utf8(await this.entry(row)), text));
|
|
69
|
+
}
|
|
70
|
+
// The name is the additional data, so an entry moved under another name
|
|
71
|
+
// does not open either.
|
|
72
|
+
async open(name, bytes) {
|
|
73
|
+
const text = bytes.length > NONCE ? await decrypt(this.#seal, bytes.subarray(0, NONCE), utf8(name), bytes.subarray(NONCE)) : null;
|
|
74
|
+
if (text === null)
|
|
75
|
+
throw new Error('an entry the package did not seal');
|
|
76
|
+
return JSON.parse(new TextDecoder().decode(text));
|
|
77
|
+
}
|
|
78
|
+
write(entries) {
|
|
79
|
+
return this.#pack.memory.write(this.place, entries);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export class Package {
|
|
83
|
+
memory;
|
|
84
|
+
entropy;
|
|
85
|
+
#key;
|
|
86
|
+
constructor(memory, entropy, key) {
|
|
87
|
+
this.memory = memory;
|
|
88
|
+
this.entropy = entropy;
|
|
89
|
+
this.#key = key;
|
|
90
|
+
}
|
|
91
|
+
static async open(memory, entropy, seed) {
|
|
92
|
+
return new Package(memory, entropy, await hkdf(seed, LABEL, 32));
|
|
93
|
+
}
|
|
94
|
+
derive(info, length) {
|
|
95
|
+
return hkdf(this.#key, utf8(JSON.stringify(info)), length);
|
|
96
|
+
}
|
|
97
|
+
// One ward's rows, as the package kept them.
|
|
98
|
+
async rows(name) {
|
|
99
|
+
const ward = new Ward(this, name, await this.derive(['seal', name], 32), hex(await this.derive(['place', name], 16)));
|
|
100
|
+
const rows = {};
|
|
101
|
+
const beings = {};
|
|
102
|
+
let any = false;
|
|
103
|
+
for (const [entry, bytes] of await this.memory.read(ward.place)) {
|
|
104
|
+
const { row, value } = await ward.open(entry, bytes);
|
|
105
|
+
any = true;
|
|
106
|
+
if (row === HEAD_ROW)
|
|
107
|
+
rows.ward = value;
|
|
108
|
+
else
|
|
109
|
+
beings[row] = value;
|
|
110
|
+
}
|
|
111
|
+
if (any)
|
|
112
|
+
rows.beings = beings;
|
|
113
|
+
return new PackageRows(ward, rows);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BeingClass } from '../being/index.ts';
|
|
2
|
+
import type { Module } from '../contract/index.ts';
|
|
2
3
|
import type { Catalogue } from '../ward/index.ts';
|
|
3
4
|
export declare class Registry implements Catalogue {
|
|
4
5
|
#private;
|
|
5
|
-
constructor(
|
|
6
|
-
classOf(
|
|
6
|
+
constructor(kit: readonly BeingClass[], modules: readonly Module[]);
|
|
7
|
+
classOf(kind: string): BeingClass | undefined;
|
|
8
|
+
kindsOf(module: string): string[];
|
|
7
9
|
}
|
package/dist/harbor/registry.js
CHANGED
|
@@ -1,13 +1,78 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The catalogue's index: every kind the harbor runs, from the kit's own
|
|
3
|
+
// classes and the modules the catalogue loaded. It resolves a kind a row
|
|
4
|
+
// keeps, and decides nothing about what stands. It refuses any set of
|
|
5
|
+
// modules two readings could split:
|
|
6
|
+
//
|
|
7
|
+
// a module whose name is no kind, or is the kit's, or two of one name
|
|
8
|
+
// a class with no kind of its own, or one outside its module's domain
|
|
9
|
+
// two classes of one kind, or one kind in two modules
|
|
10
|
+
// a contract with no kind of its own, or one kind for two contracts
|
|
11
|
+
// a kind that is a class and a contract
|
|
12
|
+
//
|
|
13
|
+
// A module's domain is its name's first two segments: `com.acme.shop`
|
|
14
|
+
// stands classes under `com.acme.` alone. A contract may be any domain's,
|
|
15
|
+
// so a faculty fulfils a contract another module declares. Two harbors hold
|
|
16
|
+
// two registries, and nothing between them names a class.
|
|
17
|
+
import { Faculty, isKind, KIT, kindOf } from '../being/index.js';
|
|
18
|
+
const domainOf = (name) => `${name.split('.').slice(0, 2).join('.')}.`;
|
|
1
19
|
export class Registry {
|
|
2
20
|
#classes = new Map();
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
21
|
+
#modules = new Map();
|
|
22
|
+
// `kit` holds the kit's own classes, which alone may be of its kinds.
|
|
23
|
+
constructor(kit, modules) {
|
|
24
|
+
const contracts = new Map();
|
|
25
|
+
const owners = new Map();
|
|
26
|
+
const add = (C, kind) => {
|
|
27
|
+
if (this.#classes.has(kind) && this.#classes.get(kind) !== C)
|
|
28
|
+
throw new Error(`two classes are of kind ${kind}`);
|
|
29
|
+
this.#classes.set(kind, C);
|
|
30
|
+
};
|
|
31
|
+
for (const C of kit)
|
|
32
|
+
add(C, kindOf(C));
|
|
33
|
+
for (const m of modules) {
|
|
34
|
+
if (!isKind(m.module) || m.module.startsWith(KIT))
|
|
35
|
+
throw new Error(`module ${String(m.module)} is no name a module may take`);
|
|
36
|
+
if (this.#modules.has(m.module))
|
|
37
|
+
throw new Error(`two modules are named ${m.module}`);
|
|
38
|
+
if (typeof m.version !== 'string' || m.version === '')
|
|
39
|
+
throw new Error(`module ${m.module} declares no version`);
|
|
40
|
+
const kinds = [];
|
|
41
|
+
for (const C of m.classes) {
|
|
42
|
+
if (typeof C !== 'function')
|
|
43
|
+
throw new Error(`module ${m.module} holds something that is no class`);
|
|
44
|
+
const kind = kindOf(C);
|
|
45
|
+
if (!kind.startsWith(domainOf(m.module)))
|
|
46
|
+
throw new Error(`kind ${kind} is outside module ${m.module}`);
|
|
47
|
+
const owner = owners.get(kind);
|
|
48
|
+
if (owner !== undefined && owner !== m.module)
|
|
49
|
+
throw new Error(`kind ${kind} is in modules ${owner} and ${m.module}`);
|
|
50
|
+
owners.set(kind, m.module);
|
|
51
|
+
add(C, kind);
|
|
52
|
+
kinds.push(kind);
|
|
53
|
+
if (!Faculty.fulfils(C))
|
|
54
|
+
continue;
|
|
55
|
+
for (let c = Object.getPrototypeOf(C); c !== Faculty; c = Object.getPrototypeOf(c)) {
|
|
56
|
+
const contract = kindOf(c);
|
|
57
|
+
if (contract.startsWith(KIT))
|
|
58
|
+
throw new Error(`kind ${contract} is the kit's`);
|
|
59
|
+
if (contracts.has(contract) && contracts.get(contract) !== c)
|
|
60
|
+
throw new Error(`two contracts are of kind ${contract}`);
|
|
61
|
+
contracts.set(contract, c);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.#modules.set(m.module, kinds);
|
|
8
65
|
}
|
|
66
|
+
for (const kind of contracts.keys()) {
|
|
67
|
+
if (this.#classes.has(kind))
|
|
68
|
+
throw new Error(`kind ${kind} is a class and a contract`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
classOf(kind) {
|
|
72
|
+
return this.#classes.get(kind);
|
|
9
73
|
}
|
|
10
|
-
|
|
11
|
-
|
|
74
|
+
// The kinds a module stands, or none.
|
|
75
|
+
kindsOf(module) {
|
|
76
|
+
return [...(this.#modules.get(module) ?? [])];
|
|
12
77
|
}
|
|
13
78
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type JsonObject } from '../being/index.ts';
|
|
2
|
+
import type { Harbor } from './harbor.ts';
|
|
3
|
+
export type RootRequest = {
|
|
4
|
+
ward?: string;
|
|
5
|
+
method?: string;
|
|
6
|
+
args?: JsonObject;
|
|
7
|
+
};
|
|
8
|
+
export declare const readRootRequest: (value: unknown) => RootRequest | null;
|
|
9
|
+
export declare const rootLine: (harbor: Harbor, value: unknown) => Promise<JsonObject>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The root line: one request, one answer, both plain JSON, for whatever
|
|
3
|
+
// carries the root's asks to a harbor in its own process. The command line
|
|
4
|
+
// carries it over a local socket, and an edge route carries it the same way.
|
|
5
|
+
//
|
|
6
|
+
// request { ward?, method?, args? } no ward is the box ward, no
|
|
7
|
+
// method the census
|
|
8
|
+
// answer { answer } what the ward-being said
|
|
9
|
+
// { silence: true } she said nothing
|
|
10
|
+
// { word } a kit word: unreached, late, ...
|
|
11
|
+
// { error } the request was not one, or the
|
|
12
|
+
// ward is none this harbor holds
|
|
13
|
+
import { isSilence, isWord, told } from '../being/index.js';
|
|
14
|
+
const object = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
15
|
+
export const readRootRequest = (value) => {
|
|
16
|
+
if (!object(value))
|
|
17
|
+
return null;
|
|
18
|
+
const { ward, method, args } = value;
|
|
19
|
+
if ((ward !== undefined && typeof ward !== 'string') || (method !== undefined && typeof method !== 'string') || (args !== undefined && !object(args)))
|
|
20
|
+
return null;
|
|
21
|
+
return { ...(ward === undefined ? {} : { ward }), ...(method === undefined ? {} : { method }), ...(args === undefined ? {} : { args }) };
|
|
22
|
+
};
|
|
23
|
+
export const rootLine = async (harbor, value) => {
|
|
24
|
+
const request = readRootRequest(value);
|
|
25
|
+
if (request === null)
|
|
26
|
+
return { error: 'not a root request' };
|
|
27
|
+
const ward = request.ward === undefined ? harbor.box : harbor.ward(request.ward);
|
|
28
|
+
if (ward === undefined)
|
|
29
|
+
return { error: 'no such ward' };
|
|
30
|
+
const out = await ward.root(request.method, request.args);
|
|
31
|
+
if (isSilence(out))
|
|
32
|
+
return { silence: true };
|
|
33
|
+
if (isWord(out))
|
|
34
|
+
return { word: told(out) };
|
|
35
|
+
return { answer: out };
|
|
36
|
+
};
|
package/dist/harbor/terrain.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Carrier, Clock, Custody, Entropy, Memory } from '../contract/index.ts';
|
|
1
|
+
import type { Carrier, Clock, Custody, Entropy, Loader, Memory } from '../contract/index.ts';
|
|
2
2
|
export declare abstract class Terrain {
|
|
3
3
|
abstract readonly entropy: Entropy;
|
|
4
4
|
abstract readonly clock: Clock;
|
|
5
5
|
abstract readonly custody: Custody;
|
|
6
6
|
abstract readonly memory: Memory;
|
|
7
|
+
abstract readonly loader: Loader;
|
|
7
8
|
abstract readonly carrier: Carrier;
|
|
8
9
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { Being, canonical, digest, Faculty, isSilence, isWord, OWNER, silence, told, WARD, word } from './being/index.ts';
|
|
2
2
|
export type { Answer, Ask, Asker, AskSpec, BeingClass, BeingLike, Blueprint, Invitation, Json, JsonObject, Occupants, Reply, Schema, Silence, Stance, Standing, Standings, Wanted, Word, WordName } from './being/index.ts';
|
|
3
3
|
export { readInvitation } from './quo/index.ts';
|
|
4
|
-
export { Carrier, Clock, Custody, Entropy, Memory, type Rows } from './contract/index.ts';
|
|
5
|
-
export { CEILING, DEFAULT, DEPTH, ownerAsk, pilots, WardBeing, type Steward, type WardBeingStance } from './ward/index.ts';
|
|
6
|
-
export { BOX, Dock, Harbor, Registry, Terrain } from './harbor/index.ts';
|
|
7
|
-
export { CryptoEntropy, HeldCustody, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World, type PointerParts } from './pointer/index.ts';
|
|
4
|
+
export { Carrier, Clock, Custody, Entropy, Loader, Memory, type Loaded, type Module, type Rows } from './contract/index.ts';
|
|
5
|
+
export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing, type Steward, type WardBeingStance } from './ward/index.ts';
|
|
6
|
+
export { BOX, Catalogue, Dock, Harbor, readRootRequest, Registry, rootLine, Terrain, type RootRequest } from './harbor/index.ts';
|
|
7
|
+
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World, type PointerParts } from './pointer/index.ts';
|
package/dist/index.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
export { Being, canonical, digest, Faculty, isSilence, isWord, OWNER, silence, told, WARD, word } from './being/index.js';
|
|
6
6
|
export { readInvitation } from './quo/index.js';
|
|
7
7
|
// What a terrain gives a harbor.
|
|
8
|
-
export { Carrier, Clock, Custody, Entropy, Memory } from './contract/index.js';
|
|
8
|
+
export { Carrier, Clock, Custody, Entropy, Loader, Memory } from './contract/index.js';
|
|
9
9
|
// Wards and the ward-being.
|
|
10
|
-
export { CEILING, DEFAULT, DEPTH, ownerAsk, pilots, WardBeing } from './ward/index.js';
|
|
11
|
-
// The onion.
|
|
12
|
-
export { BOX, Dock, Harbor, Registry, Terrain } from './harbor/index.js';
|
|
10
|
+
export { CEILING, DEFAULT, DEPTH, ownerAsk, Pilot, pilots, WardBeing } from './ward/index.js';
|
|
11
|
+
// The onion, and the one line its root's asks cross from any front.
|
|
12
|
+
export { BOX, Catalogue, Dock, Harbor, readRootRequest, Registry, rootLine, Terrain } from './harbor/index.js';
|
|
13
13
|
// The pointer dock: a whole world in one process.
|
|
14
|
-
export { CryptoEntropy, HeldCustody, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World } from './pointer/index.js';
|
|
14
|
+
export { CryptoEntropy, HeldCustody, HeldLoader, NoCarrier, PointerCarrier, PointerTerrain, SystemClock, VolatileMemory, World } from './pointer/index.js';
|
package/dist/pointer/bodies.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Clock, Custody, Entropy, Memory, type
|
|
1
|
+
import { Clock, Custody, Entropy, Loader, Memory, type Loaded, type Module } from '../contract/index.ts';
|
|
2
2
|
export declare class CryptoEntropy extends Entropy {
|
|
3
3
|
draw(length: number): Uint8Array;
|
|
4
4
|
}
|
|
@@ -12,9 +12,15 @@ export declare class SystemClock extends Clock {
|
|
|
12
12
|
export declare class HeldCustody extends Custody {
|
|
13
13
|
#private;
|
|
14
14
|
constructor(entropy: Entropy);
|
|
15
|
-
seed(
|
|
15
|
+
seed(): Promise<Uint8Array>;
|
|
16
|
+
}
|
|
17
|
+
export declare class HeldLoader extends Loader {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(modules?: readonly Module[]);
|
|
20
|
+
load(from: string): Promise<Loaded | null>;
|
|
16
21
|
}
|
|
17
22
|
export declare class VolatileMemory extends Memory {
|
|
18
23
|
#private;
|
|
19
|
-
|
|
24
|
+
read(place: string): Promise<Map<string, Uint8Array>>;
|
|
25
|
+
write(place: string, entries: ReadonlyMap<string, Uint8Array | null>): Promise<void>;
|
|
20
26
|
}
|
package/dist/pointer/bodies.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// The pointer terrain's bodies: each contract fulfilled in one process,
|
|
3
3
|
// with nothing beyond JavaScript.
|
|
4
|
-
import { Clock, Custody, Entropy, Memory } from '../contract/index.js';
|
|
4
|
+
import { Clock, Custody, Entropy, Loader, Memory } from '../contract/index.js';
|
|
5
5
|
export class CryptoEntropy extends Entropy {
|
|
6
6
|
draw(length) {
|
|
7
7
|
return globalThis.crypto.getRandomValues(new Uint8Array(length));
|
|
@@ -17,29 +17,48 @@ export class SystemClock extends Clock {
|
|
|
17
17
|
return { done, cancel: () => clearTimeout(timer) };
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
//
|
|
20
|
+
// The harbor's seed, drawn once and held by this object.
|
|
21
21
|
export class HeldCustody extends Custody {
|
|
22
|
-
#
|
|
23
|
-
#seeds = new Map();
|
|
22
|
+
#seed;
|
|
24
23
|
constructor(entropy) {
|
|
25
24
|
super();
|
|
26
|
-
this.#
|
|
25
|
+
this.#seed = entropy.draw(32);
|
|
27
26
|
}
|
|
28
|
-
seed(
|
|
29
|
-
|
|
30
|
-
if (!seed)
|
|
31
|
-
this.#seeds.set(name, (seed = this.#entropy.draw(32)));
|
|
32
|
-
return Promise.resolve(seed);
|
|
27
|
+
seed() {
|
|
28
|
+
return Promise.resolve(this.#seed.slice());
|
|
33
29
|
}
|
|
34
30
|
}
|
|
35
|
-
//
|
|
36
|
-
|
|
31
|
+
// Modules handed to this object, found by the name each declares.
|
|
32
|
+
export class HeldLoader extends Loader {
|
|
33
|
+
#modules = new Map();
|
|
34
|
+
constructor(modules = []) {
|
|
35
|
+
super();
|
|
36
|
+
for (const m of modules)
|
|
37
|
+
this.#modules.set(m.module, m);
|
|
38
|
+
}
|
|
39
|
+
load(from) {
|
|
40
|
+
const m = this.#modules.get(from);
|
|
41
|
+
return Promise.resolve(m ? { module: m.module, version: m.version, classes: m.classes, from } : null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Entries that live as long as this object, copied in and out, which is
|
|
45
|
+
// what a restart in one process finds.
|
|
37
46
|
export class VolatileMemory extends Memory {
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
#places = new Map();
|
|
48
|
+
read(place) {
|
|
49
|
+
const entries = this.#places.get(place) ?? new Map();
|
|
50
|
+
return Promise.resolve(new Map([...entries].map(([name, bytes]) => [name, bytes.slice()])));
|
|
51
|
+
}
|
|
52
|
+
write(place, entries) {
|
|
53
|
+
let kept = this.#places.get(place);
|
|
54
|
+
if (!kept)
|
|
55
|
+
this.#places.set(place, (kept = new Map()));
|
|
56
|
+
for (const [name, bytes] of entries) {
|
|
57
|
+
if (bytes === null)
|
|
58
|
+
kept.delete(name);
|
|
59
|
+
else
|
|
60
|
+
kept.set(name, bytes.slice());
|
|
61
|
+
}
|
|
62
|
+
return Promise.resolve();
|
|
44
63
|
}
|
|
45
64
|
}
|
package/dist/pointer/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { CryptoEntropy, HeldCustody, SystemClock, VolatileMemory } from './bodies.ts';
|
|
1
|
+
export { CryptoEntropy, HeldCustody, HeldLoader, SystemClock, VolatileMemory } from './bodies.ts';
|
|
2
2
|
export { NoCarrier, PointerCarrier, PointerTerrain, World, type PointerParts } from './world.ts';
|
package/dist/pointer/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// The pointer dock: a whole world with `nervur` alone.
|
|
3
|
-
export { CryptoEntropy, HeldCustody, SystemClock, VolatileMemory } from './bodies.js';
|
|
3
|
+
export { CryptoEntropy, HeldCustody, HeldLoader, SystemClock, VolatileMemory } from './bodies.js';
|
|
4
4
|
export { NoCarrier, PointerCarrier, PointerTerrain, World } from './world.js';
|