nervur 0.21.4 → 0.22.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 (339) hide show
  1. package/README.md +215 -158
  2. package/dist/browser.js +4 -3
  3. package/dist/cli.js +16 -0
  4. package/dist/{being → core/being}/being.js +10 -0
  5. package/dist/{being → core/being}/faculty.js +5 -2
  6. package/dist/{being → core/being}/index.d.ts +1 -1
  7. package/dist/{being → core/being}/index.js +1 -1
  8. package/dist/{being → core/being}/kind.d.ts +2 -0
  9. package/dist/{being → core/being}/kind.js +6 -0
  10. package/dist/{being → core/being}/types.js +1 -1
  11. package/dist/{browser → core/browser}/index.d.ts +6 -5
  12. package/dist/{browser → core/browser}/index.js +10 -10
  13. package/dist/{browser → core/browser}/worker.d.ts +2 -3
  14. package/dist/{browser → core/browser}/worker.js +8 -11
  15. package/dist/{cli → core/cli}/command.d.ts +4 -4
  16. package/dist/{cli → core/cli}/command.js +116 -66
  17. package/dist/{cli → core/cli}/edge.js +4 -2
  18. package/dist/core/cli/harbor.d.ts +25 -0
  19. package/dist/{cli → core/cli}/harbor.js +45 -33
  20. package/dist/{contract → core/contract}/index.d.ts +6 -5
  21. package/dist/{contract → core/contract}/index.js +8 -9
  22. package/dist/core/contract/link.d.ts +4 -0
  23. package/dist/core/contract/link.js +45 -0
  24. package/dist/{crypto → core/crypto}/ed25519.js +22 -14
  25. package/dist/core/crypto/subtle.d.ts +3 -0
  26. package/dist/core/crypto/subtle.js +11 -0
  27. package/dist/core/crypto/x25519.js +24 -0
  28. package/dist/core/edge/index.d.ts +164 -0
  29. package/dist/core/edge/index.js +739 -0
  30. package/dist/core/edge/kit.d.ts +2 -0
  31. package/dist/core/edge/kit.js +2 -0
  32. package/dist/core/git/http.d.ts +6 -0
  33. package/dist/core/git/http.js +102 -0
  34. package/dist/core/git/index.d.ts +5 -0
  35. package/dist/core/git/index.js +10 -0
  36. package/dist/core/git/object.d.ts +40 -0
  37. package/dist/core/git/object.js +103 -0
  38. package/dist/core/git/pack.d.ts +9 -0
  39. package/dist/core/git/pack.js +171 -0
  40. package/dist/core/git/store.d.ts +23 -0
  41. package/dist/core/git/store.js +120 -0
  42. package/dist/core/git/zlib.d.ts +6 -0
  43. package/dist/core/git/zlib.js +210 -0
  44. package/dist/core/harbor/carrying.d.ts +51 -0
  45. package/dist/core/harbor/carrying.js +69 -0
  46. package/dist/core/harbor/catalogue.d.ts +33 -0
  47. package/dist/core/harbor/catalogue.js +365 -0
  48. package/dist/core/harbor/dna.d.ts +13 -0
  49. package/dist/core/harbor/dna.js +120 -0
  50. package/dist/{harbor → core/harbor}/dock.d.ts +22 -13
  51. package/dist/core/harbor/dock.js +381 -0
  52. package/dist/core/harbor/harbor.d.ts +16 -0
  53. package/dist/core/harbor/harbor.js +438 -0
  54. package/dist/core/harbor/index.d.ts +11 -0
  55. package/dist/{harbor → core/harbor}/index.js +5 -5
  56. package/dist/core/harbor/memory.d.ts +10 -0
  57. package/dist/core/harbor/memory.js +25 -0
  58. package/dist/{harbor → core/harbor}/package.d.ts +0 -1
  59. package/dist/{harbor → core/harbor}/package.js +9 -9
  60. package/dist/core/harbor/probe.d.ts +22 -0
  61. package/dist/core/harbor/probe.js +15 -0
  62. package/dist/core/harbor/registry.d.ts +15 -0
  63. package/dist/{harbor → core/harbor}/registry.js +26 -10
  64. package/dist/core/harbor/root-line.d.ts +7 -0
  65. package/dist/core/harbor/root-line.js +9 -0
  66. package/dist/{harbor → core/harbor}/terrain.d.ts +9 -6
  67. package/dist/{harbor → core/harbor}/terrain.js +5 -4
  68. package/dist/core/http/index.d.ts +2 -0
  69. package/dist/{http → core/http}/index.js +3 -5
  70. package/dist/{line → core/line}/answer.d.ts +2 -2
  71. package/dist/{line → core/line}/frame.d.ts +0 -5
  72. package/dist/{line → core/line}/frame.js +0 -4
  73. package/dist/core/line/ground.js +7 -0
  74. package/dist/core/line/index.d.ts +4 -0
  75. package/dist/core/line/index.js +8 -0
  76. package/dist/core/line/web.d.ts +12 -0
  77. package/dist/{line → core/line}/web.js +13 -77
  78. package/dist/{node → core/node}/index.d.ts +2 -15
  79. package/dist/core/node/index.js +124 -0
  80. package/dist/{pointer → core/pointer}/bodies.d.ts +7 -5
  81. package/dist/core/pointer/bodies.js +118 -0
  82. package/dist/core/pointer/index.d.ts +2 -0
  83. package/dist/{pointer → core/pointer}/index.js +1 -1
  84. package/dist/{pointer → core/pointer}/world.d.ts +13 -9
  85. package/dist/{pointer → core/pointer}/world.js +31 -16
  86. package/dist/{proof → core/proof}/contracts.d.ts +6 -2
  87. package/dist/{proof → core/proof}/contracts.js +11 -10
  88. package/dist/core/proof/index.d.ts +3 -0
  89. package/dist/{proof → core/proof}/index.js +2 -2
  90. package/dist/core/proof/law.d.ts +25 -0
  91. package/dist/core/proof/law.js +715 -0
  92. package/dist/{tcp → core/tcp}/index.d.ts +7 -4
  93. package/dist/{tcp → core/tcp}/index.js +11 -62
  94. package/dist/{ward/ward.d.ts → core/ward/house.d.ts} +24 -10
  95. package/dist/core/ward/house.js +473 -0
  96. package/dist/core/ward/index.d.ts +5 -0
  97. package/dist/core/ward/index.js +7 -0
  98. package/dist/{ward → core/ward}/partition.d.ts +1 -0
  99. package/dist/{ward → core/ward}/stance.d.ts +2 -2
  100. package/dist/{ward → core/ward}/stance.js +5 -2
  101. package/dist/{ward/ward-being.d.ts → core/ward/ward.d.ts} +14 -6
  102. package/dist/core/ward/ward.js +140 -0
  103. package/dist/core.d.ts +10 -0
  104. package/dist/core.js +19 -0
  105. package/dist/defaults/dock/index.d.ts +4 -0
  106. package/dist/defaults/dock/index.js +11 -0
  107. package/dist/defaults/index.d.ts +5 -0
  108. package/dist/defaults/index.js +10 -0
  109. package/dist/defaults/memory/index.d.ts +12 -0
  110. package/dist/defaults/memory/index.js +39 -0
  111. package/dist/defaults/tcp/index.d.ts +16 -0
  112. package/dist/defaults/tcp/index.js +82 -0
  113. package/dist/defaults/ward/index.d.ts +4 -0
  114. package/dist/defaults/ward/index.js +11 -0
  115. package/dist/defaults/web/index.d.ts +15 -0
  116. package/dist/defaults/web/index.js +90 -0
  117. package/dist/edge.js +4 -3
  118. package/dist/index.d.ts +1 -8
  119. package/dist/index.js +13 -18
  120. package/dist/kit.d.ts +4 -0
  121. package/dist/kit.js +12 -0
  122. package/dist/node.js +4 -3
  123. package/package.json +23 -18
  124. package/src/browser.ts +4 -3
  125. package/src/cli.ts +15 -0
  126. package/src/{being → core/being}/being.ts +4 -0
  127. package/src/{being → core/being}/faculty.ts +5 -2
  128. package/src/{being → core/being}/index.ts +1 -1
  129. package/src/{being → core/being}/kind.ts +7 -0
  130. package/src/{being → core/being}/types.ts +1 -1
  131. package/src/{browser → core/browser}/index.ts +16 -13
  132. package/src/{browser → core/browser}/worker.ts +11 -14
  133. package/src/{cli → core/cli}/command.ts +113 -66
  134. package/src/{cli → core/cli}/edge.ts +4 -2
  135. package/src/{cli → core/cli}/harbor.ts +48 -31
  136. package/src/{contract → core/contract}/index.ts +14 -14
  137. package/src/core/contract/link.ts +58 -0
  138. package/src/{crypto → core/crypto}/ed25519.ts +22 -14
  139. package/src/core/crypto/subtle.ts +13 -0
  140. package/src/core/crypto/x25519.ts +23 -0
  141. package/src/core/edge/index.ts +865 -0
  142. package/src/core/git/http.ts +103 -0
  143. package/src/core/git/index.ts +10 -0
  144. package/src/core/git/object.ts +116 -0
  145. package/src/core/git/pack.ts +147 -0
  146. package/src/core/git/store.ts +122 -0
  147. package/src/core/git/zlib.ts +197 -0
  148. package/src/core/harbor/carrying.ts +124 -0
  149. package/src/core/harbor/catalogue.ts +386 -0
  150. package/src/core/harbor/dna.ts +116 -0
  151. package/src/core/harbor/dock.ts +420 -0
  152. package/src/core/harbor/harbor.ts +433 -0
  153. package/src/core/harbor/index.ts +14 -0
  154. package/src/core/harbor/memory.ts +31 -0
  155. package/src/{harbor → core/harbor}/package.ts +15 -15
  156. package/src/core/harbor/probe.ts +39 -0
  157. package/src/{harbor → core/harbor}/registry.ts +28 -11
  158. package/src/core/harbor/root-line.ts +19 -0
  159. package/src/{harbor → core/harbor}/terrain.ts +14 -7
  160. package/src/{http → core/http}/index.ts +5 -8
  161. package/src/{line → core/line}/answer.ts +3 -2
  162. package/src/{line → core/line}/frame.ts +0 -5
  163. package/src/core/line/ground.ts +18 -0
  164. package/src/core/line/index.ts +8 -0
  165. package/src/{line → core/line}/web.ts +1 -105
  166. package/src/core/node/index.ts +139 -0
  167. package/src/core/pointer/bodies.ts +122 -0
  168. package/src/core/pointer/index.ts +4 -0
  169. package/src/{pointer → core/pointer}/world.ts +45 -21
  170. package/src/{proof → core/proof}/contracts.ts +18 -13
  171. package/src/{proof → core/proof}/index.ts +3 -3
  172. package/src/core/proof/law.ts +742 -0
  173. package/src/{stand → core/stand}/stand.ts +2 -2
  174. package/src/{tcp → core/tcp}/index.ts +17 -74
  175. package/src/core/ward/house.ts +524 -0
  176. package/src/core/ward/index.ts +7 -0
  177. package/src/{ward → core/ward}/partition.ts +5 -1
  178. package/src/{ward → core/ward}/stance.ts +5 -2
  179. package/src/core/ward/ward.ts +166 -0
  180. package/src/core.ts +55 -0
  181. package/src/defaults/dock/index.ts +12 -0
  182. package/src/defaults/index.ts +10 -0
  183. package/src/defaults/memory/index.ts +50 -0
  184. package/src/defaults/tcp/index.ts +89 -0
  185. package/src/defaults/ward/index.ts +12 -0
  186. package/src/defaults/web/index.ts +97 -0
  187. package/src/edge.ts +4 -3
  188. package/src/index.ts +13 -50
  189. package/src/kit.ts +15 -0
  190. package/src/node.ts +4 -3
  191. package/dist/cli/harbor.d.ts +0 -16
  192. package/dist/cli/main.js +0 -14
  193. package/dist/crypto/subtle.d.ts +0 -10
  194. package/dist/crypto/subtle.js +0 -29
  195. package/dist/crypto/x25519.js +0 -21
  196. package/dist/edge/index.d.ts +0 -81
  197. package/dist/edge/index.js +0 -400
  198. package/dist/harbor/carrying.d.ts +0 -17
  199. package/dist/harbor/carrying.js +0 -38
  200. package/dist/harbor/catalogue.d.ts +0 -28
  201. package/dist/harbor/catalogue.js +0 -109
  202. package/dist/harbor/dock.js +0 -236
  203. package/dist/harbor/harbor.d.ts +0 -45
  204. package/dist/harbor/harbor.js +0 -347
  205. package/dist/harbor/index.d.ts +0 -11
  206. package/dist/harbor/probe.d.ts +0 -18
  207. package/dist/harbor/probe.js +0 -15
  208. package/dist/harbor/registry.d.ts +0 -10
  209. package/dist/harbor/relay.d.ts +0 -15
  210. package/dist/harbor/relay.js +0 -406
  211. package/dist/harbor/root-line.d.ts +0 -9
  212. package/dist/harbor/root-line.js +0 -38
  213. package/dist/harbor/store.d.ts +0 -13
  214. package/dist/harbor/store.js +0 -53
  215. package/dist/http/index.d.ts +0 -4
  216. package/dist/line/ground.js +0 -9
  217. package/dist/line/index.d.ts +0 -5
  218. package/dist/line/index.js +0 -9
  219. package/dist/line/push.d.ts +0 -7
  220. package/dist/line/push.js +0 -92
  221. package/dist/line/web.d.ts +0 -30
  222. package/dist/node/index.js +0 -236
  223. package/dist/pointer/bodies.js +0 -80
  224. package/dist/pointer/index.d.ts +0 -2
  225. package/dist/proof/index.d.ts +0 -3
  226. package/dist/proof/law.d.ts +0 -90
  227. package/dist/proof/law.js +0 -591
  228. package/dist/tcp/frame.d.ts +0 -25
  229. package/dist/tcp/frame.js +0 -64
  230. package/dist/ward/index.d.ts +0 -6
  231. package/dist/ward/index.js +0 -8
  232. package/dist/ward/pilot.d.ts +0 -8
  233. package/dist/ward/pilot.js +0 -50
  234. package/dist/ward/ward-being.js +0 -82
  235. package/dist/ward/ward.js +0 -290
  236. package/src/cli/main.ts +0 -13
  237. package/src/crypto/subtle.ts +0 -33
  238. package/src/crypto/x25519.ts +0 -21
  239. package/src/edge/index.ts +0 -490
  240. package/src/harbor/carrying.ts +0 -56
  241. package/src/harbor/catalogue.ts +0 -134
  242. package/src/harbor/dock.ts +0 -265
  243. package/src/harbor/harbor.ts +0 -381
  244. package/src/harbor/index.ts +0 -14
  245. package/src/harbor/probe.ts +0 -34
  246. package/src/harbor/relay.ts +0 -418
  247. package/src/harbor/root-line.ts +0 -39
  248. package/src/harbor/store.ts +0 -69
  249. package/src/line/ground.ts +0 -19
  250. package/src/line/index.ts +0 -9
  251. package/src/line/push.ts +0 -112
  252. package/src/node/index.ts +0 -241
  253. package/src/pointer/bodies.ts +0 -82
  254. package/src/pointer/index.ts +0 -4
  255. package/src/proof/law.ts +0 -591
  256. package/src/ward/index.ts +0 -8
  257. package/src/ward/pilot.ts +0 -50
  258. package/src/ward/ward-being.ts +0 -103
  259. package/src/ward/ward.ts +0 -328
  260. /package/dist/{cli/main.d.ts → cli.d.ts} +0 -0
  261. /package/dist/{being → core/being}/being.d.ts +0 -0
  262. /package/dist/{being → core/being}/digest.d.ts +0 -0
  263. /package/dist/{being → core/being}/digest.js +0 -0
  264. /package/dist/{being → core/being}/faculty.d.ts +0 -0
  265. /package/dist/{being → core/being}/types.d.ts +0 -0
  266. /package/dist/{being → core/being}/words.d.ts +0 -0
  267. /package/dist/{being → core/being}/words.js +0 -0
  268. /package/dist/{cli → core/cli}/edge.d.ts +0 -0
  269. /package/dist/{crypto → core/crypto}/aes.d.ts +0 -0
  270. /package/dist/{crypto → core/crypto}/aes.js +0 -0
  271. /package/dist/{crypto → core/crypto}/bytes.d.ts +0 -0
  272. /package/dist/{crypto → core/crypto}/bytes.js +0 -0
  273. /package/dist/{crypto → core/crypto}/ed25519.d.ts +0 -0
  274. /package/dist/{crypto → core/crypto}/hash.d.ts +0 -0
  275. /package/dist/{crypto → core/crypto}/hash.js +0 -0
  276. /package/dist/{crypto → core/crypto}/index.d.ts +0 -0
  277. /package/dist/{crypto → core/crypto}/index.js +0 -0
  278. /package/dist/{crypto → core/crypto}/json.d.ts +0 -0
  279. /package/dist/{crypto → core/crypto}/json.js +0 -0
  280. /package/dist/{crypto → core/crypto}/mlkem.d.ts +0 -0
  281. /package/dist/{crypto → core/crypto}/mlkem.js +0 -0
  282. /package/dist/{crypto → core/crypto}/x25519.d.ts +0 -0
  283. /package/dist/{folder → core/folder}/index.d.ts +0 -0
  284. /package/dist/{folder → core/folder}/index.js +0 -0
  285. /package/dist/{http → core/http}/websocket.d.ts +0 -0
  286. /package/dist/{http → core/http}/websocket.js +0 -0
  287. /package/dist/{line → core/line}/answer.js +0 -0
  288. /package/dist/{line → core/line}/ground.d.ts +0 -0
  289. /package/dist/{proof → core/proof}/expect.d.ts +0 -0
  290. /package/dist/{proof → core/proof}/expect.js +0 -0
  291. /package/dist/{quo → core/quo}/address.d.ts +0 -0
  292. /package/dist/{quo → core/quo}/address.js +0 -0
  293. /package/dist/{quo → core/quo}/door.d.ts +0 -0
  294. /package/dist/{quo → core/quo}/door.js +0 -0
  295. /package/dist/{quo → core/quo}/index.d.ts +0 -0
  296. /package/dist/{quo → core/quo}/index.js +0 -0
  297. /package/dist/{quo → core/quo}/invitation.d.ts +0 -0
  298. /package/dist/{quo → core/quo}/invitation.js +0 -0
  299. /package/dist/{quo → core/quo}/keys.d.ts +0 -0
  300. /package/dist/{quo → core/quo}/keys.js +0 -0
  301. /package/dist/{quo → core/quo}/payload.d.ts +0 -0
  302. /package/dist/{quo → core/quo}/payload.js +0 -0
  303. /package/dist/{quo → core/quo}/relations.d.ts +0 -0
  304. /package/dist/{quo → core/quo}/relations.js +0 -0
  305. /package/dist/{quo → core/quo}/reply.d.ts +0 -0
  306. /package/dist/{quo → core/quo}/reply.js +0 -0
  307. /package/dist/{quo → core/quo}/seal.d.ts +0 -0
  308. /package/dist/{quo → core/quo}/seal.js +0 -0
  309. /package/dist/{quo → core/quo}/standing.d.ts +0 -0
  310. /package/dist/{quo → core/quo}/standing.js +0 -0
  311. /package/dist/{ward → core/ward}/allowance.d.ts +0 -0
  312. /package/dist/{ward → core/ward}/allowance.js +0 -0
  313. /package/dist/{ward → core/ward}/cells.d.ts +0 -0
  314. /package/dist/{ward → core/ward}/cells.js +0 -0
  315. /package/dist/{ward → core/ward}/partition.js +0 -0
  316. /package/src/{being → core/being}/digest.ts +0 -0
  317. /package/src/{being → core/being}/words.ts +0 -0
  318. /package/src/{crypto → core/crypto}/aes.ts +0 -0
  319. /package/src/{crypto → core/crypto}/bytes.ts +0 -0
  320. /package/src/{crypto → core/crypto}/hash.ts +0 -0
  321. /package/src/{crypto → core/crypto}/index.ts +0 -0
  322. /package/src/{crypto → core/crypto}/json.ts +0 -0
  323. /package/src/{crypto → core/crypto}/mlkem.ts +0 -0
  324. /package/src/{folder → core/folder}/index.ts +0 -0
  325. /package/src/{http → core/http}/websocket.ts +0 -0
  326. /package/src/{proof → core/proof}/expect.ts +0 -0
  327. /package/src/{quo → core/quo}/address.ts +0 -0
  328. /package/src/{quo → core/quo}/door.ts +0 -0
  329. /package/src/{quo → core/quo}/index.ts +0 -0
  330. /package/src/{quo → core/quo}/invitation.ts +0 -0
  331. /package/src/{quo → core/quo}/keys.ts +0 -0
  332. /package/src/{quo → core/quo}/payload.ts +0 -0
  333. /package/src/{quo → core/quo}/relations.ts +0 -0
  334. /package/src/{quo → core/quo}/reply.ts +0 -0
  335. /package/src/{quo → core/quo}/seal.ts +0 -0
  336. /package/src/{quo → core/quo}/standing.ts +0 -0
  337. /package/src/{stand → core/stand}/main.ts +0 -0
  338. /package/src/{ward → core/ward}/allowance.ts +0 -0
  339. /package/src/{ward → core/ward}/cells.ts +0 -0
