nervur 0.19.2 → 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 +156 -104
- package/dist/being/being.d.ts +8 -15
- package/dist/being/being.js +20 -67
- package/dist/being/digest.d.ts +1 -1
- package/dist/being/digest.js +7 -27
- package/dist/being/faculty.d.ts +10 -0
- package/dist/being/faculty.js +74 -0
- package/dist/being/index.d.ts +5 -5
- package/dist/being/index.js +6 -5
- package/dist/being/kind.d.ts +4 -0
- package/dist/being/kind.js +28 -0
- package/dist/being/types.d.ts +35 -63
- package/dist/being/types.js +3 -57
- package/dist/being/words.d.ts +13 -0
- package/dist/being/words.js +14 -0
- 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 +39 -0
- package/dist/contract/index.js +33 -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 +14 -0
- package/dist/folder/index.js +87 -0
- package/dist/harbor/catalogue.d.ts +25 -0
- package/dist/harbor/catalogue.js +93 -0
- package/dist/harbor/dock.d.ts +29 -0
- package/dist/harbor/dock.js +122 -0
- package/dist/harbor/harbor.d.ts +31 -0
- package/dist/harbor/harbor.js +146 -0
- package/dist/harbor/index.d.ts +7 -10
- package/dist/harbor/index.js +9 -18
- package/dist/harbor/package.d.ts +10 -0
- package/dist/harbor/package.js +115 -0
- package/dist/harbor/registry.d.ts +9 -0
- package/dist/harbor/registry.js +78 -0
- package/dist/harbor/root-line.d.ts +9 -0
- package/dist/harbor/root-line.js +36 -0
- package/dist/harbor/terrain.d.ts +9 -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 +26 -0
- package/dist/pointer/bodies.js +64 -0
- package/dist/pointer/index.d.ts +2 -0
- package/dist/pointer/index.js +4 -0
- package/dist/pointer/world.d.ts +35 -0
- package/dist/pointer/world.js +85 -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/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/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 +6 -10
- package/dist/ward/index.js +7 -13
- package/dist/ward/partition.d.ts +44 -60
- package/dist/ward/partition.js +56 -285
- package/dist/ward/pilot.d.ts +8 -0
- package/dist/ward/pilot.js +50 -0
- package/dist/ward/stance.d.ts +27 -20
- package/dist/ward/stance.js +174 -406
- package/dist/ward/ward-being.d.ts +35 -0
- package/dist/ward/ward-being.js +82 -0
- package/dist/ward/ward.d.ts +45 -11
- package/dist/ward/ward.js +239 -357
- package/package.json +24 -31
- package/src/being/being.ts +35 -78
- package/src/being/digest.ts +7 -35
- package/src/being/faculty.ts +73 -0
- package/src/being/index.ts +6 -6
- package/src/being/kind.ts +30 -0
- package/src/being/types.ts +50 -146
- package/src/being/words.ts +31 -0
- 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 +73 -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 +92 -0
- package/src/harbor/catalogue.ts +110 -0
- package/src/harbor/dock.ts +137 -0
- package/src/harbor/harbor.ts +180 -0
- package/src/harbor/index.ts +9 -19
- package/src/harbor/package.ts +131 -0
- package/src/harbor/registry.ts +73 -0
- package/src/harbor/root-line.ts +36 -0
- package/src/harbor/terrain.ts +15 -0
- package/src/index.ts +20 -0
- package/src/pointer/bodies.ts +65 -0
- package/src/pointer/index.ts +4 -0
- package/src/pointer/world.ts +94 -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 +20 -0
- package/src/stand/stand.ts +220 -0
- package/src/tcp/frame.ts +78 -0
- package/src/tcp/index.ts +173 -0
- package/src/ward/allowance.ts +37 -75
- package/src/ward/cells.ts +63 -176
- package/src/ward/index.ts +7 -17
- package/src/ward/partition.ts +86 -326
- package/src/ward/pilot.ts +50 -0
- package/src/ward/stance.ts +186 -420
- package/src/ward/ward-being.ts +103 -0
- package/src/ward/ward.ts +256 -359
- 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,171 @@
|
|
|
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
|
-
- **Ward.** One process of its harbor. A being plus ward functions. Keeps
|
|
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.
|
|
30
|
-
|
|
31
|
-
## This package
|
|
32
|
-
|
|
33
|
-
A TypeScript implementation, written against Node's own type stripping, with
|
|
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`.
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
src/being/ the Being side: what a being author imports, if anything
|
|
40
|
-
src/ward/ the ward: the Ground contract, door, seal, arithmetic, heirs, stance, allowance
|
|
41
|
-
src/harbor/ MemoryHarbor, the box's being, and the harbor core with its store, reach and dialer
|
|
42
|
-
src/conformance/ behaviours any ward must show, written against the truth
|
|
43
|
-
src/vector/ vector mode, and the world the door's cases are taken in
|
|
13
|
+
```sh
|
|
14
|
+
npm install nervur
|
|
44
15
|
```
|
|
45
16
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
somebody else's ward rather than for this one.
|
|
50
|
-
|
|
51
|
-
## Requirements
|
|
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.
|
|
52
20
|
|
|
53
|
-
|
|
54
|
-
it ships JavaScript with declarations, so nothing is compiled on the way in.
|
|
21
|
+
## A world in one process
|
|
55
22
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
23
|
+
```js
|
|
24
|
+
import { Being, Faculty, World } from 'nervur';
|
|
25
|
+
|
|
26
|
+
// A contract, and a faculty that fulfils it. Every class names its kind.
|
|
27
|
+
class Clock extends Faculty {
|
|
28
|
+
static kind = 'org.example.clock';
|
|
29
|
+
}
|
|
30
|
+
class SystemTime extends Clock {
|
|
31
|
+
static kind = 'org.example.system-time';
|
|
32
|
+
static asks = { now: {} };
|
|
33
|
+
now() {
|
|
34
|
+
return { now: Date.now() };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A being, who lends a clock by its contract.
|
|
39
|
+
class Greeter extends Being {
|
|
40
|
+
static kind = 'org.example.greeter';
|
|
41
|
+
static asks = { hello: {} };
|
|
42
|
+
async hello(args, asker) {
|
|
43
|
+
if (!this.stance.standings.get('clock')) await this.stance.lend(Clock, 'clock');
|
|
44
|
+
const time = await this.stance.standings.get('clock')?.ask('now');
|
|
45
|
+
return { hello: asker.id ?? 'stranger', time };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The module these classes are: its name, its version, its classes.
|
|
50
|
+
const greetings = { module: 'org.example.greetings', version: '1.0.0', classes: [SystemTime, Greeter] };
|
|
51
|
+
|
|
52
|
+
// A harbor whose terrain can load that module, and whose catalogue runs it.
|
|
53
|
+
const world = new World();
|
|
54
|
+
const harbor = await world.harbor('home', [greetings]);
|
|
55
|
+
await harbor.root('ask', { being: 'catalogue', method: 'add', args: { from: 'org.example.greetings' } });
|
|
56
|
+
await harbor.root('open', { key: 'clock', class: 'org.example.system-time' });
|
|
57
|
+
await harbor.root('host', { ward: 'alice' });
|
|
58
|
+
await harbor.ward('alice').root('boot', { key: 'greeter', class: 'org.example.greeter' });
|
|
59
|
+
```
|
|
66
60
|
|
|
67
|
-
|
|
61
|
+
A second harbor in the same world takes an invitation on the greeter and
|
|
62
|
+
asks it through the world, sealed as Quo's bytes. `world.restart('home')`
|
|
63
|
+
opens the harbor again from what its memory kept.
|
|
64
|
+
|
|
65
|
+
## Kinds
|
|
66
|
+
|
|
67
|
+
A row keeps the kind of the class a being is born of, and a being lends
|
|
68
|
+
by a contract's kind. A kind is declared on the class, because a bundler
|
|
69
|
+
renames classes. It is a domain you own, reversed, then a name, as Apple
|
|
70
|
+
and Matrix name things: `com.acme.shop`. Every segment is lowercase
|
|
71
|
+
letters, digits and `-`, and there are three at least. Each class
|
|
72
|
+
declares its own: a subclass that stands, and every contract between a
|
|
73
|
+
faculty and `Faculty`. `org.nervur.` is the kit's, and `org.example.` is
|
|
74
|
+
for examples like the one above. A module's classes stand under its own
|
|
75
|
+
domain: `com.acme.shop` stands `com.acme.*` kinds. A harbor refuses a
|
|
76
|
+
module in which one kind names two classes or two contracts, or names a
|
|
77
|
+
class and a contract.
|
|
78
|
+
|
|
79
|
+
## Modules
|
|
80
|
+
|
|
81
|
+
Your classes come in modules: `{ module, version, classes }`, a module
|
|
82
|
+
named as a kind is. A harbor's catalogue is a faculty of its own, and her
|
|
83
|
+
cells name the modules the harbor runs. `add { from }` runs the module
|
|
84
|
+
the terrain's loader finds there, and `remove { module }` stops one no
|
|
85
|
+
being stands on. A restart loads every module she names and refuses to
|
|
86
|
+
stand when one is missing. A module found at a new version wakes, and
|
|
87
|
+
`modules` shows the version kept and the version running.
|
|
88
|
+
|
|
89
|
+
## The onion
|
|
90
|
+
|
|
91
|
+
A harbor is one package. Only the seed and the terrain's bodies, its
|
|
92
|
+
loader among them, stand outside it.
|
|
93
|
+
|
|
94
|
+
1. The terrain gives the harbor's seed, which opens its memory. The
|
|
95
|
+
terrain keeps that memory as sealed bytes and reads none of it.
|
|
96
|
+
2. The box ward unpacks under the seed, its ward-being the dock.
|
|
97
|
+
3. The catalogue wakes, and loads every module she names.
|
|
98
|
+
4. The other faculties are born from their rows.
|
|
99
|
+
5. The routes the dock keeps are handed to the carrier.
|
|
100
|
+
6. Every ward the dock hosts unpacks, under the seed the dock keeps for it.
|
|
101
|
+
7. In every ward, the ward-being first, then each being from her row.
|
|
102
|
+
|
|
103
|
+
Cells decide what stands. The modules only resolve a kind a row keeps.
|
|
104
|
+
Opening a harbor on empty memory is its genesis: the dock stands the
|
|
105
|
+
catalogue, and the root's asks write the rest: `add` on the catalogue,
|
|
106
|
+
`open`, `host`, `route`, and `boot` on a ward. `host { ward, seed? }`
|
|
107
|
+
stands a ward under a seed you name, or a drawn one.
|
|
108
|
+
|
|
109
|
+
## What it holds
|
|
110
|
+
|
|
111
|
+
- **Being, Faculty.** What you extend. A faculty's contract is its class
|
|
112
|
+
and every class between it and `Faculty`.
|
|
113
|
+
- **Entropy, Clock, Custody, Memory, Carrier, Loader.** The contracts a
|
|
114
|
+
terrain fulfils. Each has one suite, and every body that fulfils it
|
|
115
|
+
passes it.
|
|
116
|
+
- **Harbor, Terrain, Dock, Catalogue, Registry, WardBeing.** The onion.
|
|
117
|
+
- **World, PointerTerrain** and the pointer bodies, `HeldLoader` among
|
|
118
|
+
them. A whole world with this package alone.
|
|
119
|
+
- **FolderMemory, FolderCustody**, from `nervur/folder`.
|
|
120
|
+
- **TcpCarrier, TcpListener**, from `nervur/tcp`: Quo over TCP. Route a
|
|
121
|
+
ward pk to `host:port` on the carrier, hand the listener your harbor,
|
|
122
|
+
and harbors in two processes, or a harbor and another kit, speak.
|
|
123
|
+
|
|
124
|
+
Other carriers, storage beyond a folder, key custody, and every screen
|
|
125
|
+
are `@nervur-org/dock`'s, as classes fulfilling these contracts.
|
|
126
|
+
|
|
127
|
+
## The command
|
|
128
|
+
|
|
129
|
+
`nervur` stands a harbor on this machine: its seed and sealed memory in a
|
|
130
|
+
folder, Quo over TCP as its carrier. A module is a file that exports
|
|
131
|
+
`module`, `version` and `classes`.
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
nervur init --dir ./harbor
|
|
135
|
+
nervur module add ./greetings.js --dir ./harbor
|
|
136
|
+
nervur serve --dir ./harbor --port 7000
|
|
137
|
+
nervur host alice --dir ./harbor
|
|
138
|
+
nervur boot --ward alice greeter org.example.greeter --dir ./harbor
|
|
139
|
+
nervur route <ward pk> host:port --dir ./harbor
|
|
140
|
+
nervur ask --ward alice greeter hello --dir ./harbor
|
|
141
|
+
```
|
|
68
142
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
143
|
+
`init` makes the harbor and prints its pk and an owner invitation. `serve`
|
|
144
|
+
listens for Quo on TCP and for the root's asks on a local socket, open to
|
|
145
|
+
you alone. Every other command is one root ask, sent to the served
|
|
146
|
+
harbor, or answered from the folder when none is served. Each prints one
|
|
147
|
+
JSON line. `--dir` defaults to `$NERVUR_DIR`, then `~/.nervur`. The
|
|
148
|
+
catalogue keeps a module by its file's absolute path, and routes live in
|
|
149
|
+
the harbor like everything else, so a restart finds both. `nervur
|
|
150
|
+
modules` shows what runs, and `nervur module remove <module>` stops one.
|
|
151
|
+
|
|
152
|
+
A harbor elsewhere is piloted with the owner invitation its `init`
|
|
153
|
+
printed, from a harbor of your own:
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
nervur pilot far '<owner invitation>' host:port --dir ./mine
|
|
157
|
+
nervur host shop --via far --dir ./mine
|
|
75
158
|
```
|
|
76
159
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
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.
|
|
160
|
+
`pilot` boots an `org.nervur.pilot` being in your harbor that holds the
|
|
161
|
+
invitation as an ordinary Quo relation, kept in your harbor's memory. With
|
|
162
|
+
`--via far`, any command is asked of the far harbor as its owner, sealed
|
|
163
|
+
over TCP, except `own` and `disown`, which stay the far root's.
|
|
164
|
+
`nervur unboot far` lets it go.
|
|
165
|
+
|
|
166
|
+
`rootLine(harbor, request)` is the same request in code, for any other
|
|
167
|
+
front: `{ ward?, method?, args? }` in, one JSON answer out.
|
|
115
168
|
|
|
116
169
|
## License
|
|
117
170
|
|
|
118
|
-
Apache-2.0.
|
|
119
|
-
[NOTICE](NOTICE); every source file carries an SPDX line.
|
|
171
|
+
Apache-2.0.
|
package/dist/being/being.d.ts
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
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 {
|
|
8
|
+
static readonly kind: string;
|
|
9
9
|
static cells: JsonObject;
|
|
10
10
|
static asks: Record<string, AskSpec>;
|
|
11
11
|
readonly stance: Stance;
|
|
12
12
|
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;
|
|
13
|
+
get cells(): JsonObject;
|
|
14
|
+
notes(asker: Asker): JsonObject | undefined;
|
|
22
15
|
describe(asker: Asker): Blueprint;
|
|
23
|
-
answer(asker: Asker, method
|
|
16
|
+
answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
|
|
24
17
|
}
|
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,15 @@ const wrote = (self, name) => {
|
|
|
12
8
|
}
|
|
13
9
|
return false;
|
|
14
10
|
};
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
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. Her kind is
|
|
13
|
+
// never inherited: see `kind.ts`.
|
|
21
14
|
export class Being {
|
|
22
|
-
//
|
|
23
|
-
//
|
|
15
|
+
// The name her row keeps for this class. Every class that stands declares
|
|
16
|
+
// its own.
|
|
17
|
+
static kind = 'org.nervur.being';
|
|
18
|
+
// Her cells' defaults, written at birth only where a key is missing.
|
|
24
19
|
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
20
|
static asks = {};
|
|
28
21
|
stance;
|
|
29
22
|
constructor(stance) {
|
|
@@ -33,10 +26,8 @@ export class Being {
|
|
|
33
26
|
if (RESERVED.has(name))
|
|
34
27
|
throw new Error(`ask '${name}' is a reserved name`);
|
|
35
28
|
if (!wrote(this, name))
|
|
36
|
-
throw new Error(`ask '${name}' has no method
|
|
29
|
+
throw new Error(`ask '${name}' has no method`);
|
|
37
30
|
}
|
|
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
31
|
for (const [k, v] of Object.entries(C.cells))
|
|
41
32
|
if (!Object.hasOwn(stance.cells, k))
|
|
42
33
|
stance.cells[k] = structuredClone(v);
|
|
@@ -44,66 +35,28 @@ export class Being {
|
|
|
44
35
|
get cells() {
|
|
45
36
|
return this.stance.cells;
|
|
46
37
|
}
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
// The notes she invited this asker under.
|
|
39
|
+
notes(asker) {
|
|
40
|
+
return asker.id === undefined ? undefined : this.stance.occupants.notes(asker.id);
|
|
49
41
|
}
|
|
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
42
|
describe(asker) {
|
|
78
43
|
const C = this.constructor;
|
|
79
|
-
const
|
|
44
|
+
const notes = this.notes(asker);
|
|
80
45
|
const asks = [];
|
|
81
46
|
for (const [name, spec] of Object.entries(C.asks)) {
|
|
82
|
-
if (spec.for && !spec.for(
|
|
47
|
+
if (spec.for && !spec.for(asker, notes))
|
|
83
48
|
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);
|
|
49
|
+
asks.push({ name, ...(spec.description === undefined ? {} : { description: spec.description }), input: spec.input ?? { type: 'object' } });
|
|
90
50
|
}
|
|
91
51
|
return { asks, notes: {} };
|
|
92
52
|
}
|
|
93
|
-
|
|
94
|
-
async answer(asker, method, args = {}) {
|
|
53
|
+
async answer(asker, method, args) {
|
|
95
54
|
if (method === undefined)
|
|
96
55
|
return this.describe(asker);
|
|
97
56
|
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
57
|
const spec = Object.hasOwn(C.asks, method) ? C.asks[method] : undefined;
|
|
104
|
-
if (!spec || (spec.for && !spec.for(this.
|
|
58
|
+
if (!spec || (spec.for && !spec.for(asker, this.notes(asker))))
|
|
105
59
|
return { error: 'unknown ask' };
|
|
106
|
-
|
|
107
|
-
return fn.call(this, args ?? {}, asker);
|
|
60
|
+
return this[method].call(this, args, asker);
|
|
108
61
|
}
|
|
109
62
|
}
|
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,10 @@
|
|
|
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 readonly kind: string;
|
|
6
|
+
static contracts(C: abstract new (...args: never[]) => unknown): string[];
|
|
7
|
+
static fulfils(C: unknown): C is typeof Faculty;
|
|
8
|
+
describe(asker: Asker): Blueprint;
|
|
9
|
+
answer(asker: Asker, method: string | undefined, args: JsonObject): Promise<Reply>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
// static override kind = 'com.acme.timer' }
|
|
7
|
+
// class IntervalTimer extends Timer { one class that fulfils it
|
|
8
|
+
// static override kind = 'com.acme.interval-timer' }
|
|
9
|
+
//
|
|
10
|
+
// A being lends by the contract's kind and never learns the class. Every
|
|
11
|
+
// class between the faculty and `Faculty` is a contract and declares its
|
|
12
|
+
// own kind, and a faculty fulfils each of them and her own. Two asks are the
|
|
13
|
+
// faculty's own and answered to the ward-being that opened her alone:
|
|
14
|
+
// `offer`, an invitation on herself for a ward the dock vouches for, and
|
|
15
|
+
// `retract`, an offer that was not taken.
|
|
16
|
+
import { Being } from './being.js';
|
|
17
|
+
import { kindOf } from './kind.js';
|
|
18
|
+
import { WARD } from './types.js';
|
|
19
|
+
export class Faculty extends Being {
|
|
20
|
+
static kind = 'org.nervur.faculty';
|
|
21
|
+
// The contracts a class fulfils: itself and every class between it and
|
|
22
|
+
// `Faculty`, by kind. A class among them with no kind of its own throws.
|
|
23
|
+
static contracts(C) {
|
|
24
|
+
const kinds = [];
|
|
25
|
+
for (let c = C; typeof c === 'function' && c !== Faculty; c = Object.getPrototypeOf(c))
|
|
26
|
+
kinds.push(kindOf(c));
|
|
27
|
+
return kinds;
|
|
28
|
+
}
|
|
29
|
+
static fulfils(C) {
|
|
30
|
+
return typeof C === 'function' && C.prototype instanceof Faculty;
|
|
31
|
+
}
|
|
32
|
+
#open() {
|
|
33
|
+
if (this.cells.offers === undefined)
|
|
34
|
+
this.cells.offers = {};
|
|
35
|
+
return this.cells.offers;
|
|
36
|
+
}
|
|
37
|
+
async #offer() {
|
|
38
|
+
const n = (this.cells.offered ?? 0) + 1;
|
|
39
|
+
this.cells.offered = n;
|
|
40
|
+
const id = `lent:${n}`;
|
|
41
|
+
const invitation = await this.stance.occupants.invite(id);
|
|
42
|
+
if (invitation === null)
|
|
43
|
+
return { error: 'not invited' };
|
|
44
|
+
this.#open()[invitation.heir] = id;
|
|
45
|
+
return { invitation };
|
|
46
|
+
}
|
|
47
|
+
#retract(args) {
|
|
48
|
+
const open = this.#open();
|
|
49
|
+
const id = typeof args.heir === 'string' && Object.hasOwn(open, args.heir) ? open[args.heir] : undefined;
|
|
50
|
+
if (id === undefined)
|
|
51
|
+
return { retracted: null };
|
|
52
|
+
this.stance.occupants.remove(id);
|
|
53
|
+
delete open[args.heir];
|
|
54
|
+
return { retracted: id };
|
|
55
|
+
}
|
|
56
|
+
describe(asker) {
|
|
57
|
+
const blueprint = super.describe(asker);
|
|
58
|
+
if (asker.id !== WARD)
|
|
59
|
+
return blueprint;
|
|
60
|
+
return { ...blueprint, asks: [...blueprint.asks, { name: 'offer', input: { type: 'object' } }, { name: 'retract', input: { type: 'object', required: ['heir'] } }] };
|
|
61
|
+
}
|
|
62
|
+
// An offer is closed by the first word from the occupant it was made for.
|
|
63
|
+
answer(asker, method, args) {
|
|
64
|
+
if (asker.id === WARD && method === 'offer')
|
|
65
|
+
return this.#offer();
|
|
66
|
+
if (asker.id === WARD && method === 'retract')
|
|
67
|
+
return Promise.resolve(this.#retract(args));
|
|
68
|
+
const open = this.#open();
|
|
69
|
+
for (const [heir, id] of Object.entries(open))
|
|
70
|
+
if (id === asker.id)
|
|
71
|
+
delete open[heir];
|
|
72
|
+
return super.answer(asker, method, args);
|
|
73
|
+
}
|
|
74
|
+
}
|
package/dist/being/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Being, type AskSpec } from './being.ts';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
5
|
-
export { OWNER,
|
|
6
|
-
export type
|
|
2
|
+
export { canonical, digest } from './digest.ts';
|
|
3
|
+
export { Faculty } from './faculty.ts';
|
|
4
|
+
export { isKind, KIT, kindOf, ownKind } from './kind.ts';
|
|
5
|
+
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';
|
|
6
|
+
export { isSilence, isWord, silence, told, word, type Silence, type Word, type WordName } from './words.ts';
|
package/dist/being/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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 { OWNER,
|
|
4
|
+
export { canonical, digest } from './digest.js';
|
|
5
|
+
export { Faculty } from './faculty.js';
|
|
6
|
+
export { isKind, KIT, kindOf, ownKind } from './kind.js';
|
|
7
|
+
export { OWNER, WARD } from './types.js';
|
|
8
|
+
export { isSilence, isWord, silence, told, word } from './words.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// A kind: the name a row keeps for the class a being is born of, and the
|
|
3
|
+
// name a contract is lent by. It is declared on the class, never taken from
|
|
4
|
+
// the name the language gives it, because a bundler renames classes.
|
|
5
|
+
//
|
|
6
|
+
// static kind = 'com.acme.shop'
|
|
7
|
+
//
|
|
8
|
+
// A kind is a reversed domain its author owns, then a name: at least three
|
|
9
|
+
// segments joined by `.`, each lowercase letters, digits and `-`, starting
|
|
10
|
+
// with a letter or digit. `org.nervur.` is the kit's own. A class declares
|
|
11
|
+
// its own kind: one it inherits names its parent, so it is none.
|
|
12
|
+
export const KIT = 'org.nervur.';
|
|
13
|
+
const KIND = /^[a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*){2,}$/;
|
|
14
|
+
export const isKind = (text) => typeof text === 'string' && KIND.test(text);
|
|
15
|
+
// The kind a class declares on itself, or undefined.
|
|
16
|
+
export const ownKind = (C) => {
|
|
17
|
+
if (typeof C !== 'function' || !Object.hasOwn(C, 'kind'))
|
|
18
|
+
return undefined;
|
|
19
|
+
const kind = C.kind;
|
|
20
|
+
return isKind(kind) ? kind : undefined;
|
|
21
|
+
};
|
|
22
|
+
// The kind a class declares on itself, or a throw naming the class.
|
|
23
|
+
export const kindOf = (C) => {
|
|
24
|
+
const kind = ownKind(C);
|
|
25
|
+
if (kind === undefined)
|
|
26
|
+
throw new Error(`class ${typeof C === 'function' ? C.name : String(C)} declares no kind of its own`);
|
|
27
|
+
return kind;
|
|
28
|
+
};
|