nervur 0.22.0 → 0.22.2-3

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 (398) hide show
  1. package/AUTHORING.md +741 -0
  2. package/KIT-SPEC.md +286 -0
  3. package/LICENSE +1 -1
  4. package/NOTICE +6 -2
  5. package/README.md +97 -424
  6. package/dist/app/app-ground.d.ts +18 -0
  7. package/dist/app/app-ground.js +25 -0
  8. package/dist/app/index.d.ts +2 -0
  9. package/dist/app/index.js +5 -0
  10. package/dist/app/native.d.ts +46 -0
  11. package/dist/app/native.js +101 -0
  12. package/dist/being/being.d.ts +229 -0
  13. package/dist/being/being.js +20 -0
  14. package/dist/being/covers.d.ts +9 -0
  15. package/dist/being/covers.js +21 -0
  16. package/dist/being/index.d.ts +3 -0
  17. package/dist/being/index.js +5 -0
  18. package/dist/being/need.d.ts +49 -0
  19. package/dist/being/need.js +33 -0
  20. package/dist/being/schema.d.ts +100 -0
  21. package/dist/being/schema.js +132 -0
  22. package/dist/being/table.d.ts +42 -0
  23. package/dist/being/table.js +288 -0
  24. package/dist/bench/bench-ground.d.ts +55 -0
  25. package/dist/bench/bench-ground.js +101 -0
  26. package/dist/bench/bench.d.ts +87 -0
  27. package/dist/bench/bench.js +260 -0
  28. package/dist/bench/fake-carry.d.ts +32 -0
  29. package/dist/bench/fake-carry.js +56 -0
  30. package/dist/bench/fake-clock.d.ts +17 -0
  31. package/dist/bench/fake-clock.js +34 -0
  32. package/dist/bench/fake-custody.d.ts +9 -0
  33. package/dist/bench/fake-custody.js +10 -0
  34. package/dist/bench/fake-faculty.d.ts +25 -0
  35. package/dist/bench/fake-faculty.js +48 -0
  36. package/dist/bench/fake-keys.d.ts +5 -0
  37. package/dist/bench/fake-keys.js +9 -0
  38. package/dist/bench/fake-memory.d.ts +14 -0
  39. package/dist/bench/fake-memory.js +46 -0
  40. package/dist/bench/fake-network.d.ts +57 -0
  41. package/dist/bench/fake-network.js +169 -0
  42. package/dist/bench/index.d.ts +11 -0
  43. package/dist/bench/index.js +13 -0
  44. package/dist/bench/seeded.d.ts +11 -0
  45. package/dist/bench/seeded.js +37 -0
  46. package/dist/bench/settle.d.ts +2 -0
  47. package/dist/bench/settle.js +49 -0
  48. package/dist/bench/stewards.d.ts +17 -0
  49. package/dist/bench/stewards.js +20 -0
  50. package/dist/bodies/class-list.d.ts +14 -0
  51. package/dist/bodies/class-list.js +33 -0
  52. package/dist/bodies/joined-carry.d.ts +23 -0
  53. package/dist/bodies/joined-carry.js +42 -0
  54. package/dist/bodies/noble-crypto.d.ts +22 -0
  55. package/dist/bodies/noble-crypto.js +88 -0
  56. package/dist/bodies/seed-keys.d.ts +10 -0
  57. package/dist/bodies/seed-keys.js +42 -0
  58. package/dist/bodies/strict-tools.d.ts +10 -0
  59. package/dist/bodies/strict-tools.js +306 -0
  60. package/dist/bodies/web-carry.d.ts +65 -0
  61. package/dist/bodies/web-carry.js +224 -0
  62. package/dist/bodies/web-clock.d.ts +12 -0
  63. package/dist/bodies/web-clock.js +33 -0
  64. package/dist/browser/browser-ground.d.ts +69 -0
  65. package/dist/browser/browser-ground.js +219 -0
  66. package/dist/browser/index.d.ts +4 -0
  67. package/dist/browser/index.js +7 -0
  68. package/dist/browser/indexeddb-memory.d.ts +17 -0
  69. package/dist/browser/indexeddb-memory.js +97 -0
  70. package/dist/browser/locked-custody.d.ts +25 -0
  71. package/dist/browser/locked-custody.js +89 -0
  72. package/dist/browser/origin-classes.d.ts +7 -0
  73. package/dist/browser/origin-classes.js +31 -0
  74. package/dist/foundation.d.ts +150 -0
  75. package/dist/foundation.js +4 -0
  76. package/dist/ground/ground.d.ts +177 -0
  77. package/dist/ground/ground.js +363 -0
  78. package/dist/house/crossing.d.ts +37 -0
  79. package/dist/house/crossing.js +131 -0
  80. package/dist/house/house.d.ts +80 -0
  81. package/dist/house/house.js +1753 -0
  82. package/dist/house/rows-shape.d.ts +103 -0
  83. package/dist/house/rows-shape.js +12 -0
  84. package/dist/house/rows.d.ts +27 -0
  85. package/dist/house/rows.js +168 -0
  86. package/dist/index.d.ts +29 -1
  87. package/dist/index.js +25 -14
  88. package/dist/node/bridge.d.ts +17 -0
  89. package/dist/node/bridge.js +188 -0
  90. package/dist/node/cli.js +170 -0
  91. package/dist/node/custody.d.ts +20 -0
  92. package/dist/node/custody.js +34 -0
  93. package/dist/node/file-keys.d.ts +7 -0
  94. package/dist/node/file-keys.js +30 -0
  95. package/dist/node/file-memory.d.ts +13 -0
  96. package/dist/node/file-memory.js +91 -0
  97. package/dist/node/folder-classes.d.ts +5 -0
  98. package/dist/node/folder-classes.js +40 -0
  99. package/dist/node/gone.d.ts +2 -0
  100. package/dist/node/gone.js +7 -0
  101. package/dist/node/hand.d.ts +29 -0
  102. package/dist/node/hand.js +85 -0
  103. package/dist/node/held-keys.d.ts +14 -0
  104. package/dist/node/held-keys.js +36 -0
  105. package/dist/node/http.d.ts +11 -0
  106. package/dist/node/http.js +90 -0
  107. package/dist/node/index.d.ts +13 -0
  108. package/dist/node/index.js +15 -0
  109. package/dist/node/keychain-keys.d.ts +10 -0
  110. package/dist/node/keychain-keys.js +51 -0
  111. package/dist/node/ledger-memory.d.ts +17 -0
  112. package/dist/node/ledger-memory.js +151 -0
  113. package/dist/node/lock.d.ts +10 -0
  114. package/dist/node/lock.js +78 -0
  115. package/dist/node/node-ground.d.ts +33 -0
  116. package/dist/node/node-ground.js +147 -0
  117. package/dist/node/notify.d.ts +1 -0
  118. package/dist/node/notify.js +18 -0
  119. package/dist/node/tcp-carry.d.ts +39 -0
  120. package/dist/node/tcp-carry.js +216 -0
  121. package/dist/node/websocket.d.ts +6 -0
  122. package/dist/node/websocket.js +118 -0
  123. package/dist/quo/frame.d.ts +20 -0
  124. package/dist/quo/frame.js +48 -0
  125. package/dist/quo/room.d.ts +154 -0
  126. package/dist/quo/room.js +362 -0
  127. package/dist/serve/index.d.ts +27 -0
  128. package/dist/serve/index.js +55 -0
  129. package/package.json +49 -47
  130. package/dist/browser.d.ts +0 -1
  131. package/dist/browser.js +0 -8
  132. package/dist/cli.js +0 -16
  133. package/dist/core/being/being.d.ts +0 -17
  134. package/dist/core/being/being.js +0 -72
  135. package/dist/core/being/digest.d.ts +0 -3
  136. package/dist/core/being/digest.js +0 -17
  137. package/dist/core/being/faculty.d.ts +0 -10
  138. package/dist/core/being/faculty.js +0 -77
  139. package/dist/core/being/index.d.ts +0 -6
  140. package/dist/core/being/index.js +0 -8
  141. package/dist/core/being/kind.d.ts +0 -6
  142. package/dist/core/being/kind.js +0 -34
  143. package/dist/core/being/types.d.ts +0 -61
  144. package/dist/core/being/types.js +0 -4
  145. package/dist/core/being/words.d.ts +0 -13
  146. package/dist/core/being/words.js +0 -14
  147. package/dist/core/browser/index.d.ts +0 -37
  148. package/dist/core/browser/index.js +0 -196
  149. package/dist/core/browser/worker.d.ts +0 -11
  150. package/dist/core/browser/worker.js +0 -37
  151. package/dist/core/cli/command.d.ts +0 -13
  152. package/dist/core/cli/command.js +0 -330
  153. package/dist/core/cli/edge.d.ts +0 -7
  154. package/dist/core/cli/edge.js +0 -79
  155. package/dist/core/cli/harbor.d.ts +0 -25
  156. package/dist/core/cli/harbor.js +0 -115
  157. package/dist/core/contract/index.d.ts +0 -42
  158. package/dist/core/contract/index.js +0 -34
  159. package/dist/core/contract/link.d.ts +0 -4
  160. package/dist/core/contract/link.js +0 -45
  161. package/dist/core/crypto/aes.d.ts +0 -3
  162. package/dist/core/crypto/aes.js +0 -24
  163. package/dist/core/crypto/bytes.d.ts +0 -6
  164. package/dist/core/crypto/bytes.js +0 -33
  165. package/dist/core/crypto/ed25519.d.ts +0 -3
  166. package/dist/core/crypto/ed25519.js +0 -93
  167. package/dist/core/crypto/hash.d.ts +0 -2
  168. package/dist/core/crypto/hash.js +0 -11
  169. package/dist/core/crypto/index.d.ts +0 -7
  170. package/dist/core/crypto/index.js +0 -10
  171. package/dist/core/crypto/json.d.ts +0 -8
  172. package/dist/core/crypto/json.js +0 -250
  173. package/dist/core/crypto/mlkem.d.ts +0 -12
  174. package/dist/core/crypto/mlkem.js +0 -36
  175. package/dist/core/crypto/subtle.d.ts +0 -3
  176. package/dist/core/crypto/subtle.js +0 -11
  177. package/dist/core/crypto/x25519.d.ts +0 -2
  178. package/dist/core/crypto/x25519.js +0 -24
  179. package/dist/core/edge/index.d.ts +0 -164
  180. package/dist/core/edge/index.js +0 -739
  181. package/dist/core/edge/kit.d.ts +0 -2
  182. package/dist/core/edge/kit.js +0 -2
  183. package/dist/core/folder/index.d.ts +0 -18
  184. package/dist/core/folder/index.js +0 -130
  185. package/dist/core/git/http.d.ts +0 -6
  186. package/dist/core/git/http.js +0 -102
  187. package/dist/core/git/index.d.ts +0 -5
  188. package/dist/core/git/index.js +0 -10
  189. package/dist/core/git/object.d.ts +0 -40
  190. package/dist/core/git/object.js +0 -103
  191. package/dist/core/git/pack.d.ts +0 -9
  192. package/dist/core/git/pack.js +0 -171
  193. package/dist/core/git/store.d.ts +0 -23
  194. package/dist/core/git/store.js +0 -120
  195. package/dist/core/git/zlib.d.ts +0 -6
  196. package/dist/core/git/zlib.js +0 -210
  197. package/dist/core/harbor/carrying.d.ts +0 -51
  198. package/dist/core/harbor/carrying.js +0 -69
  199. package/dist/core/harbor/catalogue.d.ts +0 -33
  200. package/dist/core/harbor/catalogue.js +0 -365
  201. package/dist/core/harbor/dna.d.ts +0 -13
  202. package/dist/core/harbor/dna.js +0 -120
  203. package/dist/core/harbor/dock.d.ts +0 -62
  204. package/dist/core/harbor/dock.js +0 -381
  205. package/dist/core/harbor/harbor.d.ts +0 -16
  206. package/dist/core/harbor/harbor.js +0 -438
  207. package/dist/core/harbor/index.d.ts +0 -11
  208. package/dist/core/harbor/index.js +0 -14
  209. package/dist/core/harbor/memory.d.ts +0 -10
  210. package/dist/core/harbor/memory.js +0 -25
  211. package/dist/core/harbor/package.d.ts +0 -20
  212. package/dist/core/harbor/package.js +0 -172
  213. package/dist/core/harbor/probe.d.ts +0 -22
  214. package/dist/core/harbor/probe.js +0 -15
  215. package/dist/core/harbor/registry.d.ts +0 -15
  216. package/dist/core/harbor/registry.js +0 -110
  217. package/dist/core/harbor/root-line.d.ts +0 -7
  218. package/dist/core/harbor/root-line.js +0 -9
  219. package/dist/core/harbor/terrain.d.ts +0 -25
  220. package/dist/core/harbor/terrain.js +0 -28
  221. package/dist/core/http/index.d.ts +0 -2
  222. package/dist/core/http/index.js +0 -154
  223. package/dist/core/http/websocket.d.ts +0 -29
  224. package/dist/core/http/websocket.js +0 -133
  225. package/dist/core/line/answer.d.ts +0 -8
  226. package/dist/core/line/answer.js +0 -60
  227. package/dist/core/line/frame.d.ts +0 -29
  228. package/dist/core/line/frame.js +0 -77
  229. package/dist/core/line/ground.d.ts +0 -3
  230. package/dist/core/line/ground.js +0 -7
  231. package/dist/core/line/index.d.ts +0 -4
  232. package/dist/core/line/index.js +0 -8
  233. package/dist/core/line/web.d.ts +0 -12
  234. package/dist/core/line/web.js +0 -117
  235. package/dist/core/node/index.d.ts +0 -23
  236. package/dist/core/node/index.js +0 -124
  237. package/dist/core/pointer/bodies.d.ts +0 -31
  238. package/dist/core/pointer/bodies.js +0 -118
  239. package/dist/core/pointer/index.d.ts +0 -2
  240. package/dist/core/pointer/index.js +0 -4
  241. package/dist/core/pointer/world.d.ts +0 -45
  242. package/dist/core/pointer/world.js +0 -106
  243. package/dist/core/proof/contracts.d.ts +0 -19
  244. package/dist/core/proof/contracts.js +0 -116
  245. package/dist/core/proof/expect.d.ts +0 -6
  246. package/dist/core/proof/expect.js +0 -18
  247. package/dist/core/proof/index.d.ts +0 -3
  248. package/dist/core/proof/index.js +0 -14
  249. package/dist/core/proof/law.d.ts +0 -25
  250. package/dist/core/proof/law.js +0 -715
  251. package/dist/core/quo/address.d.ts +0 -19
  252. package/dist/core/quo/address.js +0 -84
  253. package/dist/core/quo/door.d.ts +0 -34
  254. package/dist/core/quo/door.js +0 -174
  255. package/dist/core/quo/index.d.ts +0 -9
  256. package/dist/core/quo/index.js +0 -12
  257. package/dist/core/quo/invitation.d.ts +0 -8
  258. package/dist/core/quo/invitation.js +0 -19
  259. package/dist/core/quo/keys.d.ts +0 -40
  260. package/dist/core/quo/keys.js +0 -79
  261. package/dist/core/quo/payload.d.ts +0 -15
  262. package/dist/core/quo/payload.js +0 -55
  263. package/dist/core/quo/relations.d.ts +0 -40
  264. package/dist/core/quo/relations.js +0 -33
  265. package/dist/core/quo/reply.d.ts +0 -13
  266. package/dist/core/quo/reply.js +0 -35
  267. package/dist/core/quo/seal.d.ts +0 -42
  268. package/dist/core/quo/seal.js +0 -78
  269. package/dist/core/quo/standing.d.ts +0 -39
  270. package/dist/core/quo/standing.js +0 -90
  271. package/dist/core/tcp/index.d.ts +0 -27
  272. package/dist/core/tcp/index.js +0 -209
  273. package/dist/core/ward/allowance.d.ts +0 -14
  274. package/dist/core/ward/allowance.js +0 -32
  275. package/dist/core/ward/cells.d.ts +0 -7
  276. package/dist/core/ward/cells.js +0 -74
  277. package/dist/core/ward/house.d.ts +0 -67
  278. package/dist/core/ward/house.js +0 -473
  279. package/dist/core/ward/index.d.ts +0 -5
  280. package/dist/core/ward/index.js +0 -7
  281. package/dist/core/ward/partition.d.ts +0 -50
  282. package/dist/core/ward/partition.js +0 -66
  283. package/dist/core/ward/stance.d.ts +0 -29
  284. package/dist/core/ward/stance.js +0 -186
  285. package/dist/core/ward/ward.d.ts +0 -44
  286. package/dist/core/ward/ward.js +0 -140
  287. package/dist/core.d.ts +0 -10
  288. package/dist/core.js +0 -19
  289. package/dist/defaults/dock/index.d.ts +0 -4
  290. package/dist/defaults/dock/index.js +0 -11
  291. package/dist/defaults/index.d.ts +0 -5
  292. package/dist/defaults/index.js +0 -10
  293. package/dist/defaults/memory/index.d.ts +0 -12
  294. package/dist/defaults/memory/index.js +0 -39
  295. package/dist/defaults/tcp/index.d.ts +0 -16
  296. package/dist/defaults/tcp/index.js +0 -82
  297. package/dist/defaults/ward/index.d.ts +0 -4
  298. package/dist/defaults/ward/index.js +0 -11
  299. package/dist/defaults/web/index.d.ts +0 -15
  300. package/dist/defaults/web/index.js +0 -90
  301. package/dist/edge.d.ts +0 -1
  302. package/dist/edge.js +0 -9
  303. package/dist/kit.d.ts +0 -4
  304. package/dist/kit.js +0 -12
  305. package/dist/node.d.ts +0 -1
  306. package/dist/node.js +0 -8
  307. package/src/browser.ts +0 -10
  308. package/src/cli.ts +0 -15
  309. package/src/core/being/being.ts +0 -84
  310. package/src/core/being/digest.ts +0 -18
  311. package/src/core/being/faculty.ts +0 -76
  312. package/src/core/being/index.ts +0 -8
  313. package/src/core/being/kind.ts +0 -37
  314. package/src/core/being/types.ts +0 -74
  315. package/src/core/being/words.ts +0 -31
  316. package/src/core/browser/index.ts +0 -219
  317. package/src/core/browser/worker.ts +0 -63
  318. package/src/core/cli/command.ts +0 -311
  319. package/src/core/cli/edge.ts +0 -86
  320. package/src/core/cli/harbor.ts +0 -120
  321. package/src/core/contract/index.ts +0 -82
  322. package/src/core/contract/link.ts +0 -58
  323. package/src/core/crypto/aes.ts +0 -26
  324. package/src/core/crypto/bytes.ts +0 -37
  325. package/src/core/crypto/ed25519.ts +0 -92
  326. package/src/core/crypto/hash.ts +0 -14
  327. package/src/core/crypto/index.ts +0 -10
  328. package/src/core/crypto/json.ts +0 -241
  329. package/src/core/crypto/mlkem.ts +0 -38
  330. package/src/core/crypto/subtle.ts +0 -13
  331. package/src/core/crypto/x25519.ts +0 -23
  332. package/src/core/edge/index.ts +0 -865
  333. package/src/core/folder/index.ts +0 -137
  334. package/src/core/git/http.ts +0 -103
  335. package/src/core/git/index.ts +0 -10
  336. package/src/core/git/object.ts +0 -116
  337. package/src/core/git/pack.ts +0 -147
  338. package/src/core/git/store.ts +0 -122
  339. package/src/core/git/zlib.ts +0 -197
  340. package/src/core/harbor/carrying.ts +0 -124
  341. package/src/core/harbor/catalogue.ts +0 -386
  342. package/src/core/harbor/dna.ts +0 -116
  343. package/src/core/harbor/dock.ts +0 -420
  344. package/src/core/harbor/harbor.ts +0 -433
  345. package/src/core/harbor/index.ts +0 -14
  346. package/src/core/harbor/memory.ts +0 -31
  347. package/src/core/harbor/package.ts +0 -195
  348. package/src/core/harbor/probe.ts +0 -39
  349. package/src/core/harbor/registry.ts +0 -107
  350. package/src/core/harbor/root-line.ts +0 -19
  351. package/src/core/harbor/terrain.ts +0 -54
  352. package/src/core/http/index.ts +0 -147
  353. package/src/core/http/websocket.ts +0 -124
  354. package/src/core/line/answer.ts +0 -60
  355. package/src/core/line/frame.ts +0 -83
  356. package/src/core/line/ground.ts +0 -18
  357. package/src/core/line/index.ts +0 -8
  358. package/src/core/line/web.ts +0 -123
  359. package/src/core/node/index.ts +0 -139
  360. package/src/core/pointer/bodies.ts +0 -122
  361. package/src/core/pointer/index.ts +0 -4
  362. package/src/core/pointer/world.ts +0 -134
  363. package/src/core/proof/contracts.ts +0 -134
  364. package/src/core/proof/expect.ts +0 -25
  365. package/src/core/proof/index.ts +0 -14
  366. package/src/core/proof/law.ts +0 -742
  367. package/src/core/quo/address.ts +0 -87
  368. package/src/core/quo/door.ts +0 -180
  369. package/src/core/quo/index.ts +0 -12
  370. package/src/core/quo/invitation.ts +0 -19
  371. package/src/core/quo/keys.ts +0 -91
  372. package/src/core/quo/payload.ts +0 -61
  373. package/src/core/quo/relations.ts +0 -62
  374. package/src/core/quo/reply.ts +0 -38
  375. package/src/core/quo/seal.ts +0 -101
  376. package/src/core/quo/standing.ts +0 -111
  377. package/src/core/stand/main.ts +0 -20
  378. package/src/core/stand/stand.ts +0 -259
  379. package/src/core/tcp/index.ts +0 -227
  380. package/src/core/ward/allowance.ts +0 -47
  381. package/src/core/ward/cells.ts +0 -77
  382. package/src/core/ward/house.ts +0 -524
  383. package/src/core/ward/index.ts +0 -7
  384. package/src/core/ward/partition.ts +0 -117
  385. package/src/core/ward/stance.ts +0 -207
  386. package/src/core/ward/ward.ts +0 -166
  387. package/src/core.ts +0 -55
  388. package/src/defaults/dock/index.ts +0 -12
  389. package/src/defaults/index.ts +0 -10
  390. package/src/defaults/memory/index.ts +0 -50
  391. package/src/defaults/tcp/index.ts +0 -89
  392. package/src/defaults/ward/index.ts +0 -12
  393. package/src/defaults/web/index.ts +0 -97
  394. package/src/edge.ts +0 -11
  395. package/src/index.ts +0 -18
  396. package/src/kit.ts +0 -15
  397. package/src/node.ts +0 -10
  398. /package/dist/{cli.d.ts → node/cli.d.ts} +0 -0
