@starlove/orbit 1.4.0 → 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.62905fcb.js +0 -952
  204. package/dist/full/js/app.62905fcb.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 -229
  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,521 @@
1
+ /**
2
+ * One board's records in memory, and the reader and writer over them. The
3
+ * tables are arrays in the order records were written, so "then by when it
4
+ * was written" is array order, and every sort here is stable.
5
+ */
6
+ import { OrbitError } from "../../core/errors.js";
7
+ import { matches } from "./search.js";
8
+ export function emptyTables() {
9
+ return {
10
+ board: null,
11
+ states: [],
12
+ labels: [],
13
+ tickets: [],
14
+ ticketLabels: [],
15
+ comments: [],
16
+ relations: [],
17
+ crossBoardRelations: [],
18
+ entries: [],
19
+ verdicts: [],
20
+ attachments: [],
21
+ events: [],
22
+ search: new Map()
23
+ };
24
+ }
25
+ /**
26
+ * Every call gives other callers a turn first, as a real database's I/O does,
27
+ * so the conformance cases see the interleavings an adapter has to survive.
28
+ */
29
+ const yieldTurn = () => Promise.resolve();
30
+ const copy = (record) => ({ ...record });
31
+ /** Sets the fields a patch names; a field given as undefined is not named. */
32
+ function apply(record, patch) {
33
+ for (const [key, value] of Object.entries(patch)) {
34
+ if (value !== undefined)
35
+ Object.assign(record, { [key]: value });
36
+ }
37
+ }
38
+ const copies = (records) => records.map(copy);
39
+ /** Code-point order, as SQLite compares text. */
40
+ export function byCodePoint(a, b) {
41
+ let at = 0;
42
+ while (at < a.length && at < b.length) {
43
+ const left = a.codePointAt(at) ?? 0;
44
+ const right = b.codePointAt(at) ?? 0;
45
+ if (left !== right)
46
+ return left - right;
47
+ at += left > 0xffff ? 2 : 1;
48
+ }
49
+ return a.length - b.length;
50
+ }
51
+ const byCreated = (records) => [...records].sort((a, b) => (a.created_at < b.created_at ? -1 : a.created_at > b.created_at ? 1 : 0));
52
+ const asciiLower = (text) => text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
53
+ function find(records, id) {
54
+ return records.find((record) => record.id === id);
55
+ }
56
+ function filterTickets(tickets, filter) {
57
+ const ids = filter.ids ? new Set(filter.ids) : null;
58
+ return tickets
59
+ .filter((ticket) => (filter.archived === undefined || (ticket.archived_at !== null) === filter.archived) &&
60
+ (filter.parent === undefined || ticket.parent_ticket_id === filter.parent) &&
61
+ (filter.state === undefined || ticket.state_id === filter.state) &&
62
+ (ids === null || ids.has(ticket.id)))
63
+ .sort((a, b) => a.number - b.number);
64
+ }
65
+ /** A reader over `tables()`: committed tables for a read, the draft for a write. */
66
+ export function reader(tables, ready = yieldTurn) {
67
+ const t = async () => {
68
+ await ready();
69
+ return tables();
70
+ };
71
+ return {
72
+ board: {
73
+ async get() {
74
+ const { board } = await t();
75
+ if (!board)
76
+ throw new OrbitError("board_not_found");
77
+ return copy(board);
78
+ }
79
+ },
80
+ states: {
81
+ async list() {
82
+ return copies([...(await t()).states].sort((a, b) => a.position - b.position));
83
+ },
84
+ async get(id) {
85
+ const state = find((await t()).states, id);
86
+ return state ? copy(state) : null;
87
+ }
88
+ },
89
+ labels: {
90
+ async list() {
91
+ return copies([...(await t()).labels].sort((a, b) => byCodePoint(a.name, b.name)));
92
+ }
93
+ },
94
+ tickets: {
95
+ async get(id) {
96
+ const ticket = find((await t()).tickets, id);
97
+ return ticket ? copy(ticket) : null;
98
+ },
99
+ async byNumber(number) {
100
+ const ticket = (await t()).tickets.find((candidate) => candidate.number === number);
101
+ return ticket ? copy(ticket) : null;
102
+ },
103
+ async byTitle(title) {
104
+ const wanted = asciiLower(title);
105
+ const found = (await t()).tickets
106
+ .filter((ticket) => asciiLower(ticket.title) === wanted)
107
+ .sort((a, b) => Number(a.archived_at !== null) - Number(b.archived_at !== null) || b.number - a.number)[0];
108
+ return found ? copy(found) : null;
109
+ },
110
+ async list(filter = {}) {
111
+ return copies(filterTickets((await t()).tickets, filter));
112
+ },
113
+ async lastNumber() {
114
+ return (await t()).tickets.reduce((top, ticket) => Math.max(top, ticket.number), 0);
115
+ }
116
+ },
117
+ ticketLabels: {
118
+ async list(ticketIds) {
119
+ const { ticketLabels } = await t();
120
+ const wanted = ticketIds ? new Set(ticketIds) : null;
121
+ return copies(ticketLabels.filter((link) => wanted === null || wanted.has(link.ticket_id)));
122
+ }
123
+ },
124
+ comments: {
125
+ async get(id) {
126
+ const comment = find((await t()).comments, id);
127
+ return comment ? copy(comment) : null;
128
+ },
129
+ async list(ticketId) {
130
+ const all = (await t()).comments;
131
+ return copies(byCreated(ticketId === undefined ? all : all.filter((comment) => comment.ticket_id === ticketId)));
132
+ },
133
+ async countByTicket(options = {}) {
134
+ const counts = new Map();
135
+ for (const comment of (await t()).comments) {
136
+ if (options.forAgents && comment.omitted_for_ai === 1)
137
+ continue;
138
+ if (options.exceptBody !== undefined && comment.body === options.exceptBody)
139
+ continue;
140
+ counts.set(comment.ticket_id, (counts.get(comment.ticket_id) ?? 0) + 1);
141
+ }
142
+ return [...counts].map(([ticket_id, count]) => ({ ticket_id, count }));
143
+ }
144
+ },
145
+ relations: {
146
+ async get(id) {
147
+ const relation = find((await t()).relations, id);
148
+ return relation ? copy(relation) : null;
149
+ },
150
+ async list(ticketId) {
151
+ const all = (await t()).relations;
152
+ return copies(byCreated(ticketId === undefined ? all : all.filter((r) => r.source_ticket_id === ticketId || r.target_ticket_id === ticketId)));
153
+ }
154
+ },
155
+ crossBoardRelations: {
156
+ async list(ticketId) {
157
+ const all = (await t()).crossBoardRelations;
158
+ return copies(byCreated(ticketId === undefined ? all : all.filter((r) => r.source_ticket_id === ticketId)));
159
+ }
160
+ },
161
+ entries: {
162
+ async get(id) {
163
+ const entry = find((await t()).entries, id);
164
+ return entry ? copy(entry) : null;
165
+ },
166
+ async list() {
167
+ return copies(byCreated((await t()).entries));
168
+ }
169
+ },
170
+ verdicts: {
171
+ async get(id) {
172
+ const verdict = find((await t()).verdicts, id);
173
+ return verdict ? copy(verdict) : null;
174
+ },
175
+ async list(ticketId) {
176
+ const all = (await t()).verdicts;
177
+ return copies(byCreated(ticketId === undefined ? all : all.filter((v) => v.ticket_id === ticketId)));
178
+ }
179
+ },
180
+ attachments: {
181
+ async get(id) {
182
+ const attachment = find((await t()).attachments, id);
183
+ return attachment ? copy(attachment) : null;
184
+ },
185
+ async list(ticketId) {
186
+ const all = (await t()).attachments;
187
+ return copies(byCreated(ticketId === undefined ? all : all.filter((a) => a.ticket_id === ticketId)));
188
+ }
189
+ },
190
+ events: {
191
+ async list(filter = {}) {
192
+ const types = filter.types ? new Set(filter.types) : null;
193
+ return copies(byCreated((await t()).events.filter((event) => (filter.ticketId === undefined || event.ticket_id === filter.ticketId) &&
194
+ (types === null || types.has(event.type)) &&
195
+ (filter.since === undefined || event.created_at >= filter.since))));
196
+ },
197
+ async latest(ticketIds, types) {
198
+ const wanted = new Set(ticketIds);
199
+ const kinds = new Set(types);
200
+ const newest = new Map();
201
+ for (const event of byCreated((await t()).events)) {
202
+ if (event.ticket_id !== null && wanted.has(event.ticket_id) && kinds.has(event.type))
203
+ newest.set(event.ticket_id, event);
204
+ }
205
+ return copies([...newest.values()]);
206
+ },
207
+ async after(id, limit) {
208
+ const { events } = await t();
209
+ const at = events.findIndex((event) => event.id === id);
210
+ return at < 0 ? [] : copies(events.slice(at + 1, at + 1 + limit));
211
+ },
212
+ async last() {
213
+ const { events } = await t();
214
+ const event = events[events.length - 1];
215
+ return event ? copy(event) : null;
216
+ }
217
+ },
218
+ search: {
219
+ async query(text, options) {
220
+ const { search, tickets } = await t();
221
+ const open = filterTickets(tickets, { archived: false });
222
+ return open
223
+ .filter((ticket) => {
224
+ const document = search.get(ticket.id);
225
+ return document !== undefined && matches(document, text);
226
+ })
227
+ .slice(0, options.limit)
228
+ .map((ticket) => ({ ticket_id: ticket.id, rank: 0 }));
229
+ }
230
+ }
231
+ };
232
+ }
233
+ /**
234
+ * A writer over a draft. Every check the port names is made here, in the
235
+ * order the port lists them, so a write that breaks one throws before it
236
+ * changes anything.
237
+ */
238
+ export function writer(boardId, draft, log, checks, guard) {
239
+ const read = reader(() => draft, guard);
240
+ const fresh = (records, record) => {
241
+ if (find(records, record.id))
242
+ throw new OrbitError("duplicate_id");
243
+ };
244
+ const onBoard = (id) => {
245
+ if (id !== boardId)
246
+ throw new OrbitError("invalid_reference");
247
+ };
248
+ const ticketHere = (id) => {
249
+ if (id !== null && !find(draft.tickets, id))
250
+ throw new OrbitError("invalid_reference");
251
+ };
252
+ const stateHere = (id) => {
253
+ if (!find(draft.states, id))
254
+ throw new OrbitError("invalid_reference");
255
+ };
256
+ const setBoard = (board) => {
257
+ onBoard(board.id);
258
+ if (checks.slugTaken(board.slug))
259
+ throw new OrbitError("board_slug_taken");
260
+ draft.board = copy(board);
261
+ };
262
+ function mustFind(records, id, code) {
263
+ const record = find(records, id);
264
+ if (!record)
265
+ throw new OrbitError(code);
266
+ return record;
267
+ }
268
+ function removeTicket(id) {
269
+ mustFind(draft.tickets, id, "ticket_not_found");
270
+ const doomedVerdicts = new Set(draft.verdicts.filter((verdict) => verdict.ticket_id === id).map((verdict) => verdict.id));
271
+ draft.tickets = draft.tickets.filter((ticket) => ticket.id !== id);
272
+ draft.comments = draft.comments.filter((comment) => comment.ticket_id !== id);
273
+ draft.ticketLabels = draft.ticketLabels.filter((link) => link.ticket_id !== id);
274
+ draft.relations = draft.relations.filter((relation) => relation.source_ticket_id !== id && relation.target_ticket_id !== id);
275
+ draft.crossBoardRelations = draft.crossBoardRelations.filter((relation) => relation.source_ticket_id !== id);
276
+ draft.verdicts = draft.verdicts
277
+ .filter((verdict) => !doomedVerdicts.has(verdict.id))
278
+ .map((verdict) => (verdict.supersedes_prior_review_id !== null && doomedVerdicts.has(verdict.supersedes_prior_review_id) ? { ...verdict, supersedes_prior_review_id: null } : verdict));
279
+ draft.attachments = draft.attachments.filter((attachment) => attachment.ticket_id !== id);
280
+ draft.events = draft.events.map((event) => (event.ticket_id === id ? { ...event, ticket_id: null } : event));
281
+ draft.entries = draft.entries.map((entry) => (entry.ticket_id === id ? { ...entry, ticket_id: null } : entry));
282
+ draft.search.delete(id);
283
+ }
284
+ return {
285
+ async clear() {
286
+ await guard();
287
+ Object.assign(draft, emptyTables());
288
+ },
289
+ board: {
290
+ ...read.board,
291
+ async set(board) {
292
+ await guard();
293
+ setBoard(board);
294
+ },
295
+ async update(patch) {
296
+ await guard();
297
+ if (!draft.board)
298
+ throw new OrbitError("board_not_found");
299
+ const next = copy(draft.board);
300
+ apply(next, patch);
301
+ setBoard(next);
302
+ return copy(draft.board);
303
+ }
304
+ },
305
+ states: {
306
+ ...read.states,
307
+ async insert(state) {
308
+ await guard();
309
+ fresh(draft.states, state);
310
+ onBoard(state.board_id);
311
+ if (draft.states.some((other) => other.name === state.name))
312
+ throw new OrbitError("state_name_taken");
313
+ draft.states.push(copy(state));
314
+ },
315
+ async update(id, patch) {
316
+ await guard();
317
+ const state = mustFind(draft.states, id, "state_not_found");
318
+ if (patch.name !== undefined && draft.states.some((other) => other.id !== id && other.name === patch.name))
319
+ throw new OrbitError("state_name_taken");
320
+ apply(state, patch);
321
+ return copy(state);
322
+ },
323
+ async remove(id) {
324
+ await guard();
325
+ mustFind(draft.states, id, "state_not_found");
326
+ if (draft.tickets.some((ticket) => ticket.state_id === id))
327
+ throw new OrbitError("state_has_tickets");
328
+ draft.states = draft.states.filter((state) => state.id !== id);
329
+ }
330
+ },
331
+ labels: {
332
+ ...read.labels,
333
+ async insert(label) {
334
+ await guard();
335
+ fresh(draft.labels, label);
336
+ onBoard(label.board_id);
337
+ if (draft.labels.some((other) => other.name === label.name))
338
+ throw new OrbitError("label_name_taken");
339
+ draft.labels.push(copy(label));
340
+ }
341
+ },
342
+ tickets: {
343
+ ...read.tickets,
344
+ async create(ticket) {
345
+ await guard();
346
+ const number = draft.tickets.reduce((top, other) => Math.max(top, other.number), 0) + 1;
347
+ const made = { ...ticket, number };
348
+ fresh(draft.tickets, made);
349
+ onBoard(made.board_id);
350
+ stateHere(made.state_id);
351
+ draft.tickets.push(made);
352
+ return copy(made);
353
+ },
354
+ async insert(ticket) {
355
+ await guard();
356
+ fresh(draft.tickets, ticket);
357
+ onBoard(ticket.board_id);
358
+ stateHere(ticket.state_id);
359
+ if (draft.tickets.some((other) => other.number === ticket.number))
360
+ throw new OrbitError("ticket_number_taken");
361
+ draft.tickets.push(copy(ticket));
362
+ },
363
+ async update(id, patch) {
364
+ await guard();
365
+ const ticket = mustFind(draft.tickets, id, "ticket_not_found");
366
+ if (patch.state_id !== undefined)
367
+ stateHere(patch.state_id);
368
+ apply(ticket, patch);
369
+ return copy(ticket);
370
+ },
371
+ async remove(id) {
372
+ await guard();
373
+ removeTicket(id);
374
+ }
375
+ },
376
+ ticketLabels: {
377
+ ...read.ticketLabels,
378
+ async set(ticketId, labelIds) {
379
+ await guard();
380
+ ticketHere(ticketId);
381
+ for (const labelId of labelIds)
382
+ if (!find(draft.labels, labelId))
383
+ throw new OrbitError("invalid_reference");
384
+ draft.ticketLabels = draft.ticketLabels.filter((link) => link.ticket_id !== ticketId);
385
+ for (const labelId of new Set(labelIds))
386
+ draft.ticketLabels.push({ ticket_id: ticketId, label_id: labelId });
387
+ },
388
+ async add(ticketId, labelId) {
389
+ await guard();
390
+ ticketHere(ticketId);
391
+ if (!find(draft.labels, labelId))
392
+ throw new OrbitError("invalid_reference");
393
+ if (!draft.ticketLabels.some((link) => link.ticket_id === ticketId && link.label_id === labelId)) {
394
+ draft.ticketLabels.push({ ticket_id: ticketId, label_id: labelId });
395
+ }
396
+ }
397
+ },
398
+ comments: {
399
+ ...read.comments,
400
+ async insert(comment) {
401
+ await guard();
402
+ fresh(draft.comments, comment);
403
+ ticketHere(comment.ticket_id);
404
+ draft.comments.push(copy(comment));
405
+ },
406
+ async update(id, patch) {
407
+ await guard();
408
+ const comment = mustFind(draft.comments, id, "comment_not_found");
409
+ comment.omitted_for_ai = patch.omitted_for_ai;
410
+ return copy(comment);
411
+ }
412
+ },
413
+ relations: {
414
+ ...read.relations,
415
+ async insert(relation) {
416
+ await guard();
417
+ fresh(draft.relations, relation);
418
+ ticketHere(relation.source_ticket_id);
419
+ ticketHere(relation.target_ticket_id);
420
+ const repeat = draft.relations.some((other) => other.source_ticket_id === relation.source_ticket_id && other.target_ticket_id === relation.target_ticket_id && other.type === relation.type);
421
+ if (repeat)
422
+ throw new OrbitError("relation_exists");
423
+ draft.relations.push(copy(relation));
424
+ },
425
+ async remove(id) {
426
+ await guard();
427
+ mustFind(draft.relations, id, "relation_not_found");
428
+ draft.relations = draft.relations.filter((relation) => relation.id !== id);
429
+ }
430
+ },
431
+ crossBoardRelations: {
432
+ ...read.crossBoardRelations,
433
+ async insert(relation) {
434
+ await guard();
435
+ fresh(draft.crossBoardRelations, relation);
436
+ ticketHere(relation.source_ticket_id);
437
+ const repeat = draft.crossBoardRelations.some((other) => other.source_ticket_id === relation.source_ticket_id &&
438
+ other.target_board_id === relation.target_board_id &&
439
+ other.target_ticket_id === relation.target_ticket_id &&
440
+ other.type === relation.type);
441
+ if (repeat)
442
+ throw new OrbitError("relation_exists");
443
+ draft.crossBoardRelations.push(copy(relation));
444
+ },
445
+ async remove(id) {
446
+ await guard();
447
+ mustFind(draft.crossBoardRelations, id, "relation_not_found");
448
+ draft.crossBoardRelations = draft.crossBoardRelations.filter((relation) => relation.id !== id);
449
+ }
450
+ },
451
+ entries: {
452
+ ...read.entries,
453
+ async insert(entry) {
454
+ await guard();
455
+ fresh(draft.entries, entry);
456
+ onBoard(entry.board_id);
457
+ ticketHere(entry.ticket_id);
458
+ draft.entries.push(copy(entry));
459
+ },
460
+ async update(id, patch) {
461
+ await guard();
462
+ const entry = mustFind(draft.entries, id, "board_entry_not_found");
463
+ apply(entry, patch);
464
+ return copy(entry);
465
+ },
466
+ async remove(id) {
467
+ await guard();
468
+ mustFind(draft.entries, id, "board_entry_not_found");
469
+ draft.entries = draft.entries.filter((entry) => entry.id !== id);
470
+ }
471
+ },
472
+ verdicts: {
473
+ ...read.verdicts,
474
+ async insert(verdict) {
475
+ await guard();
476
+ fresh(draft.verdicts, verdict);
477
+ ticketHere(verdict.ticket_id);
478
+ if (verdict.supersedes_prior_review_id !== null && !find(draft.verdicts, verdict.supersedes_prior_review_id))
479
+ throw new OrbitError("invalid_reference");
480
+ draft.verdicts.push(copy(verdict));
481
+ }
482
+ },
483
+ attachments: {
484
+ ...read.attachments,
485
+ async insert(attachment) {
486
+ await guard();
487
+ fresh(draft.attachments, attachment);
488
+ onBoard(attachment.board_id);
489
+ ticketHere(attachment.ticket_id);
490
+ draft.attachments.push(copy(attachment));
491
+ },
492
+ async remove(id) {
493
+ await guard();
494
+ mustFind(draft.attachments, id, "attachment_not_found");
495
+ draft.attachments = draft.attachments.filter((attachment) => attachment.id !== id);
496
+ }
497
+ },
498
+ events: {
499
+ ...read.events,
500
+ async append(event) {
501
+ await guard();
502
+ fresh(draft.events, event);
503
+ ticketHere(event.ticket_id);
504
+ draft.events.push(copy(event));
505
+ log.events.push(copy(event));
506
+ }
507
+ },
508
+ search: {
509
+ ...read.search,
510
+ async index(ticketId, document) {
511
+ await guard();
512
+ ticketHere(ticketId);
513
+ draft.search.set(ticketId, { ...document });
514
+ },
515
+ async remove(ticketId) {
516
+ await guard();
517
+ draft.search.delete(ticketId);
518
+ }
519
+ }
520
+ };
521
+ }
@@ -0,0 +1,2 @@
1
+ export { createMemoryStore, MemoryData } from "./store.ts";
2
+ export { createMemoryBlobStore } from "./blobs.ts";
@@ -0,0 +1,2 @@
1
+ export { createMemoryStore, MemoryData } from "./store.js";
2
+ export { createMemoryBlobStore } from "./blobs.js";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Search in memory: the reference for what `SearchReader.query` must match.
3
+ * A real adapter ranks; this one only filters, and lists hits by card number.
4
+ */
5
+ import type { SearchDocument } from "../../store/types.ts";
6
+ export declare function matches(document: SearchDocument, query: string): boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Search in memory: the reference for what `SearchReader.query` must match.
3
+ * A real adapter ranks; this one only filters, and lists hits by card number.
4
+ */
5
+ import { literalQuery, queryWords } from "../../store/search-words.js";
6
+ /**
7
+ * A document's words: runs of letters, digits and `_`, lowercased, accents
8
+ * folded (as SQLite's unicode61 tokenizer with `_` as a token character).
9
+ */
10
+ function words(text) {
11
+ return text
12
+ .normalize("NFD")
13
+ .replace(/\p{M}/gu, "")
14
+ .toLowerCase()
15
+ .split(/[^\p{L}\p{N}_]+/u)
16
+ .filter(Boolean);
17
+ }
18
+ const asciiLower = (text) => text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
19
+ export function matches(document, query) {
20
+ const wanted = queryWords(query);
21
+ if (wanted.length === 0) {
22
+ const text = literalQuery(query);
23
+ if (text === null)
24
+ return false;
25
+ const literal = asciiLower(text);
26
+ return [document.title, document.description, document.details, document.comments].some((field) => asciiLower(field).includes(literal));
27
+ }
28
+ const have = words([document.title, document.description, document.type, document.details, document.comments].join("\n"));
29
+ return wanted.every((word) => have.some((token) => token.startsWith(word)));
30
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The memory adapter: Orbit's storage port over plain objects. It is the
3
+ * reference the conformance suite is written against, the fast store for the
4
+ * core's own tests, and the smallest example of an adapter.
5
+ *
6
+ * Several stores can share one MemoryData, as several processes share a data
7
+ * folder: each write is serialised across all of them, and each hears the
8
+ * others' changes. A write works on a copy of the board's tables and swaps it
9
+ * in when the body returns, so a read always holds one committed state.
10
+ */
11
+ import type { BoardChange, Id, RegisteredBoard } from "../../core/model.ts";
12
+ import type { OrbitStore } from "../../store/types.ts";
13
+ import type { Tables, WriteLog } from "./board.ts";
14
+ /** Everything one Orbit keeps, shared by every memory store opened over it. */
15
+ export declare class MemoryData {
16
+ /** In the order boards were registered. */
17
+ directory: RegisteredBoard[];
18
+ readonly settings: Map<string, string>;
19
+ /** Each board's committed tables. A write replaces them whole and never changes them in place. */
20
+ readonly tables: Map<string, Tables>;
21
+ private readonly tails;
22
+ private readonly listeners;
23
+ /** Runs `body` once every earlier body with the same key has finished. */
24
+ exclusive<T>(key: string, body: () => Promise<T>): Promise<T>;
25
+ listen(boardId: Id, listener: (change: BoardChange) => void): () => void;
26
+ /** Tells every listener on the board, after the write that made them has committed. */
27
+ announce(boardId: Id, log: WriteLog): void;
28
+ entry(id: Id): RegisteredBoard | undefined;
29
+ slugTaken(slug: string, exceptId: Id): boolean;
30
+ /** Swaps in a write's tables and, when the write changed the board's record, brings its listing in line (as SQLite does). */
31
+ commit(boardId: Id, draft: Tables): void;
32
+ }
33
+ /** A store over `data`; pass the same MemoryData to two stores to have two views of one Orbit. */
34
+ export declare function createMemoryStore(data?: MemoryData): OrbitStore;