@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.
Files changed (285) hide show
  1. package/GETTING_STARTED.md +128 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +6 -0
  4. package/README.md +37 -0
  5. package/api/quo-api.md +196 -0
  6. package/api/route.ts +227 -0
  7. package/beings/GLOSSARY.md +118 -0
  8. package/beings/WORLDS.md +567 -0
  9. package/beings/avatar.ts +101 -0
  10. package/beings/carry.ts +159 -0
  11. package/beings/clock.ts +552 -0
  12. package/beings/courier.ts +107 -0
  13. package/beings/desk.ts +128 -0
  14. package/beings/doorbell.ts +129 -0
  15. package/beings/envoy.ts +218 -0
  16. package/beings/index.ts +16 -0
  17. package/beings/join.ts +51 -0
  18. package/beings/link.ts +104 -0
  19. package/beings/look.ts +139 -0
  20. package/beings/porter.ts +43 -0
  21. package/beings/presence.ts +39 -0
  22. package/beings/quo-app-bells.md +280 -0
  23. package/beings/quo-dock.md +1942 -0
  24. package/beings/setup.ts +45 -0
  25. package/beings/side.ts +60 -0
  26. package/beings/user.ts +459 -0
  27. package/beings/way.ts +65 -0
  28. package/cli/bundles.ts +20 -0
  29. package/cli/client.ts +73 -0
  30. package/cli/daemon.ts +581 -0
  31. package/cli/estate/Caddyfile +33 -0
  32. package/cli/estate/quo.service +35 -0
  33. package/cli/estate/systems.quo.plist +44 -0
  34. package/cli/estate.ts +48 -0
  35. package/cli/folder.ts +27 -0
  36. package/cli/oauth.ts +32 -0
  37. package/cli/pilot.ts +38 -0
  38. package/cli/quo-dock.md +1942 -0
  39. package/cli/quo.ts +203 -0
  40. package/cli/stand.ts +160 -0
  41. package/dist/api/route.d.ts +21 -0
  42. package/dist/api/route.js +193 -0
  43. package/dist/beings/avatar.d.ts +32 -0
  44. package/dist/beings/avatar.js +100 -0
  45. package/dist/beings/carry.d.ts +12 -0
  46. package/dist/beings/carry.js +174 -0
  47. package/dist/beings/clock.d.ts +161 -0
  48. package/dist/beings/clock.js +528 -0
  49. package/dist/beings/courier.d.ts +50 -0
  50. package/dist/beings/courier.js +96 -0
  51. package/dist/beings/desk.d.ts +53 -0
  52. package/dist/beings/desk.js +135 -0
  53. package/dist/beings/doorbell.d.ts +78 -0
  54. package/dist/beings/doorbell.js +133 -0
  55. package/dist/beings/envoy.d.ts +54 -0
  56. package/dist/beings/envoy.js +211 -0
  57. package/dist/beings/index.d.ts +14 -0
  58. package/dist/beings/index.js +16 -0
  59. package/dist/beings/join.d.ts +15 -0
  60. package/dist/beings/join.js +42 -0
  61. package/dist/beings/link.d.ts +14 -0
  62. package/dist/beings/link.js +91 -0
  63. package/dist/beings/look.d.ts +30 -0
  64. package/dist/beings/look.js +104 -0
  65. package/dist/beings/porter.d.ts +14 -0
  66. package/dist/beings/porter.js +41 -0
  67. package/dist/beings/presence.d.ts +8 -0
  68. package/dist/beings/presence.js +21 -0
  69. package/dist/beings/setup.d.ts +3 -0
  70. package/dist/beings/setup.js +39 -0
  71. package/dist/beings/side.d.ts +32 -0
  72. package/dist/beings/side.js +37 -0
  73. package/dist/beings/user.d.ts +315 -0
  74. package/dist/beings/user.js +462 -0
  75. package/dist/beings/way.d.ts +19 -0
  76. package/dist/beings/way.js +28 -0
  77. package/dist/cli/bundles.d.ts +5 -0
  78. package/dist/cli/bundles.js +17 -0
  79. package/dist/cli/client.d.ts +15 -0
  80. package/dist/cli/client.js +58 -0
  81. package/dist/cli/daemon.d.ts +50 -0
  82. package/dist/cli/daemon.js +584 -0
  83. package/dist/cli/estate/Caddyfile +33 -0
  84. package/dist/cli/estate/quo.service +35 -0
  85. package/dist/cli/estate/systems.quo.plist +44 -0
  86. package/dist/cli/estate.d.ts +5 -0
  87. package/dist/cli/estate.js +50 -0
  88. package/dist/cli/folder.d.ts +2 -0
  89. package/dist/cli/folder.js +25 -0
  90. package/dist/cli/oauth.d.ts +2 -0
  91. package/dist/cli/oauth.js +32 -0
  92. package/dist/cli/pilot.d.ts +5 -0
  93. package/dist/cli/pilot.js +22 -0
  94. package/dist/cli/quo.d.ts +2 -0
  95. package/dist/cli/quo.js +201 -0
  96. package/dist/cli/stand.d.ts +37 -0
  97. package/dist/cli/stand.js +110 -0
  98. package/dist/dock/agents.d.ts +102 -0
  99. package/dist/dock/agents.js +105 -0
  100. package/dist/dock/apns.d.ts +27 -0
  101. package/dist/dock/apns.js +98 -0
  102. package/dist/dock/console.d.ts +6 -0
  103. package/dist/dock/console.js +20 -0
  104. package/dist/dock/dialers.d.ts +56 -0
  105. package/dist/dock/dialers.js +75 -0
  106. package/dist/dock/faculty.d.ts +24 -0
  107. package/dist/dock/faculty.js +142 -0
  108. package/dist/dock/fcm.d.ts +13 -0
  109. package/dist/dock/fcm.js +77 -0
  110. package/dist/dock/grants.d.ts +41 -0
  111. package/dist/dock/grants.js +99 -0
  112. package/dist/dock/http2.d.ts +4 -0
  113. package/dist/dock/http2.js +68 -0
  114. package/dist/dock/index.d.ts +41 -0
  115. package/dist/dock/index.js +113 -0
  116. package/dist/dock/local.d.ts +13 -0
  117. package/dist/dock/local.js +44 -0
  118. package/dist/dock/location.d.ts +19 -0
  119. package/dist/dock/location.js +11 -0
  120. package/dist/dock/notifications.d.ts +38 -0
  121. package/dist/dock/notifications.js +60 -0
  122. package/dist/dock/push.d.ts +79 -0
  123. package/dist/dock/push.js +132 -0
  124. package/dist/dock/service.d.ts +4 -0
  125. package/dist/dock/service.js +107 -0
  126. package/dist/dock/timer.d.ts +52 -0
  127. package/dist/dock/timer.js +149 -0
  128. package/dist/dock/webpush.d.ts +12 -0
  129. package/dist/dock/webpush.js +144 -0
  130. package/dist/harbor/browser.d.ts +15 -0
  131. package/dist/harbor/browser.js +82 -0
  132. package/dist/harbor/capacitor.d.ts +28 -0
  133. package/dist/harbor/capacitor.js +218 -0
  134. package/dist/harbor/disk.d.ts +27 -0
  135. package/dist/harbor/disk.js +249 -0
  136. package/dist/harbor/edge/alarm.d.ts +10 -0
  137. package/dist/harbor/edge/alarm.js +38 -0
  138. package/dist/harbor/edge/edge.d.ts +54 -0
  139. package/dist/harbor/edge/edge.js +370 -0
  140. package/dist/harbor/edge/exercise.d.ts +7 -0
  141. package/dist/harbor/edge/exercise.js +232 -0
  142. package/dist/harbor/edge/given.d.ts +26 -0
  143. package/dist/harbor/edge/given.js +42 -0
  144. package/dist/harbor/edge/platform.d.ts +39 -0
  145. package/dist/harbor/edge/storage.d.ts +30 -0
  146. package/dist/harbor/edge/storage.js +180 -0
  147. package/dist/harbor/edge/worker.d.ts +17 -0
  148. package/dist/harbor/edge/worker.js +67 -0
  149. package/dist/harbor/files.d.ts +15 -0
  150. package/dist/harbor/files.js +220 -0
  151. package/dist/harbor/http.d.ts +19 -0
  152. package/dist/harbor/http.js +177 -0
  153. package/dist/harbor/idb.d.ts +15 -0
  154. package/dist/harbor/idb.js +0 -0
  155. package/dist/harbor/quo.d.ts +10 -0
  156. package/dist/harbor/quo.js +42 -0
  157. package/dist/harbor/resolve.d.ts +16 -0
  158. package/dist/harbor/resolve.js +17 -0
  159. package/dist/harbor/seal.d.ts +3 -0
  160. package/dist/harbor/seal.js +25 -0
  161. package/dist/harbor/sealed.d.ts +35 -0
  162. package/dist/harbor/sealed.js +162 -0
  163. package/dist/harbor/tauri.d.ts +16 -0
  164. package/dist/harbor/tauri.js +59 -0
  165. package/dist/harbor/ward.d.ts +9 -0
  166. package/dist/harbor/ward.js +15 -0
  167. package/dist/human/app.d.ts +32 -0
  168. package/dist/human/app.js +527 -0
  169. package/dist/human/apps.d.ts +10 -0
  170. package/dist/human/apps.js +29 -0
  171. package/dist/human/dom.d.ts +2 -0
  172. package/dist/human/dom.js +15 -0
  173. package/dist/human/door.d.ts +19 -0
  174. package/dist/human/door.js +92 -0
  175. package/dist/human/guest.d.ts +3 -0
  176. package/dist/human/guest.js +33 -0
  177. package/dist/human/html.d.ts +55 -0
  178. package/dist/human/html.js +451 -0
  179. package/dist/human/local.d.ts +7 -0
  180. package/dist/human/local.js +10 -0
  181. package/dist/human/mark.d.ts +8 -0
  182. package/dist/human/mark.js +47 -0
  183. package/dist/human/move.d.ts +17 -0
  184. package/dist/human/move.js +90 -0
  185. package/dist/human/screen.d.ts +19 -0
  186. package/dist/human/screen.js +119 -0
  187. package/dist/human/style.d.ts +2 -0
  188. package/dist/human/style.js +60 -0
  189. package/dist/human/tab.bundle.txt +4775 -0
  190. package/dist/human/tab.d.ts +15 -0
  191. package/dist/human/tab.js +409 -0
  192. package/dist/human/tree.d.ts +50 -0
  193. package/dist/human/tree.js +157 -0
  194. package/dist/human/wake.d.ts +34 -0
  195. package/dist/human/wake.js +179 -0
  196. package/dist/human/web.d.ts +33 -0
  197. package/dist/human/web.js +184 -0
  198. package/dist/human/worker.bundle.txt +3868 -0
  199. package/dist/human/worker.d.ts +1 -0
  200. package/dist/human/worker.js +81 -0
  201. package/dist/human/worlds.d.ts +24 -0
  202. package/dist/human/worlds.js +99 -0
  203. package/dist/mcp/agent.d.ts +10 -0
  204. package/dist/mcp/agent.js +78 -0
  205. package/dist/mcp/allow.d.ts +20 -0
  206. package/dist/mcp/allow.js +88 -0
  207. package/dist/mcp/http.d.ts +27 -0
  208. package/dist/mcp/http.js +92 -0
  209. package/dist/mcp/oauth.d.ts +91 -0
  210. package/dist/mcp/oauth.js +241 -0
  211. package/dist/mcp/route.d.ts +15 -0
  212. package/dist/mcp/route.js +42 -0
  213. package/dist/mcp/runner.d.ts +45 -0
  214. package/dist/mcp/runner.js +99 -0
  215. package/dist/mcp/server.d.ts +11 -0
  216. package/dist/mcp/server.js +102 -0
  217. package/dock/agents.ts +121 -0
  218. package/dock/apns.ts +138 -0
  219. package/dock/console.ts +23 -0
  220. package/dock/dialers.ts +85 -0
  221. package/dock/faculty.ts +148 -0
  222. package/dock/fcm.ts +103 -0
  223. package/dock/grants.ts +111 -0
  224. package/dock/http2.ts +70 -0
  225. package/dock/index.ts +133 -0
  226. package/dock/local.ts +48 -0
  227. package/dock/location.ts +22 -0
  228. package/dock/notifications.ts +67 -0
  229. package/dock/push.ts +161 -0
  230. package/dock/quo-app-bells.md +280 -0
  231. package/dock/quo-dock.md +1942 -0
  232. package/dock/service.ts +100 -0
  233. package/dock/timer.ts +174 -0
  234. package/dock/webpush.ts +182 -0
  235. package/harbor/browser.ts +97 -0
  236. package/harbor/capacitor.ts +223 -0
  237. package/harbor/disk.ts +237 -0
  238. package/harbor/edge/alarm.ts +48 -0
  239. package/harbor/edge/edge.ts +407 -0
  240. package/harbor/edge/exercise.ts +241 -0
  241. package/harbor/edge/given.ts +106 -0
  242. package/harbor/edge/platform.d.ts +39 -0
  243. package/harbor/edge/storage.ts +179 -0
  244. package/harbor/edge/text.d.ts +15 -0
  245. package/harbor/edge/worker.ts +70 -0
  246. package/harbor/edge/wrangler.toml +30 -0
  247. package/harbor/files.ts +221 -0
  248. package/harbor/http.ts +188 -0
  249. package/harbor/idb.ts +0 -0
  250. package/harbor/quo-harbor.md +850 -0
  251. package/harbor/quo.ts +44 -0
  252. package/harbor/resolve.ts +31 -0
  253. package/harbor/seal.ts +26 -0
  254. package/harbor/sealed.ts +183 -0
  255. package/harbor/tauri.ts +79 -0
  256. package/harbor/ward.ts +28 -0
  257. package/human/app.ts +580 -0
  258. package/human/apps.ts +47 -0
  259. package/human/dom.ts +21 -0
  260. package/human/door.ts +113 -0
  261. package/human/guest.ts +34 -0
  262. package/human/html.ts +490 -0
  263. package/human/local.ts +30 -0
  264. package/human/mark.ts +53 -0
  265. package/human/move.ts +122 -0
  266. package/human/quo-human.md +1042 -0
  267. package/human/screen.ts +136 -0
  268. package/human/style.ts +63 -0
  269. package/human/tab.bundle.txt +4775 -0
  270. package/human/tab.ts +446 -0
  271. package/human/tree.ts +217 -0
  272. package/human/wake.ts +191 -0
  273. package/human/web.ts +259 -0
  274. package/human/worker.bundle.txt +3868 -0
  275. package/human/worker.ts +124 -0
  276. package/human/worlds.ts +127 -0
  277. package/mcp/agent.ts +94 -0
  278. package/mcp/allow.ts +131 -0
  279. package/mcp/http.ts +92 -0
  280. package/mcp/oauth.ts +270 -0
  281. package/mcp/quo-mcp.md +343 -0
  282. package/mcp/route.ts +45 -0
  283. package/mcp/runner.ts +136 -0
  284. package/mcp/server.ts +101 -0
  285. 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
+ }