nervur 0.22.2-8 → 0.23.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/AUTHORING.md +58 -34
- package/COMMAND.md +72 -23
- package/FACES.md +4 -5
- package/FACULTIES.md +18 -11
- package/GROUNDS.md +160 -82
- package/README.md +6 -6
- package/dist/bench/bench-ground.d.ts +3 -3
- package/dist/bench/bench-ground.js +23 -9
- package/dist/browser/browser-ground.d.ts +4 -4
- package/dist/browser/browser-ground.js +55 -11
- package/dist/edge/edge-ground.d.ts +5 -5
- package/dist/edge/edge-ground.js +58 -55
- package/dist/edge/secret-unlock.d.ts +1 -1
- package/dist/edge/secret-unlock.js +1 -1
- package/dist/ground/dock.d.ts +1460 -0
- package/dist/ground/dock.js +1159 -0
- package/dist/ground/ground.d.ts +86 -269
- package/dist/ground/ground.js +496 -820
- package/dist/ground/views.d.ts +17 -0
- package/dist/ground/views.js +20 -0
- package/dist/house/crossing.d.ts +3 -2
- package/dist/house/crossing.js +5 -4
- package/dist/house/house.d.ts +3 -1
- package/dist/house/house.js +92 -23
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/node/cli.js +63 -31
- package/dist/node/hand.d.ts +4 -3
- package/dist/node/hand.js +3 -3
- package/dist/node/node-ground.d.ts +6 -8
- package/dist/node/node-ground.js +127 -163
- package/dist/node/shell.d.ts +11 -0
- package/dist/node/shell.js +35 -0
- package/dist/node/tcp-carry.d.ts +2 -0
- package/dist/node/tcp-carry.js +8 -1
- package/package.json +2 -1
package/GROUNDS.md
CHANGED
|
@@ -14,24 +14,41 @@ kept by what the ground stands on: a file, a keychain, a Worker's
|
|
|
14
14
|
secret. The part of a ground that answers it is its unlock. Everything
|
|
15
15
|
else the ground keeps is sealed under that key.
|
|
16
16
|
|
|
17
|
-
**It
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
17
|
+
**It unpacks in layers, each a faculty.** Every faculty has one
|
|
18
|
+
lifecycle. A registry holds faculties by name, each an object `{ takes?,
|
|
19
|
+
up, install? }`. Standing one raises it by its `up` into a body, the
|
|
20
|
+
living instance houses and other bodies use. `install` does the slow
|
|
21
|
+
work once for each entry, and a body's `down` lets go of what its `up`
|
|
22
|
+
opened.
|
|
23
|
+
|
|
24
|
+
| Layer | Its faculties | What they are |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| primordial | the memory, the unlock, crypto, tools, the clock | the host's, read from the environment alone |
|
|
27
|
+
| the ground's work | `ground` | the library's, offered to the dock alone |
|
|
28
|
+
| the dock | a house of the library's beings | the drawer, and the hand's one road |
|
|
29
|
+
| the ladder | the terrain's defaults and your entries | carries, code, and every custom faculty |
|
|
30
|
+
| the hand | a socket, a key, a channel | the owner's reach, up last and down first |
|
|
31
|
+
|
|
32
|
+
**It keeps its drawer as cells of the dock's beings.** The dock is a
|
|
33
|
+
house every ground stands, and its beings are the library's. A twin
|
|
34
|
+
stands for each faculty entry, a being for each house, and one for each
|
|
35
|
+
secret. Their cells hold every entry, what each faculty installed, each
|
|
36
|
+
house's seed and ward, every secret, and the ground's bound on every
|
|
37
|
+
ask. No row of the ground's stands beside them.
|
|
38
|
+
|
|
39
|
+
**Every name an entry gives is a standing.** A house's code, its memory
|
|
40
|
+
and the faculties it uses, and a faculty's registry, its callees and its
|
|
41
|
+
secrets, are relations between those beings. The dock's steward
|
|
42
|
+
introduces each, and her notes name the grant. An entry that names a
|
|
43
|
+
faculty or a secret the dock does not hold is refused, and nothing
|
|
44
|
+
lands.
|
|
45
|
+
|
|
46
|
+
**Its houses keep their places in the same memory.** The dock, each
|
|
47
|
+
house and each faculty sees a view of the ground's memory under a prefix
|
|
48
|
+
of its own. A house seals its own rows, and the ground seals each
|
|
49
|
+
faculty's. So what rests on the disk is ciphertext, and copying the key,
|
|
50
|
+
the memory and the code moves the whole ground. A memory the key opens
|
|
51
|
+
no dock in is refused.
|
|
35
52
|
|
|
36
53
|
**It stands its bodies on a ladder.** The ground's own registry is its
|
|
37
54
|
terrain's. A body may carry a registry of its own, and the faculties an
|
|
@@ -39,12 +56,13 @@ entry raises `from` it stand after it. A body stands after each body its
|
|
|
39
56
|
entry names in `faculties`, whose objects its `up` receives. A body that
|
|
40
57
|
fails to stand stays down and says why, and the ground boots beside it.
|
|
41
58
|
|
|
42
|
-
**Its terrain names default entries, and
|
|
43
|
-
|
|
44
|
-
|
|
59
|
+
**Its terrain names default entries, and yours win.** Each ground stands
|
|
60
|
+
its carries and its bodies of code from entries of its own. Its one
|
|
61
|
+
clock is primordial, which the dock and every house receive.
|
|
62
|
+
An entry you land under the same name stands in its place.
|
|
45
63
|
|
|
46
64
|
```text
|
|
47
|
-
recipe { make: 'module', args: { at: 'recipe.ts' } }
|
|
65
|
+
recipe { from: 'folder', make: 'module', args: { at: 'recipe.ts' } }
|
|
48
66
|
payments { from: 'recipe', make: 'payments', secrets: ['stripe-key'], kinds: ['com.acme.order'] }
|
|
49
67
|
```
|
|
50
68
|
|
|
@@ -58,6 +76,11 @@ each body's name, it holds the args that body hands this house.
|
|
|
58
76
|
shop { classes: { faculty: 'folder', at: 'shop' }, faculties: ['payments'], wait: 60000 }
|
|
59
77
|
```
|
|
60
78
|
|
|
79
|
+
**A change answers what stands, and an error means nothing landed.**
|
|
80
|
+
Adding or updating a faculty answers why its body is down, where it is.
|
|
81
|
+
Adding or updating a house answers its ward, or why it stays closed.
|
|
82
|
+
The entry lands either way, and the hand mends it later.
|
|
83
|
+
|
|
61
84
|
**It updates an entry in one write.** `update` lands the new entry over
|
|
62
85
|
the old. A house closes and opens again on it, as the same ward. A body
|
|
63
86
|
goes down, installs where its entry moved, and goes up, and each house
|
|
@@ -65,13 +88,21 @@ naming it opens again. `restart` takes a body down and up on its entry.
|
|
|
65
88
|
|
|
66
89
|
**It grants a faculty in two steps.** A house uses only the faculties its
|
|
67
90
|
entry names. Inside it, a being holds one only where the faculty's entry
|
|
68
|
-
names her class in `kinds`, or where it names none. So a raw
|
|
91
|
+
names her class in `kinds`, or where it names none. So a raw program
|
|
69
92
|
reaches one house and one class inside it.
|
|
70
93
|
|
|
71
94
|
**It hands each faculty its secrets.** A secret is set through the hand
|
|
72
|
-
and kept in
|
|
73
|
-
receives, and no other code reads one. The hand lists their names
|
|
74
|
-
|
|
95
|
+
and kept in a being's cells. A faculty's entry names the secrets its
|
|
96
|
+
`up` receives, and no other code reads one. The hand lists their names
|
|
97
|
+
and the entries that name each, and never a value. A secret's cells are
|
|
98
|
+
shown to no one, and a changed secret reaches a body when it goes up
|
|
99
|
+
again.
|
|
100
|
+
|
|
101
|
+
**It holds each entry to what its faculty takes.** A faculty declares
|
|
102
|
+
`takes`: a schema its args meet, and the secrets its entry must name.
|
|
103
|
+
The hand refuses an entry that fails it, or that names a secret not
|
|
104
|
+
kept, and says what failed. `facultiesCatalog` shows every faculty the
|
|
105
|
+
ground can raise, and what each takes.
|
|
75
106
|
|
|
76
107
|
**It hooks every door and runs one listener.** Each house's door goes to
|
|
77
108
|
the ground's carry, which also delivers between the ground's own houses
|
|
@@ -79,36 +110,64 @@ without touching the network. Everything that speaks HTTP, Quo over the
|
|
|
79
110
|
web and every face, is a handler on one listener. A faculty stood while
|
|
80
111
|
the ground runs is served at once.
|
|
81
112
|
|
|
82
|
-
**
|
|
83
|
-
ground
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
**The dock's steward reaches the ground through two faculties.** The
|
|
114
|
+
`ground` faculty raises and lowers bodies, opens and closes houses,
|
|
115
|
+
shows the catalogue and moves a house's places. It is offered to the
|
|
116
|
+
dock's beings alone. The `shell` runs a command on the ground's machine,
|
|
117
|
+
where the terrain has one. The dock is offered it as any house is
|
|
118
|
+
offered a body. Its steward stands on the shell's twin, and the twin's
|
|
119
|
+
entry grants it to the dock's shell being alone. No entry names, makes
|
|
120
|
+
or grants either.
|
|
121
|
+
|
|
122
|
+
**A boot writes only what moved.** Each twin raises its body by a read,
|
|
123
|
+
and her cells are written only where what came of it differs. So a wake
|
|
124
|
+
that finds nothing changed writes nothing.
|
|
125
|
+
|
|
126
|
+
**It serves its owner's hand, and the hand has one road.** The hand
|
|
127
|
+
answers `describe`, and otherwise asks a being of the dock or of a house
|
|
128
|
+
as `root`. A request that names a house asks a being there. One that
|
|
129
|
+
names none asks the dock's steward. `describe` shows her asks, every
|
|
130
|
+
faculty with its methods or why it is down, and every house.
|
|
131
|
+
|
|
132
|
+
| The dock's asks | What they do | Who asks them |
|
|
87
133
|
| --- | --- | --- |
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
and
|
|
134
|
+
| `housesAdd`, `housesUpdate`, `housesRemove`, `housesList` | the houses | `root`, and a pilot |
|
|
135
|
+
| `facultiesAdd`, `facultiesUpdate`, `facultiesRestart`, `facultiesRemove`, `facultiesList` | the faculties | `root`, and a pilot |
|
|
136
|
+
| `facultiesCatalog` | every faculty the ground can raise, and what it takes | `root`, and a pilot |
|
|
137
|
+
| `waitSet`, `waitShow` | the ground's bound on every ask | `root`, and a pilot |
|
|
138
|
+
| `secretsSet`, `secretsRemove`, `secretsList` | the secrets | `root` alone |
|
|
139
|
+
| `movesOut`, `movesIn` | a house moved between grounds | `root` alone |
|
|
140
|
+
| `callFaculty` | a faculty's method, called by name | `root` alone |
|
|
141
|
+
| `shellRun` | a command on the ground's machine | `root` alone |
|
|
142
|
+
| `pilotsInvite`, `pilotsDismiss`, `pilotsList` | who pilots the ground | `root` alone |
|
|
143
|
+
| `boot` | the ladder stood and every house opened, as each boot asks | `root` alone |
|
|
144
|
+
|
|
145
|
+
**An ask of the dock is safe to send twice.** A request may carry
|
|
146
|
+
`call`, its call id. The same id sent again answers what the first
|
|
147
|
+
answered, and runs nothing twice.
|
|
148
|
+
|
|
149
|
+
**It lets a pilot possess it from afar.** `pilotsInvite` mints an
|
|
150
|
+
invitation to the dock's steward, and the owner hands it to one being.
|
|
151
|
+
That being then holds a standing on the dock, and asks the houses and
|
|
152
|
+
faculties asks through it. Her class reaches them typed with
|
|
153
|
+
`this.held(id, DockPilot)`, the need `nervur` exports. Secrets, moves,
|
|
154
|
+
the shell and faculties' methods stay the hand's alone. A secret passes
|
|
155
|
+
through `secretsSet`, which answers nothing, so no answer holds one.
|
|
156
|
+
|
|
157
|
+
**It moves a house by its seed and its memory.** `movesOut` takes a
|
|
158
|
+
house out as its seed and every place of its memory, and another ground
|
|
159
|
+
takes them in with `movesIn`. The house keeps its identity, so every
|
|
160
|
+
relation still answers and no far house notices.
|
|
102
161
|
|
|
103
162
|
## The five grounds
|
|
104
163
|
|
|
105
|
-
| Ground | Where it runs | When it wakes | Its unlock | Its memory |
|
|
106
|
-
| --- | --- | --- | --- | --- |
|
|
107
|
-
| NodeGround | a server, a desktop, a Pi | always on | a key file, or the macOS keychain | a ledger in its folder |
|
|
108
|
-
| EdgeGround | a Cloudflare Worker and its Durable Object | per request, per message, per alarm | the Worker's secret | the object's storage |
|
|
109
|
-
| BrowserGround | a page or its service worker | while a tab is open, and by push | a key sealed under one the browser never hands out | IndexedDB |
|
|
110
|
-
| AppGround | an iOS or Android app's web view | per launch, and by push | the Keychain or the Keystore | the app's native store |
|
|
111
|
-
| BenchGround | memory, in a test | as the test moves its clock | a key drawn from the bench's seed | memory in the process |
|
|
164
|
+
| Ground | Where it runs | When it wakes | Its unlock | Its memory | Its hand |
|
|
165
|
+
| --- | --- | --- | --- | --- | --- |
|
|
166
|
+
| NodeGround | a server, a desktop, a Pi | always on | a key file, or the macOS keychain | a ledger in its folder | a socket in its folder |
|
|
167
|
+
| EdgeGround | a Cloudflare Worker and its Durable Object | per request, per message, per alarm | the Worker's secret | the object's storage | a key the Worker holds |
|
|
168
|
+
| BrowserGround | a page or its service worker | while a tab is open, and by push | a key sealed under one the browser never hands out | IndexedDB | a channel between the origin's tabs |
|
|
169
|
+
| AppGround | an iOS or Android app's web view | per launch, and by push | the Keychain or the Keystore | the app's native store | a channel, as in a page |
|
|
170
|
+
| BenchGround | memory, in a test | as the test moves its clock | a key drawn from the bench's seed | memory in the process | the test's own |
|
|
112
171
|
|
|
113
172
|
The same class runs unchanged on every row. What differs is the bodies
|
|
114
173
|
each ground hands its houses.
|
|
@@ -126,17 +185,29 @@ port is set, on the web. [The command](COMMAND.md) lists its settings
|
|
|
126
185
|
and runs its hand.
|
|
127
186
|
|
|
128
187
|
**Its registry holds its terrain's faculties.** `folder` serves each
|
|
129
|
-
house its classes from a folder inside
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
188
|
+
house its classes from a folder inside the code folder its args name.
|
|
189
|
+
Its body carries a registry of two more, rooted there. `module` imports
|
|
190
|
+
a module as a registry, whose `faculties` export holds `{ up, install?
|
|
191
|
+
}` by name. `bridge` starts a program, in any language, and hands it the
|
|
192
|
+
secrets its entry names as its environment. An entry stands either with
|
|
193
|
+
`from: 'folder'`. `tcp` and `web` are its carries, `listener` its one
|
|
194
|
+
listener, and `shell` its shell. Its clock is the library's, and
|
|
195
|
+
primordial.
|
|
196
|
+
|
|
197
|
+
**Its code gives the default entries.** `folder`, `listener`, `tcp`,
|
|
198
|
+
`web` and `shell` stand from entries the library fixes. TCP
|
|
199
|
+
listens on the loopback at 9110, so nothing beyond the machine reaches
|
|
200
|
+
a new ground. A server names its bind through the hand. The web names
|
|
201
|
+
`listener` in its `faculties` and listens on no port. An entry of the
|
|
202
|
+
same name you land stands in their place, so a port changes through the
|
|
203
|
+
hand. Name
|
|
204
|
+
`listener` there too, since the web serves the listener it calls.
|
|
205
|
+
|
|
206
|
+
**A port another process holds keeps that carry down with why.** The
|
|
207
|
+
ground boots beside it until the hand moves the port. A `tcp` entry
|
|
208
|
+
whose args name no port only dials, and its invitations name no TCP
|
|
209
|
+
address. Its environment names only `NERVUR_STATE`, `NERVUR_UNLOCK` and
|
|
210
|
+
`NERVUR_HAND`.
|
|
140
211
|
|
|
141
212
|
**Its key rests in `state/key`, its owner's alone.** A key file others
|
|
142
213
|
may read is refused, as ssh refuses a key. On macOS,
|
|
@@ -144,10 +215,17 @@ may read is refused, as ssh refuses a key. On macOS,
|
|
|
144
215
|
instead. Keep `state/` as you keep an ssh key: whoever holds the key and
|
|
145
216
|
the ledger holds the ground.
|
|
146
217
|
|
|
147
|
-
**It keeps its memory as a ledger
|
|
148
|
-
the line before it and synced before it
|
|
149
|
-
but its end is refused. A witness file
|
|
150
|
-
restored copy that is behind.
|
|
218
|
+
**It keeps its memory as a ledger, which holds the folder's lock.** Every
|
|
219
|
+
write is one line, chained to the line before it and synced before it
|
|
220
|
+
counts. A ledger edited anywhere but its end is refused. A witness file
|
|
221
|
+
kept on another disk refuses a restored copy that is behind. While the
|
|
222
|
+
ledger stands, a second ground on the same state is refused.
|
|
223
|
+
|
|
224
|
+
**Its shell runs a command for the hand alone.** `nervur shell run --
|
|
225
|
+
<command>` runs it in the code folder, and answers its exit code and
|
|
226
|
+
what it printed. Its environment holds `PATH`, `HOME` and a few more of
|
|
227
|
+
the process's, and never a secret or a `NERVUR_` variable. No pilot and
|
|
228
|
+
no being reaches it.
|
|
151
229
|
|
|
152
230
|
**It runs a program as a faculty, in any language.** The bridge starts a
|
|
153
231
|
program and speaks one JSON value a line on its standard streams. The
|
|
@@ -179,21 +257,20 @@ The Worker binds the object's class as `GROUND`, with SQLite storage.
|
|
|
179
257
|
no `from`. A house's entry names its code with `classes: { faculty:
|
|
180
258
|
'bundle', at: 'shop' }`.
|
|
181
259
|
|
|
182
|
-
**Its
|
|
260
|
+
**Its Worker holds two secrets and nothing else.**
|
|
183
261
|
|
|
184
|
-
|
|
|
262
|
+
| Variable | What it names |
|
|
185
263
|
| --- | --- |
|
|
186
264
|
| `NERVUR_SECRET` | the ground's key, sixty-four hex digits; set as a secret |
|
|
187
265
|
| `NERVUR_HAND` | the key its hand answers; set as a secret |
|
|
188
|
-
| `NERVUR_ADDRESSES` | the addresses it is reached at, by commas, the held line first |
|
|
189
|
-
| `NERVUR_ORIGINS` | the page origins it answers |
|
|
190
|
-
| `NERVUR_ALLOW_PRIVATE` | `1` to let it dial a private address |
|
|
191
|
-
| `NERVUR_WAIT` | the longest any ask may run, a minute where unset |
|
|
192
266
|
|
|
193
267
|
A Worker learns no name it is reached by, so it writes only the
|
|
194
|
-
addresses `
|
|
195
|
-
|
|
196
|
-
|
|
268
|
+
addresses its `web` entry names, the held line first. Name them through
|
|
269
|
+
the hand with `facultiesUpdate`, as `addresses` in the args of `web`,
|
|
270
|
+
beside its `origins` and `allowPrivate`, and the dock keeps them.
|
|
271
|
+
Every ask runs a minute at most, and `waitSet` keeps a shorter bound.
|
|
272
|
+
Its faculties take their secrets from the dock, as on every ground. It
|
|
273
|
+
has no shell.
|
|
197
274
|
|
|
198
275
|
**Its hand answers its key alone.** The hand is `POST /nervur/hand`, with
|
|
199
276
|
the key as a bearer token and one request as the body. Every other
|
|
@@ -215,18 +292,19 @@ the object. A watch held on that line answers the moment the change lands.
|
|
|
215
292
|
|
|
216
293
|
**A BrowserGround is one ground for its origin.** Every tab and the
|
|
217
294
|
service worker of an origin share it. The tab holding a Web Lock runs
|
|
218
|
-
the ground, and every other tab
|
|
219
|
-
answers no face. Whoever serves the origin's script
|
|
220
|
-
ground, so its origin serves nothing else.
|
|
295
|
+
the ground, and its hand answers every other tab over a channel. It
|
|
296
|
+
only dials, so it answers no face. Whoever serves the origin's script
|
|
297
|
+
possesses the ground, so its origin serves nothing else.
|
|
221
298
|
[The package's start](README.md) opens one in a page.
|
|
222
299
|
|
|
223
300
|
**Its registry is the one the page hands.** `BrowserGround.open({
|
|
224
301
|
registry })` joins it to the terrain's own, whose faculty `origin`
|
|
225
302
|
serves each house its classes from the origin.
|
|
226
303
|
|
|
227
|
-
**An AppGround is a BrowserGround with the shell's bodies.** The
|
|
228
|
-
hands in two small interfaces, one for secrets and one for
|
|
229
|
-
the library holds the rest. The ground's key rests in the
|
|
304
|
+
**An AppGround is a BrowserGround with the shell's bodies.** The app's
|
|
305
|
+
native shell hands in two small interfaces, one for secrets and one for
|
|
306
|
+
a store, and the library holds the rest. The ground's key rests in the
|
|
307
|
+
secret store.
|
|
230
308
|
|
|
231
309
|
## BenchGround
|
|
232
310
|
|
package/README.md
CHANGED
|
@@ -96,8 +96,8 @@ Run the ground in the folder, and leave it running.
|
|
|
96
96
|
npx nervur up .
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
In a second shell, add the house once. The ground keeps its entry
|
|
100
|
-
sealed
|
|
99
|
+
In a second shell, add the house once. The ground keeps its entry as a
|
|
100
|
+
sealed cell of its dock, and opens it again at every start.
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
103
|
npx nervur houses add name=main classes='{"faculty":"folder","at":"house"}'
|
|
@@ -130,8 +130,7 @@ const ground = await BrowserGround.open();
|
|
|
130
130
|
|
|
131
131
|
// The house's code is a module on this origin, and its rows rest sealed in IndexedDB.
|
|
132
132
|
await ground.hand({
|
|
133
|
-
|
|
134
|
-
method: 'add',
|
|
133
|
+
method: 'housesAdd',
|
|
135
134
|
args: { name: 'main', classes: { faculty: 'origin', at: '/house/index.js' } },
|
|
136
135
|
});
|
|
137
136
|
|
|
@@ -154,10 +153,11 @@ keeps. [Writing for nervur](AUTHORING.md) shows both.
|
|
|
154
153
|
| Entry | For |
|
|
155
154
|
| --- | --- |
|
|
156
155
|
| `nervur/being` | writing a being: `Being`, `s`, `need`, `tableOf`, `Args`, `Result`, `Json`, `Table` |
|
|
157
|
-
| `nervur` | any engine: `Ground`, `House` and the bodies they take |
|
|
156
|
+
| `nervur` | any engine: `Ground`, `House`, `DockPilot`, `vouchesOf` and the bodies they take |
|
|
158
157
|
| `nervur/node` | a ground on Node: `NodeGround`, the `nervur` command, and its bodies |
|
|
159
158
|
| `nervur/browser` | a ground in a page or its service worker: `BrowserGround` and its bodies |
|
|
160
|
-
| `nervur/
|
|
159
|
+
| `nervur/edge` | a ground on the edge, a Worker and its Durable Object: `EdgeGround` and its bodies |
|
|
160
|
+
| `nervur/app` | a ground in a phone's app: `AppGround`, and the three interfaces its shell fills: `NativeShell`, `NativeSecrets` and `NativeStore` |
|
|
161
161
|
| `nervur/serve` | a faculty's program in JavaScript: `serve` |
|
|
162
162
|
| `nervur/bench` | tests: `Bench`, `BenchGround` and `FakeNetwork` |
|
|
163
163
|
|
|
@@ -5,7 +5,6 @@ import { FakeUnlock } from './fake-unlock.ts';
|
|
|
5
5
|
import { SeededCrypto } from './seeded.ts';
|
|
6
6
|
type BeingClass = ConstructorParameters<typeof ClassList>[0]['steward'];
|
|
7
7
|
type Entry = Parameters<Ground['add']>[1];
|
|
8
|
-
type HandAsk = Parameters<Ground['ask']>[0];
|
|
9
8
|
type Standing = Awaited<ReturnType<Ground['add']>>;
|
|
10
9
|
/** A house module: what a folder's index exports. */
|
|
11
10
|
export interface HouseModule {
|
|
@@ -61,8 +60,9 @@ export declare class BenchGround {
|
|
|
61
60
|
add(name: string, from?: string | Entry, rest?: Omit<Entry, 'classes'>): Promise<Standing>;
|
|
62
61
|
remove(name: string): Promise<void>;
|
|
63
62
|
list(): readonly Standing[];
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
/** A being in a house asked as `root`, the house's steward where no id is named. */
|
|
64
|
+
ask(request: Parameters<Ground['ask']>[0]): ReturnType<Ground['ask']>;
|
|
65
|
+
/** The ground's hand, as its owner holds it: `describe`, or an ask of a being in a house, or of the dock's steward where no house is named. */
|
|
66
66
|
hand(request: Parameters<Ground['hand']>[0]): ReturnType<Ground['hand']>;
|
|
67
67
|
/**
|
|
68
68
|
* A request to the ground's one listener, with no socket: each faculty's
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
// and its memory in a machine that outlives it, and opens house modules by
|
|
4
4
|
// name. So a test turns it off, opens it again on the same machine, or
|
|
5
5
|
// moves the machine to another host.
|
|
6
|
+
import { s } from '../being/index.js';
|
|
6
7
|
import { ClassList, Ground } from '../index.js';
|
|
7
8
|
import { FakeMemory } from './fake-memory.js';
|
|
8
9
|
import { clockOf } from './fake-network.js';
|
|
9
10
|
import { FakeUnlock } from './fake-unlock.js';
|
|
10
11
|
import { SeededCrypto } from './seeded.js';
|
|
11
12
|
// Registries' faculties as one, the first first: a name one holds is refused to the next, never replaced.
|
|
13
|
+
const NONE = { args: s.object({}) };
|
|
12
14
|
const joined = (...each) => {
|
|
13
15
|
const all = {};
|
|
14
16
|
for (const faculties of each) {
|
|
@@ -67,13 +69,24 @@ export class BenchGround {
|
|
|
67
69
|
const living = Object.fromEntries(Object.entries(faculties).map(([name, { kinds: _kinds, ...body }]) => [name, { up: () => body }]));
|
|
68
70
|
const own = {
|
|
69
71
|
faculties: {
|
|
70
|
-
'bench-unlock': { up: () => ({ serves: 'unlock', object: machine.unlock }) },
|
|
71
|
-
'bench-memory': { up: () => ({ serves: 'memory', object: machine.memory }) },
|
|
72
|
-
seeded: { up: () => ({ serves: 'crypto', object: machine.crypto }) },
|
|
73
|
-
|
|
74
|
-
'bench-
|
|
75
|
-
|
|
72
|
+
'bench-unlock': { takes: NONE, up: () => ({ serves: 'unlock', object: machine.unlock }) },
|
|
73
|
+
'bench-memory': { takes: NONE, up: () => ({ serves: 'memory', object: machine.memory }) },
|
|
74
|
+
seeded: { takes: NONE, up: () => ({ serves: 'crypto', object: machine.crypto }) },
|
|
75
|
+
// The test holds the hand in its own process, so this hand listens on nothing.
|
|
76
|
+
'bench-hand': { takes: NONE, up: () => ({ serves: 'hand' }) },
|
|
77
|
+
'bench-clock': { takes: NONE, up: () => ({ serves: 'clock', object: clockOf(network) }) },
|
|
78
|
+
// Its listener on the network is the ground's one, which its entry names in `faculties`.
|
|
79
|
+
'bench-carry': {
|
|
80
|
+
takes: NONE,
|
|
81
|
+
up: ({ faculties: called }) => {
|
|
82
|
+
const listener = called.listener;
|
|
83
|
+
const serve = async (request) => (await listener.fetch(request)) ?? new Response(null, { status: 404 });
|
|
84
|
+
return { serves: 'carry', schemes: ['bench'], object: network.join(host, { names, listens: names.length > 0, serve }) };
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
fake: { takes: NONE, up: () => ({ serves: 'memory', house: ({ house }) => machine.memoryOf(house) }) },
|
|
76
88
|
module: {
|
|
89
|
+
takes: NONE,
|
|
77
90
|
up: () => ({
|
|
78
91
|
serves: 'classes',
|
|
79
92
|
house: ({ args }) => {
|
|
@@ -89,8 +102,8 @@ export class BenchGround {
|
|
|
89
102
|
this.#ground = await Ground.open({
|
|
90
103
|
// The bench's own faculties, then the test's beside them, never in their place.
|
|
91
104
|
registry: { faculties: joined(own.faculties ?? {}, living, registry.faculties ?? {}) },
|
|
92
|
-
primordial: { unlock: { make: 'bench-unlock' }, memory: { make: 'bench-memory' }, crypto: { make: 'seeded' }, tools: { make: 'strict' } },
|
|
93
|
-
entries: {
|
|
105
|
+
primordial: { unlock: { make: 'bench-unlock' }, memory: { make: 'bench-memory' }, crypto: { make: 'seeded' }, tools: { make: 'strict' }, clock: { make: 'bench-clock' }, hand: { make: 'bench-hand' } },
|
|
106
|
+
entries: { listener: { make: 'listener' }, carry: { make: 'bench-carry', faculties: ['listener'] }, module: { make: 'module' }, fake: { make: 'fake' } },
|
|
94
107
|
});
|
|
95
108
|
// Each living faculty stands on its first up, as its owner would stand it through the hand.
|
|
96
109
|
for (const [name, { kinds }] of Object.entries(faculties)) {
|
|
@@ -122,10 +135,11 @@ export class BenchGround {
|
|
|
122
135
|
list() {
|
|
123
136
|
return this.#live().list();
|
|
124
137
|
}
|
|
138
|
+
/** A being in a house asked as `root`, the house's steward where no id is named. */
|
|
125
139
|
ask(request) {
|
|
126
140
|
return this.#live().ask(request);
|
|
127
141
|
}
|
|
128
|
-
/** The ground's hand, as its owner holds it: `describe`, a
|
|
142
|
+
/** The ground's hand, as its owner holds it: `describe`, or an ask of a being in a house, or of the dock's steward where no house is named. */
|
|
129
143
|
hand(request) {
|
|
130
144
|
return this.#live().hand(request);
|
|
131
145
|
}
|
|
@@ -39,16 +39,16 @@ export interface BrowserGroundOptions {
|
|
|
39
39
|
/** Whether it dials private and loopback addresses, as a page on localhost does. */
|
|
40
40
|
readonly allowPrivate?: boolean;
|
|
41
41
|
}
|
|
42
|
-
/** One request of the hand, as every terrain serves it. */
|
|
42
|
+
/** One request of the hand, as every terrain serves it: `describe`, or an ask of a being in the house it names, or of the dock's steward where it names none. */
|
|
43
43
|
export interface BrowserHandRequest {
|
|
44
44
|
readonly describe?: true;
|
|
45
|
-
readonly faculty?: string;
|
|
46
45
|
readonly house?: string;
|
|
47
46
|
readonly id?: string;
|
|
48
47
|
readonly method?: string;
|
|
49
48
|
readonly args?: Json;
|
|
50
49
|
readonly after?: Answer;
|
|
51
50
|
readonly cells?: true;
|
|
51
|
+
readonly call?: string;
|
|
52
52
|
}
|
|
53
53
|
export declare class BrowserGround {
|
|
54
54
|
#private;
|
|
@@ -65,8 +65,8 @@ export declare class BrowserGround {
|
|
|
65
65
|
/** Resolves once this page runs the ground, and rejects where its boot failed. */
|
|
66
66
|
led(): Promise<void>;
|
|
67
67
|
/**
|
|
68
|
-
* The hand: `describe`,
|
|
69
|
-
*
|
|
68
|
+
* The hand: `describe`, or an ask of a being in a named house or of the
|
|
69
|
+
* dock's steward, answered by whichever page runs the ground.
|
|
70
70
|
*/
|
|
71
71
|
hand(request: BrowserHandRequest): Promise<Answer | {
|
|
72
72
|
readonly describe: Json;
|
|
@@ -6,6 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
+
import { s } from '../being/schema.js';
|
|
9
10
|
import { WebCarry } from '../bodies/web-carry.js';
|
|
10
11
|
import { Ground, joinedRegistry } from '../ground/ground.js';
|
|
11
12
|
import { IndexedDbMemory } from './indexeddb-memory.js';
|
|
@@ -19,6 +20,7 @@ let joinOn;
|
|
|
19
20
|
/** The origin's ground on stores of the terrain's own: an AppGround's, or a proof's in memory. No entry exports it. */
|
|
20
21
|
export const openOn = (options, stores) => joinOn(options, stores);
|
|
21
22
|
const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
23
|
+
const NONE = { args: s.object({}) };
|
|
22
24
|
const random = () => Array.from(crypto.getRandomValues(new Uint8Array(8)), (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
23
25
|
const defaults = (name) => ({
|
|
24
26
|
locks: navigator.locks,
|
|
@@ -92,8 +94,8 @@ export class BrowserGround {
|
|
|
92
94
|
return this.#leading;
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
95
|
-
* The hand: `describe`,
|
|
96
|
-
*
|
|
97
|
+
* The hand: `describe`, or an ask of a being in a named house or of the
|
|
98
|
+
* dock's steward, answered by whichever page runs the ground.
|
|
97
99
|
*/
|
|
98
100
|
async hand(request) {
|
|
99
101
|
if (this.#closed)
|
|
@@ -127,8 +129,6 @@ export class BrowserGround {
|
|
|
127
129
|
this.#say({ kind: 'up', leader: this.#self });
|
|
128
130
|
else if (said.kind === 'up')
|
|
129
131
|
this.#heard(said.leader);
|
|
130
|
-
else if (said.kind === 'ask' && said.to === this.#self)
|
|
131
|
-
void this.#answer(said.id, said.request);
|
|
132
132
|
else if (said.kind === 'answer') {
|
|
133
133
|
const pending = this.#pending.get(said.id);
|
|
134
134
|
this.#pending.delete(said.id);
|
|
@@ -136,10 +136,6 @@ export class BrowserGround {
|
|
|
136
136
|
}
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
async #answer(id, request) {
|
|
140
|
-
const answer = this.#ground === undefined ? { error: { message: 'the ground moved to another page; ask again' } } : await this.#local(request);
|
|
141
|
-
this.#say({ kind: 'answer', id, answer });
|
|
142
|
-
}
|
|
143
139
|
// A new page runs the ground: asks sent to the one before may never be answered, so they fail, and whoever waited goes on.
|
|
144
140
|
#heard(leader) {
|
|
145
141
|
if (leader !== this.#leader)
|
|
@@ -162,13 +158,20 @@ export class BrowserGround {
|
|
|
162
158
|
#queue() {
|
|
163
159
|
this.#platform.locks
|
|
164
160
|
.request(`${this.#name}-ground`, { signal: this.#abort.signal }, async () => {
|
|
161
|
+
let booted;
|
|
165
162
|
try {
|
|
166
|
-
|
|
163
|
+
booted = await this.#boot();
|
|
167
164
|
}
|
|
168
165
|
catch (error) {
|
|
169
166
|
this.#failed(error);
|
|
170
167
|
return;
|
|
171
168
|
}
|
|
169
|
+
// A page that closed while its boot ran lets the ground go at once, and the lock with it.
|
|
170
|
+
if (this.#closed) {
|
|
171
|
+
await booted.close();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
this.#ground = booted;
|
|
172
175
|
this.#heard(this.#self);
|
|
173
176
|
this.#say({ kind: 'up', leader: this.#self });
|
|
174
177
|
this.#led();
|
|
@@ -189,25 +192,60 @@ export class BrowserGround {
|
|
|
189
192
|
const own = {
|
|
190
193
|
faculties: {
|
|
191
194
|
'store-unlock': {
|
|
195
|
+
takes: NONE,
|
|
192
196
|
up: async () => {
|
|
193
197
|
const unlock = await stores.unlock(`${name}-unlock`);
|
|
194
198
|
return { serves: 'unlock', object: unlock, down: closing(unlock) };
|
|
195
199
|
},
|
|
196
200
|
},
|
|
197
201
|
'store-memory': {
|
|
202
|
+
takes: NONE,
|
|
198
203
|
up: async () => {
|
|
199
204
|
const memory = await stores.memory(`${name}-ground`);
|
|
200
205
|
return { serves: 'memory', object: memory, down: closing(memory) };
|
|
201
206
|
},
|
|
202
207
|
},
|
|
208
|
+
// The hand, for every other page of the origin: the asks sent to this page on the channel, each answered there.
|
|
209
|
+
'channel-hand': {
|
|
210
|
+
takes: { args: s.object({ channel: s.string(), self: s.string(), persisted: s.boolean() }) },
|
|
211
|
+
up: ({ args, faculties }) => {
|
|
212
|
+
const ground = faculties.ground;
|
|
213
|
+
const channel = platform.channel(args.channel);
|
|
214
|
+
let open = true;
|
|
215
|
+
channel.onmessage = (event) => {
|
|
216
|
+
const said = event.data;
|
|
217
|
+
if (said.kind !== 'ask' || said.to !== args.self)
|
|
218
|
+
return;
|
|
219
|
+
// An ask this hand's close ended is answered by no one here: the page that runs the ground next is asked again.
|
|
220
|
+
void ground.hand(said.request).then((answer) => {
|
|
221
|
+
if (!open)
|
|
222
|
+
return;
|
|
223
|
+
channel.postMessage({
|
|
224
|
+
kind: 'answer',
|
|
225
|
+
id: said.id,
|
|
226
|
+
answer: said.request.describe === true && 'result' in answer ? { result: { ...answer.result, persisted: args.persisted === true } } : answer,
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
serves: 'hand',
|
|
232
|
+
down: () => {
|
|
233
|
+
open = false;
|
|
234
|
+
channel.close();
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
},
|
|
203
239
|
// It only dials, so it writes no address into an invitation.
|
|
204
240
|
web: {
|
|
241
|
+
takes: { args: s.object({ allowPrivate: s.optional(s.boolean()) }) },
|
|
205
242
|
up: ({ args }) => {
|
|
206
243
|
const web = new WebCarry({ allowPrivate: args.allowPrivate === true });
|
|
207
244
|
return { serves: 'carry', schemes: ['https', 'http', 'wss', 'ws'], object: web };
|
|
208
245
|
},
|
|
209
246
|
},
|
|
210
247
|
origin: {
|
|
248
|
+
takes: NONE,
|
|
211
249
|
up: () => ({
|
|
212
250
|
serves: 'classes',
|
|
213
251
|
house: ({ args }) => {
|
|
@@ -222,9 +260,15 @@ export class BrowserGround {
|
|
|
222
260
|
const wait = this.#options.wait;
|
|
223
261
|
return Ground.open({
|
|
224
262
|
registry: joinedRegistry(own, this.#options.registry ?? {}),
|
|
225
|
-
primordial: {
|
|
226
|
-
|
|
263
|
+
primordial: {
|
|
264
|
+
unlock: { make: 'store-unlock' },
|
|
265
|
+
memory: { make: 'store-memory' },
|
|
266
|
+
crypto: { make: 'noble' },
|
|
267
|
+
tools: { make: 'strict' },
|
|
227
268
|
clock: { make: 'clock' },
|
|
269
|
+
hand: { make: 'channel-hand', args: { channel: `${name}-hand`, self: this.#self, persisted: this.#persisted }, faculties: ['ground'] },
|
|
270
|
+
},
|
|
271
|
+
entries: {
|
|
228
272
|
origin: { make: 'origin' },
|
|
229
273
|
web: { make: 'web', args: this.#options.allowPrivate === true ? { allowPrivate: true } : {} },
|
|
230
274
|
},
|