@starlove/orbit 1.4.1 → 2.0.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 (329) hide show
  1. package/Dockerfile +10 -8
  2. package/README.md +146 -164
  3. package/dist/full/csp.txt +1 -0
  4. package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
  5. package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
  6. package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
  7. package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
  8. package/dist/full/fonts/Mona_Sans/README.txt +10 -0
  9. package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
  10. package/dist/full/fonts/Newsreader/README.txt +8 -0
  11. package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
  12. package/dist/full/index.html +36 -653
  13. package/dist/full/js/app.BmuponPJ.js +32 -0
  14. package/dist/full/js/app.BmuponPJ.js.map +1 -0
  15. package/dist/full/js/chunk.1fJGQGbW.js +2 -0
  16. package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
  17. package/dist/full/js/chunk.5D7JO2zp.js +2 -0
  18. package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
  19. package/dist/full/js/chunk.BrNn_D79.js +2 -0
  20. package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
  21. package/dist/full/js/chunk.CokJXIqM.js +2 -0
  22. package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
  23. package/dist/full/js/chunk.D_Haqy6R.js +2 -0
  24. package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
  25. package/dist/full/js/chunk.RO0jsH6I.js +15 -0
  26. package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
  27. package/dist/full/js/chunk.dqWOEtBM.js +2 -0
  28. package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
  29. package/dist/full/js/warm.f260bbbc.js +15 -0
  30. package/dist/full/orbits-favicon.png +0 -0
  31. package/dist/full/orbits-logo-bg-wide.png +0 -0
  32. package/dist/full/orbits-logo-bg.png +0 -0
  33. package/dist/full/orbits-logo.png +0 -0
  34. package/dist/full/styles.2b154ffe.css +1 -0
  35. package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
  36. package/dist/full/version.json +2 -2
  37. package/docs/AGENT_GUIDE.md +114 -0
  38. package/docs/UPDATING.md +249 -64
  39. package/lib/adapters/indexeddb/blobs.d.ts +3 -0
  40. package/lib/adapters/indexeddb/blobs.js +54 -0
  41. package/lib/adapters/indexeddb/database.d.ts +60 -0
  42. package/lib/adapters/indexeddb/database.js +126 -0
  43. package/lib/adapters/indexeddb/index.d.ts +5 -0
  44. package/lib/adapters/indexeddb/index.js +3 -0
  45. package/lib/adapters/indexeddb/locks.d.ts +14 -0
  46. package/lib/adapters/indexeddb/locks.js +40 -0
  47. package/lib/adapters/indexeddb/store.d.ts +40 -0
  48. package/lib/adapters/indexeddb/store.js +424 -0
  49. package/lib/adapters/indexeddb/tables.d.ts +43 -0
  50. package/lib/adapters/indexeddb/tables.js +127 -0
  51. package/lib/adapters/memory/blobs.d.ts +3 -0
  52. package/lib/adapters/memory/blobs.js +44 -0
  53. package/lib/adapters/memory/board.d.ts +41 -0
  54. package/lib/adapters/memory/board.js +521 -0
  55. package/lib/adapters/memory/index.d.ts +2 -0
  56. package/lib/adapters/memory/index.js +2 -0
  57. package/lib/adapters/memory/search.d.ts +6 -0
  58. package/lib/adapters/memory/search.js +30 -0
  59. package/lib/adapters/memory/store.d.ts +34 -0
  60. package/lib/adapters/memory/store.js +266 -0
  61. package/lib/adapters/sqlite/backups.d.ts +53 -0
  62. package/lib/adapters/sqlite/backups.js +232 -0
  63. package/lib/adapters/sqlite/blobs.d.ts +14 -0
  64. package/lib/adapters/sqlite/blobs.js +109 -0
  65. package/lib/adapters/sqlite/board.d.ts +77 -0
  66. package/lib/adapters/sqlite/board.js +328 -0
  67. package/lib/adapters/sqlite/database.d.ts +67 -0
  68. package/lib/adapters/sqlite/database.js +285 -0
  69. package/lib/adapters/sqlite/index.d.ts +3 -0
  70. package/lib/adapters/sqlite/index.js +2 -0
  71. package/lib/adapters/sqlite/lock.d.ts +11 -0
  72. package/lib/adapters/sqlite/lock.js +34 -0
  73. package/lib/adapters/sqlite/records.d.ts +54 -0
  74. package/lib/adapters/sqlite/records.js +546 -0
  75. package/lib/adapters/sqlite/schema.d.ts +54 -0
  76. package/lib/adapters/sqlite/schema.js +276 -0
  77. package/lib/adapters/sqlite/store.d.ts +39 -0
  78. package/lib/adapters/sqlite/store.js +385 -0
  79. package/lib/browser/in-page.d.ts +31 -0
  80. package/lib/browser/in-page.js +46 -0
  81. package/lib/browser/old-preview.d.ts +13 -0
  82. package/lib/browser/old-preview.js +80 -0
  83. package/lib/browser/preview-board.d.ts +8 -0
  84. package/lib/browser/preview-board.js +69 -0
  85. package/lib/cli/main.d.ts +7 -0
  86. package/lib/cli/main.js +343 -0
  87. package/lib/cli/orbit.d.ts +8 -0
  88. package/lib/cli/orbit.js +25 -0
  89. package/lib/client/http.d.ts +38 -0
  90. package/lib/client/http.js +334 -0
  91. package/lib/client/local.d.ts +26 -0
  92. package/lib/client/local.js +190 -0
  93. package/lib/client/object-url.d.ts +8 -0
  94. package/lib/client/object-url.js +21 -0
  95. package/lib/client/types.d.ts +268 -0
  96. package/lib/client/types.js +8 -0
  97. package/lib/commands/agent-tools.d.ts +31 -0
  98. package/lib/commands/agent-tools.js +100 -0
  99. package/lib/commands/index.d.ts +56 -0
  100. package/lib/commands/index.js +298 -0
  101. package/lib/commands/parse.d.ts +73 -0
  102. package/lib/commands/parse.js +231 -0
  103. package/lib/commands/present.d.ts +134 -0
  104. package/lib/commands/present.js +198 -0
  105. package/lib/commands/session.d.ts +85 -0
  106. package/lib/commands/session.js +138 -0
  107. package/lib/commands/verbs.d.ts +35 -0
  108. package/lib/commands/verbs.js +842 -0
  109. package/lib/core/actors.d.ts +18 -0
  110. package/lib/core/actors.js +45 -0
  111. package/lib/core/agent.d.ts +128 -0
  112. package/lib/core/agent.js +323 -0
  113. package/lib/core/boards.d.ts +113 -0
  114. package/lib/core/boards.js +288 -0
  115. package/lib/core/bootstrap.d.ts +47 -0
  116. package/lib/core/bootstrap.js +100 -0
  117. package/lib/core/bytes.d.ts +10 -0
  118. package/lib/core/bytes.js +52 -0
  119. package/lib/core/card-paths.d.ts +25 -0
  120. package/lib/core/card-paths.js +44 -0
  121. package/lib/core/context.d.ts +60 -0
  122. package/lib/core/context.js +42 -0
  123. package/lib/core/cross-board.d.ts +81 -0
  124. package/lib/core/cross-board.js +228 -0
  125. package/lib/core/describe.d.ts +21 -0
  126. package/lib/core/describe.js +81 -0
  127. package/lib/core/errors.d.ts +159 -0
  128. package/lib/core/errors.js +160 -0
  129. package/lib/core/files.d.ts +79 -0
  130. package/lib/core/files.js +262 -0
  131. package/lib/core/history.d.ts +20 -0
  132. package/lib/core/history.js +53 -0
  133. package/lib/core/image-types.d.ts +16 -0
  134. package/lib/core/image-types.js +36 -0
  135. package/lib/core/model.d.ts +263 -0
  136. package/lib/core/model.js +34 -0
  137. package/lib/core/nebula-link.d.ts +14 -0
  138. package/lib/core/nebula-link.js +39 -0
  139. package/lib/core/relations.d.ts +58 -0
  140. package/lib/core/relations.js +112 -0
  141. package/lib/core/search.d.ts +38 -0
  142. package/lib/core/search.js +92 -0
  143. package/lib/core/sessions.d.ts +44 -0
  144. package/lib/core/sessions.js +158 -0
  145. package/lib/core/snapshot-schema.d.ts +295 -0
  146. package/lib/core/snapshot-schema.js +82 -0
  147. package/lib/core/snapshots.d.ts +75 -0
  148. package/lib/core/snapshots.js +409 -0
  149. package/lib/core/states.d.ts +25 -0
  150. package/lib/core/states.js +126 -0
  151. package/lib/core/text.d.ts +40 -0
  152. package/lib/core/text.js +113 -0
  153. package/lib/core/tickets.d.ts +69 -0
  154. package/lib/core/tickets.js +311 -0
  155. package/lib/core/trello.d.ts +17 -0
  156. package/lib/core/trello.js +269 -0
  157. package/lib/core/verdicts.d.ts +28 -0
  158. package/lib/core/verdicts.js +92 -0
  159. package/lib/core/views.d.ts +95 -0
  160. package/lib/core/views.js +186 -0
  161. package/lib/http/app.d.ts +46 -0
  162. package/lib/http/app.js +491 -0
  163. package/lib/mcp-server.d.ts +10 -0
  164. package/lib/mcp-server.js +120 -0
  165. package/lib/node/assets.d.ts +7 -0
  166. package/lib/node/assets.js +68 -0
  167. package/lib/node/data-folder.d.ts +21 -0
  168. package/lib/node/data-folder.js +34 -0
  169. package/lib/node/env.d.ts +38 -0
  170. package/lib/node/env.js +49 -0
  171. package/lib/node/host-check.d.ts +24 -0
  172. package/lib/node/host-check.js +53 -0
  173. package/lib/node/host-services.d.ts +9 -0
  174. package/lib/node/host-services.js +150 -0
  175. package/lib/node/local.d.ts +22 -0
  176. package/lib/node/local.js +61 -0
  177. package/lib/node/migrate.d.ts +53 -0
  178. package/lib/node/migrate.js +635 -0
  179. package/lib/node/package.d.ts +7 -0
  180. package/lib/node/package.js +14 -0
  181. package/lib/node/repo-board.d.ts +49 -0
  182. package/lib/node/repo-board.js +245 -0
  183. package/lib/node/serve.d.ts +8 -0
  184. package/lib/node/serve.js +106 -0
  185. package/lib/orbit.d.ts +47 -0
  186. package/lib/orbit.js +44 -0
  187. package/lib/quiet-sqlite-warning.d.ts +1 -0
  188. package/lib/quiet-sqlite-warning.js +18 -0
  189. package/lib/store/blob-path.d.ts +6 -0
  190. package/lib/store/blob-path.js +12 -0
  191. package/lib/store/conformance.d.ts +52 -0
  192. package/lib/store/conformance.js +1449 -0
  193. package/lib/store/search-words.d.ts +10 -0
  194. package/lib/store/search-words.js +19 -0
  195. package/lib/store/types.d.ts +452 -0
  196. package/lib/store/types.js +34 -0
  197. package/llms.txt +63 -0
  198. package/package.json +109 -25
  199. package/src/cli/orbit.js +11 -658
  200. package/src/mcp-server.js +16 -876
  201. package/SKILL-ORBIT.md +0 -151
  202. package/build.mjs +0 -210
  203. package/dist/full/js/app.4e6fbe20.js +0 -952
  204. package/dist/full/js/app.4e6fbe20.js.map +0 -7
  205. package/dist/full/styles.4e60d824.css +0 -1
  206. package/docs/AGENT_PROTOCOL.md +0 -424
  207. package/docs/MCP_SETUP.md +0 -54
  208. package/docs/ORBIT_DISPATCH.md +0 -280
  209. package/docs/SS-Orbit-Dark.png +0 -0
  210. package/docs/SS-Orbit-Light.png +0 -0
  211. package/docs/mcp-client.example.json +0 -13
  212. package/docs/setup/orbit-init-wizard.md +0 -174
  213. package/public/apple-touch-icon.png +0 -0
  214. package/public/cursors/live_arrow.cur +0 -0
  215. package/public/cursors/live_link.cur +0 -0
  216. package/public/cursors/live_move.cur +0 -0
  217. package/public/cursors/live_select.cur +0 -0
  218. package/public/cursors/live_unavail.cur +0 -0
  219. package/public/favicon-32x32.png +0 -0
  220. package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
  221. package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
  222. package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
  223. package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
  224. package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
  225. package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
  226. package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
  227. package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
  228. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
  229. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
  230. package/public/fonts/Chakra_Petch/OFL.txt +0 -93
  231. package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
  232. package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
  233. package/public/fonts/Inter/OFL.txt +0 -93
  234. package/public/fonts/Inter/README.txt +0 -118
  235. package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  236. package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
  237. package/public/fonts/JetBrains_Mono/README.txt +0 -79
  238. package/public/fonts/OpenRing-Regular.ttf +0 -0
  239. package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  240. package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  241. package/public/fonts/Open_Sans/README.txt +0 -100
  242. package/public/fonts/Orbitron/OFL.txt +0 -93
  243. package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
  244. package/public/fonts/Orbitron/README.txt +0 -68
  245. package/public/fonts/Rajdhani/OFL.txt +0 -93
  246. package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
  247. package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
  248. package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
  249. package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
  250. package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
  251. package/public/fonts/runescape-uf.ttf +0 -0
  252. package/public/index.html +0 -769
  253. package/public/js/actor-labels.js +0 -65
  254. package/public/js/api.js +0 -39
  255. package/public/js/app.js +0 -45
  256. package/public/js/artifact-drawer.js +0 -79
  257. package/public/js/board-export.js +0 -161
  258. package/public/js/board-menu.js +0 -374
  259. package/public/js/card-actions.js +0 -545
  260. package/public/js/config.js +0 -20
  261. package/public/js/create-card.js +0 -190
  262. package/public/js/dom.js +0 -19
  263. package/public/js/drawer.js +0 -273
  264. package/public/js/font-preference.js +0 -230
  265. package/public/js/format.js +0 -433
  266. package/public/js/help-menu.js +0 -31
  267. package/public/js/icons.js +0 -16
  268. package/public/js/kanban.js +0 -694
  269. package/public/js/local-backend.js +0 -1015
  270. package/public/js/main.js +0 -161
  271. package/public/js/modal.js +0 -33
  272. package/public/js/motion-preference.js +0 -64
  273. package/public/js/panel.js +0 -58
  274. package/public/js/router.js +0 -134
  275. package/public/js/search.js +0 -56
  276. package/public/js/settings-tabs.js +0 -7
  277. package/public/js/settings.js +0 -1634
  278. package/public/js/sse.js +0 -159
  279. package/public/js/starscape.js +0 -332
  280. package/public/js/state.js +0 -129
  281. package/public/js/text-editing.js +0 -128
  282. package/public/js/theme-preference.js +0 -95
  283. package/public/js/ticket-detail.js +0 -1516
  284. package/public/js/ticket-typeahead.js +0 -49
  285. package/public/js/toast.js +0 -89
  286. package/public/js/topbar-notes.js +0 -155
  287. package/public/js/transport.js +0 -22
  288. package/public/js/typeahead.js +0 -206
  289. package/public/js/unread.js +0 -121
  290. package/public/js/update-check.js +0 -58
  291. package/public/js/url-routes.js +0 -94
  292. package/public/orbits-favicon.png +0 -0
  293. package/public/orbits-logo-bg-wide.png +0 -0
  294. package/public/orbits-logo-bg.png +0 -0
  295. package/public/orbits-logo.png +0 -0
  296. package/public/styles.css +0 -7709
  297. package/src/core/agent.js +0 -387
  298. package/src/core/agents-md.js +0 -109
  299. package/src/core/artifacts.js +0 -149
  300. package/src/core/attachments.js +0 -238
  301. package/src/core/auth.js +0 -85
  302. package/src/core/backups.js +0 -142
  303. package/src/core/board.js +0 -148
  304. package/src/core/boards.js +0 -409
  305. package/src/core/bootstrap.js +0 -170
  306. package/src/core/css-bundle.js +0 -30
  307. package/src/core/db.js +0 -315
  308. package/src/core/delete-board.js +0 -163
  309. package/src/core/dispatch.js +0 -1007
  310. package/src/core/events.js +0 -314
  311. package/src/core/http.js +0 -115
  312. package/src/core/migrate.js +0 -198
  313. package/src/core/paths.js +0 -55
  314. package/src/core/provision-repo-board.js +0 -119
  315. package/src/core/queries.js +0 -246
  316. package/src/core/registry.js +0 -211
  317. package/src/core/relations.js +0 -163
  318. package/src/core/review-verdicts.js +0 -138
  319. package/src/core/router.js +0 -556
  320. package/src/core/search.js +0 -183
  321. package/src/core/seed.js +0 -234
  322. package/src/core/snapshots.js +0 -692
  323. package/src/core/states.js +0 -167
  324. package/src/core/system-picker.js +0 -109
  325. package/src/core/tickets.js +0 -400
  326. package/src/core/util.js +0 -106
  327. package/src/core/version-check.js +0 -154
  328. package/src/mcp/orbit-client.js +0 -287
  329. package/src/server.js +0 -115
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Connections to Orbit's SQLite files, and the rules every statement runs
3
+ * under: SQLite never waits on the server's thread (busy_timeout 0, retried
4
+ * here on a timer), a database's own error never reaches an answer (each is
5
+ * mapped to the port's codes), and a file without v2's version is never
6
+ * opened for writing.
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ import { closeSync, linkSync, openSync, readSync, renameSync, rmSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { basename, dirname, join, resolve, sep } from "node:path";
12
+ import { DatabaseSync } from "node:sqlite";
13
+ import { isOrbitError, OrbitError } from "../../core/errors.js";
14
+ import { APPLICATION_ID, SCHEMA_VERSION } from "./schema.js";
15
+ /**
16
+ * The port's code for each UNIQUE constraint in the schema, as SQLite names
17
+ * it ("UNIQUE constraint failed: states.name"). A repeated record id is
18
+ * `duplicate_id` everywhere.
19
+ */
20
+ const UNIQUE_CODES = {
21
+ "boards.slug": "board_slug_taken",
22
+ "states.name": "state_name_taken",
23
+ "labels.name": "label_name_taken",
24
+ "tickets.number": "ticket_number_taken",
25
+ "relations.source_ticket_id, relations.target_ticket_id, relations.type": "relation_exists",
26
+ "cross_board_relations.source_ticket_id, cross_board_relations.target_board_id, cross_board_relations.target_ticket_id, cross_board_relations.type": "relation_exists"
27
+ };
28
+ const SQLITE_BUSY = 5;
29
+ const SQLITE_LOCKED = 6;
30
+ const SQLITE_FULL = 13;
31
+ const SQLITE_CONSTRAINT_FOREIGNKEY = 787;
32
+ const SQLITE_CONSTRAINT_PRIMARYKEY = 1555;
33
+ const SQLITE_CONSTRAINT_UNIQUE = 2067;
34
+ /** SQLite's code from its message, for a Node whose errors don't carry `errcode`. */
35
+ function codeFromMessage(error) {
36
+ const message = error instanceof Error ? error.message : "";
37
+ if (/database (table )?is locked|database is busy/.test(message))
38
+ return SQLITE_BUSY;
39
+ if (message.includes("database or disk is full"))
40
+ return SQLITE_FULL;
41
+ if (message.includes("FOREIGN KEY constraint failed"))
42
+ return SQLITE_CONSTRAINT_FOREIGNKEY;
43
+ if (message.startsWith("UNIQUE constraint failed"))
44
+ return SQLITE_CONSTRAINT_UNIQUE;
45
+ return undefined;
46
+ }
47
+ /** An OrbitError for anything a statement throws, the original kept as its cause. */
48
+ export function storageError(error, meaning = {}) {
49
+ if (isOrbitError(error))
50
+ return error;
51
+ const code = error?.errcode ?? codeFromMessage(error);
52
+ if (typeof code !== "number")
53
+ return new OrbitError("storage_failed", { cause: error });
54
+ const primary = code & 0xff;
55
+ if (primary === SQLITE_BUSY || primary === SQLITE_LOCKED)
56
+ return new OrbitError("storage_busy", { cause: error });
57
+ if (primary === SQLITE_FULL)
58
+ return new OrbitError("storage_full", { cause: error });
59
+ if (code === SQLITE_CONSTRAINT_FOREIGNKEY)
60
+ return new OrbitError(meaning.foreignKey ?? "invalid_reference", { cause: error });
61
+ if (code === SQLITE_CONSTRAINT_UNIQUE || code === SQLITE_CONSTRAINT_PRIMARYKEY) {
62
+ const columns = error.message.replace(/^UNIQUE constraint failed: /, "");
63
+ const known = UNIQUE_CODES[columns] ?? (/^\w+\.id$/.test(columns) ? "duplicate_id" : undefined);
64
+ if (known)
65
+ return new OrbitError(known, { cause: error });
66
+ }
67
+ return new OrbitError("storage_failed", { cause: error });
68
+ }
69
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
70
+ /**
71
+ * Runs `attempt` until it isn't refused as busy, waiting on a timer between
72
+ * tries (1 ms, doubling to 50 ms), so the thread stays free for everything
73
+ * else. After `deadline` (a Date.now() value) the refusal stands: `storage_busy`.
74
+ */
75
+ export async function untilFree(attempt, deadline) {
76
+ let delay = 1;
77
+ for (;;) {
78
+ try {
79
+ return attempt();
80
+ }
81
+ catch (error) {
82
+ const mapped = storageError(error);
83
+ if (mapped.code !== "storage_busy" || Date.now() + delay > deadline)
84
+ throw mapped;
85
+ await sleep(delay);
86
+ delay = Math.min(delay * 2, 50);
87
+ }
88
+ }
89
+ }
90
+ /** One connection to one file, with each statement prepared once. */
91
+ export class Connection {
92
+ path;
93
+ db;
94
+ prepared = new Map();
95
+ closed = false;
96
+ constructor(path, options = {}) {
97
+ this.path = path;
98
+ try {
99
+ this.db = new DatabaseSync(path);
100
+ }
101
+ catch (error) {
102
+ throw storageError(error);
103
+ }
104
+ try {
105
+ this.db.exec(`PRAGMA foreign_keys = ON; PRAGMA busy_timeout = 0; PRAGMA synchronous = NORMAL;${options.queryOnly ? " PRAGMA query_only = ON;" : ""}`);
106
+ }
107
+ catch (error) {
108
+ this.db.close();
109
+ throw storageError(error);
110
+ }
111
+ }
112
+ exec(sql) {
113
+ try {
114
+ this.db.exec(sql);
115
+ }
116
+ catch (error) {
117
+ throw storageError(error);
118
+ }
119
+ }
120
+ statement(sql) {
121
+ let statement = this.prepared.get(sql);
122
+ if (!statement) {
123
+ statement = this.db.prepare(sql);
124
+ this.prepared.set(sql, statement);
125
+ }
126
+ return statement;
127
+ }
128
+ run(sql, params = [], meaning) {
129
+ try {
130
+ const result = this.statement(sql).run(...params);
131
+ return { changes: Number(result.changes), lastInsertRowid: Number(result.lastInsertRowid) };
132
+ }
133
+ catch (error) {
134
+ throw storageError(error, meaning);
135
+ }
136
+ }
137
+ get(sql, params = [], meaning) {
138
+ try {
139
+ return this.statement(sql).get(...params);
140
+ }
141
+ catch (error) {
142
+ throw storageError(error, meaning);
143
+ }
144
+ }
145
+ all(sql, params = []) {
146
+ try {
147
+ return this.statement(sql).all(...params);
148
+ }
149
+ catch (error) {
150
+ throw storageError(error);
151
+ }
152
+ }
153
+ /**
154
+ * Ends a transaction left open by a failure; quiet if there is none (SQLite
155
+ * may have ended it with the failure). It asks SQLite by trying, which
156
+ * reads the same on every Node.
157
+ */
158
+ rollback() {
159
+ if (this.closed)
160
+ return;
161
+ try {
162
+ this.db.exec("ROLLBACK");
163
+ }
164
+ catch {
165
+ // No transaction was open.
166
+ }
167
+ }
168
+ /** Ends a read's transaction. */
169
+ finishRead() {
170
+ if (this.closed)
171
+ return;
172
+ try {
173
+ this.db.exec("COMMIT");
174
+ }
175
+ catch {
176
+ this.rollback();
177
+ }
178
+ }
179
+ close() {
180
+ if (this.closed)
181
+ return;
182
+ this.closed = true;
183
+ this.prepared.clear();
184
+ this.db.close();
185
+ }
186
+ }
187
+ // The files themselves
188
+ /** The header fields that say whose file it is (SQLite's file format: offsets 60 and 68, big-endian). */
189
+ function readHeader(path) {
190
+ let fd;
191
+ try {
192
+ fd = openSync(path, "r");
193
+ }
194
+ catch (error) {
195
+ if (error.code === "ENOENT")
196
+ return null;
197
+ throw new OrbitError("storage_failed", { cause: error });
198
+ }
199
+ try {
200
+ const header = Buffer.alloc(100);
201
+ const length = readSync(fd, header, 0, 100, 0);
202
+ const sqlite = length === 100 && header.toString("latin1", 0, 16) === "SQLite format 3\0";
203
+ return { sqlite, userVersion: sqlite ? header.readInt32BE(60) : 0, applicationId: sqlite ? header.readInt32BE(68) : 0 };
204
+ }
205
+ finally {
206
+ closeSync(fd);
207
+ }
208
+ }
209
+ const OLDER_DATA = "This data folder holds boards from an older Orbit (1.x, or 2.0 before its rebuild), which this Orbit doesn't open. Nothing in it was changed. Keep it as it is: ";
210
+ /**
211
+ * The way out for a folder of older data. The older Orbit's own default,
212
+ * ~/.orbit (os.homedir() reads HOME, as Orbit does), takes the short form,
213
+ * which reads it into this Orbit's default folder.
214
+ */
215
+ function migrationNeeded(path) {
216
+ const older = join(homedir(), ".orbit");
217
+ if (resolve(path).startsWith(older + sep))
218
+ return `${OLDER_DATA}with DATA_DIR unset, \`orbit migrate\` brings its boards into ~/.starscape/orbit, the folder this Orbit uses, and orbit run then opens them.`;
219
+ return `${OLDER_DATA}\`orbit migrate <this folder> --to <a new folder>\` brings its boards into a new folder, and DATA_DIR then points there.`;
220
+ }
221
+ const NEWER_ORBIT = "This data folder was written by a newer Orbit. Nothing in it was changed. Update Orbit to open it.";
222
+ /**
223
+ * Whether the file at `path` is one this build may open: true when it is v2's
224
+ * at this schema version, false when there is no file. Anything else is
225
+ * refused without being opened, so its bytes stay as they are: a file of 1.x
226
+ * or the first 2.0 (no version) with `data_needs_migration`, a newer
227
+ * schema's with `data_from_newer_orbit`.
228
+ */
229
+ export function checkFile(path) {
230
+ const header = readHeader(path);
231
+ if (header === null)
232
+ return false;
233
+ if (!header.sqlite)
234
+ throw new OrbitError("storage_failed", { message: `${basename(path)} in this data folder isn't a database.` });
235
+ if (header.applicationId !== APPLICATION_ID || header.userVersion < 1)
236
+ throw new OrbitError("data_needs_migration", { message: migrationNeeded(path) });
237
+ if (header.userVersion > SCHEMA_VERSION)
238
+ throw new OrbitError("data_from_newer_orbit", { message: NEWER_ORBIT });
239
+ // v2 has one schema version so far; a later one upgrades older v2 files here instead.
240
+ if (header.userVersion < SCHEMA_VERSION)
241
+ throw new OrbitError("data_needs_migration", { message: migrationNeeded(path) });
242
+ return true;
243
+ }
244
+ /**
245
+ * Makes a database at `path` with `schema`, all at once: it is built under a
246
+ * temporary name and linked into place, so no other process ever sees a file
247
+ * without its version, and when two make the same file at once one wins and
248
+ * the other's attempt is discarded. True when this call made it.
249
+ */
250
+ export function createFile(path, schema) {
251
+ const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.creating`);
252
+ try {
253
+ const db = new DatabaseSync(temporary);
254
+ try {
255
+ db.exec(`PRAGMA application_id = ${String(APPLICATION_ID)}; PRAGMA user_version = ${String(SCHEMA_VERSION)}; PRAGMA journal_mode = WAL;`);
256
+ db.exec(`BEGIN; ${schema} COMMIT;`);
257
+ }
258
+ finally {
259
+ db.close();
260
+ }
261
+ try {
262
+ linkSync(temporary, path);
263
+ return true;
264
+ }
265
+ catch (error) {
266
+ const code = error.code;
267
+ if (code === "EEXIST")
268
+ return false;
269
+ if (code !== "EPERM" && code !== "ENOTSUP" && code !== "EXDEV")
270
+ throw error;
271
+ // A file system without hard links: a rename, checked first.
272
+ if (readHeader(path) !== null)
273
+ return false;
274
+ renameSync(temporary, path);
275
+ return true;
276
+ }
277
+ }
278
+ catch (error) {
279
+ throw storageError(error);
280
+ }
281
+ finally {
282
+ for (const suffix of ["", "-wal", "-shm", "-journal"])
283
+ rmSync(`${temporary}${suffix}`, { force: true });
284
+ }
285
+ }
@@ -0,0 +1,3 @@
1
+ export { openSqliteStore } from "./store.ts";
2
+ export type { SqliteStore, SqliteStoreOptions } from "./store.ts";
3
+ export { createSqliteBlobStore } from "./blobs.ts";
@@ -0,0 +1,2 @@
1
+ export { openSqliteStore } from "./store.js";
2
+ export { createSqliteBlobStore } from "./blobs.js";
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Runs bodies one at a time, in the order they asked: the in-process half of
3
+ * keeping a board's writes one at a time (SQLite's write lock is the other,
4
+ * between processes).
5
+ */
6
+ export declare class Lock {
7
+ private tail;
8
+ /** Waits for every earlier holder, then returns the function that lets go. */
9
+ acquire(): Promise<() => void>;
10
+ run<T>(body: () => Promise<T>): Promise<T>;
11
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Runs bodies one at a time, in the order they asked: the in-process half of
3
+ * keeping a board's writes one at a time (SQLite's write lock is the other,
4
+ * between processes).
5
+ */
6
+ export class Lock {
7
+ tail = Promise.resolve();
8
+ /** Waits for every earlier holder, then returns the function that lets go. */
9
+ async acquire() {
10
+ let release = () => undefined;
11
+ const next = new Promise((resolve) => {
12
+ release = resolve;
13
+ });
14
+ const previous = this.tail;
15
+ this.tail = previous.then(() => next);
16
+ await previous;
17
+ let released = false;
18
+ return () => {
19
+ if (released)
20
+ return;
21
+ released = true;
22
+ release();
23
+ };
24
+ }
25
+ async run(body) {
26
+ const release = await this.acquire();
27
+ try {
28
+ return await body();
29
+ }
30
+ finally {
31
+ release();
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * One board's records in SQL: the port's BoardReader and BoardWriter over a
3
+ * connection. Each method is one statement, prepared once per connection, and
4
+ * reads that serve a whole board return every row at once. Records come back
5
+ * built field for field in the order src/core/model.ts lists them, with the
6
+ * board's id added where a record names its board (board.db holds one board,
7
+ * so its rows don't repeat it).
8
+ */
9
+ import type { Board, CrossBoardRelation, Id, OrbitEvent } from "../../core/model.ts";
10
+ import type { BoardReader, BoardWriter } from "../../store/types.ts";
11
+ import type { Connection, Row } from "./database.ts";
12
+ /** A kind of record: its table, its fields in the model's order, and the statements every kind shares. */
13
+ interface Kind<T> {
14
+ table: string;
15
+ fields: readonly (keyof T & string)[];
16
+ /** The fields stored as columns: all but `board_id`. */
17
+ columns: readonly string[];
18
+ select: string;
19
+ insert: string;
20
+ }
21
+ export declare const BOARD: Kind<Board>;
22
+ export declare const CROSS: Kind<CrossBoardRelation>;
23
+ export declare const EVENT: Kind<OrbitEvent>;
24
+ /** The record a row holds, built in the model's field order. */
25
+ export declare function toRecord<T>(of: Kind<T>, row: Row, boardId: Id): T;
26
+ /** What a reader or writer works over. */
27
+ export interface Over {
28
+ c: Connection;
29
+ boardId: Id;
30
+ /** Awaited before each call: the writer's check that its write is still open. */
31
+ before: () => Promise<void>;
32
+ }
33
+ /** For readers, which have nothing to check before a call. */
34
+ export declare const noWait: () => Promise<void>;
35
+ /** The port's BoardReader over a connection. */
36
+ export declare function reader({ c, boardId, before }: Over): BoardReader;
37
+ /** What a write did that its store acts on once it commits. */
38
+ export interface WriteLog {
39
+ /** Events appended, with their place in the board's history. */
40
+ events: {
41
+ seq: number;
42
+ event: OrbitEvent;
43
+ }[];
44
+ /** The board's own record as the write left it, when the write changed it. */
45
+ board: Board | null;
46
+ }
47
+ /** Checks a writer makes against the rest of the data folder. */
48
+ export interface WriteChecks {
49
+ /** Another board has this slug. */
50
+ slugTaken(slug: string): boolean;
51
+ }
52
+ /** The port's BoardWriter over a connection in an open transaction. */
53
+ export declare function writer(over: Over, log: WriteLog, checks: WriteChecks): BoardWriter;
54
+ export {};