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,69 @@
1
+ import type { Json } from '../being/being.ts';
2
+ import type { Memory } from '../foundation.ts';
3
+ import { Ground, type Bodies, type Custody, type Faculty } from '../ground/ground.ts';
4
+ import type { Answer } from '../house/rows-shape.ts';
5
+ import { type Load } from './origin-classes.ts';
6
+ /** What a page hands its ground: the faculties it makes, by name, and any custom body. */
7
+ export interface BrowserRecipe {
8
+ readonly faculties?: () => Readonly<Record<string, Faculty | Promise<Faculty>>>;
9
+ readonly bodies?: Partial<Bodies>;
10
+ }
11
+ /** What the ground takes of its engine. Each is the page's own where omitted. */
12
+ export interface BrowserPlatform {
13
+ readonly locks: Pick<LockManager, 'request'>;
14
+ readonly channel: (name: string) => Pick<BroadcastChannel, 'postMessage' | 'close'> & {
15
+ onmessage: ((event: MessageEvent) => void) | null;
16
+ };
17
+ readonly custody: (name: string) => Promise<Custody>;
18
+ readonly memory: (name: string) => Promise<Memory>;
19
+ /** Where its code stands; its classes load from under it alone. */
20
+ readonly origin: string;
21
+ /** Asks the browser to keep its storage, and answers whether it will. */
22
+ readonly persist: () => Promise<boolean>;
23
+ /** Loads a module of classes by URL: `import()`, or in a service worker a map of the worker's own imports. */
24
+ readonly load: Load;
25
+ }
26
+ export interface BrowserGroundOptions {
27
+ /** The ground's name on its origin, which names its lock, its channel and its storage. `nervur` where omitted. */
28
+ readonly name?: string;
29
+ readonly recipe?: BrowserRecipe;
30
+ readonly platform?: Partial<BrowserPlatform>;
31
+ /** The ground's bound on every ask, in milliseconds. */
32
+ readonly wait?: number;
33
+ /** Whether it dials private and loopback addresses, as a page on localhost does. */
34
+ readonly allowPrivate?: boolean;
35
+ }
36
+ /** One request of the hand, as every terrain serves it. */
37
+ export interface BrowserHandRequest {
38
+ readonly describe?: true;
39
+ readonly faculty?: string;
40
+ readonly house?: string;
41
+ readonly id?: string;
42
+ readonly method?: string;
43
+ readonly args?: Json;
44
+ readonly after?: Answer;
45
+ }
46
+ export declare class BrowserGround {
47
+ #private;
48
+ private constructor();
49
+ /**
50
+ * The origin's ground, joined: this page runs it where it takes the lock,
51
+ * and reaches the page that runs it where another holds the lock.
52
+ */
53
+ static open(options?: BrowserGroundOptions): Promise<BrowserGround>;
54
+ /** Whether this page runs the ground now. */
55
+ get leading(): boolean;
56
+ /** The ground, where this page runs it. */
57
+ get ground(): Ground | undefined;
58
+ /** Resolves once this page runs the ground, and rejects where its boot failed. */
59
+ led(): Promise<void>;
60
+ /**
61
+ * The hand: `describe`, a faculty's method, or an ask of a being in a
62
+ * named house, answered by whichever page runs the ground.
63
+ */
64
+ hand(request: BrowserHandRequest): Promise<Answer | {
65
+ readonly describe: Json;
66
+ }>;
67
+ /** This page leaves: its ground closes where it ran one, and the lock passes to the next page. */
68
+ close(): Promise<void>;
69
+ }
@@ -0,0 +1,219 @@
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 { JoinedCarry } from '../bodies/joined-carry.js';
10
+ import { WebCarry } from '../bodies/web-carry.js';
11
+ import { Ground } from '../ground/ground.js';
12
+ import { IndexedDbMemory } from './indexeddb-memory.js';
13
+ import { LockedCustody } from './locked-custody.js';
14
+ import { OriginClasses } from './origin-classes.js';
15
+ const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
16
+ const random = () => Array.from(crypto.getRandomValues(new Uint8Array(8)), (byte) => byte.toString(16).padStart(2, '0')).join('');
17
+ // Bodies a recipe adds beside the ground's own, never in their place.
18
+ const joined = (own, added = {}) => {
19
+ for (const name of Object.keys(added))
20
+ if (name in own)
21
+ throw new TypeError(`the recipe names a body ${name}, which is the ground's own`);
22
+ return { ...own, ...added };
23
+ };
24
+ const defaults = (name) => ({
25
+ locks: navigator.locks,
26
+ channel: (channel) => new BroadcastChannel(channel),
27
+ custody: (custody) => LockedCustody.open(custody),
28
+ memory: (memory) => IndexedDbMemory.open(memory),
29
+ origin: globalThis.location?.origin ?? `nervur:${name}`,
30
+ persist: async () => (await navigator.storage?.persist?.()) ?? false,
31
+ load: (href) => import(__rewriteRelativeImportExtension(href)),
32
+ });
33
+ export class BrowserGround {
34
+ #name;
35
+ #platform;
36
+ #options;
37
+ #self = random();
38
+ #channel;
39
+ #abort = new AbortController();
40
+ // Asks sent to another page's ground, by id, with the page they went to.
41
+ #pending = new Map();
42
+ #ground;
43
+ #persisted = false;
44
+ #leader;
45
+ #waiting = [];
46
+ #release;
47
+ #leading;
48
+ #led;
49
+ #failed;
50
+ #closed = false;
51
+ constructor(name, platform, options) {
52
+ this.#name = name;
53
+ this.#platform = platform;
54
+ this.#options = options;
55
+ this.#channel = platform.channel(`${name}-hand`);
56
+ this.#leading = new Promise((resolve, reject) => {
57
+ this.#led = resolve;
58
+ this.#failed = reject;
59
+ });
60
+ this.#leading.catch(() => undefined);
61
+ }
62
+ /**
63
+ * The origin's ground, joined: this page runs it where it takes the lock,
64
+ * and reaches the page that runs it where another holds the lock.
65
+ */
66
+ static async open(options = {}) {
67
+ const name = options.name ?? 'nervur';
68
+ if (!NAME.test(name))
69
+ throw new TypeError(`no ground is named ${name}`);
70
+ const platform = { ...defaults(name), ...options.platform };
71
+ const joining = new BrowserGround(name, platform, options);
72
+ joining.#listen();
73
+ joining.#queue();
74
+ joining.#say({ kind: 'who' });
75
+ return joining;
76
+ }
77
+ /** Whether this page runs the ground now. */
78
+ get leading() {
79
+ return this.#ground !== undefined;
80
+ }
81
+ /** The ground, where this page runs it. */
82
+ get ground() {
83
+ return this.#ground;
84
+ }
85
+ /** Resolves once this page runs the ground, and rejects where its boot failed. */
86
+ led() {
87
+ return this.#leading;
88
+ }
89
+ /**
90
+ * The hand: `describe`, a faculty's method, or an ask of a being in a
91
+ * named house, answered by whichever page runs the ground.
92
+ */
93
+ async hand(request) {
94
+ if (this.#closed)
95
+ return { error: { message: 'the ground is closed here' } };
96
+ if (this.#ground !== undefined)
97
+ return this.#local(request);
98
+ if (this.#leader === undefined)
99
+ await new Promise((resolve) => this.#waiting.push(resolve));
100
+ if (this.#ground !== undefined)
101
+ return this.#local(request);
102
+ const id = random();
103
+ const to = this.#leader;
104
+ return new Promise((settle) => {
105
+ this.#pending.set(id, { to, settle });
106
+ this.#say({ kind: 'ask', id, to, request });
107
+ });
108
+ }
109
+ async #local(request) {
110
+ const answer = await this.#ground.hand(request);
111
+ if (request.describe === true && 'result' in answer)
112
+ return { result: { ...answer.result, persisted: this.#persisted } };
113
+ return answer;
114
+ }
115
+ #say(said) {
116
+ this.#channel.postMessage(said);
117
+ }
118
+ #listen() {
119
+ this.#channel.onmessage = (event) => {
120
+ const said = event.data;
121
+ if (said.kind === 'who' && this.#ground !== undefined)
122
+ this.#say({ kind: 'up', leader: this.#self });
123
+ else if (said.kind === 'up')
124
+ this.#heard(said.leader);
125
+ else if (said.kind === 'ask' && said.to === this.#self)
126
+ void this.#answer(said.id, said.request);
127
+ else if (said.kind === 'answer') {
128
+ const pending = this.#pending.get(said.id);
129
+ this.#pending.delete(said.id);
130
+ pending?.settle(said.answer);
131
+ }
132
+ };
133
+ }
134
+ async #answer(id, request) {
135
+ const answer = this.#ground === undefined ? { error: { message: 'the ground moved to another page; ask again' } } : await this.#local(request);
136
+ this.#say({ kind: 'answer', id, answer });
137
+ }
138
+ // A new page runs the ground: asks sent to the one before may never be answered, so they fail, and whoever waited goes on.
139
+ #heard(leader) {
140
+ if (leader !== this.#leader)
141
+ this.#orphan((to) => to !== leader);
142
+ this.#leader = leader;
143
+ const waiting = this.#waiting;
144
+ this.#waiting = [];
145
+ for (const resolve of waiting)
146
+ resolve();
147
+ }
148
+ #orphan(lost) {
149
+ for (const [id, pending] of this.#pending) {
150
+ if (!lost(pending.to))
151
+ continue;
152
+ this.#pending.delete(id);
153
+ pending.settle({ error: { message: 'the page that ran the ground closed; ask again' } });
154
+ }
155
+ }
156
+ // The lock, asked once and held until this page closes.
157
+ #queue() {
158
+ this.#platform.locks
159
+ .request(`${this.#name}-ground`, { signal: this.#abort.signal }, async () => {
160
+ try {
161
+ this.#ground = await this.#boot();
162
+ }
163
+ catch (error) {
164
+ this.#failed(error);
165
+ return;
166
+ }
167
+ this.#heard(this.#self);
168
+ this.#say({ kind: 'up', leader: this.#self });
169
+ this.#led();
170
+ await new Promise((resolve) => {
171
+ this.#release = resolve;
172
+ });
173
+ })
174
+ .catch(() => undefined);
175
+ }
176
+ // The boot: its own custody and memory, the recipe's faculties, and the record's houses.
177
+ async #boot() {
178
+ const name = this.#name;
179
+ const platform = this.#platform;
180
+ const recipe = this.#options.recipe ?? {};
181
+ const custody = await platform.custody(`${name}-custody`);
182
+ const memory = await platform.memory(`${name}-ground`);
183
+ this.#persisted = await platform.persist().catch(() => false);
184
+ // It only dials, so it writes no address into an invitation.
185
+ const web = new WebCarry({ allowPrivate: this.#options.allowPrivate === true });
186
+ const carry = new JoinedCarry({ https: web, http: web });
187
+ const faculties = {};
188
+ for (const [faculty, made] of Object.entries(recipe.faculties?.() ?? {}))
189
+ faculties[faculty] = await made;
190
+ const bodies = {
191
+ memory: joined({ indexeddb: ({ house }) => platform.memory(`${name}-house-${house}`) }, recipe.bodies?.memory),
192
+ classes: joined({
193
+ origin: ({ args }) => {
194
+ if (typeof args.at !== 'string')
195
+ throw new Error('the origin body names its module in at');
196
+ return OriginClasses.open(args.at, platform.origin, platform.load);
197
+ },
198
+ }, recipe.bodies?.classes),
199
+ };
200
+ const wait = this.#options.wait;
201
+ return Ground.open({ custody, memory, carry, bodies, faculties, ...(wait === undefined ? {} : { wait }) });
202
+ }
203
+ /** This page leaves: its ground closes where it ran one, and the lock passes to the next page. */
204
+ async close() {
205
+ if (this.#closed)
206
+ return;
207
+ this.#closed = true;
208
+ this.#abort.abort();
209
+ const ground = this.#ground;
210
+ this.#ground = undefined;
211
+ await ground?.close();
212
+ this.#release?.();
213
+ this.#orphan(() => true);
214
+ for (const resolve of this.#waiting)
215
+ resolve();
216
+ this.#waiting = [];
217
+ this.#channel.close();
218
+ }
219
+ }
@@ -0,0 +1,4 @@
1
+ export { BrowserGround, type BrowserGroundOptions, type BrowserHandRequest, type BrowserPlatform, type BrowserRecipe } from './browser-ground.ts';
2
+ export { IndexedDbMemory } from './indexeddb-memory.ts';
3
+ export { IndexedDbShelf, LockedCustody, type Shelf } from './locked-custody.ts';
4
+ export { OriginClasses, type Load } from './origin-classes.ts';
@@ -0,0 +1,7 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // `nervur/browser`: the ground of a page and its service worker, and the
3
+ // bodies it passes, for whoever writes a ground of their own.
4
+ export { BrowserGround } from './browser-ground.js';
5
+ export { IndexedDbMemory } from './indexeddb-memory.js';
6
+ export { IndexedDbShelf, LockedCustody } from './locked-custody.js';
7
+ export { OriginClasses } from './origin-classes.js';
@@ -0,0 +1,17 @@
1
+ import type { Memory, PlaceRead } from '../foundation.ts';
2
+ export declare class IndexedDbMemory implements Memory {
3
+ #private;
4
+ private constructor();
5
+ /** The memory named `name`, its database made on first use. `factory` is the engine's `indexedDB` where omitted. */
6
+ static open(name: string, factory?: IDBFactory): Promise<IndexedDbMemory>;
7
+ read({ place }: {
8
+ place: string;
9
+ }): Promise<PlaceRead>;
10
+ list(): Promise<readonly string[]>;
11
+ write({ writes, expect, }: {
12
+ writes: Readonly<Record<string, Readonly<Record<string, Uint8Array | null>>>>;
13
+ expect: Readonly<Record<string, string | null>>;
14
+ }): Promise<Readonly<Record<string, string | null>> | null>;
15
+ /** The database let go. */
16
+ close(): void;
17
+ }
@@ -0,0 +1,97 @@
1
+ const PLACES = 'places';
2
+ const COUNT = 'count';
3
+ const done = (request) => new Promise((resolve, reject) => {
4
+ request.onsuccess = () => resolve(request.result);
5
+ request.onerror = () => reject(request.error ?? new Error('IndexedDB refused the request'));
6
+ });
7
+ const copied = (entries) => Object.fromEntries(Object.entries(entries).map(([name, bytes]) => [name, bytes.slice()]));
8
+ export class IndexedDbMemory {
9
+ #db;
10
+ constructor(db) {
11
+ this.#db = db;
12
+ }
13
+ /** The memory named `name`, its database made on first use. `factory` is the engine's `indexedDB` where omitted. */
14
+ static async open(name, factory = indexedDB) {
15
+ const opening = factory.open(name, 1);
16
+ opening.onupgradeneeded = () => {
17
+ opening.result.createObjectStore(PLACES);
18
+ opening.result.createObjectStore(COUNT);
19
+ };
20
+ return new IndexedDbMemory(await done(opening));
21
+ }
22
+ async read({ place }) {
23
+ const held = (await done(this.#db.transaction(PLACES, 'readonly').objectStore(PLACES).get(place)));
24
+ if (held === undefined)
25
+ return { entries: {}, version: null };
26
+ return { entries: copied(held.entries), version: String(held.version) };
27
+ }
28
+ async list() {
29
+ const keys = await done(this.#db.transaction(PLACES, 'readonly').objectStore(PLACES).getAllKeys());
30
+ return keys.map(String);
31
+ }
32
+ write({ writes, expect, }) {
33
+ const places = [...new Set([...Object.keys(writes), ...Object.keys(expect)])];
34
+ return new Promise((resolve, reject) => {
35
+ const transaction = this.#db.transaction([PLACES, COUNT], 'readwrite');
36
+ const store = transaction.objectStore(PLACES);
37
+ const count = transaction.objectStore(COUNT);
38
+ const held = new Map();
39
+ let versions = null;
40
+ let refused = false;
41
+ transaction.oncomplete = () => resolve(versions);
42
+ transaction.onabort = () => (refused ? resolve(null) : reject(transaction.error ?? new Error('IndexedDB aborted the write')));
43
+ // Every place and the counter are read inside the transaction, and the writes follow in the same one.
44
+ const counted = count.get(COUNT);
45
+ const land = () => {
46
+ if (held.size < places.length || counted.readyState !== 'done')
47
+ return;
48
+ for (const name of places) {
49
+ const version = held.get(name) === undefined ? null : String(held.get(name).version);
50
+ if (!(name in expect) || expect[name] !== version) {
51
+ refused = true;
52
+ transaction.abort();
53
+ return;
54
+ }
55
+ }
56
+ let next = counted.result ?? 0;
57
+ const landed = {};
58
+ for (const [name, entries] of Object.entries(writes)) {
59
+ const kept = { ...(held.get(name)?.entries ?? {}) };
60
+ for (const [entry, bytes] of Object.entries(entries)) {
61
+ if (bytes === null)
62
+ delete kept[entry];
63
+ else
64
+ kept[entry] = bytes.slice();
65
+ }
66
+ next += 1;
67
+ if (Object.keys(kept).length === 0) {
68
+ store.delete(name);
69
+ landed[name] = null;
70
+ }
71
+ else {
72
+ store.put({ entries: kept, version: next }, name);
73
+ landed[name] = String(next);
74
+ }
75
+ }
76
+ count.put(next, COUNT);
77
+ versions = landed;
78
+ };
79
+ counted.onsuccess = land;
80
+ for (const place of places) {
81
+ const request = store.get(place);
82
+ request.onsuccess = () => {
83
+ held.set(place, request.result);
84
+ land();
85
+ };
86
+ }
87
+ if (places.length === 0)
88
+ counted.onsuccess = () => {
89
+ versions = {};
90
+ };
91
+ });
92
+ }
93
+ /** The database let go. */
94
+ close() {
95
+ this.#db.close();
96
+ }
97
+ }
@@ -0,0 +1,25 @@
1
+ import type { Keys } from '../foundation.ts';
2
+ import type { Custody } from '../ground/ground.ts';
3
+ /** Where custody keeps its key and its sealed seeds: values kept as they are. */
4
+ export interface Shelf {
5
+ get(key: string): Promise<unknown>;
6
+ set(key: string, value: unknown): Promise<void>;
7
+ }
8
+ /** A shelf in IndexedDB, which keeps a `CryptoKey` whole. */
9
+ export declare class IndexedDbShelf implements Shelf {
10
+ #private;
11
+ private constructor();
12
+ static open(name: string, factory?: IDBFactory): Promise<IndexedDbShelf>;
13
+ get(key: string): Promise<unknown>;
14
+ set(key: string, value: unknown): Promise<void>;
15
+ close(): void;
16
+ }
17
+ export declare class LockedCustody implements Custody {
18
+ #private;
19
+ constructor(shelf: Shelf, subtle?: SubtleCrypto);
20
+ /** The custody of the origin, on a shelf in IndexedDB named `name`. */
21
+ static open(name?: string, factory?: IDBFactory): Promise<LockedCustody>;
22
+ keys({ house }: {
23
+ house: string;
24
+ }): Promise<Keys>;
25
+ }
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // A BrowserGround's custody: one seed for each house, sealed with AES-GCM
3
+ // under a key the browser keeps and never hands out. The key is made once,
4
+ // unextractable, and kept on a shelf beside the sealed seeds, since
5
+ // IndexedDB keeps a key as it is and never as bytes. A script on the
6
+ // origin may use the key and can never copy it: a lock, not a wall.
7
+ import { SeedKeys } from '../bodies/seed-keys.js';
8
+ const NAME = /^[a-z0-9][a-z0-9._-]{0,63}$/;
9
+ const KEY = 'key';
10
+ const STORE = 'shelf';
11
+ const done = (request) => new Promise((resolve, reject) => {
12
+ request.onsuccess = () => resolve(request.result);
13
+ request.onerror = () => reject(request.error ?? new Error('IndexedDB refused the request'));
14
+ });
15
+ /** A shelf in IndexedDB, which keeps a `CryptoKey` whole. */
16
+ export class IndexedDbShelf {
17
+ #db;
18
+ constructor(db) {
19
+ this.#db = db;
20
+ }
21
+ static async open(name, factory = indexedDB) {
22
+ const opening = factory.open(name, 1);
23
+ opening.onupgradeneeded = () => {
24
+ opening.result.createObjectStore(STORE);
25
+ };
26
+ return new IndexedDbShelf(await done(opening));
27
+ }
28
+ get(key) {
29
+ return done(this.#db.transaction(STORE, 'readonly').objectStore(STORE).get(key));
30
+ }
31
+ async set(key, value) {
32
+ const transaction = this.#db.transaction(STORE, 'readwrite');
33
+ transaction.objectStore(STORE).put(value, key);
34
+ await new Promise((resolve, reject) => {
35
+ transaction.oncomplete = () => resolve();
36
+ transaction.onabort = () => reject(transaction.error ?? new Error('IndexedDB aborted the write'));
37
+ });
38
+ }
39
+ close() {
40
+ this.#db.close();
41
+ }
42
+ }
43
+ const hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
44
+ export class LockedCustody {
45
+ #shelf;
46
+ #subtle;
47
+ // One seed is drawn at a time, so two first uses of a house never draw two.
48
+ #turn = Promise.resolve();
49
+ constructor(shelf, subtle = crypto.subtle) {
50
+ this.#shelf = shelf;
51
+ this.#subtle = subtle;
52
+ }
53
+ /** The custody of the origin, on a shelf in IndexedDB named `name`. */
54
+ static async open(name = 'nervur-custody', factory = indexedDB) {
55
+ return new LockedCustody(await IndexedDbShelf.open(name, factory));
56
+ }
57
+ keys({ house }) {
58
+ if (!NAME.test(house))
59
+ return Promise.reject(new TypeError(`no house is named ${house}`));
60
+ const turn = this.#turn.then(() => this.#keys(house));
61
+ this.#turn = turn.catch(() => undefined);
62
+ return turn;
63
+ }
64
+ async #keys(house) {
65
+ const key = await this.#key();
66
+ // The house's name is sealed in, so one house's seed never opens as another's.
67
+ const additionalData = new TextEncoder().encode(house);
68
+ let sealed = (await this.#shelf.get(`seed:${house}`));
69
+ if (sealed === undefined) {
70
+ const seed = crypto.getRandomValues(new Uint8Array(32));
71
+ const iv = crypto.getRandomValues(new Uint8Array(12));
72
+ sealed = { iv, data: new Uint8Array(await this.#subtle.encrypt({ name: 'AES-GCM', iv, additionalData }, key, seed)) };
73
+ seed.fill(0);
74
+ await this.#shelf.set(`seed:${house}`, sealed);
75
+ }
76
+ const seed = new Uint8Array(await this.#subtle.decrypt({ name: 'AES-GCM', iv: sealed.iv, additionalData }, key, sealed.data));
77
+ const keys = new SeedKeys(hex(seed));
78
+ seed.fill(0);
79
+ return keys;
80
+ }
81
+ async #key() {
82
+ const kept = (await this.#shelf.get(KEY));
83
+ if (kept !== undefined)
84
+ return kept;
85
+ const made = await this.#subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
86
+ await this.#shelf.set(KEY, made);
87
+ return made;
88
+ }
89
+ }
@@ -0,0 +1,7 @@
1
+ import type { Classes } from '../foundation.ts';
2
+ /** What loads a module by its URL: the engine's `import()` where none is handed. */
3
+ export type Load = (href: string) => Promise<unknown>;
4
+ export declare const OriginClasses: Readonly<{
5
+ /** The classes of the module at `at`, resolved against `origin`, loaded once. */
6
+ open(at: string, origin: string, load?: Load): Promise<Classes>;
7
+ }>;
@@ -0,0 +1,31 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ // SPDX-License-Identifier: Apache-2.0
10
+ // Classes from the ground's own origin. A module at a path under the
11
+ // origin exports `steward`, and `public` and `beings` where it has them, as
12
+ // `ClassList` takes them. A path that resolves off the origin is refused,
13
+ // as a folder of code on a NodeGround stays inside its folder. A service
14
+ // worker may not `import()`, so there the worker's own build hands a
15
+ // loader that answers the same paths from modules it imported itself.
16
+ import { ClassList } from '../bodies/class-list.js';
17
+ export const OriginClasses = Object.freeze({
18
+ /** The classes of the module at `at`, resolved against `origin`, loaded once. */
19
+ async open(at, origin, load = (href) => import(__rewriteRelativeImportExtension(href))) {
20
+ const base = new URL(origin.endsWith('/') ? origin : `${origin}/`);
21
+ const url = new URL(at, base);
22
+ if (url.origin !== base.origin || !url.href.startsWith(base.href))
23
+ throw new Error(`the code at ${at} stands off ${base.href}`);
24
+ const module = (await load(url.href));
25
+ if (module.steward === undefined)
26
+ throw new Error(`${url.href} exports no steward`);
27
+ if (module.beings !== undefined && !Array.isArray(module.beings))
28
+ throw new Error(`${url.href} exports beings that are not a list`);
29
+ return new ClassList({ steward: module.steward, ...(module.public === undefined ? {} : { public: module.public }), ...(module.beings === undefined ? {} : { beings: module.beings }) });
30
+ },
31
+ });