@@ -0,0 +1,132 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // The schema builder. Each call writes JSON Schema 2020-12 in the closed
3
+ // subset the house checks, and carries the TypeScript type beside it.
4
+ export const HANDLE_MEDIA = 'application/vnd.nervur.handle';
5
+ export const INVITATION_MEDIA = 'application/vnd.nervur.invitation';
6
+ /** The keywords the house checks, and no other. */
7
+ export const KEYWORDS = new Set([
8
+ 'type',
9
+ 'properties',
10
+ 'required',
11
+ 'additionalProperties',
12
+ 'enum',
13
+ 'const',
14
+ 'items',
15
+ 'minLength',
16
+ 'maxLength',
17
+ 'minimum',
18
+ 'maximum',
19
+ 'pattern',
20
+ 'anyOf',
21
+ 'contentEncoding',
22
+ 'contentMediaType',
23
+ ]);
24
+ const TYPES = new Set(['object', 'array', 'string', 'number', 'integer', 'boolean', 'null']);
25
+ const OPTIONAL = Symbol('optional');
26
+ const frozen = (schema) => Object.freeze(schema);
27
+ const bounded = (schema, bounds = {}) => {
28
+ for (const [key, value] of Object.entries(bounds))
29
+ if (value !== undefined)
30
+ Object.assign(schema, { [key]: value });
31
+ return schema;
32
+ };
33
+ export const s = Object.freeze({
34
+ object: (properties) => {
35
+ const required = Object.keys(properties).filter((key) => !(OPTIONAL in properties[key]));
36
+ const plain = Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, strip(value)]));
37
+ return frozen({ type: 'object', properties: Object.freeze(plain), required: Object.freeze(required), additionalProperties: false });
38
+ },
39
+ string: (lengths) => frozen(bounded({ type: 'string' }, lengths)),
40
+ number: (bounds) => frozen(bounded({ type: 'number' }, bounds)),
41
+ integer: (bounds) => frozen(bounded({ type: 'integer' }, bounds)),
42
+ boolean: () => frozen({ type: 'boolean' }),
43
+ array: (items) => frozen({ type: 'array', items: strip(items) }),
44
+ enum: (values) => frozen({ enum: Object.freeze([...values]) }),
45
+ const: (value) => frozen({ const: value }),
46
+ // Lengths count bytes, and the schema counts their hex, two digits a byte.
47
+ bytes: (lengths) => frozen(bounded({ type: 'string', contentEncoding: 'base16' }, { minLength: lengths?.minLength === undefined ? undefined : lengths.minLength * 2, maxLength: lengths?.maxLength === undefined ? undefined : lengths.maxLength * 2 })),
48
+ optional: (schema) => Object.freeze({ ...schema, [OPTIONAL]: true }),
49
+ handle: () => frozen({ type: 'string', contentEncoding: 'base16', contentMediaType: HANDLE_MEDIA }),
50
+ invitation: () => frozen({ type: 'string', contentEncoding: 'base16', contentMediaType: INVITATION_MEDIA }),
51
+ reply: (method) => frozen({
52
+ anyOf: Object.freeze([
53
+ s.object({ result: method.result ?? s.const(null) }),
54
+ s.object({ error: s.object({ message: s.string() }) }),
55
+ ]),
56
+ }),
57
+ });
58
+ const strip = (schema) => {
59
+ if (!(OPTIONAL in schema))
60
+ return schema;
61
+ const { [OPTIONAL]: _mark, ...plain } = schema;
62
+ return frozen(plain);
63
+ };
64
+ /**
65
+ * Every way a schema leaves the subset, each as a sentence naming where.
66
+ * The empty list is a schema the house checks.
67
+ */
68
+ export const outside = (schema, at = 'the schema') => {
69
+ if (typeof schema !== 'object' || schema === null || Array.isArray(schema))
70
+ return [`${at} is not a schema object`];
71
+ const found = [];
72
+ const node = schema;
73
+ for (const key of Object.keys(node))
74
+ if (!KEYWORDS.has(key))
75
+ found.push(`${at} names ${key}, outside the subset`);
76
+ const type = node.type;
77
+ if (type !== undefined && !(typeof type === 'string' && TYPES.has(type)))
78
+ found.push(`${at} has a type the subset does not name`);
79
+ if (node.contentEncoding !== undefined && node.contentEncoding !== 'base16')
80
+ found.push(`${at} has a contentEncoding other than base16`);
81
+ const media = node.contentMediaType;
82
+ if (media !== undefined && media !== HANDLE_MEDIA && media !== INVITATION_MEDIA)
83
+ found.push(`${at} has a contentMediaType that is not a mark`);
84
+ if (media !== undefined && node.contentEncoding !== 'base16')
85
+ found.push(`${at} marks a relation that is not base16`);
86
+ for (const key of ['minLength', 'maxLength']) {
87
+ const value = node[key];
88
+ if (value !== undefined && !(Number.isInteger(value) && value >= 0))
89
+ found.push(`${at} has a ${key} that is not a count`);
90
+ }
91
+ for (const key of ['minimum', 'maximum']) {
92
+ if (node[key] !== undefined && !Number.isFinite(node[key]))
93
+ found.push(`${at} has a ${key} that is not a number`);
94
+ }
95
+ if (node.pattern !== undefined)
96
+ found.push(...pattern(node.pattern, at));
97
+ if (node.properties !== undefined) {
98
+ if (typeof node.properties !== 'object' || node.properties === null || Array.isArray(node.properties))
99
+ found.push(`${at} has properties that are not an object`);
100
+ else
101
+ for (const [key, value] of Object.entries(node.properties))
102
+ found.push(...outside(value, `${at}.${key}`));
103
+ }
104
+ if (node.required !== undefined && !(Array.isArray(node.required) && node.required.every((key) => typeof key === 'string')))
105
+ found.push(`${at} has required that is not a list of names`);
106
+ if (node.additionalProperties !== undefined && typeof node.additionalProperties !== 'boolean')
107
+ found.push(`${at} has additionalProperties that is not true or false`);
108
+ if (node.enum !== undefined && !(Array.isArray(node.enum) && node.enum.every(scalar)))
109
+ found.push(`${at} has an enum that is not a list of scalars`);
110
+ if ('const' in node && !scalar(node.const))
111
+ found.push(`${at} has a const that is not a scalar`);
112
+ if (node.items !== undefined)
113
+ found.push(...outside(node.items, `${at}[]`));
114
+ if (node.anyOf !== undefined) {
115
+ if (!Array.isArray(node.anyOf) || node.anyOf.length === 0)
116
+ found.push(`${at} has an anyOf that is not a list of schemas`);
117
+ else
118
+ node.anyOf.forEach((branch, index) => found.push(...outside(branch, `${at}|${index}`)));
119
+ }
120
+ return found;
121
+ };
122
+ const scalar = (value) => value === null || ['string', 'number', 'boolean'].includes(typeof value);
123
+ const pattern = (value, at) => {
124
+ if (typeof value !== 'string')
125
+ return [`${at} has a pattern that is not a string`];
126
+ try {
127
+ return RegExp(value, 'u').unicode ? [] : [`${at} has a pattern that is not unicode`];
128
+ }
129
+ catch {
130
+ return [`${at} has a pattern that does not compile`];
131
+ }
132
+ };
@@ -0,0 +1,42 @@
1
+ import { type Json, type Me } from './being.ts';
2
+ import { type Blueprint, type BlueprintMethod } from './need.ts';
3
+ /** The roles the house names. */
4
+ export declare const HOUSE_ROLES: ReadonlySet<string>;
5
+ /** The state of a class that names none. */
6
+ export declare const READY = "ready";
7
+ /** An ask's entry, settled. `null` is the entry's omission. */
8
+ export interface TableEntry extends BlueprintMethod {
9
+ readonly method: string;
10
+ /** Where the ask exists; `null`, every state. */
11
+ readonly in: readonly string[] | null;
12
+ /** Who may call it; `null`, every occupant but handles. */
13
+ readonly for: readonly string[] | null;
14
+ /** Where it may land; `null`, the state it began in. */
15
+ readonly to: readonly string[] | null;
16
+ /** Its author wrote `idempotent: false`, which a public being's default needs to know. */
17
+ readonly effect: boolean;
18
+ readonly examples: readonly Json[];
19
+ }
20
+ /** A class as the house reads her. */
21
+ export interface Table {
22
+ readonly kind: string;
23
+ readonly description?: string;
24
+ readonly view?: string;
25
+ readonly cells: Readonly<Record<string, Json>>;
26
+ readonly needs: Readonly<Record<string, Blueprint>>;
27
+ readonly roles: Readonly<Record<string, (asker: never, me: never) => boolean>>;
28
+ readonly state: (me: Me<Record<string, Json>>) => string;
29
+ /** The state of her default cells. */
30
+ readonly first: string;
31
+ readonly states: readonly string[];
32
+ readonly asks: Readonly<Record<string, TableEntry>>;
33
+ }
34
+ /** A class the house will not resolve, and every reason why. */
35
+ export declare class Refused extends Error {
36
+ readonly reasons: readonly string[];
37
+ constructor(what: string, reasons: readonly string[]);
38
+ }
39
+ /** Whether a value is JSON, as cells and notes hold. */
40
+ export declare const isJson: (value: unknown) => value is Json;
41
+ /** Her class checked and settled, or refused with every reason. */
42
+ export declare const resolve: (Class: unknown) => Table;
@@ -0,0 +1,288 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A class resolved: her declaration checked and settled into the table the
3
+ // house reads. Every way a class fails is gathered, and the refusal names
4
+ // each one.
5
+ import { DECLARATION, MEMBERS } from './being.js';
6
+ import { blueprintOf, method, WAIT_BOUND } from './need.js';
7
+ import { outside } from './schema.js';
8
+ /** The roles the house names. */
9
+ export const HOUSE_ROLES = new Set(['handle', 'stranger', 'steward', 'being', 'root']);
10
+ /** The state of a class that names none. */
11
+ export const READY = 'ready';
12
+ /** A class the house will not resolve, and every reason why. */
13
+ export class Refused extends Error {
14
+ reasons;
15
+ constructor(what, reasons) {
16
+ super(`${what} is refused: ${reasons.join('; ')}`);
17
+ this.name = 'Refused';
18
+ this.reasons = reasons;
19
+ }
20
+ }
21
+ const KIND = /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*){2,}$/;
22
+ const VIEW_BYTES = 65_536;
23
+ const NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
24
+ const BLUEPRINT_NAME = /^[a-z][a-z0-9._-]*$/;
25
+ const ENTRY_FIELDS = new Set(['in', 'for', 'to', 'args', 'result', 'hints', 'examples', 'description', 'wait']);
26
+ const METHOD_FIELDS = new Set(['args', 'result', 'hints', 'description', 'wait']);
27
+ const HINTS = new Set(['readOnly', 'idempotent', 'destructive']);
28
+ const EXAMPLE_FIELDS = new Set(['description', 'cells', 'role', 'args', 'fakes', 'gives']);
29
+ const EVERY_OBJECT = new Set(Object.getOwnPropertyNames(Object.prototype));
30
+ const plain = (value) => {
31
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
32
+ return false;
33
+ const proto = Object.getPrototypeOf(value);
34
+ return proto === Object.prototype || proto === null;
35
+ };
36
+ /** Whether a value is JSON, as cells and notes hold. */
37
+ export const isJson = (value) => {
38
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
39
+ return true;
40
+ if (typeof value === 'number')
41
+ return Number.isFinite(value);
42
+ if (Array.isArray(value))
43
+ return value.every(isJson);
44
+ return plain(value) && Object.values(value).every(isJson);
45
+ };
46
+ const names = (value, where, reasons) => {
47
+ if (value === undefined)
48
+ return null;
49
+ const list = typeof value === 'string' ? [value] : value;
50
+ if (!Array.isArray(list) || list.length === 0 || !list.every((name) => typeof name === 'string' && name.length > 0)) {
51
+ reasons.push(`${where} is not a name or a list of names`);
52
+ return [];
53
+ }
54
+ return Object.freeze([...new Set(list)]);
55
+ };
56
+ // Args are always an object: one, or a choice of several, as a reply is.
57
+ const objectSchema = (schema) => schema.type === 'object' || (Array.isArray(schema.anyOf) && schema.anyOf.length > 0 && schema.anyOf.every((branch) => plain(branch) && objectSchema(branch)));
58
+ const methodChecked = (spec, fields, where, reasons) => {
59
+ if (!plain(spec)) {
60
+ reasons.push(`${where} is not an object`);
61
+ return undefined;
62
+ }
63
+ for (const key of Object.keys(spec))
64
+ if (!fields.has(key))
65
+ reasons.push(`${where} names ${key}, which is no field`);
66
+ const { args, result, hints, description, wait } = spec;
67
+ if (wait !== undefined && !(Number.isInteger(wait) && wait > 0 && wait <= WAIT_BOUND))
68
+ reasons.push(`${where}.wait is not a count of milliseconds within five minutes`);
69
+ if (args !== undefined) {
70
+ reasons.push(...outside(args, `${where}.args`));
71
+ if (plain(args) && !objectSchema(args))
72
+ reasons.push(`${where}.args is not an object schema`);
73
+ }
74
+ if (result !== undefined)
75
+ reasons.push(...outside(result, `${where}.result`));
76
+ if (description !== undefined && typeof description !== 'string')
77
+ reasons.push(`${where}.description is not a string`);
78
+ if (hints !== undefined) {
79
+ if (!plain(hints))
80
+ reasons.push(`${where}.hints is not an object`);
81
+ else {
82
+ for (const [key, value] of Object.entries(hints)) {
83
+ if (!HINTS.has(key))
84
+ reasons.push(`${where}.hints names ${key}, which is no hint`);
85
+ else if (typeof value !== 'boolean')
86
+ reasons.push(`${where}.hints.${key} is not true or false`);
87
+ }
88
+ if (hints.readOnly === true && hints.idempotent === false)
89
+ reasons.push(`${where} is readOnly and not idempotent`);
90
+ }
91
+ }
92
+ return method(spec);
93
+ };
94
+ const blueprintChecked = (blueprint, where, reasons) => {
95
+ if (typeof blueprint.name !== 'string' || !BLUEPRINT_NAME.test(blueprint.name))
96
+ reasons.push(`${where} has a name that is no blueprint name`);
97
+ for (const [name, spec] of Object.entries(blueprint.methods)) {
98
+ if (!NAME.test(name))
99
+ reasons.push(`${where}.${name} is no method name`);
100
+ methodChecked(spec, METHOD_FIELDS, `${where}.${name}`, reasons);
101
+ }
102
+ return blueprint;
103
+ };
104
+ const examplesChecked = (examples, where, reasons) => {
105
+ if (examples === undefined)
106
+ return [];
107
+ if (!Array.isArray(examples)) {
108
+ reasons.push(`${where}.examples is not a list`);
109
+ return [];
110
+ }
111
+ examples.forEach((example, index) => {
112
+ if (!plain(example) || !isJson(example)) {
113
+ reasons.push(`${where}.examples[${index}] is not a JSON object`);
114
+ return;
115
+ }
116
+ for (const key of Object.keys(example))
117
+ if (!EXAMPLE_FIELDS.has(key))
118
+ reasons.push(`${where}.examples[${index}] names ${key}, which is no field`);
119
+ });
120
+ return Object.freeze([...examples]);
121
+ };
122
+ /** Her class checked and settled, or refused with every reason. */
123
+ export const resolve = (Class) => {
124
+ const declaration = typeof Class === 'function' ? Class[DECLARATION] : undefined;
125
+ if (declaration === undefined)
126
+ throw new Refused('the class', ['it is not a class of Being.of']);
127
+ const reasons = [];
128
+ const kind = declaration.kind;
129
+ if (typeof kind !== 'string' || !KIND.test(kind))
130
+ reasons.push('its kind is not a reversed domain and a name');
131
+ const what = typeof kind === 'string' ? kind : 'the class';
132
+ if (declaration.description !== undefined && typeof declaration.description !== 'string')
133
+ reasons.push('its description is not a string');
134
+ // A view is data a screen renders, never more than a screen holds.
135
+ if (declaration.view !== undefined && (typeof declaration.view !== 'string' || new TextEncoder().encode(declaration.view).length > VIEW_BYTES))
136
+ reasons.push('its view is not text of at most 64 KiB');
137
+ const cells = declaration.cells ?? {};
138
+ if (!plain(cells) || !isJson(cells))
139
+ reasons.push('its cells are not a JSON object');
140
+ // Needs, each a blueprint under her own member name.
141
+ const needs = {};
142
+ const declaredNeeds = declaration.needs ?? {};
143
+ if (!plain(declaredNeeds))
144
+ reasons.push('its needs are not an object');
145
+ else {
146
+ for (const [member, value] of Object.entries(declaredNeeds)) {
147
+ const blueprint = blueprintOf(value);
148
+ if (blueprint === undefined)
149
+ reasons.push(`its need ${member} is not a need`);
150
+ else
151
+ needs[member] = blueprintChecked(blueprint, `its need ${member}`, reasons);
152
+ }
153
+ }
154
+ // Roles, the author's tests over the asker and her cells.
155
+ const roles = declaration.roles ?? {};
156
+ if (!plain(roles))
157
+ reasons.push('its roles are not an object');
158
+ for (const [name, test] of Object.entries(plain(roles) ? roles : {})) {
159
+ if (HOUSE_ROLES.has(name))
160
+ reasons.push(`its role ${name} is the house's`);
161
+ if (typeof test !== 'function')
162
+ reasons.push(`its role ${name} is not a function`);
163
+ }
164
+ // Asks, each an entry and a method.
165
+ const asks = {};
166
+ const declaredAsks = declaration.asks;
167
+ if (!plain(declaredAsks))
168
+ reasons.push('its asks are not an object');
169
+ const prototype = Class.prototype;
170
+ for (const [name, entry] of Object.entries(plain(declaredAsks) ? declaredAsks : {})) {
171
+ const where = `its ask ${name}`;
172
+ if (!NAME.test(name))
173
+ reasons.push(`${where} is no method name`);
174
+ const settled = methodChecked(entry, ENTRY_FIELDS, where, reasons);
175
+ if (settled === undefined)
176
+ continue;
177
+ const raw = entry;
178
+ if (typeof prototype[name] !== 'function')
179
+ reasons.push(`${where} has no method`);
180
+ asks[name] = Object.freeze({
181
+ ...settled,
182
+ method: name,
183
+ in: names(raw.in, `${where}.in`, reasons),
184
+ for: names(raw.for, `${where}.for`, reasons),
185
+ to: names(raw.to, `${where}.to`, reasons),
186
+ effect: raw.hints?.idempotent === false,
187
+ examples: examplesChecked(raw.examples, where, reasons),
188
+ });
189
+ }
190
+ // Member names: a need, an ask, a member of Being and her own methods.
191
+ const own = new Set();
192
+ for (let proto = prototype; proto !== null && proto !== Object.prototype; proto = Object.getPrototypeOf(proto)) {
193
+ for (const key of Object.getOwnPropertyNames(proto))
194
+ if (key !== 'constructor')
195
+ own.add(key);
196
+ }
197
+ for (const member of Object.keys(needs)) {
198
+ if (member in asks)
199
+ reasons.push(`its need ${member} and its ask ${member} share a name`);
200
+ if (MEMBERS.has(member))
201
+ reasons.push(`its need ${member} and Being's ${member} share a name`);
202
+ if (own.has(member))
203
+ reasons.push(`its need ${member} and its method ${member} share a name`);
204
+ if (EVERY_OBJECT.has(member))
205
+ reasons.push(`its need ${member} is a member of every object`);
206
+ }
207
+ for (const name of Object.keys(asks)) {
208
+ if (MEMBERS.has(name))
209
+ reasons.push(`its ask ${name} and Being's ${name} share a name`);
210
+ if (EVERY_OBJECT.has(name))
211
+ reasons.push(`its ask ${name} is a member of every object`);
212
+ }
213
+ // The state, and the state of her default cells.
214
+ const state = declaration.state ?? (() => READY);
215
+ let first = READY;
216
+ if (declaration.state !== undefined) {
217
+ if (typeof declaration.state !== 'function')
218
+ reasons.push('its state is not a function');
219
+ else {
220
+ try {
221
+ first = declaration.state({ id: '', position: 'normal', cells: cells });
222
+ }
223
+ catch {
224
+ reasons.push('its state throws on its default cells');
225
+ }
226
+ if (typeof first !== 'string' || first.length === 0)
227
+ reasons.push('its state names no state on its default cells');
228
+ }
229
+ }
230
+ const states = table(asks, roles, first, declaration.state !== undefined, reasons);
231
+ if (reasons.length > 0)
232
+ throw new Refused(what, reasons);
233
+ return Object.freeze({
234
+ kind,
235
+ ...(declaration.description === undefined ? {} : { description: declaration.description }),
236
+ ...(declaration.view === undefined ? {} : { view: declaration.view }),
237
+ cells: cells,
238
+ needs: Object.freeze(needs),
239
+ roles: Object.freeze({ ...roles }),
240
+ state: state,
241
+ first,
242
+ states,
243
+ asks: Object.freeze(asks),
244
+ });
245
+ };
246
+ // The table: every state reached from the first, every ask reached by a
247
+ // role, and every role named by an ask. A terminal state is allowed.
248
+ const table = (asks, roles, first, stated, reasons) => {
249
+ const named = new Set([first]);
250
+ for (const entry of Object.values(asks))
251
+ for (const name of [...(entry.in ?? []), ...(entry.to ?? [])])
252
+ named.add(name);
253
+ if (!stated)
254
+ for (const name of named)
255
+ if (name !== READY)
256
+ reasons.push(`it names the state ${name} and has no state of its own`);
257
+ const reached = new Set([first]);
258
+ const queue = [first];
259
+ while (queue.length > 0) {
260
+ const at = queue.shift();
261
+ for (const entry of Object.values(asks)) {
262
+ if (entry.in !== null && !entry.in.includes(at))
263
+ continue;
264
+ for (const next of entry.to ?? [at]) {
265
+ if (reached.has(next))
266
+ continue;
267
+ reached.add(next);
268
+ queue.push(next);
269
+ }
270
+ }
271
+ }
272
+ for (const name of named)
273
+ if (!reached.has(name))
274
+ reasons.push(`no ask reaches the state ${name}`);
275
+ const declared = new Set(Object.keys(roles));
276
+ const used = new Set();
277
+ for (const entry of Object.values(asks)) {
278
+ for (const role of entry.for ?? []) {
279
+ used.add(role);
280
+ if (!declared.has(role) && !HOUSE_ROLES.has(role))
281
+ reasons.push(`its ask ${entry.method} is for ${role}, which is no role`);
282
+ }
283
+ }
284
+ for (const role of declared)
285
+ if (!used.has(role))
286
+ reasons.push(`its role ${role} is named by no ask`);
287
+ return Object.freeze([...named]);
288
+ };
@@ -0,0 +1,55 @@
1
+ import type { BeingClass, Memory } from '../foundation.ts';
2
+ import { Ground, type Bodies, type Faculty, type HandAsk, type Standing, type Entry } from '../ground/ground.ts';
3
+ import type { FakeClock } from './fake-clock.ts';
4
+ import { FakeCustody } from './fake-custody.ts';
5
+ import { FakeMemory } from './fake-memory.ts';
6
+ import type { FakeNetwork } from './fake-network.ts';
7
+ /** A house module: what a folder's index exports. */
8
+ export interface HouseModule {
9
+ readonly steward: BeingClass;
10
+ readonly public?: BeingClass;
11
+ readonly beings?: readonly BeingClass[];
12
+ }
13
+ /** What a ground keeps across its lives: its seeds, its record, and each house's memory. */
14
+ export declare class Machine {
15
+ readonly custody: FakeCustody;
16
+ readonly memory: FakeMemory;
17
+ readonly houses: Map<string, FakeMemory>;
18
+ constructor(seed: string);
19
+ memoryOf(house: string): Memory;
20
+ }
21
+ export interface BenchGroundOptions {
22
+ readonly network: FakeNetwork;
23
+ /** Its host on the network. */
24
+ readonly host: string;
25
+ /** The names that point at it; a ground with none is a device, and is never dialled. */
26
+ readonly names?: readonly string[];
27
+ readonly clock: FakeClock;
28
+ /** The house modules its entries name, by name. */
29
+ readonly modules?: Readonly<Record<string, HouseModule>>;
30
+ readonly faculties?: Readonly<Record<string, Faculty>>;
31
+ /** Custom bodies beside the bench's own `fake` memory and `module` classes, as a recipe adds them. */
32
+ readonly bodies?: Partial<Bodies>;
33
+ /** The machine it opens on; a fresh one, seeded by its host, where none is named. */
34
+ readonly machine?: Machine;
35
+ }
36
+ export declare class BenchGround {
37
+ #private;
38
+ readonly host: string;
39
+ readonly machine: Machine;
40
+ private constructor();
41
+ static open(options: BenchGroundOptions): Promise<BenchGround>;
42
+ /** The ground on its machine again, joined to the network, every house of its record open. */
43
+ up(): Promise<void>;
44
+ /** The ground off: its houses closed, its host answering nothing. Its machine keeps everything. */
45
+ down(): Promise<void>;
46
+ /** A house of a module by that module's name, on fake memory. */
47
+ /**
48
+ * A house: of a module by that module's name, on fake memory, or on the
49
+ * whole entry the test gives, custom bodies and all.
50
+ */
51
+ add(name: string, from?: string | Entry, rest?: Omit<Entry, 'memory' | 'classes'>): Promise<Standing>;
52
+ remove(name: string): Promise<void>;
53
+ list(): readonly Standing[];
54
+ ask(request: HandAsk): ReturnType<Ground['ask']>;
55
+ }
@@ -0,0 +1,101 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A ground in memory. It joins a FakeNetwork under its host, keeps its
3
+ // seeds, its record and each house's memory in a machine that outlives it,
4
+ // and opens house modules by name. So a test turns it off, opens it again
5
+ // on the same machine, or moves the machine to another host.
6
+ import { ClassList } from '../bodies/class-list.js';
7
+ import { Ground } from '../ground/ground.js';
8
+ import { FakeCustody } from './fake-custody.js';
9
+ import { FakeMemory } from './fake-memory.js';
10
+ /** What a ground keeps across its lives: its seeds, its record, and each house's memory. */
11
+ export class Machine {
12
+ custody;
13
+ memory = new FakeMemory();
14
+ houses = new Map();
15
+ constructor(seed) {
16
+ this.custody = new FakeCustody(seed);
17
+ }
18
+ memoryOf(house) {
19
+ return this.houses.get(house) ?? this.houses.set(house, new FakeMemory()).get(house);
20
+ }
21
+ }
22
+ export class BenchGround {
23
+ host;
24
+ machine;
25
+ #options;
26
+ #ground;
27
+ constructor(options, machine) {
28
+ this.host = options.host;
29
+ this.machine = machine;
30
+ this.#options = options;
31
+ }
32
+ static async open(options) {
33
+ const bench = new BenchGround(options, options.machine ?? new Machine(options.host));
34
+ await bench.up();
35
+ return bench;
36
+ }
37
+ #live() {
38
+ if (this.#ground === undefined)
39
+ throw new Error(`the ground ${this.host} is down`);
40
+ return this.#ground;
41
+ }
42
+ /** The ground on its machine again, joined to the network, every house of its record open. */
43
+ async up() {
44
+ if (this.#ground !== undefined)
45
+ return;
46
+ const { network, host, names = [], clock, modules = {}, faculties = {}, bodies = {} } = this.#options;
47
+ network.up(host);
48
+ const carry = network.join(host, { names, listens: names.length > 0 });
49
+ // The bench's own bodies, and the test's beside them, never in their place.
50
+ const joined = (own, added = {}) => {
51
+ for (const name of Object.keys(added))
52
+ if (name in own)
53
+ throw new TypeError(`the body ${name} is the bench's own`);
54
+ return { ...own, ...added };
55
+ };
56
+ this.#ground = await Ground.open({
57
+ custody: this.machine.custody,
58
+ memory: this.machine.memory,
59
+ carry,
60
+ clock,
61
+ faculties,
62
+ bodies: {
63
+ memory: joined({ fake: ({ house }) => this.machine.memoryOf(house) }, bodies.memory),
64
+ classes: joined({
65
+ module: ({ args }) => {
66
+ const module = modules[args.name];
67
+ if (module === undefined)
68
+ throw new Error(`no house module ${String(args.name)}`);
69
+ return new ClassList({ steward: module.steward, ...(module.public === undefined ? {} : { public: module.public }), beings: module.beings ?? [] });
70
+ },
71
+ }, bodies.classes),
72
+ },
73
+ });
74
+ }
75
+ /** The ground off: its houses closed, its host answering nothing. Its machine keeps everything. */
76
+ async down() {
77
+ const ground = this.#ground;
78
+ if (ground === undefined)
79
+ return;
80
+ this.#ground = undefined;
81
+ await ground.close();
82
+ this.#options.network.down(this.host);
83
+ }
84
+ /** A house of a module by that module's name, on fake memory. */
85
+ /**
86
+ * A house: of a module by that module's name, on fake memory, or on the
87
+ * whole entry the test gives, custom bodies and all.
88
+ */
89
+ add(name, from = name, rest = {}) {
90
+ return this.#live().add(name, typeof from === 'string' ? { memory: { body: 'fake' }, classes: { body: 'module', name: from }, ...rest } : from);
91
+ }
92
+ remove(name) {
93
+ return this.#live().remove(name);
94
+ }
95
+ list() {
96
+ return this.#live().list();
97
+ }
98
+ ask(request) {
99
+ return this.#live().ask(request);
100
+ }
101
+ }