@@ -0,0 +1,739 @@
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
+ import { Custody, Loader, Memory, moduleOf } from '../contract/index.js';
10
+ import { concat, decrypt, encrypt, hex, hkdf, isHex, sha256, unhex, utf8 } from '../crypto/index.js';
11
+ import { carrierIn, Harbor, landPlaces, probeGround, readCarried, UNDIALED } from '../harbor/index.js';
12
+ import { answerHeld, answerPost, askFrame, FrameReader } from '../line/index.js';
13
+ import { CryptoEntropy, PointerTerrain } from '../pointer/index.js';
14
+ import { tcpAddress, tcpAt } from '../quo/index.js';
15
+ export const EDGE = 'edge';
16
+ const ENTRY = 'm/';
17
+ const SEED = 'c/seed';
18
+ // The most keys one storage call takes.
19
+ const BATCH = 128;
20
+ const SEAL_INFO = utf8('nervur edge custody');
21
+ const MAX_ID = 0xffff_ffff;
22
+ // The calls of a child, each by the number the shell gave it, and what
23
+ // each did to the memory: a miss or a refused keep is told to the call it
24
+ // happened in and to no other, and so is a keep that landed. A call that
25
+ // was refused and kept nothing is asked again whole in the next child. One
26
+ // that kept a step before it was refused, a line's seq before it asked
27
+ // out, is not, since a box asked again would find that step taken: it
28
+ // answers what the child answered, as after a crash.
29
+ export class ChildCalls {
30
+ #context;
31
+ #refused = new Set();
32
+ #kept = new Set();
33
+ constructor(context) {
34
+ this.#context = context;
35
+ }
36
+ run(call, work) {
37
+ return this.#context ? this.#context.run(call, work) : work();
38
+ }
39
+ // The call running now missed a module, or had a keep refused.
40
+ refused() {
41
+ const call = this.#context?.getStore();
42
+ if (call !== undefined)
43
+ this.#refused.add(call);
44
+ }
45
+ // A keep of the call running now landed.
46
+ kept() {
47
+ const call = this.#context?.getStore();
48
+ if (call !== undefined)
49
+ this.#kept.add(call);
50
+ }
51
+ // Whether a call that ended kept nothing and was refused, so it is asked
52
+ // again; told once per call.
53
+ again(call) {
54
+ const again = this.#refused.delete(call) && !this.#kept.has(call);
55
+ this.#kept.delete(call);
56
+ return again;
57
+ }
58
+ }
59
+ // The edge's loader: the modules its child was loaded with, each found by
60
+ // its blob's id. A blob it does not carry is a miss, kept with its source,
61
+ // so the shell loads the next child carrying it and asks again; a blob the
62
+ // shell found does not load here is refused with its reason. From the
63
+ // first miss the child is spent and its memory keeps nothing more, since a
64
+ // ward keeps every row it was told at once and the call that missed told
65
+ // some, the seq of the box it opened among them.
66
+ export class EdgeLoader extends Loader {
67
+ #carried;
68
+ #refused = new Map();
69
+ #missed = new Map();
70
+ #calls;
71
+ constructor(carried = [], calls) {
72
+ super();
73
+ this.#carried = new Map(carried.map((c) => [c.blob, c.module]));
74
+ this.#calls = calls;
75
+ }
76
+ get carried() {
77
+ return [...this.#carried.keys()];
78
+ }
79
+ refuse(refused) {
80
+ for (const [blob, reason] of Object.entries(refused))
81
+ this.#refused.set(blob, reason);
82
+ }
83
+ get refused() {
84
+ return this.#refused;
85
+ }
86
+ load(source, id) {
87
+ if (this.#carried.has(id))
88
+ return Promise.resolve().then(() => moduleOf(this.#carried.get(id)));
89
+ const refused = this.#refused.get(id);
90
+ if (refused !== undefined)
91
+ return Promise.reject(new Error(refused));
92
+ this.#missed.set(id, source);
93
+ this.#calls?.refused();
94
+ return Promise.reject(new Error(`blob ${id} is carried by the next child`));
95
+ }
96
+ // Whether the child missed a blob, so it is spent.
97
+ get missing() {
98
+ return this.#missed.size > 0;
99
+ }
100
+ // Every blob the child missed, with its source.
101
+ missed() {
102
+ return new Map(this.#missed);
103
+ }
104
+ }
105
+ // A harbor's name on the edge: one path segment, so it is where the
106
+ // harbor answers, `/<name>/quo`.
107
+ export const isHarborName = (name) => typeof name === 'string' && /^[a-z0-9][a-z0-9-]{0,62}$/.test(name);
108
+ const NONCE = 12;
109
+ // A pack as JSON reads, or null for anything else.
110
+ export const readPack = (value) => {
111
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
112
+ return null;
113
+ const pack = {};
114
+ for (const [name, entry] of Object.entries(value)) {
115
+ const { sealed, places } = (entry ?? {});
116
+ const carried = readCarried({ places });
117
+ if (!isHarborName(name) || !isHex(sealed) || sealed.length <= NONCE * 2 || carried === null)
118
+ return null;
119
+ pack[name] = { sealed: sealed, places: carried.places };
120
+ }
121
+ return pack;
122
+ };
123
+ const sealKey = (secret) => hkdf(utf8(secret), SEAL_INFO, 32);
124
+ // A seed sealed under a worker's secret, as a pack keeps it.
125
+ export const sealSeed = async (secret, seed, entropy = new CryptoEntropy()) => {
126
+ const nonce = entropy.draw(NONCE);
127
+ return hex(concat(nonce, await encrypt(await sealKey(secret), nonce, new Uint8Array(0), seed)));
128
+ };
129
+ const chunks = (items) => Array.from({ length: Math.ceil(items.length / BATCH) }, (_, i) => items.slice(i * BATCH, (i + 1) * BATCH));
130
+ const bytesOf = (value) => new Uint8Array(value instanceof ArrayBuffer ? value : value);
131
+ // A body keeps its keys under `root`, so one object may hold more than one
132
+ // keeping.
133
+ // A body refuses every keep while `voided` says so: in a child, from the
134
+ // moment its loader misses a module, so a spent child keeps nothing more,
135
+ // and tells the child's calls which keeps it refused and which landed.
136
+ export class EdgeMemory extends Memory {
137
+ #storage;
138
+ #entry;
139
+ #voided;
140
+ #calls;
141
+ constructor(storage, root = '', voided = () => false, calls) {
142
+ super();
143
+ this.#storage = storage;
144
+ this.#entry = `${root}${ENTRY}`;
145
+ this.#voided = voided;
146
+ this.#calls = calls;
147
+ }
148
+ #refuse() {
149
+ if (!this.#voided())
150
+ return null;
151
+ this.#calls?.refused();
152
+ return Promise.reject(new Error('a module was missed, so this call keeps nothing'));
153
+ }
154
+ async read(place) {
155
+ const prefix = `${this.#entry}${place}/`;
156
+ const listed = await this.#storage.list({ prefix });
157
+ return new Map([...listed].map(([key, value]) => [key.slice(prefix.length), bytesOf(value).slice()]));
158
+ }
159
+ write(place, entries) {
160
+ const refused = this.#refuse();
161
+ if (refused)
162
+ return refused;
163
+ const put = [];
164
+ const gone = [];
165
+ for (const [name, bytes] of entries) {
166
+ if (bytes === null)
167
+ gone.push(`${this.#entry}${place}/${name}`);
168
+ else
169
+ put.push([`${this.#entry}${place}/${name}`, bytes.slice()]);
170
+ }
171
+ return this.#storage
172
+ .transaction(async (txn) => {
173
+ for (const batch of chunks(put))
174
+ await txn.put(Object.fromEntries(batch));
175
+ for (const batch of chunks(gone))
176
+ await txn.delete(batch);
177
+ })
178
+ .then(() => this.#calls?.kept());
179
+ }
180
+ async places() {
181
+ const entry = this.#entry;
182
+ const keys = [...(await this.#storage.list({ prefix: entry })).keys()];
183
+ return [...new Set(keys.map((key) => key.slice(entry.length, key.indexOf('/', entry.length))))];
184
+ }
185
+ async forget(place) {
186
+ await this.#refuse();
187
+ const keys = [...(await this.#storage.list({ prefix: `${this.#entry}${place}/` })).keys()];
188
+ await this.#storage.transaction(async (txn) => {
189
+ for (const batch of chunks(keys))
190
+ await txn.delete(batch);
191
+ });
192
+ this.#calls?.kept();
193
+ }
194
+ }
195
+ // The seed, kept sealed under a key the worker's secret gives, so the
196
+ // storage alone opens nothing: the one a pack landed, or one drawn the
197
+ // first time it is asked for. A worker with no secret stands no harbor.
198
+ export class EdgeCustody extends Custody {
199
+ #storage;
200
+ #secret;
201
+ #entropy;
202
+ #key;
203
+ constructor(storage, secret, entropy = new CryptoEntropy(), root = '') {
204
+ super();
205
+ this.#storage = storage;
206
+ this.#secret = secret;
207
+ this.#entropy = entropy;
208
+ this.#key = `${root}${SEED}`;
209
+ }
210
+ // Whether a seed is kept, so a pack lands only where none is.
211
+ async kept() {
212
+ return (await this.#storage.get(this.#key)) !== undefined;
213
+ }
214
+ // A packed seed, sealed as `sealSeed` sealed it, kept as it came.
215
+ async land(sealed, txn = this.#storage) {
216
+ const bytes = unhex(sealed);
217
+ await txn.put({ [this.#key]: { nonce: bytes.slice(0, NONCE), sealed: bytes.slice(NONCE) } });
218
+ }
219
+ async seed() {
220
+ if (!this.#secret)
221
+ throw new Error('the worker holds no NERVUR_SECRET');
222
+ const key = await sealKey(this.#secret);
223
+ return this.#storage.transaction(async (txn) => {
224
+ const kept = (await txn.get(this.#key));
225
+ if (kept) {
226
+ const seed = await decrypt(key, bytesOf(kept.nonce), new Uint8Array(0), bytesOf(kept.sealed));
227
+ if (seed === null)
228
+ throw new Error('the seed does not open under this NERVUR_SECRET');
229
+ return seed;
230
+ }
231
+ const seed = this.#entropy.draw(32);
232
+ const nonce = this.#entropy.draw(NONCE);
233
+ await txn.put({ [this.#key]: { nonce, sealed: await encrypt(key, nonce, new Uint8Array(0), seed) } });
234
+ return seed.slice();
235
+ });
236
+ }
237
+ }
238
+ class EdgeLine {
239
+ #socket;
240
+ #writer;
241
+ #waiting = new Map();
242
+ #next = 1;
243
+ #closed = false;
244
+ constructor(socket, onClose) {
245
+ this.#socket = socket;
246
+ this.#writer = socket.writable.getWriter();
247
+ const reader = new FrameReader();
248
+ void (async () => {
249
+ try {
250
+ for await (const chunk of socket.readable) {
251
+ for (const frame of reader.push(new Uint8Array(chunk))) {
252
+ if (frame.kind === 'ask')
253
+ continue;
254
+ const done = this.#waiting.get(frame.id);
255
+ this.#waiting.delete(frame.id);
256
+ done?.(frame.kind === 'reply' ? frame.box : null);
257
+ }
258
+ if (reader.bad)
259
+ break;
260
+ }
261
+ }
262
+ catch {
263
+ // A line that broke answers nothing more.
264
+ }
265
+ this.#closed = true;
266
+ onClose();
267
+ for (const done of this.#waiting.values())
268
+ done(null);
269
+ this.#waiting.clear();
270
+ await socket.close().catch(() => undefined);
271
+ })();
272
+ }
273
+ ask(pk, box) {
274
+ if (this.#closed)
275
+ return Promise.resolve(null);
276
+ const id = this.#next;
277
+ this.#next = this.#next === MAX_ID ? 1 : this.#next + 1;
278
+ return new Promise((resolve) => {
279
+ this.#waiting.set(id, resolve);
280
+ this.#writer.write(askFrame(id, pk, box)).catch(() => resolve(null));
281
+ });
282
+ }
283
+ close() {
284
+ this.#closed = true;
285
+ return this.#socket.close().catch(() => undefined);
286
+ }
287
+ }
288
+ const sockets = 'cloudflare:sockets';
289
+ const connector = async () => {
290
+ try {
291
+ return (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ sockets))).connect;
292
+ }
293
+ catch {
294
+ return null;
295
+ }
296
+ };
297
+ // The worker's sockets under the TCP carrier: it dials on the worker's
298
+ // `connect`, one line per address, and takes no inbound TCP.
299
+ export class EdgeSockets {
300
+ #lines = new Map();
301
+ async dial(address, pk, bytes) {
302
+ const at = tcpAddress(address);
303
+ if (at === null || !isHex(pk, 64))
304
+ return UNDIALED;
305
+ const line = await this.#line(at);
306
+ return line ? line.ask(unhex(pk), bytes) : UNDIALED;
307
+ }
308
+ #line({ host, port }) {
309
+ const at = tcpAt(host, port);
310
+ let line = this.#lines.get(at);
311
+ if (!line) {
312
+ line = (async () => {
313
+ const connect = await connector();
314
+ if (!connect)
315
+ return null;
316
+ try {
317
+ const socket = connect({ hostname: host, port });
318
+ await socket.opened;
319
+ return new EdgeLine(socket, () => this.#lines.delete(at));
320
+ }
321
+ catch {
322
+ return null;
323
+ }
324
+ })();
325
+ this.#lines.set(at, line);
326
+ void line.then((open) => open ?? this.#lines.delete(at));
327
+ }
328
+ return line;
329
+ }
330
+ listen() {
331
+ return Promise.reject(new Error('an edge takes no inbound TCP'));
332
+ }
333
+ async close() {
334
+ const lines = [...this.#lines.values()];
335
+ this.#lines.clear();
336
+ for (const line of await Promise.all(lines))
337
+ await line?.close();
338
+ }
339
+ }
340
+ const edgeServe = (listening, listen) => {
341
+ listening.at = listen;
342
+ const authority = listen.port === 0 ? listen.host : `${tcpAt(listen.host, listen.port).slice('tcp://'.length)}`;
343
+ const [post, held] = listen.port === 0 ? ['https', 'wss'] : ['http', 'ws'];
344
+ return Promise.resolve({
345
+ port: listen.port,
346
+ at: [`${post}://${authority}${listen.path}`, `${held}://${authority}${listen.path}`],
347
+ close: () => {
348
+ if (listening.at === listen)
349
+ listening.at = undefined;
350
+ return Promise.resolve();
351
+ },
352
+ });
353
+ };
354
+ export class EdgeTerrain extends PointerTerrain {
355
+ given;
356
+ #listening;
357
+ constructor(parts) {
358
+ const entropy = new CryptoEntropy();
359
+ const { storage } = parts.given.state;
360
+ const loader = parts.given.loader ?? new EdgeLoader();
361
+ const listening = { at: undefined };
362
+ super({
363
+ entropy,
364
+ loader,
365
+ custody: new EdgeCustody(storage, parts.given.env.NERVUR_SECRET, entropy),
366
+ memory: new EdgeMemory(storage, '', () => loader.missing, parts.given.calls),
367
+ tcp: () => new EdgeSockets(),
368
+ web: (_ground, listen) => edgeServe(listening, listen),
369
+ defaults: parts.defaults,
370
+ });
371
+ this.given = parts.given;
372
+ this.#listening = listening;
373
+ }
374
+ // Where the web carrier listens now, which the shell answers at.
375
+ get listening() {
376
+ return this.#listening.at;
377
+ }
378
+ // A packed harbor lands where the object keeps no seed yet: its places
379
+ // first and its seed last, so a start cut short lands it whole again,
380
+ // and once the seed is kept the storage is the harbor and a new deploy
381
+ // leaves it be. A seed sealed under another secret does not open.
382
+ async claim() {
383
+ const packed = this.given.package;
384
+ const custody = this.custody;
385
+ if (!packed || (await custody.kept()))
386
+ return;
387
+ await landPlaces(this.memory, { places: packed.places });
388
+ await custody.land(packed.sealed);
389
+ }
390
+ }
391
+ const isGiven = (value) => {
392
+ const given = value;
393
+ return typeof given?.state?.storage?.transaction === 'function' && typeof given.env === 'object';
394
+ };
395
+ // A harbor handed a Durable Object's state and environment, and the
396
+ // loader of the modules its child carries.
397
+ export const edgeGround = {
398
+ name: EDGE,
399
+ fits: ({ given }) => isGiven(given),
400
+ terrain: ({ given }, defaults) => new EdgeTerrain({ given: given, defaults }),
401
+ };
402
+ // The harbor a request is for: the first segment of its path.
403
+ export const harborNameOf = (request) => {
404
+ const name = new URL(request.url).pathname.split('/')[1];
405
+ return isHarborName(name) ? name : null;
406
+ };
407
+ // The key a child is loaded under: the commit `live` names, where the
408
+ // child carries that commit's modules, and else that commit and every
409
+ // blob the child carries.
410
+ export const childKey = (live, blobs, liveBlobs) => {
411
+ const carried = [...new Set(blobs)].sort();
412
+ const same = carried.length === new Set(liveBlobs).size && liveBlobs.every((b) => carried.includes(b));
413
+ return same ? live : `${live}+${carried.join('+')}`;
414
+ };
415
+ // The whole harbor inside a child: its bodies on the storage the facet
416
+ // keeps, its loader over the modules the child's worker imported, and the
417
+ // web door the shell hands each box through. It opens the harbor once, and
418
+ // after every call says whether the shell must move to another child.
419
+ export class HarborChild {
420
+ #state;
421
+ #env;
422
+ #key;
423
+ #loader;
424
+ #calls;
425
+ #opening;
426
+ // The terrain the child opened its harbor on, which says where its web
427
+ // carrier listens.
428
+ #terrain;
429
+ #settled;
430
+ constructor(state, env, key, carried, context) {
431
+ this.#state = state;
432
+ this.#env = env;
433
+ this.#key = key;
434
+ this.#calls = new ChildCalls(context);
435
+ this.#loader = new EdgeLoader(carried, this.#calls);
436
+ }
437
+ // The harbor, opened inside the object's block so no call reaches it
438
+ // half open, its web carrier listening at `/<name>/quo`. A child that
439
+ // missed a module of `live` as the harbor booted stands nothing more:
440
+ // the shell moves to the next before it hands this one a call.
441
+ async open(name, host, port, packed, refused) {
442
+ this.#loader.refuse(refused);
443
+ this.#opening ??= this.#state.blockConcurrencyWhile(async () => {
444
+ const terrain = probeGround({ given: { state: this.#state, env: this.#env, loader: this.#loader, calls: this.#calls, ...(packed ? { package: packed } : {}) } }).terrain;
445
+ this.#terrain = terrain;
446
+ const harbor = await Harbor.open(terrain);
447
+ if (this.#loader.missing)
448
+ return harbor;
449
+ await harbor.ask({ method: 'stand' });
450
+ const { beings } = (await harbor.ask({})).answer.notes;
451
+ const web = carrierIn(beings, terrain.defaults.classes, 'web');
452
+ if (web === null)
453
+ throw new Error('no class this edge runs carries web');
454
+ if (web.open !== undefined)
455
+ await harbor.ask(web.open);
456
+ if (terrain.listening?.path !== `/${name}/quo`)
457
+ await harbor.ask({ method: 'ask', args: { being: web.key, method: 'listen', args: { host, port, path: `/${name}/quo` } } });
458
+ return harbor;
459
+ });
460
+ const harbor = await this.#opening;
461
+ const at = this.#terrain?.listening;
462
+ // A plain copy, since what crosses to the shell is cloned.
463
+ const listen = at ? { host: at.host, port: at.port, path: at.path, origins: [...at.origins] } : null;
464
+ return { listen, wanted: await this.#wanted(harbor) };
465
+ }
466
+ // One box from the web door under the shell's number for the call, its
467
+ // reply, and whether it is asked again. A call that lands in a spent
468
+ // child is asked again untouched.
469
+ async arrive(pk, box, call) {
470
+ const harbor = await this.#opening;
471
+ if (!harbor)
472
+ return { reply: null, wanted: null, again: false };
473
+ if (this.#loader.missing)
474
+ return { reply: null, wanted: await this.#wanted(harbor), again: true };
475
+ const reply = await this.#calls.run(call, () => harbor.arrive(pk, box)).catch(() => null);
476
+ return { reply, wanted: await this.#wanted(harbor), again: this.#calls.again(call) };
477
+ }
478
+ async #wanted(harbor) {
479
+ const missed = this.#loader.missed();
480
+ const catalogue = async (method) => (await harbor.ask({ method: 'ask', args: { being: 'catalogue', method } })).answer.answer;
481
+ const live = (await catalogue('live')).live;
482
+ if (missed.size === 0 && live === this.#settled)
483
+ return null;
484
+ const sources = (await catalogue('sources')).sources;
485
+ const modules = new Map(sources.map((s) => [s.blob, s.source]));
486
+ for (const [blob, source] of missed)
487
+ modules.set(blob, source);
488
+ for (const blob of this.#loader.refused.keys())
489
+ modules.delete(blob);
490
+ const liveBlobs = sources.map((s) => s.blob);
491
+ if (missed.size === 0 && childKey(live, [...modules.keys()], liveBlobs) === this.#key) {
492
+ this.#settled = live;
493
+ return null;
494
+ }
495
+ return { live, liveBlobs, modules: Object.fromEntries(modules), missed: [...missed.keys()] };
496
+ }
497
+ }
498
+ const FACET = 'harbor';
499
+ const CHILD = 'Harbor';
500
+ const MAIN = 'harbor.js';
501
+ const PROBE = 'probe.js';
502
+ const BOOT = 'boot';
503
+ const COMPATIBILITY = '2026-09-01';
504
+ // The most moves one call makes before the shell gives up on it.
505
+ const ROUNDS = 8;
506
+ // A module's file at the child's root, where its `'nervur'` names the kit.
507
+ const moduleFile = (blob) => `${blob}.js`;
508
+ // A child's main module: the kit's child as the facet's class, over the
509
+ // modules it carries, each imported by its blob's file.
510
+ const childMain = (key, blobs) => [
511
+ "import { DurableObject } from 'cloudflare:workers';",
512
+ "import { AsyncLocalStorage } from 'node:async_hooks';",
513
+ "import { HarborChild } from 'nervur';",
514
+ ...blobs.map((b, i) => `import * as m${String(i)} from '${moduleFile(b)}';`),
515
+ `const carried = [${blobs.map((b, i) => `{ blob: '${b}', module: m${String(i)} }`).join(', ')}];`,
516
+ 'export class Harbor extends DurableObject {',
517
+ ` #child = new HarborChild(this.ctx, this.env, ${JSON.stringify(key)}, carried, new AsyncLocalStorage());`,
518
+ ' open(name, host, port, packed, refused) { return this.#child.open(name, host, port, packed, refused); }',
519
+ ' arrive(pk, box, call) { return this.#child.arrive(pk, box, call); }',
520
+ '}',
521
+ '',
522
+ ].join('\n');
523
+ // A probe's main module: the module alone beside the kit, so one that
524
+ // does not load fails here and never in a harbor's child.
525
+ const probeMain = (blob) => `import '${moduleFile(blob)}';\nexport default { fetch() { return new Response(null, { status: 204 }); } };\n`;
526
+ const said = (e) => (e instanceof Error ? e.message : String(e));
527
+ // A Durable Object class whose every instance is one harbor, by the name
528
+ // the worker reached it under, the one its pack holds under that name. The
529
+ // object is a shell: it keeps the harbor's storage as its child's facet,
530
+ // answers the web at `/<name>/quo`, and hands each box to the child it
531
+ // loaded for the commit `live` names. A call that misses a module loads
532
+ // the next child carrying it and is asked there again, since a miss comes
533
+ // before `live` or any cell moves; a module that does not load on its own
534
+ // is refused with its reason, and `live` stays. An object answers the one
535
+ // name it opened under, and a name its pack does not hold is 404, with
536
+ // nothing written. The shell never runs a harbor's code and reads no cell.
537
+ export const harborShell = ({ kit, pack }) => class {
538
+ #state;
539
+ #env;
540
+ #refused = {};
541
+ #name;
542
+ #packed;
543
+ #at;
544
+ #kit;
545
+ #key;
546
+ #child;
547
+ #listen = null;
548
+ #standing;
549
+ #turn = Promise.resolve();
550
+ // The number the shell gives each call it hands a child.
551
+ #calls = 0;
552
+ // The calls inside the child now, and the moves waiting for them to end.
553
+ #inside = 0;
554
+ #ended = [];
555
+ // Where the child said the shell must go, the latest word of the child
556
+ // that stands.
557
+ #wanted;
558
+ constructor(state, env) {
559
+ this.#state = state;
560
+ this.#env = env;
561
+ }
562
+ async fetch(request) {
563
+ const name = harborNameOf(request);
564
+ if (name === null || (this.#name !== undefined && this.#name !== name))
565
+ return new Response(null, { status: 404 });
566
+ if (this.#name === undefined) {
567
+ const packed = typeof pack === 'function' ? pack(this.#env) : pack;
568
+ const held = packed && Object.hasOwn(packed, name) ? packed[name] : undefined;
569
+ if (!held)
570
+ return new Response(null, { status: 404 });
571
+ const url = new URL(request.url);
572
+ this.#name = name;
573
+ this.#packed = held;
574
+ this.#at = { host: url.hostname, port: url.port === '' ? 0 : Number(url.port) };
575
+ }
576
+ this.#standing ??= this.#serial(() => this.#open(BOOT, {}, 0)).catch((e) => {
577
+ this.#standing = undefined;
578
+ throw e;
579
+ });
580
+ await this.#standing;
581
+ const listen = this.#listen;
582
+ if (!listen)
583
+ return new Response(null, { status: 404 });
584
+ const door = { arrive: (pk, box) => this.#arrive(pk, box) };
585
+ if (request.headers.get('upgrade')?.toLowerCase() !== 'websocket')
586
+ return answerPost(door, listen, request);
587
+ const offered = (request.headers.get('sec-websocket-protocol') ?? '').split(',').map((p) => p.trim());
588
+ if (new URL(request.url).pathname !== listen.path || !offered.includes('quo'))
589
+ return new Response(null, { status: 400 });
590
+ const Pair = globalThis.WebSocketPair;
591
+ const pair = new Pair();
592
+ const [client, server] = [pair[0], pair[1]];
593
+ server.accept();
594
+ const side = { send: (body) => server.send(new Uint8Array(body)), close: () => server.close() };
595
+ server.addEventListener('message', (event) => answerHeld(door, side, typeof event.data === 'string' ? event.data : new Uint8Array(event.data)));
596
+ return new Response(null, { status: 101, webSocket: client, headers: { 'sec-websocket-protocol': 'quo' } });
597
+ }
598
+ // One move at a time.
599
+ #serial(work) {
600
+ const run = this.#turn.then(work, work);
601
+ this.#turn = run.catch(() => undefined);
602
+ return run;
603
+ }
604
+ #loader() {
605
+ const loader = this.#env.LOADER;
606
+ if (!loader)
607
+ throw new Error('the worker binds no LOADER: its wrangler.toml names a worker_loaders binding');
608
+ return loader;
609
+ }
610
+ #kitId() {
611
+ return (this.#kit ??= sha256(utf8(kit)).then((digest) => hex(digest).slice(0, 16)));
612
+ }
613
+ // The child under `key`, carrying `modules`, stood as the facet in the
614
+ // one before's place, and opened. A staging child carries a blob a
615
+ // call missed beside what `live` names, and keeps its key until that
616
+ // call, asked again, moves `live`.
617
+ async #open(key, modules, depth, staging = false) {
618
+ const id = `${await this.#kitId()}:${key}`;
619
+ const blobs = Object.keys(modules).sort();
620
+ const secret = this.#env.NERVUR_SECRET;
621
+ const code = {
622
+ compatibilityDate: COMPATIBILITY,
623
+ // `AsyncLocalStorage` alone of Node's modules, to tell calls apart.
624
+ compatibilityFlags: ['nodejs_als'],
625
+ mainModule: MAIN,
626
+ modules: { [MAIN]: { js: childMain(key, blobs) }, nervur: { js: kit }, ...Object.fromEntries(blobs.map((b) => [moduleFile(b), { js: modules[b] }])) },
627
+ env: secret === undefined ? {} : { NERVUR_SECRET: secret },
628
+ };
629
+ const worker = this.#loader().get(id, () => Promise.resolve(code));
630
+ if (this.#child)
631
+ this.#state.facets.abort(FACET, new Error('live moved'));
632
+ const child = this.#state.facets.get(FACET, () => Promise.resolve({ class: worker.getDurableObjectClass(CHILD) }));
633
+ this.#child = child;
634
+ this.#key = key;
635
+ this.#wanted = undefined;
636
+ const opened = await child.open(this.#name, this.#at.host, this.#at.port, this.#packed ?? null, { ...this.#refused });
637
+ this.#listen = opened.listen;
638
+ if (opened.wanted && (!staging || opened.wanted.missed.length > 0))
639
+ await this.#move(opened.wanted, depth + 1);
640
+ }
641
+ // Where a child said the shell must go: each blob it missed probed, one
642
+ // that does not load refused, and the child for what remains loaded.
643
+ // A call that missed kept nothing, so a fresh child takes it whole,
644
+ // under the same key where every blob it missed is refused.
645
+ async #move(wanted, depth) {
646
+ if (depth > ROUNDS)
647
+ throw new Error('the harbor settles on no child');
648
+ for (const blob of wanted.missed) {
649
+ if (Object.hasOwn(this.#refused, blob))
650
+ continue;
651
+ const reason = await this.#probe(blob, wanted.modules[blob] ?? '');
652
+ if (reason !== null)
653
+ this.#refused[blob] = reason;
654
+ }
655
+ const modules = Object.fromEntries(Object.entries(wanted.modules).filter(([blob]) => !Object.hasOwn(this.#refused, blob)));
656
+ const key = childKey(wanted.live, Object.keys(modules), wanted.liveBlobs);
657
+ const staging = wanted.missed.length > 0;
658
+ if (key === this.#key && !staging)
659
+ return;
660
+ return this.#open(key, modules, depth, staging);
661
+ }
662
+ // Null where a module loads on its own beside the kit, and else why not.
663
+ async #probe(blob, source) {
664
+ const code = {
665
+ compatibilityDate: COMPATIBILITY,
666
+ mainModule: PROBE,
667
+ modules: { [PROBE]: { js: probeMain(blob) }, nervur: { js: kit }, [moduleFile(blob)]: { js: source } },
668
+ env: {},
669
+ };
670
+ try {
671
+ const worker = this.#loader().get(`${await this.#kitId()}:probe:${blob}`, () => Promise.resolve(code));
672
+ const answered = await worker.getEntrypoint().fetch(new Request('https://probe.invalid/'));
673
+ return answered.status === 204 ? null : `the module does not load on the edge: ${await answered.text()}`;
674
+ }
675
+ catch (e) {
676
+ return `the module does not load on the edge: ${said(e)}`;
677
+ }
678
+ }
679
+ // A box handed to the child, and its answer. Where the child says the
680
+ // shell must move, the move waits until no call is inside, so none is
681
+ // cut short, and no call enters while it waits. A call that missed a
682
+ // module told nothing, and is asked again in the next child.
683
+ async #arrive(pk, box) {
684
+ for (let round = 0; round <= ROUNDS; round += 1) {
685
+ let turn;
686
+ do {
687
+ turn = this.#turn;
688
+ await turn;
689
+ } while (turn !== this.#turn);
690
+ const child = this.#child;
691
+ const call = (this.#calls += 1);
692
+ this.#inside += 1;
693
+ let arrived;
694
+ try {
695
+ arrived = await child.arrive(pk, box, call);
696
+ }
697
+ catch {
698
+ arrived = null;
699
+ }
700
+ finally {
701
+ this.#inside -= 1;
702
+ if (this.#inside === 0)
703
+ for (const end of this.#ended.splice(0))
704
+ end();
705
+ }
706
+ if (arrived === null)
707
+ return null;
708
+ if (arrived.wanted && child === this.#child) {
709
+ this.#wanted = arrived.wanted;
710
+ void this.#serial(() => this.#moved(child)).catch(() => undefined);
711
+ }
712
+ if (!arrived.again)
713
+ return arrived.reply;
714
+ }
715
+ return null;
716
+ }
717
+ // The move the child that stands asked for, once no call is inside it.
718
+ async #moved(child) {
719
+ if (this.#inside > 0)
720
+ await new Promise((end) => this.#ended.push(end));
721
+ const wanted = this.#wanted;
722
+ if (child !== this.#child || !wanted)
723
+ return;
724
+ this.#wanted = undefined;
725
+ await this.#move(wanted, 0);
726
+ }
727
+ };
728
+ // A worker of many harbors: each request goes to the object of the
729
+ // harbor its path names, in the Durable Object namespace bound as
730
+ // `binding`, and a path naming none is 404.
731
+ export const harborWorker = (binding = 'HARBOR') => ({
732
+ fetch(request, env) {
733
+ const name = harborNameOf(request);
734
+ const objects = env[binding];
735
+ if (name === null || !objects)
736
+ return Promise.resolve(new Response(null, { status: 404 }));
737
+ return objects.get(objects.idFromName(name)).fetch(request);
738
+ },
739
+ });