@nervur-org/dock 0.5.0 → 0.6.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 +7 -4
- package/api/route.ts +4 -72
- package/beings/avatar.ts +11 -7
- package/beings/carry.ts +129 -116
- package/beings/clock.ts +11 -7
- package/beings/desk.ts +7 -6
- package/beings/doorbell.ts +36 -7
- package/beings/index.ts +17 -4
- package/beings/join.ts +7 -4
- package/beings/link.ts +1 -1
- package/beings/porter.ts +5 -1
- package/beings/presence.ts +15 -5
- package/beings/setup.ts +27 -21
- package/beings/user.ts +26 -17
- package/beings/way.ts +1 -1
- package/{dock → box}/agents.ts +37 -44
- package/{dock → box}/apns.ts +4 -4
- package/box/box.ts +31 -0
- package/box/contract.ts +41 -0
- package/box/dialers.ts +88 -0
- package/box/dock.ts +148 -0
- package/box/envoy.ts +96 -0
- package/box/faculty.ts +111 -0
- package/{dock → box}/fcm.ts +1 -1
- package/box/grants.ts +90 -0
- package/box/http.ts +93 -0
- package/{dock → box}/http2.ts +7 -7
- package/box/index.ts +201 -0
- package/box/join.ts +78 -0
- package/box/local.ts +37 -0
- package/{dock → box}/location.ts +15 -3
- package/{dock → box}/notifications.ts +17 -5
- package/box/push.ts +85 -0
- package/box/ring.ts +85 -0
- package/{dock → box}/service.ts +2 -2
- package/box/timer.ts +150 -0
- package/box/tree.ts +22 -0
- package/{dock → box}/webpush.ts +2 -2
- package/cli/client.ts +1 -1
- package/cli/daemon.ts +101 -103
- package/cli/estate.ts +2 -3
- package/cli/nervur.ts +44 -25
- package/cli/stand.ts +1 -1
- package/dist/api/route.d.ts +1 -2
- package/dist/api/route.js +4 -76
- package/dist/beings/avatar.d.ts +4 -3
- package/dist/beings/avatar.js +9 -6
- package/dist/beings/carry.d.ts +10 -12
- package/dist/beings/carry.js +119 -127
- package/dist/beings/clock.js +10 -7
- package/dist/beings/desk.js +8 -7
- package/dist/beings/doorbell.d.ts +3 -1
- package/dist/beings/doorbell.js +37 -4
- package/dist/beings/index.d.ts +24 -4
- package/dist/beings/index.js +15 -4
- package/dist/beings/join.d.ts +1 -0
- package/dist/beings/join.js +6 -4
- package/dist/beings/link.d.ts +0 -1
- package/dist/beings/link.js +1 -1
- package/dist/beings/porter.js +5 -1
- package/dist/beings/presence.d.ts +1 -0
- package/dist/beings/presence.js +12 -3
- package/dist/beings/setup.d.ts +5 -2
- package/dist/beings/setup.js +16 -15
- package/dist/beings/user.d.ts +4 -3
- package/dist/beings/user.js +18 -11
- package/dist/{dock → box}/agents.d.ts +7 -4
- package/dist/{dock → box}/agents.js +30 -37
- package/dist/{dock → box}/apns.d.ts +1 -1
- package/dist/{dock → box}/apns.js +3 -3
- package/dist/box/box.d.ts +9 -0
- package/dist/box/box.js +18 -0
- package/dist/box/contract.d.ts +6 -0
- package/dist/box/contract.js +35 -0
- package/dist/{dock → box}/dialers.d.ts +7 -4
- package/dist/box/dialers.js +76 -0
- package/dist/box/dock.d.ts +182 -0
- package/dist/box/dock.js +128 -0
- package/dist/box/envoy.d.ts +44 -0
- package/dist/box/envoy.js +75 -0
- package/dist/box/faculty.d.ts +68 -0
- package/dist/box/faculty.js +97 -0
- package/dist/{dock → box}/fcm.d.ts +1 -1
- package/dist/{dock → box}/grants.d.ts +4 -3
- package/dist/box/grants.js +78 -0
- package/dist/box/http.d.ts +26 -0
- package/dist/box/http.js +89 -0
- package/dist/{dock → box}/http2.d.ts +1 -1
- package/dist/{dock → box}/http2.js +6 -6
- package/dist/box/index.d.ts +46 -0
- package/dist/box/index.js +179 -0
- package/dist/box/join.d.ts +19 -0
- package/dist/box/join.js +67 -0
- package/dist/{dock → box}/local.d.ts +3 -2
- package/dist/box/local.js +27 -0
- package/dist/{dock → box}/location.d.ts +3 -0
- package/dist/box/location.js +21 -0
- package/dist/{dock → box}/notifications.d.ts +3 -0
- package/dist/{dock → box}/notifications.js +14 -4
- package/dist/{dock → box}/push.d.ts +12 -18
- package/dist/box/push.js +83 -0
- package/dist/box/ring.d.ts +18 -0
- package/dist/box/ring.js +55 -0
- package/dist/{dock → box}/service.d.ts +1 -1
- package/dist/{dock → box}/service.js +1 -1
- package/dist/{dock → box}/timer.d.ts +2 -0
- package/dist/box/timer.js +127 -0
- package/dist/box/tree.d.ts +5 -0
- package/dist/box/tree.js +14 -0
- package/dist/{dock → box}/webpush.d.ts +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/daemon.d.ts +2 -8
- package/dist/cli/daemon.js +102 -107
- package/dist/cli/estate.js +2 -3
- package/dist/cli/nervur.js +44 -24
- package/dist/cli/stand.d.ts +0 -14
- package/dist/cli/stand.js +1 -1
- package/dist/harbor/browser.d.ts +8 -5
- package/dist/harbor/browser.js +20 -21
- package/dist/harbor/capacitor.d.ts +3 -3
- package/dist/harbor/capacitor.js +9 -8
- package/dist/harbor/disk.d.ts +9 -9
- package/dist/harbor/disk.js +56 -51
- package/dist/harbor/edge/alarm.d.ts +4 -2
- package/dist/harbor/edge/alarm.js +9 -19
- package/dist/harbor/edge/edge.d.ts +7 -6
- package/dist/harbor/edge/edge.js +58 -53
- package/dist/harbor/edge/exercise.js +4 -4
- package/dist/harbor/edge/given.d.ts +0 -1
- package/dist/harbor/edge/given.js +0 -29
- package/dist/harbor/edge/storage.d.ts +0 -2
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/http.js +4 -2
- package/dist/harbor/sealed.d.ts +0 -5
- package/dist/harbor/sealed.js +0 -39
- package/dist/harbor/tauri.d.ts +2 -2
- package/dist/harbor/tauri.js +13 -10
- package/dist/human/app.d.ts +1 -1
- package/dist/human/app.js +10 -12
- package/dist/human/door.d.ts +0 -1
- package/dist/human/door.js +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/move.js +1 -1
- package/dist/human/tab.bundle.txt +3629 -2824
- package/dist/human/wake.d.ts +5 -2
- package/dist/human/wake.js +23 -14
- package/dist/human/web.d.ts +0 -1
- package/dist/human/web.js +3 -3
- package/dist/human/worker.bundle.txt +2945 -2118
- package/dist/human/worlds.js +3 -3
- package/dist/mcp/agent.d.ts +0 -1
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/allow.js +3 -2
- package/dist/mcp/http.d.ts +0 -1
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/oauth.d.ts +3 -4
- package/dist/mcp/oauth.js +4 -4
- package/dist/mcp/route.d.ts +1 -1
- package/dist/mcp/runner.d.ts +0 -1
- package/dist/mcp/runner.js +1 -1
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +1 -1
- package/harbor/browser.ts +22 -24
- package/harbor/capacitor.ts +9 -8
- package/harbor/disk.ts +53 -48
- package/harbor/edge/alarm.ts +15 -12
- package/harbor/edge/edge.ts +61 -55
- package/harbor/edge/exercise.ts +4 -4
- package/harbor/edge/given.ts +3 -33
- package/harbor/edge/storage.ts +2 -2
- package/harbor/http.ts +5 -3
- package/harbor/sealed.ts +0 -35
- package/harbor/tauri.ts +13 -10
- package/human/app.ts +12 -13
- package/human/door.ts +1 -1
- package/human/html.ts +1 -1
- package/human/local.ts +1 -1
- package/human/move.ts +1 -1
- package/human/tab.bundle.txt +3629 -2824
- package/human/wake.ts +36 -16
- package/human/web.ts +4 -4
- package/human/worker.bundle.txt +2945 -2118
- package/human/worlds.ts +3 -3
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +2 -1
- package/mcp/http.ts +1 -1
- package/mcp/oauth.ts +4 -4
- package/mcp/route.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +1 -1
- package/package.json +9 -8
- package/beings/envoy.ts +0 -218
- package/dist/beings/envoy.d.ts +0 -54
- package/dist/beings/envoy.js +0 -211
- package/dist/dock/console.d.ts +0 -6
- package/dist/dock/console.js +0 -20
- package/dist/dock/dialers.js +0 -75
- package/dist/dock/faculty.d.ts +0 -24
- package/dist/dock/faculty.js +0 -142
- package/dist/dock/grants.js +0 -99
- package/dist/dock/index.d.ts +0 -41
- package/dist/dock/index.js +0 -113
- package/dist/dock/local.js +0 -44
- package/dist/dock/location.js +0 -11
- package/dist/dock/push.js +0 -132
- package/dist/dock/timer.js +0 -149
- package/dock/console.ts +0 -23
- package/dock/dialers.ts +0 -85
- package/dock/faculty.ts +0 -148
- package/dock/grants.ts +0 -111
- package/dock/index.ts +0 -133
- package/dock/local.ts +0 -48
- package/dock/push.ts +0 -161
- package/dock/timer.ts +0 -174
- /package/dist/{dock → box}/fcm.js +0 -0
- /package/dist/{dock → box}/webpush.js +0 -0
package/dist/harbor/edge/edge.js
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { isSilence } from 'nervur';
|
|
2
|
-
import {
|
|
2
|
+
import { BEINGS } from '../../beings/index.js';
|
|
3
3
|
import { setup } from '../../beings/setup.js';
|
|
4
4
|
import { Harbor, Socket as Held } from 'nervur/harbor';
|
|
5
5
|
import { quoRoute } from '../quo.js';
|
|
6
6
|
import { webRoute } from '../../human/web.js';
|
|
7
|
-
import { DOCK, TIMER, Console, facultyOf, settled, stand } from '../../dock/index.js';
|
|
8
7
|
import { Alarm, alarmsIn } from './alarm.js';
|
|
9
|
-
import {
|
|
8
|
+
import { BOX_SEED, GRANTS, Grants, Http, TIMER, WIRE, harborageOver, keyed, lent, settled, standHarbor } from '../../box/index.js';
|
|
10
9
|
import { find, letIn } from '../../beings/join.js';
|
|
11
10
|
import { apiRoute } from '../../api/route.js';
|
|
12
11
|
import { mcpRoute } from '../../mcp/route.js';
|
|
13
12
|
import { McpHttp } from '../../mcp/http.js';
|
|
14
13
|
import { Exchange } from '../../mcp/allow.js';
|
|
15
14
|
import { DurableStorage, danceInStorage, folderInStorage, settingsInStorage, wearInStorage } from './storage.js';
|
|
16
|
-
import {
|
|
15
|
+
import {} from './given.js';
|
|
17
16
|
import { sealKey } from '../seal.js';
|
|
18
|
-
// The beings a deployed worker carries. The Clock is among them because this
|
|
19
|
-
// terrain lends a timer: a box that lends one and holds no class that can ask
|
|
20
|
-
// for a wake lends a name nobody there could use.
|
|
21
|
-
export const BUILT_IN = { User, Desk, Avatar, Clock };
|
|
22
17
|
const json = (status, v, headers = {}) => new Response(JSON.stringify(v), { status, headers: { 'content-type': 'application/json', ...headers } });
|
|
23
18
|
export class EdgeHarbor extends Harbor {
|
|
24
19
|
sockets = new Set();
|
|
@@ -32,9 +27,6 @@ export class EdgeHarbor extends Harbor {
|
|
|
32
27
|
// The model side's dance, one key in the same object: clients, pending
|
|
33
28
|
// requests and codes, all of them living minutes.
|
|
34
29
|
dance;
|
|
35
|
-
// The object's storage, unprefixed and unsealed: the alarm is the object's
|
|
36
|
-
// own and is no row.
|
|
37
|
-
#storage;
|
|
38
30
|
#booted;
|
|
39
31
|
// The MCP endpoint, one per instance: a session is a client's connection
|
|
40
32
|
// and belongs to the harbor, not to the request that opened it.
|
|
@@ -47,18 +39,32 @@ export class EdgeHarbor extends Harbor {
|
|
|
47
39
|
if (!env.NERVUR_SEED_KEY)
|
|
48
40
|
throw new Error('no NERVUR_SEED_KEY: a seed is never kept plain on the edge');
|
|
49
41
|
const harbor = new EdgeHarbor(new DurableStorage(state.storage, await sealKey(env.NERVUR_SEED_KEY)), env.NERVUR_ROOT, deployed);
|
|
50
|
-
|
|
42
|
+
// What a worker brings to its own ward, and it is the whole of it. A
|
|
43
|
+
// bearer to an identity in a world is a record in the grants' cells and
|
|
44
|
+
// nothing else, so the grants are the same code here as on a disk. The
|
|
45
|
+
// timer is the same class too, with the one method that knows a terrain
|
|
46
|
+
// replaced: `Alarm` arms the object's own storage alarm.
|
|
47
|
+
//
|
|
48
|
+
// Nothing else is brought, each for its own reason. A worker is at no
|
|
49
|
+
// place and before no screen, so no location and no notifications. Push
|
|
50
|
+
// rings under this world's own web origin, which a worker learns from
|
|
51
|
+
// the request and not at boot. And no process stands behind a worker, so
|
|
52
|
+
// no way in over a socket, no dialers and no agents.
|
|
53
|
+
// Only the object may set its own alarm, so the two calls are handed to
|
|
54
|
+
// the timer in the class body this object brings.
|
|
55
|
+
const alarms = alarmsIn(state.storage);
|
|
56
|
+
class ObjectAlarm extends Alarm {
|
|
57
|
+
constructor(stance, box) {
|
|
58
|
+
super(stance, box, alarms);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
harbor.faculties = { box: { Grants, Http }, harbor: { edge: { Alarm: ObjectAlarm } } };
|
|
51
62
|
return harbor;
|
|
52
63
|
}
|
|
53
64
|
constructor(store, root, deployed = {}) {
|
|
54
|
-
|
|
55
|
-
// ward's own front desk and the grants its routes honour. They are named
|
|
56
|
-
// here and not left to the deploy, because `lent` names classes and a
|
|
57
|
-
// name the loader cannot find is a ward that would not boot.
|
|
58
|
-
super(store, async () => ({ ...BUILT_IN, Console, Grants, Alarm, ...deployed.classes }));
|
|
65
|
+
super(store, async () => keyed({ ...BEINGS, classes: deployed.classes ?? {} }));
|
|
59
66
|
this.root = root;
|
|
60
67
|
this.deployed = deployed;
|
|
61
|
-
this.#storage = store.storage;
|
|
62
68
|
// The rows live beside the wards in the same object, under the same
|
|
63
69
|
// prefix, so an exercise standing two harbors in one object gives each
|
|
64
70
|
// its own settings and its own design.
|
|
@@ -72,11 +78,14 @@ export class EdgeHarbor extends Harbor {
|
|
|
72
78
|
async given() {
|
|
73
79
|
return { ...this.deployed, settings: await this.settings.load(), folder: this.folder };
|
|
74
80
|
}
|
|
75
|
-
//
|
|
76
|
-
// the boot; a harbor that
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
|
|
81
|
+
// The classes this terrain brings to its own ward, as a tree. Set before
|
|
82
|
+
// the first request and read at the boot; a harbor that brings none stands
|
|
83
|
+
// no ward of its own and lends nothing. The deploy's own classes join them,
|
|
84
|
+
// so an estate's faculty stands beside the terrain's.
|
|
85
|
+
faculties;
|
|
86
|
+
// What this harbor is to its faculties: the box ward's save, which on this
|
|
87
|
+
// terrain is the one place the box keeps its state, and up once booted.
|
|
88
|
+
box = harborageOver(() => this.wards.get(BOX_SEED)?.save() ?? Promise.resolve());
|
|
80
89
|
// Once per instance, on the first request: the platform may have woken a
|
|
81
90
|
// fresh instance, and every ward boots from storage before anything is
|
|
82
91
|
// answered.
|
|
@@ -89,22 +98,13 @@ export class EdgeHarbor extends Harbor {
|
|
|
89
98
|
// keeps, then the beings who took what the box lends: a box is up when
|
|
90
99
|
// they have landed.
|
|
91
100
|
async boot() {
|
|
92
|
-
if (this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
// partition like every faculty's: there is no second place on this
|
|
96
|
-
// terrain for the box to keep its state.
|
|
97
|
-
const grants = facultyOf(this, GRANTS);
|
|
98
|
-
if (grants)
|
|
99
|
-
grants.persist = () => this.wards.get(DOCK)?.save() ?? Promise.resolve();
|
|
100
|
-
// The clock here is the object's alarm, and only the object may set its
|
|
101
|
-
// own, so the timer is handed the two calls the same way.
|
|
102
|
-
const timer = facultyOf(this, TIMER);
|
|
103
|
-
if (timer)
|
|
104
|
-
timer.alarms = alarmsIn(this.#storage);
|
|
101
|
+
if (this.faculties) {
|
|
102
|
+
const tree = { ...this.faculties, classes: this.deployed.classes ?? {} };
|
|
103
|
+
await standHarbor(this, tree, this.box);
|
|
105
104
|
}
|
|
106
105
|
await super.boot();
|
|
107
|
-
await settled();
|
|
106
|
+
await settled(this);
|
|
107
|
+
await this.box.raise();
|
|
108
108
|
}
|
|
109
109
|
// The alarm went off: no request, no route and nobody asking, which is the
|
|
110
110
|
// whole point of it. The box ward is up by now, so the round runs and the
|
|
@@ -115,7 +115,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
115
115
|
// saw it. A box whose alarm fired and whose partition was not written would
|
|
116
116
|
// fire the same minute again on its next wake.
|
|
117
117
|
async rang() {
|
|
118
|
-
const timer =
|
|
118
|
+
const timer = await lent(this, TIMER);
|
|
119
119
|
if (!timer)
|
|
120
120
|
return;
|
|
121
121
|
try {
|
|
@@ -130,8 +130,8 @@ export class EdgeHarbor extends Harbor {
|
|
|
130
130
|
// The box's grants, for the routes that honour a bearer. Undefined is a
|
|
131
131
|
// harbor that stands no box ward, which is the exercise and nothing a
|
|
132
132
|
// deploy does.
|
|
133
|
-
|
|
134
|
-
return
|
|
133
|
+
grants() {
|
|
134
|
+
return lent(this, GRANTS);
|
|
135
135
|
}
|
|
136
136
|
// The web side, built per request because its whole configuration is rows
|
|
137
137
|
// a root writes while the worker runs. It is the same `webRoute` the daemon
|
|
@@ -171,7 +171,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
171
171
|
// as a folder with several wards and no mark does. The box's own ward is
|
|
172
172
|
// furniture and never a candidate.
|
|
173
173
|
here() {
|
|
174
|
-
const worlds = [...this.wards.keys()].filter((n) => n !==
|
|
174
|
+
const worlds = [...this.wards.keys()].filter((n) => n !== BOX_SEED);
|
|
175
175
|
return worlds.length === 1 ? worlds[0] : '';
|
|
176
176
|
}
|
|
177
177
|
// What a route asks of a ward here: her avatar as she stands, and her way
|
|
@@ -190,17 +190,16 @@ export class EdgeHarbor extends Harbor {
|
|
|
190
190
|
};
|
|
191
191
|
// The worlds a client may be let into: every ward here but the box's own.
|
|
192
192
|
#worlds = () => [...this.wards]
|
|
193
|
-
.filter(([ward]) => ward !==
|
|
193
|
+
.filter(([ward]) => ward !== BOX_SEED)
|
|
194
194
|
.map(([ward, h]) => ({ ward, pk: h.pk, user: h.record.user }));
|
|
195
195
|
// The program side. The same `apiRoute` the daemon mounts, on the grants of
|
|
196
196
|
// this box's own ward: a request with no bearer is the stranger every route
|
|
197
197
|
// knows, and one with a bearer this box granted is an occupant.
|
|
198
|
-
async api(request, path
|
|
199
|
-
const grants = this.grants;
|
|
198
|
+
async api(request, path) {
|
|
199
|
+
const grants = await this.grants();
|
|
200
200
|
if (!grants)
|
|
201
201
|
return json(503, { error: 'this box stands no ward of its own, so it grants nothing' });
|
|
202
|
-
const
|
|
203
|
-
const route = apiRoute({ wards: this.wards, here: () => this.here() }, { grants, resolve: this.#resolve, join: this.#join, web: at.web });
|
|
202
|
+
const route = apiRoute({ wards: this.wards, here: () => this.here() }, { grants, resolve: this.#resolve, join: this.#join });
|
|
204
203
|
return (await route(request, path.slice('/api'.length))) ?? json(404, { error: 'no such route' });
|
|
205
204
|
}
|
|
206
205
|
// The model side: the OAuth dance, kept in this object's own storage, and
|
|
@@ -210,7 +209,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
210
209
|
// while one is held and a client that comes back to a woken instance
|
|
211
210
|
// reconnects, which is what a reconnect is for.
|
|
212
211
|
async mcp(request, path, base) {
|
|
213
|
-
const grants = this.grants;
|
|
212
|
+
const grants = await this.grants();
|
|
214
213
|
if (!grants)
|
|
215
214
|
return json(503, { error: 'this box stands no ward of its own, so it grants nothing' });
|
|
216
215
|
const endpoint = (this.#mcp ??= new McpHttp(this.#resolve, async () => {
|
|
@@ -225,7 +224,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
225
224
|
// The dance's discovery under this prefix, for a caller that reaches the
|
|
226
225
|
// harbor at a path rather than at an origin of its own.
|
|
227
226
|
async wellKnown(request, path, base) {
|
|
228
|
-
const grants = this.grants;
|
|
227
|
+
const grants = await this.grants();
|
|
229
228
|
if (!grants)
|
|
230
229
|
return json(404, { error: 'no such route' });
|
|
231
230
|
const route = await mcpRoute(this.dance, await this.routes(base), null, grants);
|
|
@@ -234,7 +233,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
234
233
|
// The exchange, for the allow page on the web side: the dance it finishes
|
|
235
234
|
// is the same one the model side keeps.
|
|
236
235
|
async exchange(base) {
|
|
237
|
-
const grants = this.grants;
|
|
236
|
+
const grants = await this.grants();
|
|
238
237
|
if (!grants)
|
|
239
238
|
return undefined;
|
|
240
239
|
const route = await mcpRoute(this.dance, await this.routes(base), null, grants);
|
|
@@ -252,7 +251,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
252
251
|
async fetch(request, path, base) {
|
|
253
252
|
await this.ready();
|
|
254
253
|
const out = await this.#route(request, path, base);
|
|
255
|
-
await
|
|
254
|
+
await (await lent(this, TIMER))?.setting;
|
|
256
255
|
return out;
|
|
257
256
|
}
|
|
258
257
|
async #route(request, path, base) {
|
|
@@ -263,7 +262,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
263
262
|
if (path === '/web' || path.startsWith('/web/'))
|
|
264
263
|
return this.web(request, path, base);
|
|
265
264
|
if (path === '/api' || path.startsWith('/api/'))
|
|
266
|
-
return this.api(request, path
|
|
265
|
+
return this.api(request, path);
|
|
267
266
|
if (path === '/mcp' || path.startsWith('/mcp/'))
|
|
268
267
|
return this.mcp(request, path, base);
|
|
269
268
|
if (path.startsWith('/.well-known/'))
|
|
@@ -275,7 +274,13 @@ export class EdgeHarbor extends Harbor {
|
|
|
275
274
|
return this.upgrade();
|
|
276
275
|
return (await quoRoute(this)(request, path.slice('/quo'.length))) ?? json(404, { error: 'no such route' });
|
|
277
276
|
}
|
|
278
|
-
|
|
277
|
+
// Every other path is the box's wire: a faculty that claims it answers,
|
|
278
|
+
// as the object's path and never the platform's prefix.
|
|
279
|
+
const wire = await lent(this, WIRE);
|
|
280
|
+
if (!wire)
|
|
281
|
+
return json(404, { error: 'no such route' });
|
|
282
|
+
const url = new URL(request.url);
|
|
283
|
+
return wire.handle(new Request(new URL(`${path}${url.search}`, url.origin), request));
|
|
279
284
|
}
|
|
280
285
|
health() {
|
|
281
286
|
return {
|
|
@@ -328,7 +333,7 @@ export class EdgeHarbor extends Harbor {
|
|
|
328
333
|
if (this.wards.has(name))
|
|
329
334
|
return json(409, { error: `ward ${name} already exists here` });
|
|
330
335
|
const hosted = await this.create(name, user);
|
|
331
|
-
await setup(hosted, user);
|
|
336
|
+
await setup(hosted, user, typeof body.init.class === 'string' ? { home: body.init.class } : {});
|
|
332
337
|
return json(200, { name, pk: hosted.pk, user });
|
|
333
338
|
}
|
|
334
339
|
// The settings, merged over what is kept and answered whole, so a root
|
|
@@ -180,14 +180,14 @@ export async function exercise(request, base, path, state, env) {
|
|
|
180
180
|
// the same refusal. That the number a deployment runs under is the
|
|
181
181
|
// platform's own is the line below it, which asks the store directly.
|
|
182
182
|
test('[edge] a row past the object s ceiling is refused, the arrival that wrote it fails at the door, and she stands at what is kept', {}, async () => {
|
|
183
|
-
const
|
|
183
|
+
const sealed = await sealKey(env.NERVUR_SEED_KEY ?? '');
|
|
184
184
|
const small = 8_000;
|
|
185
|
-
const h = new EdgeHarbor(new DurableStorage(state.storage,
|
|
185
|
+
const h = new EdgeHarbor(new DurableStorage(state.storage, sealed, `c:${Date.now()}:`, small), undefined, DEPLOYED);
|
|
186
186
|
await h.ready();
|
|
187
187
|
const w = await h.create(`ceiling-${Date.now()}`);
|
|
188
188
|
for (const [k, c] of [
|
|
189
|
-
['P', 'Printer'],
|
|
190
|
-
['A', 'Customer'],
|
|
189
|
+
['P', 'classes/Printer'],
|
|
190
|
+
['A', 'classes/Customer'],
|
|
191
191
|
])
|
|
192
192
|
assert.deepEqual(await w.ask('boot', { key: k, class: c }), { booted: k });
|
|
193
193
|
const printer = w.being('P');
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { BeingClass } from 'nervur';
|
|
2
2
|
import type { Apps, Folder } from '../../human/web.ts';
|
|
3
3
|
import type { Routes } from '../../mcp/route.ts';
|
|
4
|
-
export declare const LENT: Record<string, string>;
|
|
5
4
|
export declare const ROW_CEILING = 2000000;
|
|
6
5
|
export type Code = {
|
|
7
6
|
tab: string;
|
|
@@ -1,32 +1,3 @@
|
|
|
1
|
-
import { GRANTS } from '../../dock/grants.js';
|
|
2
|
-
import { TIMER } from '../../dock/index.js';
|
|
3
|
-
// What a worker lends the beings of the wards it holds, by name to the class
|
|
4
|
-
// that stands for it. The box's state has one home on every terrain, the box
|
|
5
|
-
// ward's partition, so the edge stands that ward like every other terrain;
|
|
6
|
-
// what differs is this list, and a faculty the platform has no answer for is
|
|
7
|
-
// not lent at all rather than lent and hollow.
|
|
8
|
-
//
|
|
9
|
-
// `grants` and `timer` are lent, and they are the whole of it. A bearer to an
|
|
10
|
-
// identity in a world is a record in her cells and nothing else, so the
|
|
11
|
-
// grants are the same code here as on a disk and the routes that honour a
|
|
12
|
-
// bearer, `api.` and `mcp.`, work the moment she stands. The timer is the
|
|
13
|
-
// same class too, with the one method that knows a terrain replaced: she arms
|
|
14
|
-
// the object's own storage alarm, which is the clock here, so a home on this
|
|
15
|
-
// terrain wakes for its schedules with nobody asking. `Alarm` in
|
|
16
|
-
// `edge/alarm.ts` is that subclass.
|
|
17
|
-
//
|
|
18
|
-
// The three others every box ships are not lent here, each for its own
|
|
19
|
-
// reason. `location` and `notifications` are a place and a screen, and a
|
|
20
|
-
// worker is at neither: it runs wherever the platform put it and no human is
|
|
21
|
-
// looking at it. `push` rings under this world's own web origin, and a worker
|
|
22
|
-
// learns its origin from the request it is answering and not at boot, so a
|
|
23
|
-
// ring signed at the moment the faculty stood would be signed for nowhere.
|
|
24
|
-
//
|
|
25
|
-
// There is no process on the other side of a worker either, so `local`,
|
|
26
|
-
// `dialers` and `agents`, which the daemon adds, name nothing here: no
|
|
27
|
-
// socket a process was seen on, no line this box holds open, and no command
|
|
28
|
-
// to run an agent in.
|
|
29
|
-
export const LENT = { [GRANTS]: 'Grants', [TIMER]: 'Alarm' };
|
|
30
1
|
// The ceiling on one row in this object's storage, key and value together,
|
|
31
2
|
// in bytes. The platform publishes it for a SQLite-backed Durable Object,
|
|
32
3
|
// which is what `wrangler.toml` declares this class to be, and past it the
|
|
@@ -4,8 +4,6 @@ import type { Folder } from '../../human/web.ts';
|
|
|
4
4
|
import { type SettingsStore } from './given.ts';
|
|
5
5
|
import type { Rows, Storage } from './platform.d.ts';
|
|
6
6
|
export declare function settingsInStorage(storage: Rows, prefix?: string): SettingsStore;
|
|
7
|
-
export declare const DESIGN_ROW = "design:";
|
|
8
|
-
export declare const BEINGS_ROW = "beings";
|
|
9
7
|
export declare function folderInStorage(storage: Rows, prefix?: string): Folder;
|
|
10
8
|
export declare function wearInStorage(storage: Rows, prefix?: string): {
|
|
11
9
|
put: (file: string, text: string | null) => Promise<void>;
|
|
@@ -35,8 +35,8 @@ export function settingsInStorage(storage, prefix = '') {
|
|
|
35
35
|
// storage instead. A row is text, because a stylesheet is text and so is a
|
|
36
36
|
// bundle; a file the root has not put there answers nothing and the route
|
|
37
37
|
// declines the path.
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const DESIGN_ROW = 'design:';
|
|
39
|
+
const BEINGS_ROW = 'beings';
|
|
40
40
|
export function folderInStorage(storage, prefix = '') {
|
|
41
41
|
const design = (file) => `${prefix}${DESIGN_ROW}${file}`;
|
|
42
42
|
const held = (file) => storage.get(design(file)).then((v) => v ?? null);
|
package/dist/harbor/http.js
CHANGED
|
@@ -87,7 +87,9 @@ export async function listenHttp(harbor, host, port, quo) {
|
|
|
87
87
|
faults: Object.fromEntries([...harbor.faults]),
|
|
88
88
|
}, { headers: { 'access-control-allow-origin': '*' } });
|
|
89
89
|
}
|
|
90
|
-
|
|
90
|
+
// the longest prefix first, so a route mounted later under a longer
|
|
91
|
+
// path is still tried before the wire mounted at the root
|
|
92
|
+
for (const [prefix, handler] of [...mounts].sort(([a], [b]) => b.length - a.length)) {
|
|
91
93
|
if (url.pathname === prefix || url.pathname.startsWith(prefix + '/')) {
|
|
92
94
|
const out = await handler(asRequest(req, url), url.pathname.slice(prefix.length));
|
|
93
95
|
if (out)
|
|
@@ -161,7 +163,7 @@ function asRequest(req, url) {
|
|
|
161
163
|
async function writeResponse(out, res) {
|
|
162
164
|
const headers = {};
|
|
163
165
|
out.headers.forEach((v, k) => {
|
|
164
|
-
headers[k] = k === 'set-cookie' ? [...(Array.isArray(headers[k]) ? headers[k] : []), v] : v;
|
|
166
|
+
headers[k] = k === 'set-cookie' ? [...(Array.isArray(headers[k]) ? (headers[k]) : []), v] : v;
|
|
165
167
|
});
|
|
166
168
|
res.writeHead(out.status, headers);
|
|
167
169
|
if (!out.body)
|
package/dist/harbor/sealed.d.ts
CHANGED
package/dist/harbor/sealed.js
CHANGED
|
@@ -121,42 +121,3 @@ export class Sealed {
|
|
|
121
121
|
await this.#files.write(`quo/${this.harbor}/reach.json`, JSON.stringify(all) + '\n');
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
|
-
// Files and secrets in memory, for a suite and for a box that keeps
|
|
125
|
-
// nothing: the custody rule is proven here once and holds on every
|
|
126
|
-
// platform that hands the two.
|
|
127
|
-
export function inMemory() {
|
|
128
|
-
const disk = new Map();
|
|
129
|
-
const dirs = new Set();
|
|
130
|
-
const vault = new Map();
|
|
131
|
-
const under = (dir) => [...disk.keys()].filter((k) => k.startsWith(`${dir}/`));
|
|
132
|
-
const files = {
|
|
133
|
-
exists: async (p) => disk.has(p) || dirs.has(p),
|
|
134
|
-
read: async (p) => {
|
|
135
|
-
const v = disk.get(p);
|
|
136
|
-
if (v === undefined)
|
|
137
|
-
throw new Error(`no such file: ${p}`);
|
|
138
|
-
return v;
|
|
139
|
-
},
|
|
140
|
-
write: async (p, text) => void disk.set(p, text),
|
|
141
|
-
remove: async (p) => void disk.delete(p),
|
|
142
|
-
mkdir: async (p) => {
|
|
143
|
-
const parts = p.split('/');
|
|
144
|
-
for (let i = 1; i <= parts.length; i += 1)
|
|
145
|
-
dirs.add(parts.slice(0, i).join('/'));
|
|
146
|
-
},
|
|
147
|
-
rmdir: async (p) => {
|
|
148
|
-
for (const k of under(p))
|
|
149
|
-
disk.delete(k);
|
|
150
|
-
for (const d of [...dirs])
|
|
151
|
-
if (d === p || d.startsWith(`${p}/`))
|
|
152
|
-
dirs.delete(d);
|
|
153
|
-
},
|
|
154
|
-
list: async (p) => under(p).map((k) => k.slice(p.length + 1)).filter((k) => !k.includes('/')),
|
|
155
|
-
};
|
|
156
|
-
const secrets = {
|
|
157
|
-
get: async (i) => vault.get(i),
|
|
158
|
-
set: async (i, v) => void vault.set(i, v),
|
|
159
|
-
remove: async (i) => void vault.delete(i),
|
|
160
|
-
};
|
|
161
|
-
return { files, secrets, disk };
|
|
162
|
-
}
|
package/dist/harbor/tauri.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { BeingClass, JsonObject } from 'nervur';
|
|
2
|
-
import { Notifications } from '../
|
|
2
|
+
import { Notifications } from '../box/index.ts';
|
|
3
3
|
import { BrowserHarbor } from './browser.ts';
|
|
4
4
|
import { type Files, type Secrets } from './sealed.ts';
|
|
5
5
|
export type Core = {
|
|
@@ -8,9 +8,9 @@ export type Core = {
|
|
|
8
8
|
export declare class DeskNotifications extends Notifications {
|
|
9
9
|
#private;
|
|
10
10
|
constructor(stance: ConstructorParameters<typeof Notifications>[0], core: Core);
|
|
11
|
+
static stands(): boolean;
|
|
11
12
|
notify(args: JsonObject): Promise<JsonObject>;
|
|
12
13
|
}
|
|
13
|
-
export declare const deskLent: Record<string, string>;
|
|
14
14
|
export declare function deskFiles(core: Core): Files;
|
|
15
15
|
export declare function deskSecrets(core: Core): Secrets;
|
|
16
16
|
export declare function deskHarbor(core: Core, name?: string, classes?: Record<string, BeingClass>): Promise<BrowserHarbor>;
|
package/dist/harbor/tauri.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Notifications } from '../box/index.js';
|
|
2
2
|
import { BrowserHarbor } from './browser.js';
|
|
3
3
|
import { Sealed } from './sealed.js';
|
|
4
4
|
export class DeskNotifications extends Notifications {
|
|
@@ -7,6 +7,11 @@ export class DeskNotifications extends Notifications {
|
|
|
7
7
|
super(stance);
|
|
8
8
|
this.#core = core;
|
|
9
9
|
}
|
|
10
|
+
// A desk's notification center is its core's, and a class handed a core
|
|
11
|
+
// stands wherever it was handed one.
|
|
12
|
+
static stands() {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
10
15
|
async notify(args) {
|
|
11
16
|
const title = typeof args.title === 'string' ? args.title : '';
|
|
12
17
|
const body = typeof args.body === 'string' ? args.body : '';
|
|
@@ -14,13 +19,6 @@ export class DeskNotifications extends Notifications {
|
|
|
14
19
|
return { shown: true };
|
|
15
20
|
}
|
|
16
21
|
}
|
|
17
|
-
// What a desk lends: its notification center, under the name every box
|
|
18
|
-
// lends it under. Location is what the platform gives, which on a desk is
|
|
19
|
-
// nothing, so it is not lent rather than lent and dumb. No timer either: a
|
|
20
|
-
// desk holds presences and no home, so no Clock lives on it and a Timer
|
|
21
|
-
// there would arm for nobody. A Timer stands only on a box that hosts a
|
|
22
|
-
// home.
|
|
23
|
-
export const deskLent = { [NOTIFICATIONS]: 'Notifications' };
|
|
24
22
|
// The two seams over the core's commands. Paths are relative to the app's
|
|
25
23
|
// data folder, and the core refuses one that climbs out of it.
|
|
26
24
|
export function deskFiles(core) {
|
|
@@ -52,8 +50,13 @@ export async function deskHarbor(core, name = 'nervur', classes = {}) {
|
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
52
|
const store = await Sealed.open(name, deskFiles(core), deskSecrets(core));
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
// A desk brings its notification center and nothing else. Location is what
|
|
54
|
+
// the platform gives, which on a desk is nothing, so no class for it is
|
|
55
|
+
// brought rather than one brought and dumb. No timer either: a desk holds
|
|
56
|
+
// presences and no home, so no Clock lives on it and a Timer there would
|
|
57
|
+
// arm for nobody.
|
|
58
|
+
const h = new BrowserHarbor(store, classes);
|
|
59
|
+
await h.stand({ harbor: { DeskNotifications: Center } });
|
|
57
60
|
await h.boot();
|
|
58
61
|
return h;
|
|
59
62
|
}
|
package/dist/human/app.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Harbor } from 'nervur/harbor';
|
|
|
2
2
|
import type { Invitation, JsonObject } from 'nervur';
|
|
3
3
|
import { type Joined, type World } from './worlds.ts';
|
|
4
4
|
import { type Bell } from './wake.ts';
|
|
5
|
-
import type { Token } from '../
|
|
5
|
+
import type { Token } from '../box/index.ts';
|
|
6
6
|
import type { Config } from './tab.ts';
|
|
7
7
|
export type Learn = (at: string) => Promise<Config>;
|
|
8
8
|
export declare const learn: Learn;
|
package/dist/human/app.js
CHANGED
|
@@ -3,7 +3,7 @@ import { domSurface } from './dom.js';
|
|
|
3
3
|
import { guest } from './guest.js';
|
|
4
4
|
import { door, held as heldPage } from './door.js';
|
|
5
5
|
import { USER } from '../beings/avatar.js';
|
|
6
|
-
import {
|
|
6
|
+
import { BOX, BOX_SEED, NOTIFICATIONS, Notifications, lent, settled } from '../box/index.js';
|
|
7
7
|
import { fresh, joined, localName, ordered, relations, world } from './worlds.js';
|
|
8
8
|
import { giveWorld, takeWorld } from './move.js';
|
|
9
9
|
import { invitationOf, reachOf } from '../beings/link.js';
|
|
@@ -89,7 +89,7 @@ export async function app(o) {
|
|
|
89
89
|
// by class and a class beats the hidden attribute.
|
|
90
90
|
//
|
|
91
91
|
// Its last entry is never a world: it is this device, the box's own ward
|
|
92
|
-
// shown through
|
|
92
|
+
// shown through its own being, where a human sees what the box lends and
|
|
93
93
|
// leaves a world. A box that lends nothing has no such page.
|
|
94
94
|
const switcher = () => {
|
|
95
95
|
nav.replaceChildren();
|
|
@@ -220,16 +220,16 @@ export async function app(o) {
|
|
|
220
220
|
});
|
|
221
221
|
side = s;
|
|
222
222
|
};
|
|
223
|
-
// The box's own ward, as its harbor
|
|
223
|
+
// The box's own ward, as its harbor unpacked it: its being carrying every
|
|
224
224
|
// faculty this box lends. A box that lends nothing stands none, and there
|
|
225
225
|
// is no page of its own to show.
|
|
226
226
|
const deviceWard = async () => {
|
|
227
|
-
const ward = o.harbor.wards.get(
|
|
228
|
-
if (!ward || !ward.being(
|
|
227
|
+
const ward = o.harbor.wards.get(BOX_SEED);
|
|
228
|
+
if (!ward || !ward.being(BOX))
|
|
229
229
|
return null;
|
|
230
230
|
return ward;
|
|
231
231
|
};
|
|
232
|
-
const hasDevice = () => o.harbor.wards.get(
|
|
232
|
+
const hasDevice = () => o.harbor.wards.get(BOX_SEED)?.being(BOX) !== undefined;
|
|
233
233
|
// The device shown by itself: the last entry of the switcher, and the
|
|
234
234
|
// whole screen of a device that has joined no world. A fresh install has
|
|
235
235
|
// no world's page to carry her forms, and a first screen that says only
|
|
@@ -249,7 +249,7 @@ export async function app(o) {
|
|
|
249
249
|
if (had)
|
|
250
250
|
return { ward, rel: had };
|
|
251
251
|
const rel = await fresh(ward);
|
|
252
|
-
const inv = await ward.ask('invite', { being:
|
|
252
|
+
const inv = await ward.ask('invite', { being: BOX, id: rel.key });
|
|
253
253
|
if (!inv || typeof inv !== 'object')
|
|
254
254
|
throw new Error('this device is already held under that name');
|
|
255
255
|
const took = (await ward.ask('knock', { being: rel.key, id: USER, invitation: inv }));
|
|
@@ -362,7 +362,7 @@ export async function app(o) {
|
|
|
362
362
|
// was there a moment ago. A page paints once, so asking once more here
|
|
363
363
|
// is the difference between a device with its faculties on screen and a
|
|
364
364
|
// device that looks empty until the person navigates away and back.
|
|
365
|
-
await settled();
|
|
365
|
+
await settled(o.harbor);
|
|
366
366
|
const now = await rel.avatar.tools();
|
|
367
367
|
if ('asks' in now)
|
|
368
368
|
got = now;
|
|
@@ -464,10 +464,8 @@ export async function app(o) {
|
|
|
464
464
|
// the shell asks and does not read the answer, since the news is already
|
|
465
465
|
// on the page where the push landed.
|
|
466
466
|
const shown = async (line) => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
return;
|
|
470
|
-
await dock.ask('ask', { being: NOTIFICATIONS, method: 'notify', args: line }).catch(() => { });
|
|
467
|
+
await (await lent(o.harbor, NOTIFICATIONS))?.notify(line).catch(() => { });
|
|
468
|
+
await o.harbor.wards.get(BOX_SEED)?.save();
|
|
471
469
|
};
|
|
472
470
|
// The device ward before anything is dialed: a harbor announces the wards
|
|
473
471
|
// it hosts when it opens a socket, and a ward made after that is a pk the
|
package/dist/human/door.d.ts
CHANGED
package/dist/human/door.js
CHANGED
|
@@ -68,7 +68,7 @@ export const ours = {
|
|
|
68
68
|
...nobody,
|
|
69
69
|
link: (href, text) => `<a class="link" href="${escape(href)}">${escape(text)}</a>`,
|
|
70
70
|
};
|
|
71
|
-
|
|
71
|
+
function landedTree(l) {
|
|
72
72
|
const of = [
|
|
73
73
|
{ kind: 'image', src: MARK, alt: '' },
|
|
74
74
|
{ kind: 'text', text: l.ok ? 'That is done' : 'That did not finish', role: 'title' },
|
package/dist/human/html.js
CHANGED
|
@@ -397,7 +397,7 @@ export function page(m) {
|
|
|
397
397
|
const a = inGroup.find((x) => x.name === prefix + name);
|
|
398
398
|
return a ? one(kept, prefix)(a) : '';
|
|
399
399
|
},
|
|
400
|
-
answer: (name,
|
|
400
|
+
answer: (name, shown) => (inGroup.some((x) => x.name === prefix + name) ? answer(prefix + name, shown) : ''),
|
|
401
401
|
link: linkedBy(kept), // a carried standing vouches by her own look and never her carrier's
|
|
402
402
|
standing: () => '',
|
|
403
403
|
standings: () => '',
|
package/dist/human/move.js
CHANGED
|
@@ -10,7 +10,7 @@ const KEY = 'courier';
|
|
|
10
10
|
async function courier(harbor) {
|
|
11
11
|
const ward = harbor.wards.get(COURIER) ?? (await harbor.create(COURIER, 'me'));
|
|
12
12
|
if (!ward.being(KEY)) {
|
|
13
|
-
const out = (await ward.ask('boot', { key: KEY, class: 'Courier' }));
|
|
13
|
+
const out = (await ward.ask('boot', { key: KEY, class: 'beings/Courier' }));
|
|
14
14
|
if (out.error)
|
|
15
15
|
throw new Error(`no courier on this device: ${out.error}`);
|
|
16
16
|
}
|