@nervur-org/dock 0.3.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/GETTING_STARTED.md +128 -0
- package/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +37 -0
- package/api/quo-api.md +196 -0
- package/api/route.ts +227 -0
- package/beings/GLOSSARY.md +118 -0
- package/beings/WORLDS.md +567 -0
- package/beings/avatar.ts +101 -0
- package/beings/carry.ts +159 -0
- package/beings/clock.ts +552 -0
- package/beings/courier.ts +107 -0
- package/beings/desk.ts +128 -0
- package/beings/doorbell.ts +129 -0
- package/beings/envoy.ts +218 -0
- package/beings/index.ts +16 -0
- package/beings/join.ts +51 -0
- package/beings/link.ts +104 -0
- package/beings/look.ts +139 -0
- package/beings/porter.ts +43 -0
- package/beings/presence.ts +39 -0
- package/beings/quo-app-bells.md +280 -0
- package/beings/quo-dock.md +1942 -0
- package/beings/setup.ts +45 -0
- package/beings/side.ts +60 -0
- package/beings/user.ts +459 -0
- package/beings/way.ts +65 -0
- package/cli/bundles.ts +20 -0
- package/cli/client.ts +73 -0
- package/cli/daemon.ts +581 -0
- package/cli/estate/Caddyfile +33 -0
- package/cli/estate/quo.service +35 -0
- package/cli/estate/systems.quo.plist +44 -0
- package/cli/estate.ts +48 -0
- package/cli/folder.ts +27 -0
- package/cli/oauth.ts +32 -0
- package/cli/pilot.ts +38 -0
- package/cli/quo-dock.md +1942 -0
- package/cli/quo.ts +203 -0
- package/cli/stand.ts +160 -0
- package/dist/api/route.d.ts +21 -0
- package/dist/api/route.js +193 -0
- package/dist/beings/avatar.d.ts +32 -0
- package/dist/beings/avatar.js +100 -0
- package/dist/beings/carry.d.ts +12 -0
- package/dist/beings/carry.js +174 -0
- package/dist/beings/clock.d.ts +161 -0
- package/dist/beings/clock.js +528 -0
- package/dist/beings/courier.d.ts +50 -0
- package/dist/beings/courier.js +96 -0
- package/dist/beings/desk.d.ts +53 -0
- package/dist/beings/desk.js +135 -0
- package/dist/beings/doorbell.d.ts +78 -0
- package/dist/beings/doorbell.js +133 -0
- package/dist/beings/envoy.d.ts +54 -0
- package/dist/beings/envoy.js +211 -0
- package/dist/beings/index.d.ts +14 -0
- package/dist/beings/index.js +16 -0
- package/dist/beings/join.d.ts +15 -0
- package/dist/beings/join.js +42 -0
- package/dist/beings/link.d.ts +14 -0
- package/dist/beings/link.js +91 -0
- package/dist/beings/look.d.ts +30 -0
- package/dist/beings/look.js +104 -0
- package/dist/beings/porter.d.ts +14 -0
- package/dist/beings/porter.js +41 -0
- package/dist/beings/presence.d.ts +8 -0
- package/dist/beings/presence.js +21 -0
- package/dist/beings/setup.d.ts +3 -0
- package/dist/beings/setup.js +39 -0
- package/dist/beings/side.d.ts +32 -0
- package/dist/beings/side.js +37 -0
- package/dist/beings/user.d.ts +315 -0
- package/dist/beings/user.js +462 -0
- package/dist/beings/way.d.ts +19 -0
- package/dist/beings/way.js +28 -0
- package/dist/cli/bundles.d.ts +5 -0
- package/dist/cli/bundles.js +17 -0
- package/dist/cli/client.d.ts +15 -0
- package/dist/cli/client.js +58 -0
- package/dist/cli/daemon.d.ts +50 -0
- package/dist/cli/daemon.js +584 -0
- package/dist/cli/estate/Caddyfile +33 -0
- package/dist/cli/estate/quo.service +35 -0
- package/dist/cli/estate/systems.quo.plist +44 -0
- package/dist/cli/estate.d.ts +5 -0
- package/dist/cli/estate.js +50 -0
- package/dist/cli/folder.d.ts +2 -0
- package/dist/cli/folder.js +25 -0
- package/dist/cli/oauth.d.ts +2 -0
- package/dist/cli/oauth.js +32 -0
- package/dist/cli/pilot.d.ts +5 -0
- package/dist/cli/pilot.js +22 -0
- package/dist/cli/quo.d.ts +2 -0
- package/dist/cli/quo.js +201 -0
- package/dist/cli/stand.d.ts +37 -0
- package/dist/cli/stand.js +110 -0
- package/dist/dock/agents.d.ts +102 -0
- package/dist/dock/agents.js +105 -0
- package/dist/dock/apns.d.ts +27 -0
- package/dist/dock/apns.js +98 -0
- package/dist/dock/console.d.ts +6 -0
- package/dist/dock/console.js +20 -0
- package/dist/dock/dialers.d.ts +56 -0
- package/dist/dock/dialers.js +75 -0
- package/dist/dock/faculty.d.ts +24 -0
- package/dist/dock/faculty.js +142 -0
- package/dist/dock/fcm.d.ts +13 -0
- package/dist/dock/fcm.js +77 -0
- package/dist/dock/grants.d.ts +41 -0
- package/dist/dock/grants.js +99 -0
- package/dist/dock/http2.d.ts +4 -0
- package/dist/dock/http2.js +68 -0
- package/dist/dock/index.d.ts +41 -0
- package/dist/dock/index.js +113 -0
- package/dist/dock/local.d.ts +13 -0
- package/dist/dock/local.js +44 -0
- package/dist/dock/location.d.ts +19 -0
- package/dist/dock/location.js +11 -0
- package/dist/dock/notifications.d.ts +38 -0
- package/dist/dock/notifications.js +60 -0
- package/dist/dock/push.d.ts +79 -0
- package/dist/dock/push.js +132 -0
- package/dist/dock/service.d.ts +4 -0
- package/dist/dock/service.js +107 -0
- package/dist/dock/timer.d.ts +52 -0
- package/dist/dock/timer.js +149 -0
- package/dist/dock/webpush.d.ts +12 -0
- package/dist/dock/webpush.js +144 -0
- package/dist/harbor/browser.d.ts +15 -0
- package/dist/harbor/browser.js +82 -0
- package/dist/harbor/capacitor.d.ts +28 -0
- package/dist/harbor/capacitor.js +218 -0
- package/dist/harbor/disk.d.ts +27 -0
- package/dist/harbor/disk.js +249 -0
- package/dist/harbor/edge/alarm.d.ts +10 -0
- package/dist/harbor/edge/alarm.js +38 -0
- package/dist/harbor/edge/edge.d.ts +54 -0
- package/dist/harbor/edge/edge.js +370 -0
- package/dist/harbor/edge/exercise.d.ts +7 -0
- package/dist/harbor/edge/exercise.js +232 -0
- package/dist/harbor/edge/given.d.ts +26 -0
- package/dist/harbor/edge/given.js +42 -0
- package/dist/harbor/edge/platform.d.ts +39 -0
- package/dist/harbor/edge/storage.d.ts +30 -0
- package/dist/harbor/edge/storage.js +180 -0
- package/dist/harbor/edge/worker.d.ts +17 -0
- package/dist/harbor/edge/worker.js +67 -0
- package/dist/harbor/files.d.ts +15 -0
- package/dist/harbor/files.js +220 -0
- package/dist/harbor/http.d.ts +19 -0
- package/dist/harbor/http.js +177 -0
- package/dist/harbor/idb.d.ts +15 -0
- package/dist/harbor/idb.js +0 -0
- package/dist/harbor/quo.d.ts +10 -0
- package/dist/harbor/quo.js +42 -0
- package/dist/harbor/resolve.d.ts +16 -0
- package/dist/harbor/resolve.js +17 -0
- package/dist/harbor/seal.d.ts +3 -0
- package/dist/harbor/seal.js +25 -0
- package/dist/harbor/sealed.d.ts +35 -0
- package/dist/harbor/sealed.js +162 -0
- package/dist/harbor/tauri.d.ts +16 -0
- package/dist/harbor/tauri.js +59 -0
- package/dist/harbor/ward.d.ts +9 -0
- package/dist/harbor/ward.js +15 -0
- package/dist/human/app.d.ts +32 -0
- package/dist/human/app.js +527 -0
- package/dist/human/apps.d.ts +10 -0
- package/dist/human/apps.js +29 -0
- package/dist/human/dom.d.ts +2 -0
- package/dist/human/dom.js +15 -0
- package/dist/human/door.d.ts +19 -0
- package/dist/human/door.js +92 -0
- package/dist/human/guest.d.ts +3 -0
- package/dist/human/guest.js +33 -0
- package/dist/human/html.d.ts +55 -0
- package/dist/human/html.js +451 -0
- package/dist/human/local.d.ts +7 -0
- package/dist/human/local.js +10 -0
- package/dist/human/mark.d.ts +8 -0
- package/dist/human/mark.js +47 -0
- package/dist/human/move.d.ts +17 -0
- package/dist/human/move.js +90 -0
- package/dist/human/screen.d.ts +19 -0
- package/dist/human/screen.js +119 -0
- package/dist/human/style.d.ts +2 -0
- package/dist/human/style.js +60 -0
- package/dist/human/tab.bundle.txt +4775 -0
- package/dist/human/tab.d.ts +15 -0
- package/dist/human/tab.js +409 -0
- package/dist/human/tree.d.ts +50 -0
- package/dist/human/tree.js +157 -0
- package/dist/human/wake.d.ts +34 -0
- package/dist/human/wake.js +179 -0
- package/dist/human/web.d.ts +33 -0
- package/dist/human/web.js +184 -0
- package/dist/human/worker.bundle.txt +3868 -0
- package/dist/human/worker.d.ts +1 -0
- package/dist/human/worker.js +81 -0
- package/dist/human/worlds.d.ts +24 -0
- package/dist/human/worlds.js +99 -0
- package/dist/mcp/agent.d.ts +10 -0
- package/dist/mcp/agent.js +78 -0
- package/dist/mcp/allow.d.ts +20 -0
- package/dist/mcp/allow.js +88 -0
- package/dist/mcp/http.d.ts +27 -0
- package/dist/mcp/http.js +92 -0
- package/dist/mcp/oauth.d.ts +91 -0
- package/dist/mcp/oauth.js +241 -0
- package/dist/mcp/route.d.ts +15 -0
- package/dist/mcp/route.js +42 -0
- package/dist/mcp/runner.d.ts +45 -0
- package/dist/mcp/runner.js +99 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +102 -0
- package/dock/agents.ts +121 -0
- package/dock/apns.ts +138 -0
- package/dock/console.ts +23 -0
- package/dock/dialers.ts +85 -0
- package/dock/faculty.ts +148 -0
- package/dock/fcm.ts +103 -0
- package/dock/grants.ts +111 -0
- package/dock/http2.ts +70 -0
- package/dock/index.ts +133 -0
- package/dock/local.ts +48 -0
- package/dock/location.ts +22 -0
- package/dock/notifications.ts +67 -0
- package/dock/push.ts +161 -0
- package/dock/quo-app-bells.md +280 -0
- package/dock/quo-dock.md +1942 -0
- package/dock/service.ts +100 -0
- package/dock/timer.ts +174 -0
- package/dock/webpush.ts +182 -0
- package/harbor/browser.ts +97 -0
- package/harbor/capacitor.ts +223 -0
- package/harbor/disk.ts +237 -0
- package/harbor/edge/alarm.ts +48 -0
- package/harbor/edge/edge.ts +407 -0
- package/harbor/edge/exercise.ts +241 -0
- package/harbor/edge/given.ts +106 -0
- package/harbor/edge/platform.d.ts +39 -0
- package/harbor/edge/storage.ts +179 -0
- package/harbor/edge/text.d.ts +15 -0
- package/harbor/edge/worker.ts +70 -0
- package/harbor/edge/wrangler.toml +30 -0
- package/harbor/files.ts +221 -0
- package/harbor/http.ts +188 -0
- package/harbor/idb.ts +0 -0
- package/harbor/quo-harbor.md +850 -0
- package/harbor/quo.ts +44 -0
- package/harbor/resolve.ts +31 -0
- package/harbor/seal.ts +26 -0
- package/harbor/sealed.ts +183 -0
- package/harbor/tauri.ts +79 -0
- package/harbor/ward.ts +28 -0
- package/human/app.ts +580 -0
- package/human/apps.ts +47 -0
- package/human/dom.ts +21 -0
- package/human/door.ts +113 -0
- package/human/guest.ts +34 -0
- package/human/html.ts +490 -0
- package/human/local.ts +30 -0
- package/human/mark.ts +53 -0
- package/human/move.ts +122 -0
- package/human/quo-human.md +1042 -0
- package/human/screen.ts +136 -0
- package/human/style.ts +63 -0
- package/human/tab.bundle.txt +4775 -0
- package/human/tab.ts +446 -0
- package/human/tree.ts +217 -0
- package/human/wake.ts +191 -0
- package/human/web.ts +259 -0
- package/human/worker.bundle.txt +3868 -0
- package/human/worker.ts +124 -0
- package/human/worlds.ts +127 -0
- package/mcp/agent.ts +94 -0
- package/mcp/allow.ts +131 -0
- package/mcp/http.ts +92 -0
- package/mcp/oauth.ts +270 -0
- package/mcp/quo-mcp.md +343 -0
- package/mcp/route.ts +45 -0
- package/mcp/runner.ts +136 -0
- package/mcp/server.ts +101 -0
- package/package.json +121 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
10
|
+
// The node daemon's harbor: the core over a folder on disk. The store is
|
|
11
|
+
// `files.ts`, one folder per ward; the class bodies come from a module the
|
|
12
|
+
// ward record names, loaded from the folder; the lease is a pid file. The
|
|
13
|
+
// estate's own beings, the user being, the front desk and the avatar, are
|
|
14
|
+
// held by every harbor here: they are what every side needs, and a folder's
|
|
15
|
+
// class source adds to them.
|
|
16
|
+
//
|
|
17
|
+
// <dir>/
|
|
18
|
+
// lease pid of the one process that holds this harbor
|
|
19
|
+
// classes/index.ts the default class source, a module exporting classes
|
|
20
|
+
// tab/index.ts the classes for the tab, served bundled by the web route: human/web.ts
|
|
21
|
+
// design/ the face this world wears: tokens.css, fonts.css, fonts/, served by the web route
|
|
22
|
+
// wards/<name>/ seed, rows/, ward.json, or one sealed
|
|
23
|
+
// blob when the daemon holds QUO_SEED_KEY: see files.ts
|
|
24
|
+
// reach.json the directory's hints
|
|
25
|
+
import { mkdir, readFile, writeFile, unlink, stat } from 'node:fs/promises';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
28
|
+
import { pathToFileURL } from 'node:url';
|
|
29
|
+
import { register } from 'node:module';
|
|
30
|
+
import process from 'node:process';
|
|
31
|
+
import { User, Desk, Avatar, Presence, Clock, Courier } from '../beings/index.js';
|
|
32
|
+
import { setup } from '../beings/setup.js';
|
|
33
|
+
import { Harbor, DEFAULT_CODE } from '@nervur-org/nervur/harbor';
|
|
34
|
+
import { FACULTY_CLASSES, settled, stand } from '../dock/index.js';
|
|
35
|
+
import { Files } from './files.js';
|
|
36
|
+
export const BUILT_IN = { User, Desk, Avatar, Presence, Clock, Courier, ...FACULTY_CLASSES };
|
|
37
|
+
// The default ward, the one a caller means when naming none: the one marked,
|
|
38
|
+
// or the one ward hosted when there is exactly one, which is every harbor an
|
|
39
|
+
// adopter stands with one `quo init`. Several wards and none marked is
|
|
40
|
+
// refused by name, since agents and a door would otherwise hang off a
|
|
41
|
+
// guess. The dock ward is furniture, never a candidate.
|
|
42
|
+
export function defaultWard(marked, hosted) {
|
|
43
|
+
if (marked.length > 1)
|
|
44
|
+
throw new Error(`this device marks ${marked.join(', ')} as default, and one at most may be`);
|
|
45
|
+
const one = marked[0];
|
|
46
|
+
if (one !== undefined) {
|
|
47
|
+
if (!hosted.includes(one))
|
|
48
|
+
throw new Error(`this device says its default ward is ${one}, and hosts no such ward`);
|
|
49
|
+
return one;
|
|
50
|
+
}
|
|
51
|
+
const [first, ...more] = hosted.filter((n) => n !== 'dock');
|
|
52
|
+
if (first !== undefined && more.length === 0)
|
|
53
|
+
return first;
|
|
54
|
+
const names = first === undefined ? [] : [first, ...more];
|
|
55
|
+
throw new Error(names.length === 0 ? 'this device hosts no ward: run `quo init`' : `this device hosts ${names.join(', ')} and says which is the default: quo default <name>`);
|
|
56
|
+
}
|
|
57
|
+
// Folders held by a harbor in this process. The lease file names a pid, and
|
|
58
|
+
// a second harbor in the same pid would pass that check while still being a
|
|
59
|
+
// second ward with one pk.
|
|
60
|
+
const HELD = new Set();
|
|
61
|
+
// A class file in the harbor folder resolves its packages from where the
|
|
62
|
+
// dock is installed, `resolve.ts`: the folder has no node_modules and is not
|
|
63
|
+
// meant to. Registered once per process, before the first class loads.
|
|
64
|
+
let hooked = false;
|
|
65
|
+
function hook() {
|
|
66
|
+
if (hooked)
|
|
67
|
+
return;
|
|
68
|
+
hooked = true;
|
|
69
|
+
const self = import.meta.url;
|
|
70
|
+
// bundled into one script, as the desk's sidecar is, the dock has no URL and no folder of
|
|
71
|
+
// packages beside it to resolve to: a class file there imports nothing bare
|
|
72
|
+
if (!self)
|
|
73
|
+
return;
|
|
74
|
+
register(new URL(self.endsWith('.ts') ? './resolve.ts' : './resolve.js', self), { parentURL: self, data: { parent: self } });
|
|
75
|
+
}
|
|
76
|
+
// The class source is a module. Every export that is a class is a class the
|
|
77
|
+
// harbor holds, under its export name. The harbor never sees a body: it
|
|
78
|
+
// constructs when a ward names a class, and never chooses one.
|
|
79
|
+
async function loadClasses(path) {
|
|
80
|
+
if (!existsSync(path))
|
|
81
|
+
return {};
|
|
82
|
+
hook();
|
|
83
|
+
// Keyed by the file's own time, so a source that changed on disk is read
|
|
84
|
+
// again and a harbor boots on what it pins, not on what it once loaded.
|
|
85
|
+
const { mtimeMs } = await stat(path);
|
|
86
|
+
const mod = (await import(__rewriteRelativeImportExtension(`${pathToFileURL(path).href}?v=${mtimeMs}`)));
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const [name, v] of Object.entries(mod))
|
|
89
|
+
if (typeof v === 'function' && 'prototype' in v)
|
|
90
|
+
out[name] = v;
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
export class DiskHarbor extends Harbor {
|
|
94
|
+
dir;
|
|
95
|
+
// The default ward: the one a caller means when naming none. Not a kind of
|
|
96
|
+
// ward and nothing in Quo knows of it: a harbor is not a being and has no
|
|
97
|
+
// self to be, so it can only point at one of its wards. Its agents boot
|
|
98
|
+
// there, and its door answers for it when a caller names no ward. The one
|
|
99
|
+
// ward it hosts when there is one, the one marked otherwise, resolved by
|
|
100
|
+
// the daemon at boot; empty until then.
|
|
101
|
+
defaultWard = '';
|
|
102
|
+
// The mark is a file in the ward's own folder, `wards/<name>/default`, so
|
|
103
|
+
// it moves with the ward and nothing beside the wards says it. One at most:
|
|
104
|
+
// marking one takes the mark off every other.
|
|
105
|
+
static async setDefault(dir, name) {
|
|
106
|
+
const h = new DiskHarbor(dir);
|
|
107
|
+
const names = await h.store.list();
|
|
108
|
+
if (!names.includes(name))
|
|
109
|
+
throw new Error(`no ward ${name} in ${h.dir}`);
|
|
110
|
+
for (const n of names)
|
|
111
|
+
await unlink(join(h.dir, 'wards', n, 'default')).catch(() => { });
|
|
112
|
+
await writeFile(join(h.dir, 'wards', name, 'default'), '');
|
|
113
|
+
}
|
|
114
|
+
// Whether a ward is shown at the web route: listed at its root and its
|
|
115
|
+
// public being's page painted for a stranger. Hidden, it is not listed
|
|
116
|
+
// and a stranger meets the plain door page, while a link still knocks
|
|
117
|
+
// with its invitation and the desk still answers a proof, so nothing of
|
|
118
|
+
// the way in changes. A mark in the ward's own folder, `wards/<name>/shown`,
|
|
119
|
+
// hidden until the root shows it.
|
|
120
|
+
static async setShown(dir, name, shown) {
|
|
121
|
+
const h = new DiskHarbor(dir);
|
|
122
|
+
if (!(await h.store.list()).includes(name))
|
|
123
|
+
throw new Error(`no ward ${name} in ${h.dir}`);
|
|
124
|
+
const p = join(h.dir, 'wards', name, 'shown');
|
|
125
|
+
if (shown)
|
|
126
|
+
await writeFile(p, '');
|
|
127
|
+
else
|
|
128
|
+
await unlink(p).catch(() => { });
|
|
129
|
+
}
|
|
130
|
+
static isShown(dir, name) {
|
|
131
|
+
return existsSync(join(resolve(dir), 'wards', name, 'shown'));
|
|
132
|
+
}
|
|
133
|
+
static async marked(dir) {
|
|
134
|
+
const h = new DiskHarbor(dir);
|
|
135
|
+
return (await h.store.list()).filter((n) => existsSync(join(h.dir, 'wards', n, 'default')));
|
|
136
|
+
}
|
|
137
|
+
// The default ward read from the folder alone, for a command with no
|
|
138
|
+
// daemon in front of it and no `--ward` given.
|
|
139
|
+
static async defaultOf(dir) {
|
|
140
|
+
const h = new DiskHarbor(dir);
|
|
141
|
+
return defaultWard(await DiskHarbor.marked(dir), await h.store.list());
|
|
142
|
+
}
|
|
143
|
+
#held = false;
|
|
144
|
+
// `lend` is what this box lends the beings of every ward here: a name to
|
|
145
|
+
// an invitation on a being of its own ward, which `stand` puts up. A
|
|
146
|
+
// folder booted by a command lends nothing.
|
|
147
|
+
// `own` are this terrain's own class bodies, the faculty subclasses a box
|
|
148
|
+
// has: they stand in front of the built-ins, so a daemon that can ring
|
|
149
|
+
// hands its own `Push` and the dock ward boots that one.
|
|
150
|
+
constructor(dir, lend, own = {}) {
|
|
151
|
+
const abs = resolve(dir);
|
|
152
|
+
// The key the environment gives, the edge's name for it: from the
|
|
153
|
+
// Keychain through the app that spawned this daemon, or nothing on a
|
|
154
|
+
// droplet, whose folder stays plain under its file modes.
|
|
155
|
+
super(new Files(abs, process.env.QUO_SEED_KEY), async (rec) => ({ ...BUILT_IN, ...own, ...(await loadClasses(isAbsolute(rec.code) ? rec.code : join(abs, rec.code))) }), lend);
|
|
156
|
+
this.dir = abs;
|
|
157
|
+
}
|
|
158
|
+
// What this box lends, by name. Set before `boot` and read there; a box
|
|
159
|
+
// that says nothing stands no ward of its own and lends nothing.
|
|
160
|
+
lent;
|
|
161
|
+
// Create a harbor folder with one ward, and the root's setup in it: the
|
|
162
|
+
// front desk, public, and one user being, with the desk holding a standing
|
|
163
|
+
// to her placed by the root's knock. Boots the ward once, with no lease, to
|
|
164
|
+
// do that and to learn its pk, and writes the record. Refuses a folder that
|
|
165
|
+
// has one.
|
|
166
|
+
static async init(dir, name = 'main', user = 'me', cls = 'User', mark = false, shown = false) {
|
|
167
|
+
const h = new DiskHarbor(dir);
|
|
168
|
+
if ((await h.store.list()).includes(name))
|
|
169
|
+
throw new Error(`ward ${name} already exists in ${h.dir}`);
|
|
170
|
+
await mkdir(join(h.dir, 'classes'), { recursive: true });
|
|
171
|
+
const classes = join(h.dir, DEFAULT_CODE);
|
|
172
|
+
if (!existsSync(classes))
|
|
173
|
+
await writeFile(classes, '// The classes this harbor holds beside the built-in ones. Export each one by name.\nexport {};\n');
|
|
174
|
+
const hosted = await h.create(name, user);
|
|
175
|
+
await setup(hosted, user, cls);
|
|
176
|
+
if (mark)
|
|
177
|
+
await DiskHarbor.setDefault(h.dir, name);
|
|
178
|
+
if (shown)
|
|
179
|
+
await DiskHarbor.setShown(h.dir, name, true);
|
|
180
|
+
return { dir: h.dir, name, pk: hosted.pk, user };
|
|
181
|
+
}
|
|
182
|
+
// Take the lease and boot every ward in the folder. The lease is the
|
|
183
|
+
// harbor's vouch made a store property: two processes over one seed are two
|
|
184
|
+
// wards with one pk, so a held lease refuses the boot.
|
|
185
|
+
//
|
|
186
|
+
// The box's own ward goes up before every other, because a being born in
|
|
187
|
+
// any of them lends from her first line, and a ground that could not reach
|
|
188
|
+
// it yet would leave her holding nothing until her next birth.
|
|
189
|
+
async boot() {
|
|
190
|
+
await this.#lease();
|
|
191
|
+
if (this.lent)
|
|
192
|
+
this.lend = await stand(this, this.lent);
|
|
193
|
+
await super.boot();
|
|
194
|
+
// Every being who takes what this box lends does it at her birth, and
|
|
195
|
+
// birth is synchronous while taking a standing is not. A box is up when
|
|
196
|
+
// they have landed, not before.
|
|
197
|
+
await settled();
|
|
198
|
+
}
|
|
199
|
+
// Every ward's writing finished, then the lease. A ward says it wrote and
|
|
200
|
+
// the harbor writes it after, so a close that only dropped the lease would
|
|
201
|
+
// let the process end with a write still on its way to the disk, and the
|
|
202
|
+
// next daemon would boot on a partition older than the last reply this one
|
|
203
|
+
// gave. The lease goes last: while a write is in flight this box is still
|
|
204
|
+
// the one holding these wards.
|
|
205
|
+
async close() {
|
|
206
|
+
for (const w of this.wards.values())
|
|
207
|
+
await w.save().catch(() => { });
|
|
208
|
+
if (!this.#held)
|
|
209
|
+
return;
|
|
210
|
+
await unlink(join(this.dir, 'lease')).catch(() => { });
|
|
211
|
+
HELD.delete(this.dir);
|
|
212
|
+
this.#held = false;
|
|
213
|
+
}
|
|
214
|
+
// Boot a ward whose folder was placed here by hand.
|
|
215
|
+
async adoptFolder(name) {
|
|
216
|
+
const kept = await this.store.load(name);
|
|
217
|
+
if (!kept)
|
|
218
|
+
throw new Error(`no ward ${name} in ${this.dir}`);
|
|
219
|
+
return this.host(name, kept);
|
|
220
|
+
}
|
|
221
|
+
async #lease() {
|
|
222
|
+
if (HELD.has(this.dir))
|
|
223
|
+
throw new Error(`harbor ${this.dir} is held by pid ${process.pid}`);
|
|
224
|
+
const path = join(this.dir, 'lease');
|
|
225
|
+
if (existsSync(path)) {
|
|
226
|
+
const pid = Number(await readFile(path, 'utf8'));
|
|
227
|
+
let alive = pid !== process.pid; // our own pid with no entry in HELD is a lease we left behind
|
|
228
|
+
if (alive) {
|
|
229
|
+
try {
|
|
230
|
+
process.kill(pid, 0);
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
alive = e.code === 'EPERM'; // EPERM: somebody is home, and not us. anything else: a corpse
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (alive)
|
|
237
|
+
throw new Error(`harbor ${this.dir} is held by pid ${pid}`);
|
|
238
|
+
await unlink(path).catch(() => { }); // the corpse, so the write below can be exclusive
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
await writeFile(path, String(process.pid), { flag: 'wx', mode: 0o600 });
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
throw new Error(`harbor ${this.dir} is held: another daemon took the lease first`);
|
|
245
|
+
}
|
|
246
|
+
HELD.add(this.dir);
|
|
247
|
+
this.#held = true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Timer } from '../../dock/index.ts';
|
|
2
|
+
import type { Storage } from './platform.d.ts';
|
|
3
|
+
export type Alarms = (at: number | null) => Promise<void>;
|
|
4
|
+
export declare const alarmsIn: (storage: Storage) => Alarms;
|
|
5
|
+
export declare class Alarm extends Timer {
|
|
6
|
+
#private;
|
|
7
|
+
alarms: Alarms | undefined;
|
|
8
|
+
get setting(): Promise<void>;
|
|
9
|
+
protected arm(when: number | null): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// The box's timer on this terrain. One method of hers knows a platform, the
|
|
3
|
+
// arming, and this replaces it: the object's own storage alarm in place of a
|
|
4
|
+
// `setTimeout` that would die with the request. Everything else about her is
|
|
5
|
+
// the same class, the same door and the same suite, because when a box wakes
|
|
6
|
+
// is a terrain's answer and what it does when it wakes is not.
|
|
7
|
+
//
|
|
8
|
+
// One harbor is one Durable Object, and every ward of it lives in that
|
|
9
|
+
// object, so the alarm wakes the whole harbor and a tick from her to a home's
|
|
10
|
+
// Clock is in-process here as it is on a disk. Only the object may set its
|
|
11
|
+
// own alarm, so the two calls are handed to her after the box ward stands,
|
|
12
|
+
// the way the grants are handed their save: a faculty is built by her ward
|
|
13
|
+
// with a stance and nothing else.
|
|
14
|
+
import { Timer } from '../../dock/index.js';
|
|
15
|
+
export const alarmsIn = (storage) => async (at) => {
|
|
16
|
+
if (at === null)
|
|
17
|
+
await storage.deleteAlarm();
|
|
18
|
+
else
|
|
19
|
+
await storage.setAlarm(at);
|
|
20
|
+
};
|
|
21
|
+
export class Alarm extends Timer {
|
|
22
|
+
// Handed in after the box ward stands. A timer holding none arms nothing
|
|
23
|
+
// and wakes nobody, which is a harbor standing no box ward.
|
|
24
|
+
alarms;
|
|
25
|
+
#setting = Promise.resolve();
|
|
26
|
+
// The arming written, for the device's own code and a suite: on this
|
|
27
|
+
// terrain an arming is a write to storage, and a reader of the alarm waits
|
|
28
|
+
// for it. It crosses no door.
|
|
29
|
+
get setting() {
|
|
30
|
+
return this.#setting;
|
|
31
|
+
}
|
|
32
|
+
arm(when) {
|
|
33
|
+
// A faculty arms inside whatever ask learned the moment, and a write is
|
|
34
|
+
// not something that ask can answer for, so this is left to finish on its
|
|
35
|
+
// own. An alarm the platform refused is armed again by the next round.
|
|
36
|
+
this.#setting = (this.alarms?.(when) ?? Promise.resolve()).catch(() => { });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { BeingClass } from '@nervur-org/nervur';
|
|
2
|
+
import { Harbor, Socket as Held } from '@nervur-org/nervur/harbor';
|
|
3
|
+
import { type Folder } from '../../human/web.ts';
|
|
4
|
+
import { Grants } from '../../dock/grants.ts';
|
|
5
|
+
import { type Routes } from '../../mcp/route.ts';
|
|
6
|
+
import { Exchange } from '../../mcp/allow.ts';
|
|
7
|
+
import { DurableStorage, danceInStorage, wearInStorage } from './storage.ts';
|
|
8
|
+
import { type Deployed, type Given, type SettingsStore } from './given.ts';
|
|
9
|
+
import type { State, Env } from './platform.d.ts';
|
|
10
|
+
export declare const BUILT_IN: Record<string, BeingClass>;
|
|
11
|
+
export declare class EdgeHarbor extends Harbor {
|
|
12
|
+
#private;
|
|
13
|
+
readonly sockets: Set<Held>;
|
|
14
|
+
readonly root: string | undefined;
|
|
15
|
+
readonly deployed: Deployed;
|
|
16
|
+
readonly settings: SettingsStore;
|
|
17
|
+
readonly folder: Folder;
|
|
18
|
+
readonly wear: ReturnType<typeof wearInStorage>;
|
|
19
|
+
readonly dance: ReturnType<typeof danceInStorage>;
|
|
20
|
+
static open(state: State, env: Env, deployed?: Deployed): Promise<EdgeHarbor>;
|
|
21
|
+
constructor(store: DurableStorage, root: string | undefined, deployed?: Deployed);
|
|
22
|
+
given(): Promise<Given>;
|
|
23
|
+
lent: Record<string, string> | undefined;
|
|
24
|
+
ready(): Promise<void>;
|
|
25
|
+
boot(): Promise<void>;
|
|
26
|
+
rang(): Promise<void>;
|
|
27
|
+
get grants(): Grants | undefined;
|
|
28
|
+
web(request: Request, path: string, base: string): Promise<Response>;
|
|
29
|
+
routes(base: string): Promise<Routes & {
|
|
30
|
+
quo: string;
|
|
31
|
+
api: string;
|
|
32
|
+
}>;
|
|
33
|
+
here(): string;
|
|
34
|
+
api(request: Request, path: string, base: string): Promise<Response>;
|
|
35
|
+
mcp(request: Request, path: string, base: string): Promise<Response>;
|
|
36
|
+
wellKnown(request: Request, path: string, base: string): Promise<Response>;
|
|
37
|
+
exchange(base: string): Promise<Exchange | undefined>;
|
|
38
|
+
fetch(request: Request, path: string, base: string): Promise<Response>;
|
|
39
|
+
health(): {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
wards: {
|
|
42
|
+
[k: string]: string;
|
|
43
|
+
};
|
|
44
|
+
reaches: {
|
|
45
|
+
[k: string]: string;
|
|
46
|
+
};
|
|
47
|
+
sockets: number;
|
|
48
|
+
faults: {
|
|
49
|
+
[k: string]: number;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
upgrade(): Response;
|
|
53
|
+
rootAsk(request: Request): Promise<Response>;
|
|
54
|
+
}
|