nervur 0.19.2 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -101
- package/dist/being/being.d.ts +7 -15
- package/dist/being/being.js +16 -67
- package/dist/being/digest.d.ts +1 -1
- package/dist/being/digest.js +7 -27
- package/dist/being/faculty.d.ts +9 -0
- package/dist/being/faculty.js +68 -0
- package/dist/being/index.d.ts +4 -5
- package/dist/being/index.js +5 -5
- package/dist/being/types.d.ts +35 -62
- package/dist/being/types.js +3 -57
- package/dist/being/words.d.ts +13 -0
- package/dist/being/words.js +14 -0
- package/dist/contract/index.d.ts +26 -0
- package/dist/contract/index.js +19 -0
- package/dist/crypto/aes.d.ts +3 -0
- package/dist/crypto/aes.js +24 -0
- package/dist/crypto/bytes.d.ts +6 -0
- package/dist/crypto/bytes.js +33 -0
- package/dist/crypto/ed25519.d.ts +3 -0
- package/dist/crypto/ed25519.js +85 -0
- package/dist/crypto/hash.d.ts +2 -0
- package/dist/crypto/hash.js +11 -0
- package/dist/crypto/index.d.ts +7 -0
- package/dist/crypto/index.js +10 -0
- package/dist/crypto/json.d.ts +8 -0
- package/dist/crypto/json.js +250 -0
- package/dist/crypto/mlkem.d.ts +12 -0
- package/dist/crypto/mlkem.js +36 -0
- package/dist/crypto/subtle.d.ts +10 -0
- package/dist/crypto/subtle.js +29 -0
- package/dist/crypto/x25519.d.ts +2 -0
- package/dist/crypto/x25519.js +21 -0
- package/dist/folder/index.d.ts +13 -0
- package/dist/folder/index.js +125 -0
- package/dist/harbor/dock.d.ts +19 -0
- package/dist/harbor/dock.js +92 -0
- package/dist/harbor/harbor.d.ts +22 -0
- package/dist/harbor/harbor.js +84 -0
- package/dist/harbor/index.d.ts +4 -10
- package/dist/harbor/index.js +5 -18
- package/dist/harbor/registry.d.ts +7 -0
- package/dist/harbor/registry.js +13 -0
- package/dist/harbor/terrain.d.ts +8 -0
- package/dist/harbor/terrain.js +2 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -0
- package/dist/pointer/bodies.d.ts +20 -0
- package/dist/pointer/bodies.js +45 -0
- package/dist/pointer/index.d.ts +2 -0
- package/dist/pointer/index.js +4 -0
- package/dist/pointer/world.d.ts +34 -0
- package/dist/pointer/world.js +76 -0
- package/dist/quo/door.d.ts +34 -0
- package/dist/quo/door.js +172 -0
- package/dist/quo/index.d.ts +8 -0
- package/dist/quo/index.js +11 -0
- package/dist/quo/invitation.d.ts +7 -0
- package/dist/quo/invitation.js +15 -0
- package/dist/quo/keys.d.ts +40 -0
- package/dist/quo/keys.js +79 -0
- package/dist/quo/payload.d.ts +15 -0
- package/dist/quo/payload.js +55 -0
- package/dist/quo/relations.d.ts +40 -0
- package/dist/quo/relations.js +33 -0
- package/dist/quo/reply.d.ts +13 -0
- package/dist/quo/reply.js +35 -0
- package/dist/quo/seal.d.ts +42 -0
- package/dist/quo/seal.js +78 -0
- package/dist/quo/standing.d.ts +39 -0
- package/dist/quo/standing.js +90 -0
- package/dist/ward/allowance.d.ts +13 -9
- package/dist/ward/allowance.js +28 -67
- package/dist/ward/cells.d.ts +6 -6
- package/dist/ward/cells.js +67 -179
- package/dist/ward/index.d.ts +5 -10
- package/dist/ward/index.js +6 -13
- package/dist/ward/partition.d.ts +44 -60
- package/dist/ward/partition.js +56 -285
- package/dist/ward/stance.d.ts +29 -20
- package/dist/ward/stance.js +173 -406
- package/dist/ward/ward-being.d.ts +33 -0
- package/dist/ward/ward-being.js +76 -0
- package/dist/ward/ward.d.ts +43 -11
- package/dist/ward/ward.js +215 -363
- package/package.json +19 -33
- package/src/being/being.ts +31 -78
- package/src/being/digest.ts +7 -35
- package/src/being/faculty.ts +66 -0
- package/src/being/index.ts +5 -6
- package/src/being/types.ts +47 -145
- package/src/being/words.ts +31 -0
- package/src/contract/index.ts +44 -0
- package/src/crypto/aes.ts +26 -0
- package/src/crypto/bytes.ts +37 -0
- package/src/crypto/ed25519.ts +84 -0
- package/src/crypto/hash.ts +14 -0
- package/src/crypto/index.ts +10 -0
- package/src/crypto/json.ts +241 -0
- package/src/crypto/mlkem.ts +38 -0
- package/src/crypto/subtle.ts +33 -0
- package/src/crypto/x25519.ts +21 -0
- package/src/folder/index.ts +134 -0
- package/src/harbor/dock.ts +101 -0
- package/src/harbor/harbor.ts +105 -0
- package/src/harbor/index.ts +5 -19
- package/src/harbor/registry.ts +20 -0
- package/src/harbor/terrain.ts +13 -0
- package/src/index.ts +20 -0
- package/src/pointer/bodies.ts +47 -0
- package/src/pointer/index.ts +4 -0
- package/src/pointer/world.ts +91 -0
- package/src/quo/door.ts +178 -0
- package/src/quo/index.ts +11 -0
- package/src/quo/invitation.ts +15 -0
- package/src/quo/keys.ts +91 -0
- package/src/quo/payload.ts +61 -0
- package/src/quo/relations.ts +62 -0
- package/src/quo/reply.ts +38 -0
- package/src/quo/seal.ts +101 -0
- package/src/quo/standing.ts +111 -0
- package/src/stand/main.ts +19 -0
- package/src/stand/stand.ts +178 -0
- package/src/ward/allowance.ts +37 -75
- package/src/ward/cells.ts +63 -176
- package/src/ward/index.ts +6 -17
- package/src/ward/partition.ts +86 -326
- package/src/ward/stance.ts +185 -420
- package/src/ward/ward-being.ts +97 -0
- package/src/ward/ward.ts +229 -363
- package/dist/being/lent.d.ts +0 -32
- package/dist/being/lent.js +0 -72
- package/dist/being/silence.d.ts +0 -12
- package/dist/being/silence.js +0 -41
- package/dist/conformance/assert.d.ts +0 -11
- package/dist/conformance/assert.js +0 -106
- package/dist/conformance/beings.d.ts +0 -199
- package/dist/conformance/beings.js +0 -188
- package/dist/conformance/estate.d.ts +0 -5
- package/dist/conformance/estate.js +0 -388
- package/dist/conformance/index.d.ts +0 -80
- package/dist/conformance/index.js +0 -819
- package/dist/conformance/reach.d.ts +0 -10
- package/dist/conformance/reach.js +0 -72
- package/dist/conformance/store.d.ts +0 -5
- package/dist/conformance/store.js +0 -113
- package/dist/harbor/box.d.ts +0 -121
- package/dist/harbor/box.js +0 -121
- package/dist/harbor/core.d.ts +0 -55
- package/dist/harbor/core.js +0 -662
- package/dist/harbor/dial.d.ts +0 -9
- package/dist/harbor/dial.js +0 -81
- package/dist/harbor/memory.d.ts +0 -29
- package/dist/harbor/memory.js +0 -104
- package/dist/harbor/reach.d.ts +0 -36
- package/dist/harbor/reach.js +0 -199
- package/dist/harbor/store.d.ts +0 -35
- package/dist/harbor/store.js +0 -62
- package/dist/vector/cases.d.ts +0 -42
- package/dist/vector/cases.js +0 -223
- package/dist/vector/index.d.ts +0 -6
- package/dist/vector/index.js +0 -8
- package/dist/vector/stand.d.ts +0 -9
- package/dist/vector/stand.js +0 -77
- package/dist/vector/world.d.ts +0 -144
- package/dist/vector/world.js +0 -209
- package/dist/ward/arithmetic.d.ts +0 -31
- package/dist/ward/arithmetic.js +0 -249
- package/dist/ward/door.d.ts +0 -18
- package/dist/ward/door.js +0 -186
- package/dist/ward/ground.d.ts +0 -29
- package/dist/ward/ground.js +0 -56
- package/dist/ward/heirs.d.ts +0 -13
- package/dist/ward/heirs.js +0 -117
- package/dist/ward/json.d.ts +0 -2
- package/dist/ward/json.js +0 -163
- package/dist/ward/owner.d.ts +0 -13
- package/dist/ward/owner.js +0 -220
- package/dist/ward/seal.d.ts +0 -52
- package/dist/ward/seal.js +0 -150
- package/src/being/lent.ts +0 -72
- package/src/being/silence.ts +0 -46
- package/src/conformance/assert.ts +0 -100
- package/src/conformance/beings.ts +0 -188
- package/src/conformance/estate.ts +0 -412
- package/src/conformance/index.ts +0 -965
- package/src/conformance/reach.ts +0 -83
- package/src/conformance/store.ts +0 -125
- package/src/harbor/box.ts +0 -131
- package/src/harbor/core.ts +0 -699
- package/src/harbor/dial.ts +0 -112
- package/src/harbor/memory.ts +0 -123
- package/src/harbor/reach.ts +0 -221
- package/src/harbor/store.ts +0 -91
- package/src/vector/cases.ts +0 -257
- package/src/vector/index.ts +0 -11
- package/src/vector/stand.ts +0 -76
- package/src/vector/world.ts +0 -232
- package/src/ward/arithmetic.ts +0 -251
- package/src/ward/door.ts +0 -186
- package/src/ward/ground.ts +0 -142
- package/src/ward/heirs.ts +0 -116
- package/src/ward/json.ts +0 -144
- package/src/ward/owner.ts +0 -214
- package/src/ward/seal.ts +0 -178
package/README.md
CHANGED
|
@@ -1,119 +1,84 @@
|
|
|
1
|
-
#
|
|
1
|
+
# nervur
|
|
2
2
|
|
|
3
|
-
Nervur
|
|
4
|
-
and
|
|
3
|
+
Nervur's kit of [Quo](https://quo.systems). Quo is a protocol: an object
|
|
4
|
+
asks another object and gets an answer, without knowing where it is. This
|
|
5
|
+
package is one implementation of it, and the two names never stand for
|
|
6
|
+
the same thing. Where they disagree, Quo's spec wins.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
device, or on another planet. Quo is the protocol and this is one
|
|
9
|
-
implementation of it; the two names never stand for the same thing.
|
|
8
|
+
You write classes, beings and faculties, and nothing else. A harbor
|
|
9
|
+
unpacks a whole world from them.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
Nothing else is Quo.
|
|
11
|
+
## Install
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
beings and judges its door.
|
|
18
|
-
- **Being.** One ordinary object, one voice.
|
|
19
|
-
|
|
20
|
-
The truth is the spec, at <https://quo.systems/spec>, with the vectors it
|
|
21
|
-
is proved by at <https://quo.systems/vectors>. It is the protocol alone:
|
|
22
|
-
what any ward in any language must do for its bytes to be Quo. It is
|
|
23
|
-
self-contained and assumes nothing from any other document, this README
|
|
24
|
-
included. Read it first, at the source, which is Quo's and not this
|
|
25
|
-
package's to carry.
|
|
26
|
-
|
|
27
|
-
What this one kit chose, and another kit may refuse, is documented at
|
|
28
|
-
<https://nervur.org/docs>. It assumes the spec, and where the two disagree
|
|
29
|
-
the spec wins.
|
|
13
|
+
```sh
|
|
14
|
+
npm install nervur
|
|
15
|
+
```
|
|
30
16
|
|
|
31
|
-
|
|
17
|
+
Node 22.18 or later. The main entry imports no platform, so it runs in a
|
|
18
|
+
browser, Deno, Bun and workerd as well. `nervur/folder` keeps a harbor on
|
|
19
|
+
a disk and needs Node's file system.
|
|
32
20
|
|
|
33
|
-
A
|
|
34
|
-
no dependencies. The package ships JavaScript with declarations beside the
|
|
35
|
-
source it was emitted from, because Node strips types nowhere under
|
|
36
|
-
`node_modules`.
|
|
21
|
+
## A world in one process
|
|
37
22
|
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
23
|
+
```js
|
|
24
|
+
import { Being, Faculty, World } from 'nervur';
|
|
25
|
+
|
|
26
|
+
// A contract, and a faculty that fulfils it.
|
|
27
|
+
class Clock extends Faculty {}
|
|
28
|
+
class SystemTime extends Clock {
|
|
29
|
+
static asks = { now: {} };
|
|
30
|
+
now() {
|
|
31
|
+
return { now: Date.now() };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A being, who lends a clock by its contract.
|
|
36
|
+
class Greeter extends Being {
|
|
37
|
+
static asks = { hello: {} };
|
|
38
|
+
async hello(args, asker) {
|
|
39
|
+
if (!this.stance.standings.get('clock')) await this.stance.lend(Clock, 'clock');
|
|
40
|
+
const time = await this.stance.standings.get('clock')?.ask('now');
|
|
41
|
+
return { hello: asker.id ?? 'stranger', time };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const world = new World();
|
|
46
|
+
const harbor = await world.harbor('home', [SystemTime, Greeter]);
|
|
47
|
+
await harbor.root('open', { key: 'clock', class: 'SystemTime' });
|
|
48
|
+
await harbor.root('host', { ward: 'alice' });
|
|
49
|
+
await harbor.ward('alice').root('boot', { key: 'greeter', class: 'Greeter' });
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
somebody else's ward rather than for this one.
|
|
52
|
+
A second harbor in the same world takes an invitation on the greeter and
|
|
53
|
+
asks it through the world, sealed as Quo's bytes. `world.restart('home')`
|
|
54
|
+
opens the harbor again from what its memory kept.
|
|
50
55
|
|
|
51
|
-
##
|
|
56
|
+
## The onion
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
1. The terrain gives the box ward's seed and memory.
|
|
59
|
+
2. The box ward unpacks, its ward-being the dock.
|
|
60
|
+
3. Its faculties are born from their rows.
|
|
61
|
+
4. Every ward the dock hosts unpacks.
|
|
62
|
+
5. In every ward, the ward-being first, then each being from her row.
|
|
55
63
|
|
|
56
|
-
|
|
64
|
+
Cells decide what stands. The classes you hand a harbor only resolve a
|
|
65
|
+
name a row keeps. Opening a harbor on empty memory is its genesis, and
|
|
66
|
+
the root's asks write the rest: `open`, `host`, and `boot` on a ward.
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
one ward, one harbor with a ward per being, two harbors, and the harbor core
|
|
60
|
-
over a memory store. A world declares what it can express and a chapter it
|
|
61
|
-
cannot reach is skipped by name rather than failed, which is how a kit in
|
|
62
|
-
another language reports the same suite honestly.
|
|
68
|
+
## What it holds
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
import { MemoryHarbor, Harbor, request, dial } from 'nervur/harbor';
|
|
73
|
-
import { conform } from 'nervur/conformance';
|
|
74
|
-
import { Stand } from 'nervur/vector';
|
|
75
|
-
```
|
|
70
|
+
- **Being, Faculty.** What you extend. A faculty's contract is its class
|
|
71
|
+
and every class between it and `Faculty`.
|
|
72
|
+
- **Entropy, Clock, Custody, Memory, Carrier.** The contracts a terrain
|
|
73
|
+
fulfils. Each has one suite, and every body that fulfils it passes it.
|
|
74
|
+
- **Harbor, Terrain, Registry, Dock, WardBeing.** The onion.
|
|
75
|
+
- **World, PointerTerrain** and the pointer bodies. A whole world with
|
|
76
|
+
this package alone.
|
|
77
|
+
- **FolderMemory, FolderCustody**, from `nervur/folder`.
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
The hand to a kit in another language is Quo's own and not this package's.
|
|
80
|
-
The spec is the protocol and assumes nothing: a kit is written against it
|
|
81
|
-
and against nothing else. The vectors beside it are the byte-level hand,
|
|
82
|
-
everything a stranger can observe: `arithmetic.json`, the primitives the
|
|
83
|
-
seal rests on, SHA-256, Ed25519, X25519, HKDF and AES-256-GCM;
|
|
84
|
-
`framing.json`, Quo's own, the ward pk, the digest, the signed ask body,
|
|
85
|
-
the sealed shapes, the invitation and the knock; `wire.json`, the frames on
|
|
86
|
-
a socket and the one request a door takes; and `door.json`, the door's
|
|
87
|
-
thirteen cases, each one an arrival a ward will not answer, with the bytes
|
|
88
|
-
that arrive, the bytes that leave and the partition's digest on both sides
|
|
89
|
-
of the judgement. All of it is at <https://quo.systems>, downloadable and
|
|
90
|
-
versionless, and a kit reproduces the bytes or it is not this protocol.
|
|
91
|
-
|
|
92
|
-
`door.json` is also replayed from outside, by a verifier holding no key,
|
|
93
|
-
against a kit standing in vector mode, which the spec describes under that
|
|
94
|
-
name. This kit stands in it with `Stand` from `nervur/vector`, which is
|
|
95
|
-
vector mode and names no runtime: put it behind any listener that hands it
|
|
96
|
-
a request body and returns what it answers.
|
|
97
|
-
|
|
98
|
-
Nothing in this package is the protocol. `src/` is this kit's
|
|
99
|
-
interpretation, and `src/conformance/` is a checklist a kit ports rather
|
|
100
|
-
than a harness it runs: it imports the base class, the silence spelling and
|
|
101
|
-
this kit's store and reach, so the beings it is shown with run only in a
|
|
102
|
-
TypeScript ward.
|
|
103
|
-
|
|
104
|
-
## Versions
|
|
105
|
-
|
|
106
|
-
No compatibility promise before 1.0.0: the words may still move, and there
|
|
107
|
-
is no migration to write because there is nothing yet to migrate from. This
|
|
108
|
-
package carries the version of its own work and is bound to no other's, so a
|
|
109
|
-
number equal to `@nervur-org/dock`'s or `@nervur-org/ui`'s is a coincidence.
|
|
110
|
-
|
|
111
|
-
What ships is the emitted `dist/`, the source it came from, this file, the
|
|
112
|
-
licence and the notice. No tests and no configs. The spec and the vectors
|
|
113
|
-
are not in the tarball: they are Quo's and not this package's to carry, and
|
|
114
|
-
they stand at <https://quo.systems>, which is where this file points.
|
|
79
|
+
Carriers over a network, storage beyond a folder, key custody, and every
|
|
80
|
+
screen are `@nervur-org/dock`'s, as classes fulfilling these contracts.
|
|
115
81
|
|
|
116
82
|
## License
|
|
117
83
|
|
|
118
|
-
Apache-2.0.
|
|
119
|
-
[NOTICE](NOTICE); every source file carries an SPDX line.
|
|
84
|
+
Apache-2.0.
|
package/dist/being/being.d.ts
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
import type { Asker, Blueprint,
|
|
1
|
+
import type { Asker, Blueprint, JsonObject, Reply, Schema, Stance } from './types.ts';
|
|
2
2
|
export type AskSpec = {
|
|
3
|
-
description?: string;
|
|
4
|
-
input?: Schema;
|
|
5
|
-
|
|
6
|
-
for?: (occupant: OccupantRecord | undefined, asker: Asker) => boolean;
|
|
3
|
+
readonly description?: string;
|
|
4
|
+
readonly input?: Schema;
|
|
5
|
+
readonly for?: (asker: Asker, notes: JsonObject | undefined) => boolean;
|
|
7
6
|
};
|
|
8
7
|
export declare class Being {
|
|
9
8
|
static cells: JsonObject;
|
|
10
9
|
static asks: Record<string, AskSpec>;
|
|
11
10
|
readonly stance: Stance;
|
|
12
11
|
constructor(stance: Stance);
|
|
13
|
-
get cells():
|
|
14
|
-
|
|
15
|
-
get occupants(): Occupants;
|
|
16
|
-
lend(name: string, id: string): Promise<string | null>;
|
|
17
|
-
invite(id: string, notes?: JsonObject): Promise<Invitation | null>;
|
|
18
|
-
knock(invitation: Invitation, method?: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
|
|
19
|
-
take(id: string, invitation: Invitation): Promise<string | null>;
|
|
20
|
-
boot(className: string, key: string, id?: string): Promise<string | null>;
|
|
21
|
-
occupant(asker: Asker): OccupantRecord | undefined;
|
|
12
|
+
get cells(): JsonObject;
|
|
13
|
+
notes(asker: Asker): JsonObject | undefined;
|
|
22
14
|
describe(asker: Asker): Blueprint;
|
|
23
|
-
answer(asker: Asker, method
|
|
15
|
+
answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
|
|
24
16
|
}
|
package/dist/being/being.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
// Names a subclass may not
|
|
2
|
-
const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', '
|
|
3
|
-
// Whether she
|
|
4
|
-
// below Object's. A name Object lends every object, `hasOwnProperty` or
|
|
5
|
-
// `toString`, is not a method she wrote; and a field she assigns in her own
|
|
6
|
-
// constructor is not there yet when the base checks, so an ask is a method
|
|
7
|
-
// on the prototype and nothing else.
|
|
1
|
+
// Names a subclass may not declare as an ask, because they are the base's.
|
|
2
|
+
const RESERVED = new Set(['answer', 'describe', 'stance', 'cells', 'notes', 'constructor']);
|
|
3
|
+
// Whether she wrote a method of that name, below Object's prototype.
|
|
8
4
|
const wrote = (self, name) => {
|
|
9
5
|
for (let p = Object.getPrototypeOf(self); p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
|
|
10
6
|
if (Object.hasOwn(p, name))
|
|
@@ -12,18 +8,11 @@ const wrote = (self, name) => {
|
|
|
12
8
|
}
|
|
13
9
|
return false;
|
|
14
10
|
};
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// That is the rule: her blueprint is exactly what the class in front of you
|
|
18
|
-
// declares, in the order she chose, and merging down a chain would hand her
|
|
19
|
-
// asks she may mean to drop and an order she did not write. A subclass that
|
|
20
|
-
// means to extend says so, `static override asks = { ...Parent.asks, mine: {} }`.
|
|
11
|
+
// A subclass's statics replace its parent's and never merge: her blueprint
|
|
12
|
+
// is what the class in front of you declares, in its order.
|
|
21
13
|
export class Being {
|
|
22
|
-
// Her cells' defaults
|
|
23
|
-
// a restart keeps what she wrote.
|
|
14
|
+
// Her cells' defaults, written at birth only where a key is missing.
|
|
24
15
|
static cells = {};
|
|
25
|
-
// What she can be asked. Declaration order is blueprint order, except a
|
|
26
|
-
// name that reads as an array index, which the language lists first.
|
|
27
16
|
static asks = {};
|
|
28
17
|
stance;
|
|
29
18
|
constructor(stance) {
|
|
@@ -33,10 +22,8 @@ export class Being {
|
|
|
33
22
|
if (RESERVED.has(name))
|
|
34
23
|
throw new Error(`ask '${name}' is a reserved name`);
|
|
35
24
|
if (!wrote(this, name))
|
|
36
|
-
throw new Error(`ask '${name}' has no method
|
|
25
|
+
throw new Error(`ask '${name}' has no method`);
|
|
37
26
|
}
|
|
38
|
-
// Own keys only: a default named after a member of Object's prototype is
|
|
39
|
-
// still hers, and still missing until she writes it.
|
|
40
27
|
for (const [k, v] of Object.entries(C.cells))
|
|
41
28
|
if (!Object.hasOwn(stance.cells, k))
|
|
42
29
|
stance.cells[k] = structuredClone(v);
|
|
@@ -44,66 +31,28 @@ export class Being {
|
|
|
44
31
|
get cells() {
|
|
45
32
|
return this.stance.cells;
|
|
46
33
|
}
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
// The notes she invited this asker under.
|
|
35
|
+
notes(asker) {
|
|
36
|
+
return asker.id === undefined ? undefined : this.stance.occupants.notes(asker.id);
|
|
49
37
|
}
|
|
50
|
-
get occupants() {
|
|
51
|
-
return this.stance.occupants;
|
|
52
|
-
}
|
|
53
|
-
// A standing at one of the things this device can do, under an id of hers.
|
|
54
|
-
// The ward knocks and takes it for her; the invitation never reaches her.
|
|
55
|
-
lend(name, id) {
|
|
56
|
-
return this.stance.lend(name, id);
|
|
57
|
-
}
|
|
58
|
-
invite(id, notes) {
|
|
59
|
-
return this.stance.occupants.invite(id, notes);
|
|
60
|
-
}
|
|
61
|
-
knock(invitation, method, args, wanted) {
|
|
62
|
-
return this.stance.standings.knock(invitation, method, args, wanted);
|
|
63
|
-
}
|
|
64
|
-
take(id, invitation) {
|
|
65
|
-
return this.stance.standings.take(id, invitation);
|
|
66
|
-
}
|
|
67
|
-
// A new being of her ward, by class name, under a key she chooses. With an
|
|
68
|
-
// id, she holds a standing to the being she made, who knows her by her key.
|
|
69
|
-
boot(className, key, id) {
|
|
70
|
-
return this.stance.boot(className, key, id);
|
|
71
|
-
}
|
|
72
|
-
// The occupant record for whoever is at the door. Undefined at a public being.
|
|
73
|
-
occupant(asker) {
|
|
74
|
-
return asker.id !== undefined && Object.hasOwn(this.cells.occupants, asker.id) ? this.cells.occupants[asker.id] : undefined;
|
|
75
|
-
}
|
|
76
|
-
// Her blueprint for this asker. Override to shape it by hand.
|
|
77
38
|
describe(asker) {
|
|
78
39
|
const C = this.constructor;
|
|
79
|
-
const
|
|
40
|
+
const notes = this.notes(asker);
|
|
80
41
|
const asks = [];
|
|
81
42
|
for (const [name, spec] of Object.entries(C.asks)) {
|
|
82
|
-
if (spec.for && !spec.for(
|
|
43
|
+
if (spec.for && !spec.for(asker, notes))
|
|
83
44
|
continue;
|
|
84
|
-
|
|
85
|
-
if (spec.description !== undefined)
|
|
86
|
-
ask.description = spec.description;
|
|
87
|
-
if (spec.output !== undefined)
|
|
88
|
-
ask.output = spec.output;
|
|
89
|
-
asks.push(ask);
|
|
45
|
+
asks.push({ name, ...(spec.description === undefined ? {} : { description: spec.description }), input: spec.input ?? { type: 'object' } });
|
|
90
46
|
}
|
|
91
47
|
return { asks, notes: {} };
|
|
92
48
|
}
|
|
93
|
-
|
|
94
|
-
async answer(asker, method, args = {}) {
|
|
49
|
+
async answer(asker, method, args) {
|
|
95
50
|
if (method === undefined)
|
|
96
51
|
return this.describe(asker);
|
|
97
52
|
const C = this.constructor;
|
|
98
|
-
// Declared, by her, on purpose. `asks` is an ordinary object, so a bare
|
|
99
|
-
// lookup would also find every name on Object's prototype: `valueOf`
|
|
100
|
-
// would answer with her stance, `toString` with a string, and neither is
|
|
101
|
-
// an ask she wrote. Only her own keys are asks, which is what describe
|
|
102
|
-
// shows. What she shows is what she can be asked.
|
|
103
53
|
const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : undefined;
|
|
104
|
-
if (!spec || (spec.for && !spec.for(this.
|
|
54
|
+
if (!spec || (spec.for && !spec.for(asker, this.notes(asker))))
|
|
105
55
|
return { error: 'unknown ask' };
|
|
106
|
-
|
|
107
|
-
return fn.call(this, args ?? {}, asker);
|
|
56
|
+
return this[method].call(this, args, asker);
|
|
108
57
|
}
|
|
109
58
|
}
|
package/dist/being/digest.d.ts
CHANGED
package/dist/being/digest.js
CHANGED
|
@@ -1,37 +1,17 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
const absent = (v) => v === undefined || typeof v === 'function' || typeof v === 'symbol';
|
|
7
|
-
// JCS for I-JSON values: sorted keys, no whitespace, JSON escaping. Numbers
|
|
8
|
-
// are serialized as ES does, which is what RFC 8785 specifies. A hole in a
|
|
9
|
-
// list is null, as JSON writes it.
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A being's `seen`: SHA-256, lowercase hex, over the canonical form of her
|
|
3
|
+
// blueprint for one asker. Keys sorted by UTF-16 code units, numbers as
|
|
4
|
+
// ECMAScript writes them, no whitespace.
|
|
5
|
+
import { hex, sha256, utf8 } from '../crypto/index.js';
|
|
10
6
|
export const canonical = (v) => {
|
|
11
7
|
if (Array.isArray(v))
|
|
12
|
-
return `[${
|
|
8
|
+
return `[${v.map(canonical).join(',')}]`;
|
|
13
9
|
if (v !== null && typeof v === 'object') {
|
|
14
10
|
return `{${Object.keys(v)
|
|
15
|
-
.filter((k) => !absent(v[k]))
|
|
16
11
|
.sort()
|
|
17
12
|
.map((k) => `${JSON.stringify(k)}:${canonical(v[k])}`)
|
|
18
13
|
.join(',')}}`;
|
|
19
14
|
}
|
|
20
15
|
return JSON.stringify(v);
|
|
21
16
|
};
|
|
22
|
-
|
|
23
|
-
// which is the price of the boundary: nothing under `src/being` imports
|
|
24
|
-
// anything above it, because this is the whole world a being's own code sees
|
|
25
|
-
// and a being reaching the ward is the thing the shape is against.
|
|
26
|
-
const hex = (bytes) => Array.from(new Uint8Array(bytes), (b) => b.toString(16).padStart(2, '0')).join('');
|
|
27
|
-
// `crypto.subtle` is read at the call and never captured at load. A browser
|
|
28
|
-
// on a plain http:// origin has `crypto` without `subtle`, and a terrain may
|
|
29
|
-
// install one after this module is first imported; either way the failure is
|
|
30
|
-
// one sentence and not a TypeError from inside a digest nobody can read.
|
|
31
|
-
const subtle = () => {
|
|
32
|
-
const s = globalThis.crypto?.subtle;
|
|
33
|
-
if (!s)
|
|
34
|
-
throw new Error('this terrain has no crypto.subtle: a being needs a secure context to be described');
|
|
35
|
-
return s;
|
|
36
|
-
};
|
|
37
|
-
export const digest = async (blueprint) => hex(await subtle().digest('SHA-256', new TextEncoder().encode(canonical(blueprint))));
|
|
17
|
+
export const digest = async (blueprint) => hex(await sha256(utf8(canonical(blueprint))));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Being } from './being.ts';
|
|
2
|
+
import { type Asker, type Blueprint, type JsonObject, type Reply } from './types.ts';
|
|
3
|
+
export declare class Faculty extends Being {
|
|
4
|
+
#private;
|
|
5
|
+
static contracts(C: abstract new (...args: never[]) => unknown): string[];
|
|
6
|
+
static fulfils(C: unknown): C is typeof Faculty;
|
|
7
|
+
describe(asker: Asker): Blueprint;
|
|
8
|
+
answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A faculty: a being of the box ward that fulfils a contract. A contract is
|
|
3
|
+
// a class between `Faculty` and the class that stands, usually abstract:
|
|
4
|
+
//
|
|
5
|
+
// abstract class Timer extends Faculty { ... } the contract
|
|
6
|
+
// class IntervalTimer extends Timer { ... } one class that fulfils it
|
|
7
|
+
//
|
|
8
|
+
// A being lends by the contract and never learns the class. Two asks are the
|
|
9
|
+
// faculty's own and answered to the ward-being that opened her alone:
|
|
10
|
+
// `offer`, an invitation on herself for a ward the dock vouches for, and
|
|
11
|
+
// `retract`, an offer that was not taken.
|
|
12
|
+
import { Being } from './being.js';
|
|
13
|
+
import { WARD } from './types.js';
|
|
14
|
+
export class Faculty extends Being {
|
|
15
|
+
// The contracts a class fulfils: itself and every class between it and
|
|
16
|
+
// `Faculty`, by name.
|
|
17
|
+
static contracts(C) {
|
|
18
|
+
const names = [];
|
|
19
|
+
for (let c = C; typeof c === 'function' && c !== Faculty; c = Object.getPrototypeOf(c))
|
|
20
|
+
names.push(c.name);
|
|
21
|
+
return names;
|
|
22
|
+
}
|
|
23
|
+
static fulfils(C) {
|
|
24
|
+
return typeof C === 'function' && C.prototype instanceof Faculty;
|
|
25
|
+
}
|
|
26
|
+
#open() {
|
|
27
|
+
if (this.cells.offers === undefined)
|
|
28
|
+
this.cells.offers = {};
|
|
29
|
+
return this.cells.offers;
|
|
30
|
+
}
|
|
31
|
+
async #offer() {
|
|
32
|
+
const n = (this.cells.offered ?? 0) + 1;
|
|
33
|
+
this.cells.offered = n;
|
|
34
|
+
const id = `lent:${n}`;
|
|
35
|
+
const invitation = await this.stance.occupants.invite(id);
|
|
36
|
+
if (invitation === null)
|
|
37
|
+
return { error: 'not invited' };
|
|
38
|
+
this.#open()[invitation.heir] = id;
|
|
39
|
+
return { invitation };
|
|
40
|
+
}
|
|
41
|
+
#retract(args) {
|
|
42
|
+
const open = this.#open();
|
|
43
|
+
const id = typeof args.heir === 'string' && Object.hasOwn(open, args.heir) ? open[args.heir] : undefined;
|
|
44
|
+
if (id === undefined)
|
|
45
|
+
return { retracted: null };
|
|
46
|
+
this.stance.occupants.remove(id);
|
|
47
|
+
delete open[args.heir];
|
|
48
|
+
return { retracted: id };
|
|
49
|
+
}
|
|
50
|
+
describe(asker) {
|
|
51
|
+
const blueprint = super.describe(asker);
|
|
52
|
+
if (asker.id !== WARD)
|
|
53
|
+
return blueprint;
|
|
54
|
+
return { ...blueprint, asks: [...blueprint.asks, { name: 'offer', input: { type: 'object' } }, { name: 'retract', input: { type: 'object', required: ['heir'] } }] };
|
|
55
|
+
}
|
|
56
|
+
// An offer is closed by the first word from the occupant it was made for.
|
|
57
|
+
answer(asker, method, args) {
|
|
58
|
+
if (asker.id === WARD && method === 'offer')
|
|
59
|
+
return this.#offer();
|
|
60
|
+
if (asker.id === WARD && method === 'retract')
|
|
61
|
+
return Promise.resolve(this.#retract(args));
|
|
62
|
+
const open = this.#open();
|
|
63
|
+
for (const [heir, id] of Object.entries(open))
|
|
64
|
+
if (id === asker.id)
|
|
65
|
+
delete open[heir];
|
|
66
|
+
return super.answer(asker, method, args);
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/being/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { Being, type AskSpec } from './being.ts';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export type * from './types.ts';
|
|
2
|
+
export { canonical, digest } from './digest.ts';
|
|
3
|
+
export { Faculty } from './faculty.ts';
|
|
4
|
+
export { OWNER, WARD, type Answer, type Ask, type Asker, type BeingClass, type BeingLike, type Blueprint, type Invitation, type Json, type JsonObject, type Occupants, type Reply, type Schema, type Stance, type Standing, type Standings, type Wanted } from './types.ts';
|
|
5
|
+
export { isSilence, isWord, silence, told, word, type Silence, type Word, type WordName } from './words.ts';
|
package/dist/being/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
//
|
|
2
|
+
// What a being author imports.
|
|
3
3
|
export { Being } from './being.js';
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
4
|
+
export { canonical, digest } from './digest.js';
|
|
5
|
+
export { Faculty } from './faculty.js';
|
|
6
|
+
export { OWNER, WARD } from './types.js';
|
|
7
|
+
export { isSilence, isWord, silence, told, word } from './words.js';
|
package/dist/being/types.d.ts
CHANGED
|
@@ -1,89 +1,62 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
1
|
+
import type { Json } from '../crypto/index.ts';
|
|
2
|
+
import type { Invitation } from '../quo/index.ts';
|
|
3
|
+
import type { Silence, Word } from './words.ts';
|
|
4
|
+
export type { Invitation, Json };
|
|
5
5
|
export type JsonObject = {
|
|
6
6
|
[key: string]: Json;
|
|
7
7
|
};
|
|
8
8
|
export type Asker = {
|
|
9
|
-
id: string;
|
|
9
|
+
readonly id: string;
|
|
10
10
|
} | {
|
|
11
|
-
id?: undefined;
|
|
11
|
+
readonly id?: undefined;
|
|
12
12
|
};
|
|
13
13
|
export declare const OWNER = "OWNER";
|
|
14
|
-
export declare const
|
|
15
|
-
export
|
|
16
|
-
export type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
secret?: string;
|
|
14
|
+
export declare const WARD = "ward";
|
|
15
|
+
export type Answer = Json | Silence | Word;
|
|
16
|
+
export type Reply = Json | Silence | undefined;
|
|
17
|
+
export type Wanted = {
|
|
18
|
+
readonly time?: number;
|
|
20
19
|
};
|
|
21
|
-
export declare const invitationArgs: (inv: Invitation) => JsonObject;
|
|
22
|
-
export declare const isInvitation: (v: unknown) => v is Invitation;
|
|
23
20
|
export type Schema = JsonObject;
|
|
24
21
|
export type Ask = {
|
|
25
22
|
name: string;
|
|
26
23
|
description?: string;
|
|
27
24
|
input: Schema;
|
|
28
|
-
output?: Schema;
|
|
29
25
|
};
|
|
30
26
|
export type Blueprint = {
|
|
31
27
|
asks: Ask[];
|
|
32
28
|
notes: Json;
|
|
33
29
|
};
|
|
34
|
-
export
|
|
35
|
-
export type StandingRecord = {
|
|
36
|
-
id: string;
|
|
37
|
-
digest: string | null;
|
|
38
|
-
blueprint: Blueprint | null;
|
|
39
|
-
seen: string | null;
|
|
40
|
-
};
|
|
41
|
-
export type OccupantRecord = {
|
|
42
|
-
id: string;
|
|
43
|
-
notes: JsonObject;
|
|
44
|
-
};
|
|
45
|
-
export type Cells = {
|
|
46
|
-
standings: Record<string, StandingRecord>;
|
|
47
|
-
occupants: Record<string, OccupantRecord>;
|
|
48
|
-
[hers: string]: Json;
|
|
49
|
-
};
|
|
50
|
-
export type Silence = typeof silence;
|
|
51
|
-
export type DoorWord = 'removed' | 'absent' | 'unannounced' | 'repeated' | 'threw';
|
|
52
|
-
export type WardWord = 'unreached' | 'late' | 'invitation' | 'dropped';
|
|
53
|
-
export type WordName = DoorWord | WardWord;
|
|
54
|
-
export declare const WORD_KEY: unique symbol;
|
|
55
|
-
export type Word<W extends WordName = WordName> = {
|
|
56
|
-
readonly [K in typeof WORD_KEY]: W;
|
|
57
|
-
};
|
|
58
|
-
export type Unreached = Word<'unreached'>;
|
|
59
|
-
export type Answer = Json | Silence | Word;
|
|
60
|
-
export type Reply = Json | Silence;
|
|
61
|
-
export type Wanted = {
|
|
62
|
-
time?: number;
|
|
63
|
-
};
|
|
64
|
-
export type Standing = {
|
|
30
|
+
export interface Standing {
|
|
65
31
|
readonly id: string;
|
|
66
32
|
ask(method?: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
|
|
67
|
-
}
|
|
68
|
-
export
|
|
69
|
-
knock(invitation: Invitation, method?: string, args?: JsonObject, wanted?: Wanted): Promise<Answer>;
|
|
33
|
+
}
|
|
34
|
+
export interface Standings {
|
|
70
35
|
take(id: string, invitation: Invitation): Promise<string | null>;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
export
|
|
36
|
+
get(id: string): Standing | undefined;
|
|
37
|
+
ids(): string[];
|
|
38
|
+
remove(id: string): boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface Occupants {
|
|
76
41
|
invite(id: string, notes?: JsonObject): Promise<Invitation | null>;
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
42
|
+
notes(id: string): JsonObject | undefined;
|
|
43
|
+
ids(): string[];
|
|
44
|
+
remove(id: string): boolean;
|
|
45
|
+
}
|
|
46
|
+
export interface Stance {
|
|
47
|
+
readonly key: string;
|
|
48
|
+
readonly cells: JsonObject;
|
|
81
49
|
readonly occupants: Occupants;
|
|
82
50
|
readonly standings: Standings;
|
|
83
|
-
lend(name: string, id: string): Promise<string | null>;
|
|
84
51
|
boot(className: string, key: string, id?: string): Promise<string | null>;
|
|
85
|
-
|
|
52
|
+
lend(contract: {
|
|
53
|
+
readonly name: string;
|
|
54
|
+
}, id: string): Promise<string | null>;
|
|
55
|
+
}
|
|
86
56
|
export interface BeingLike {
|
|
87
|
-
answer(asker: Asker, method
|
|
57
|
+
answer(asker: Asker, method: string | undefined, args: JsonObject): Reply | Promise<Reply>;
|
|
58
|
+
describe?(asker: Asker): Blueprint;
|
|
88
59
|
}
|
|
89
|
-
export type BeingClass = new (stance: Stance) => BeingLike
|
|
60
|
+
export type BeingClass = (new (stance: Stance) => BeingLike) & {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
};
|