@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.
- package/Dockerfile +10 -8
- package/README.md +146 -164
- package/dist/full/csp.txt +1 -0
- package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
- package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
- package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
- package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
- package/dist/full/fonts/Mona_Sans/README.txt +10 -0
- package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
- package/dist/full/fonts/Newsreader/README.txt +8 -0
- package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
- package/dist/full/index.html +36 -653
- package/dist/full/js/app.BmuponPJ.js +32 -0
- package/dist/full/js/app.BmuponPJ.js.map +1 -0
- package/dist/full/js/chunk.1fJGQGbW.js +2 -0
- package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
- package/dist/full/js/chunk.5D7JO2zp.js +2 -0
- package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
- package/dist/full/js/chunk.BrNn_D79.js +2 -0
- package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
- package/dist/full/js/chunk.CokJXIqM.js +2 -0
- package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
- package/dist/full/js/chunk.D_Haqy6R.js +2 -0
- package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
- package/dist/full/js/chunk.RO0jsH6I.js +15 -0
- package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
- package/dist/full/js/chunk.dqWOEtBM.js +2 -0
- package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
- package/dist/full/js/warm.f260bbbc.js +15 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg-wide.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.2b154ffe.css +1 -0
- package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
- package/dist/full/version.json +2 -2
- package/docs/AGENT_GUIDE.md +114 -0
- package/docs/UPDATING.md +249 -64
- package/lib/adapters/indexeddb/blobs.d.ts +3 -0
- package/lib/adapters/indexeddb/blobs.js +54 -0
- package/lib/adapters/indexeddb/database.d.ts +60 -0
- package/lib/adapters/indexeddb/database.js +126 -0
- package/lib/adapters/indexeddb/index.d.ts +5 -0
- package/lib/adapters/indexeddb/index.js +3 -0
- package/lib/adapters/indexeddb/locks.d.ts +14 -0
- package/lib/adapters/indexeddb/locks.js +40 -0
- package/lib/adapters/indexeddb/store.d.ts +40 -0
- package/lib/adapters/indexeddb/store.js +424 -0
- package/lib/adapters/indexeddb/tables.d.ts +43 -0
- package/lib/adapters/indexeddb/tables.js +127 -0
- package/lib/adapters/memory/blobs.d.ts +3 -0
- package/lib/adapters/memory/blobs.js +44 -0
- package/lib/adapters/memory/board.d.ts +41 -0
- package/lib/adapters/memory/board.js +521 -0
- package/lib/adapters/memory/index.d.ts +2 -0
- package/lib/adapters/memory/index.js +2 -0
- package/lib/adapters/memory/search.d.ts +6 -0
- package/lib/adapters/memory/search.js +30 -0
- package/lib/adapters/memory/store.d.ts +34 -0
- package/lib/adapters/memory/store.js +266 -0
- package/lib/adapters/sqlite/backups.d.ts +53 -0
- package/lib/adapters/sqlite/backups.js +232 -0
- package/lib/adapters/sqlite/blobs.d.ts +14 -0
- package/lib/adapters/sqlite/blobs.js +109 -0
- package/lib/adapters/sqlite/board.d.ts +77 -0
- package/lib/adapters/sqlite/board.js +328 -0
- package/lib/adapters/sqlite/database.d.ts +67 -0
- package/lib/adapters/sqlite/database.js +285 -0
- package/lib/adapters/sqlite/index.d.ts +3 -0
- package/lib/adapters/sqlite/index.js +2 -0
- package/lib/adapters/sqlite/lock.d.ts +11 -0
- package/lib/adapters/sqlite/lock.js +34 -0
- package/lib/adapters/sqlite/records.d.ts +54 -0
- package/lib/adapters/sqlite/records.js +546 -0
- package/lib/adapters/sqlite/schema.d.ts +54 -0
- package/lib/adapters/sqlite/schema.js +276 -0
- package/lib/adapters/sqlite/store.d.ts +39 -0
- package/lib/adapters/sqlite/store.js +385 -0
- package/lib/browser/in-page.d.ts +31 -0
- package/lib/browser/in-page.js +46 -0
- package/lib/browser/old-preview.d.ts +13 -0
- package/lib/browser/old-preview.js +80 -0
- package/lib/browser/preview-board.d.ts +8 -0
- package/lib/browser/preview-board.js +69 -0
- package/lib/cli/main.d.ts +7 -0
- package/lib/cli/main.js +343 -0
- package/lib/cli/orbit.d.ts +8 -0
- package/lib/cli/orbit.js +25 -0
- package/lib/client/http.d.ts +38 -0
- package/lib/client/http.js +334 -0
- package/lib/client/local.d.ts +26 -0
- package/lib/client/local.js +190 -0
- package/lib/client/object-url.d.ts +8 -0
- package/lib/client/object-url.js +21 -0
- package/lib/client/types.d.ts +268 -0
- package/lib/client/types.js +8 -0
- package/lib/commands/agent-tools.d.ts +31 -0
- package/lib/commands/agent-tools.js +100 -0
- package/lib/commands/index.d.ts +56 -0
- package/lib/commands/index.js +298 -0
- package/lib/commands/parse.d.ts +73 -0
- package/lib/commands/parse.js +231 -0
- package/lib/commands/present.d.ts +134 -0
- package/lib/commands/present.js +198 -0
- package/lib/commands/session.d.ts +85 -0
- package/lib/commands/session.js +138 -0
- package/lib/commands/verbs.d.ts +35 -0
- package/lib/commands/verbs.js +842 -0
- package/lib/core/actors.d.ts +18 -0
- package/lib/core/actors.js +45 -0
- package/lib/core/agent.d.ts +128 -0
- package/lib/core/agent.js +323 -0
- package/lib/core/boards.d.ts +113 -0
- package/lib/core/boards.js +288 -0
- package/lib/core/bootstrap.d.ts +47 -0
- package/lib/core/bootstrap.js +100 -0
- package/lib/core/bytes.d.ts +10 -0
- package/lib/core/bytes.js +52 -0
- package/lib/core/card-paths.d.ts +25 -0
- package/lib/core/card-paths.js +44 -0
- package/lib/core/context.d.ts +60 -0
- package/lib/core/context.js +42 -0
- package/lib/core/cross-board.d.ts +81 -0
- package/lib/core/cross-board.js +228 -0
- package/lib/core/describe.d.ts +21 -0
- package/lib/core/describe.js +81 -0
- package/lib/core/errors.d.ts +159 -0
- package/lib/core/errors.js +160 -0
- package/lib/core/files.d.ts +79 -0
- package/lib/core/files.js +262 -0
- package/lib/core/history.d.ts +20 -0
- package/lib/core/history.js +53 -0
- package/lib/core/image-types.d.ts +16 -0
- package/lib/core/image-types.js +36 -0
- package/lib/core/model.d.ts +263 -0
- package/lib/core/model.js +34 -0
- package/lib/core/nebula-link.d.ts +14 -0
- package/lib/core/nebula-link.js +39 -0
- package/lib/core/relations.d.ts +58 -0
- package/lib/core/relations.js +112 -0
- package/lib/core/search.d.ts +38 -0
- package/lib/core/search.js +92 -0
- package/lib/core/sessions.d.ts +44 -0
- package/lib/core/sessions.js +158 -0
- package/lib/core/snapshot-schema.d.ts +295 -0
- package/lib/core/snapshot-schema.js +82 -0
- package/lib/core/snapshots.d.ts +75 -0
- package/lib/core/snapshots.js +409 -0
- package/lib/core/states.d.ts +25 -0
- package/lib/core/states.js +126 -0
- package/lib/core/text.d.ts +40 -0
- package/lib/core/text.js +113 -0
- package/lib/core/tickets.d.ts +69 -0
- package/lib/core/tickets.js +311 -0
- package/lib/core/trello.d.ts +17 -0
- package/lib/core/trello.js +269 -0
- package/lib/core/verdicts.d.ts +28 -0
- package/lib/core/verdicts.js +92 -0
- package/lib/core/views.d.ts +95 -0
- package/lib/core/views.js +186 -0
- package/lib/http/app.d.ts +46 -0
- package/lib/http/app.js +491 -0
- package/lib/mcp-server.d.ts +10 -0
- package/lib/mcp-server.js +120 -0
- package/lib/node/assets.d.ts +7 -0
- package/lib/node/assets.js +68 -0
- package/lib/node/data-folder.d.ts +21 -0
- package/lib/node/data-folder.js +34 -0
- package/lib/node/env.d.ts +38 -0
- package/lib/node/env.js +49 -0
- package/lib/node/host-check.d.ts +24 -0
- package/lib/node/host-check.js +53 -0
- package/lib/node/host-services.d.ts +9 -0
- package/lib/node/host-services.js +150 -0
- package/lib/node/local.d.ts +22 -0
- package/lib/node/local.js +61 -0
- package/lib/node/migrate.d.ts +53 -0
- package/lib/node/migrate.js +635 -0
- package/lib/node/package.d.ts +7 -0
- package/lib/node/package.js +14 -0
- package/lib/node/repo-board.d.ts +49 -0
- package/lib/node/repo-board.js +245 -0
- package/lib/node/serve.d.ts +8 -0
- package/lib/node/serve.js +106 -0
- package/lib/orbit.d.ts +47 -0
- package/lib/orbit.js +44 -0
- package/lib/quiet-sqlite-warning.d.ts +1 -0
- package/lib/quiet-sqlite-warning.js +18 -0
- package/lib/store/blob-path.d.ts +6 -0
- package/lib/store/blob-path.js +12 -0
- package/lib/store/conformance.d.ts +52 -0
- package/lib/store/conformance.js +1449 -0
- package/lib/store/search-words.d.ts +10 -0
- package/lib/store/search-words.js +19 -0
- package/lib/store/types.d.ts +452 -0
- package/lib/store/types.js +34 -0
- package/llms.txt +63 -0
- package/package.json +109 -25
- package/src/cli/orbit.js +11 -658
- package/src/mcp-server.js +16 -876
- package/SKILL-ORBIT.md +0 -151
- package/build.mjs +0 -210
- package/dist/full/js/app.4e6fbe20.js +0 -952
- package/dist/full/js/app.4e6fbe20.js.map +0 -7
- package/dist/full/styles.4e60d824.css +0 -1
- package/docs/AGENT_PROTOCOL.md +0 -424
- package/docs/MCP_SETUP.md +0 -54
- package/docs/ORBIT_DISPATCH.md +0 -280
- package/docs/SS-Orbit-Dark.png +0 -0
- package/docs/SS-Orbit-Light.png +0 -0
- package/docs/mcp-client.example.json +0 -13
- package/docs/setup/orbit-init-wizard.md +0 -174
- package/public/apple-touch-icon.png +0 -0
- package/public/cursors/live_arrow.cur +0 -0
- package/public/cursors/live_link.cur +0 -0
- package/public/cursors/live_move.cur +0 -0
- package/public/cursors/live_select.cur +0 -0
- package/public/cursors/live_unavail.cur +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
- package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
- package/public/fonts/Chakra_Petch/OFL.txt +0 -93
- package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
- package/public/fonts/Inter/OFL.txt +0 -93
- package/public/fonts/Inter/README.txt +0 -118
- package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
- package/public/fonts/JetBrains_Mono/README.txt +0 -79
- package/public/fonts/OpenRing-Regular.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
- package/public/fonts/Open_Sans/README.txt +0 -100
- package/public/fonts/Orbitron/OFL.txt +0 -93
- package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
- package/public/fonts/Orbitron/README.txt +0 -68
- package/public/fonts/Rajdhani/OFL.txt +0 -93
- package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
- package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
- package/public/fonts/runescape-uf.ttf +0 -0
- package/public/index.html +0 -769
- package/public/js/actor-labels.js +0 -65
- package/public/js/api.js +0 -39
- package/public/js/app.js +0 -45
- package/public/js/artifact-drawer.js +0 -79
- package/public/js/board-export.js +0 -161
- package/public/js/board-menu.js +0 -374
- package/public/js/card-actions.js +0 -545
- package/public/js/config.js +0 -20
- package/public/js/create-card.js +0 -190
- package/public/js/dom.js +0 -19
- package/public/js/drawer.js +0 -273
- package/public/js/font-preference.js +0 -230
- package/public/js/format.js +0 -433
- package/public/js/help-menu.js +0 -31
- package/public/js/icons.js +0 -16
- package/public/js/kanban.js +0 -694
- package/public/js/local-backend.js +0 -1015
- package/public/js/main.js +0 -161
- package/public/js/modal.js +0 -33
- package/public/js/motion-preference.js +0 -64
- package/public/js/panel.js +0 -58
- package/public/js/router.js +0 -134
- package/public/js/search.js +0 -56
- package/public/js/settings-tabs.js +0 -7
- package/public/js/settings.js +0 -1634
- package/public/js/sse.js +0 -159
- package/public/js/starscape.js +0 -332
- package/public/js/state.js +0 -129
- package/public/js/text-editing.js +0 -128
- package/public/js/theme-preference.js +0 -95
- package/public/js/ticket-detail.js +0 -1516
- package/public/js/ticket-typeahead.js +0 -49
- package/public/js/toast.js +0 -89
- package/public/js/topbar-notes.js +0 -155
- package/public/js/transport.js +0 -22
- package/public/js/typeahead.js +0 -206
- package/public/js/unread.js +0 -121
- package/public/js/update-check.js +0 -58
- package/public/js/url-routes.js +0 -94
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg-wide.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +0 -7709
- package/src/core/agent.js +0 -387
- package/src/core/agents-md.js +0 -109
- package/src/core/artifacts.js +0 -149
- package/src/core/attachments.js +0 -238
- package/src/core/auth.js +0 -85
- package/src/core/backups.js +0 -142
- package/src/core/board.js +0 -148
- package/src/core/boards.js +0 -409
- package/src/core/bootstrap.js +0 -170
- package/src/core/css-bundle.js +0 -30
- package/src/core/db.js +0 -315
- package/src/core/delete-board.js +0 -163
- package/src/core/dispatch.js +0 -1007
- package/src/core/events.js +0 -314
- package/src/core/http.js +0 -115
- package/src/core/migrate.js +0 -198
- package/src/core/paths.js +0 -55
- package/src/core/provision-repo-board.js +0 -119
- package/src/core/queries.js +0 -246
- package/src/core/registry.js +0 -211
- package/src/core/relations.js +0 -163
- package/src/core/review-verdicts.js +0 -138
- package/src/core/router.js +0 -556
- package/src/core/search.js +0 -183
- package/src/core/seed.js +0 -234
- package/src/core/snapshots.js +0 -692
- package/src/core/states.js +0 -167
- package/src/core/system-picker.js +0 -109
- package/src/core/tickets.js +0 -400
- package/src/core/util.js +0 -106
- package/src/core/version-check.js +0 -154
- package/src/mcp/orbit-client.js +0 -287
- package/src/server.js +0 -115
package/src/core/events.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
// Event recording and SSE fan-out. `recordEvent` is always called with the
|
|
2
|
-
// per-board `db` and the owning `boardId`, so events get written to the
|
|
3
|
-
// right per-board DB and SSE listeners only see events for the board they
|
|
4
|
-
// subscribed to.
|
|
5
|
-
//
|
|
6
|
-
// Same-process HTTP mutations can broadcast immediately. MCP/CLI tools mutate
|
|
7
|
-
// the same per-board SQLite DB from separate Node processes, so the web server
|
|
8
|
-
// also keeps a lightweight per-board watcher while SSE clients are connected.
|
|
9
|
-
// Watchers use SQLite `PRAGMA data_version` as the cheap "did another
|
|
10
|
-
// connection commit?" check, then drain semantic rows from the `events` table.
|
|
11
|
-
|
|
12
|
-
import { localOwnerActor } from "./auth.js";
|
|
13
|
-
import { getBoardByRegistryId, openBoardDb } from "./registry.js";
|
|
14
|
-
import { sendJson, setCors } from "./http.js";
|
|
15
|
-
import { id, now } from "./util.js";
|
|
16
|
-
|
|
17
|
-
const sseClients = new Set();
|
|
18
|
-
const boardWatchers = new Map();
|
|
19
|
-
const DEFAULT_SSE_POLL_MS = 500;
|
|
20
|
-
const MIN_SSE_POLL_MS = 25;
|
|
21
|
-
const MAX_SSE_POLL_MS = 30_000;
|
|
22
|
-
const EVENT_BATCH_LIMIT = 200;
|
|
23
|
-
// If a client's socket buffers more than this, it's not keeping up. We end
|
|
24
|
-
// the stream so the browser reconnects and replays from Last-Event-ID rather
|
|
25
|
-
// than letting Node's write buffer grow without bound.
|
|
26
|
-
const SSE_MAX_BUFFERED_BYTES = 1_000_000;
|
|
27
|
-
|
|
28
|
-
// Events written inside a tx() are not visible to other connections until
|
|
29
|
-
// COMMIT, so broadcasting them immediately would tell SSE listeners about a
|
|
30
|
-
// mutation that might still roll back. db.tx() brackets each transaction with
|
|
31
|
-
// __enterEventTx/__settleEventTx; while depth > 0 we queue and only fan out
|
|
32
|
-
// (or drop, on rollback) once the outermost transaction settles.
|
|
33
|
-
let eventTxDepth = 0;
|
|
34
|
-
const bufferedBroadcasts = [];
|
|
35
|
-
|
|
36
|
-
export function __enterEventTx() {
|
|
37
|
-
eventTxDepth += 1;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function __settleEventTx(committed) {
|
|
41
|
-
eventTxDepth -= 1;
|
|
42
|
-
if (eventTxDepth > 0) return;
|
|
43
|
-
const queued = bufferedBroadcasts.splice(0, bufferedBroadcasts.length);
|
|
44
|
-
if (!committed) return;
|
|
45
|
-
for (const item of queued) {
|
|
46
|
-
broadcastSSE(item.payload);
|
|
47
|
-
advanceWatcherCursorByRowid(item.boardId, item.rowid);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function recordEvent(db, boardId, type, ticketId, actor, body) {
|
|
52
|
-
const eventId = id();
|
|
53
|
-
const timestamp = now();
|
|
54
|
-
const result = db.prepare(
|
|
55
|
-
"INSERT INTO events (id, ticket_id, actor, type, body_json, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
56
|
-
).run(eventId, ticketId, actor, type, JSON.stringify(body || {}), timestamp);
|
|
57
|
-
|
|
58
|
-
const payload = {
|
|
59
|
-
id: eventId,
|
|
60
|
-
type,
|
|
61
|
-
ticket_id: ticketId,
|
|
62
|
-
board_id: boardId,
|
|
63
|
-
actor,
|
|
64
|
-
data: body || {},
|
|
65
|
-
timestamp
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
if (eventTxDepth > 0) {
|
|
69
|
-
bufferedBroadcasts.push({ payload, boardId, rowid: result.lastInsertRowid });
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
broadcastSSE(payload);
|
|
73
|
-
advanceWatcherCursorByRowid(boardId, result.lastInsertRowid);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function startSSEStream(req, res, url) {
|
|
77
|
-
const actor = localOwnerActor();
|
|
78
|
-
|
|
79
|
-
const boardId = url.searchParams.get("board_id") || null;
|
|
80
|
-
let board = null;
|
|
81
|
-
if (boardId) {
|
|
82
|
-
board = getBoardByRegistryId(boardId);
|
|
83
|
-
if (!board) {
|
|
84
|
-
sendJson(res, 404, { error: "board_not_found" });
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const lastEventId = req.headers["last-event-id"] || url.searchParams.get("last_event_id") || null;
|
|
90
|
-
const watcher = board ? getOrCreateBoardWatcher(board) : null;
|
|
91
|
-
|
|
92
|
-
setCors(res);
|
|
93
|
-
res.writeHead(200, {
|
|
94
|
-
"Content-Type": "text/event-stream",
|
|
95
|
-
"Cache-Control": "no-cache",
|
|
96
|
-
Connection: "keep-alive",
|
|
97
|
-
"X-Accel-Buffering": "no"
|
|
98
|
-
});
|
|
99
|
-
res.write(":ok\n\n");
|
|
100
|
-
|
|
101
|
-
if (lastEventId && board) {
|
|
102
|
-
replayEventsForBoard(res, board, lastEventId);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const heartbeat = setInterval(() => {
|
|
106
|
-
if (!res.writableEnded) res.write(":heartbeat\n\n");
|
|
107
|
-
}, 30_000);
|
|
108
|
-
|
|
109
|
-
const client = { res, boardId, boardRow: board, actor, heartbeat, watcher };
|
|
110
|
-
sseClients.add(client);
|
|
111
|
-
if (watcher) watcher.clients.add(client);
|
|
112
|
-
|
|
113
|
-
req.on("close", () => {
|
|
114
|
-
clearInterval(heartbeat);
|
|
115
|
-
sseClients.delete(client);
|
|
116
|
-
if (watcher) {
|
|
117
|
-
watcher.clients.delete(client);
|
|
118
|
-
stopBoardWatcherIfIdle(watcher);
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function getOrCreateBoardWatcher(boardRow) {
|
|
124
|
-
const existing = boardWatchers.get(boardRow.id);
|
|
125
|
-
if (existing) {
|
|
126
|
-
existing.boardRow = boardRow;
|
|
127
|
-
return existing;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const watcher = {
|
|
131
|
-
boardId: boardRow.id,
|
|
132
|
-
boardRow,
|
|
133
|
-
clients: new Set(),
|
|
134
|
-
dataVersion: dataVersionForBoard(boardRow),
|
|
135
|
-
cursor: latestEventCursor(boardRow),
|
|
136
|
-
timer: null
|
|
137
|
-
};
|
|
138
|
-
watcher.timer = setInterval(() => pollBoardWatcher(watcher), ssePollIntervalMs());
|
|
139
|
-
watcher.timer.unref?.();
|
|
140
|
-
boardWatchers.set(boardRow.id, watcher);
|
|
141
|
-
return watcher;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function stopBoardWatcherIfIdle(watcher) {
|
|
145
|
-
if (watcher.clients.size > 0) return;
|
|
146
|
-
clearInterval(watcher.timer);
|
|
147
|
-
boardWatchers.delete(watcher.boardId);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function pollBoardWatcher(watcher) {
|
|
151
|
-
if (watcher.clients.size === 0) {
|
|
152
|
-
stopBoardWatcherIfIdle(watcher);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
let nextDataVersion;
|
|
157
|
-
try {
|
|
158
|
-
nextDataVersion = dataVersionForBoard(watcher.boardRow);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
console.warn(`[orbit] Failed to read data_version for board ${watcher.boardId}: ${error.message}`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (nextDataVersion === watcher.dataVersion) return;
|
|
165
|
-
watcher.dataVersion = nextDataVersion;
|
|
166
|
-
drainBoardWatcherEvents(watcher);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function drainBoardWatcherEvents(watcher) {
|
|
170
|
-
try {
|
|
171
|
-
while (true) {
|
|
172
|
-
const rows = eventsAfterCursor(watcher.boardRow, watcher.cursor);
|
|
173
|
-
if (rows.length === 0) return;
|
|
174
|
-
|
|
175
|
-
for (const row of rows) {
|
|
176
|
-
const event = eventFromRow(watcher.boardRow, row);
|
|
177
|
-
for (const client of watcher.clients) {
|
|
178
|
-
writeSSEFrame(client.res, event);
|
|
179
|
-
}
|
|
180
|
-
watcher.cursor = eventCursor(row);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (rows.length < EVENT_BATCH_LIMIT) return;
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
console.warn(`[orbit] Failed to poll events for board ${watcher.boardId}: ${error.message}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function ssePollIntervalMs() {
|
|
191
|
-
const raw = process.env.ORBIT_SSE_POLL_MS;
|
|
192
|
-
const value = raw === undefined ? DEFAULT_SSE_POLL_MS : Number(raw);
|
|
193
|
-
if (!Number.isFinite(value) || value <= 0) return DEFAULT_SSE_POLL_MS;
|
|
194
|
-
return Math.min(MAX_SSE_POLL_MS, Math.max(MIN_SSE_POLL_MS, Math.trunc(value)));
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function dataVersionForBoard(boardRow) {
|
|
198
|
-
const row = openBoardDb(boardRow).prepare("PRAGMA data_version").get();
|
|
199
|
-
return Number(row?.data_version || 0);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function replayEventsForBoard(res, boardRow, afterId) {
|
|
203
|
-
const db = openBoardDb(boardRow);
|
|
204
|
-
const anchor = db.prepare("SELECT rowid AS event_rowid FROM events WHERE id = ?").get(afterId);
|
|
205
|
-
if (!anchor) return latestEventCursor(boardRow);
|
|
206
|
-
|
|
207
|
-
let cursor = eventCursor(anchor);
|
|
208
|
-
const rows = db
|
|
209
|
-
.prepare(
|
|
210
|
-
`SELECT e.rowid AS event_rowid, e.id, e.type, e.ticket_id, e.actor, e.body_json, e.created_at
|
|
211
|
-
FROM events e
|
|
212
|
-
WHERE e.rowid > ?
|
|
213
|
-
ORDER BY e.rowid LIMIT ${EVENT_BATCH_LIMIT}`
|
|
214
|
-
)
|
|
215
|
-
.all(anchor.event_rowid);
|
|
216
|
-
|
|
217
|
-
for (const row of rows) {
|
|
218
|
-
const event = eventFromRow(boardRow, row);
|
|
219
|
-
writeSSEFrame(res, event);
|
|
220
|
-
cursor = eventCursor(row);
|
|
221
|
-
}
|
|
222
|
-
return cursor;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function latestEventCursor(boardRow) {
|
|
226
|
-
const db = openBoardDb(boardRow);
|
|
227
|
-
const row = db
|
|
228
|
-
.prepare("SELECT rowid AS event_rowid FROM events ORDER BY rowid DESC LIMIT 1")
|
|
229
|
-
.get();
|
|
230
|
-
return eventCursor(row);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function eventCursor(row) {
|
|
234
|
-
if (!row) return null;
|
|
235
|
-
return { event_rowid: Number(row.event_rowid) };
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function compareCursors(left, right) {
|
|
239
|
-
if (!left && !right) return 0;
|
|
240
|
-
if (!left) return -1;
|
|
241
|
-
if (!right) return 1;
|
|
242
|
-
if (left.event_rowid > right.event_rowid) return 1;
|
|
243
|
-
if (left.event_rowid < right.event_rowid) return -1;
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function eventsAfterCursor(boardRow, cursor) {
|
|
248
|
-
const db = openBoardDb(boardRow);
|
|
249
|
-
if (!cursor) {
|
|
250
|
-
return db
|
|
251
|
-
.prepare(
|
|
252
|
-
`SELECT e.rowid AS event_rowid, e.id, e.type, e.ticket_id, e.actor, e.body_json, e.created_at
|
|
253
|
-
FROM events e
|
|
254
|
-
ORDER BY e.rowid LIMIT ${EVENT_BATCH_LIMIT}`
|
|
255
|
-
)
|
|
256
|
-
.all();
|
|
257
|
-
}
|
|
258
|
-
return db
|
|
259
|
-
.prepare(
|
|
260
|
-
`SELECT e.rowid AS event_rowid, e.id, e.type, e.ticket_id, e.actor, e.body_json, e.created_at
|
|
261
|
-
FROM events e
|
|
262
|
-
WHERE e.rowid > ?
|
|
263
|
-
ORDER BY e.rowid LIMIT ${EVENT_BATCH_LIMIT}`
|
|
264
|
-
)
|
|
265
|
-
.all(cursor.event_rowid);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function eventFromRow(boardRow, row) {
|
|
269
|
-
return {
|
|
270
|
-
id: row.id,
|
|
271
|
-
type: row.type,
|
|
272
|
-
ticket_id: row.ticket_id,
|
|
273
|
-
board_id: boardRow.id,
|
|
274
|
-
actor: row.actor,
|
|
275
|
-
data: parseEventBody(row.body_json),
|
|
276
|
-
timestamp: row.created_at
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function parseEventBody(bodyJson) {
|
|
281
|
-
try {
|
|
282
|
-
return JSON.parse(bodyJson || "{}");
|
|
283
|
-
} catch {
|
|
284
|
-
return {};
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function advanceWatcherCursorByRowid(boardId, rowid) {
|
|
289
|
-
const watcher = boardWatchers.get(boardId);
|
|
290
|
-
if (!watcher) return;
|
|
291
|
-
const cursor = { event_rowid: Number(rowid) };
|
|
292
|
-
if (cursor && compareCursors(cursor, watcher.cursor) > 0) {
|
|
293
|
-
watcher.cursor = cursor;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function broadcastSSE(event) {
|
|
298
|
-
for (const client of sseClients) {
|
|
299
|
-
if (client.boardId && event.board_id !== client.boardId) continue;
|
|
300
|
-
writeSSEFrame(client.res, event);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function writeSSEFrame(res, event) {
|
|
305
|
-
if (res.writableEnded) return;
|
|
306
|
-
// A backpressured client (closed laptop, dead tab) would otherwise make
|
|
307
|
-
// Node buffer every event in memory forever. Cut it loose; the EventSource
|
|
308
|
-
// client reconnects and replays missed events via Last-Event-ID.
|
|
309
|
-
if (res.writableLength > SSE_MAX_BUFFERED_BYTES) {
|
|
310
|
-
res.end();
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
res.write(`id: ${event.id}\nevent: board\ndata: ${JSON.stringify(event)}\n\n`);
|
|
314
|
-
}
|
package/src/core/http.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import { createReadStream } from "node:fs";
|
|
3
|
-
import { extname, join, resolve as resolvePath } from "node:path";
|
|
4
|
-
import { bundleCss } from "./css-bundle.js";
|
|
5
|
-
import { PUBLIC_DIR, ROOT_DIR } from "./paths.js";
|
|
6
|
-
import { httpError } from "./util.js";
|
|
7
|
-
|
|
8
|
-
export function setCors(res) {
|
|
9
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
10
|
-
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,OPTIONS");
|
|
11
|
-
res.setHeader(
|
|
12
|
-
"Access-Control-Allow-Headers",
|
|
13
|
-
"Content-Type,Authorization,X-Orbit-Actor-Type,X-Orbit-Actor-Name,X-Orbit-Actor-Id,X-Orbit-Agent-Name,X-File-Name"
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function sendJson(res, status, body) {
|
|
18
|
-
setCors(res);
|
|
19
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
20
|
-
res.end(JSON.stringify(body, null, 2));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function sendEmpty(res, status) {
|
|
24
|
-
setCors(res);
|
|
25
|
-
res.writeHead(status);
|
|
26
|
-
res.end();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const MAX_JSON_BODY_BYTES = 100 * 1024 * 1024;
|
|
30
|
-
|
|
31
|
-
export async function readJson(req, maxBytes = MAX_JSON_BODY_BYTES) {
|
|
32
|
-
const buffer = await readRaw(req, maxBytes);
|
|
33
|
-
if (buffer.length === 0) return {};
|
|
34
|
-
const text = buffer.toString("utf8");
|
|
35
|
-
if (!text.trim()) return {};
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(text);
|
|
38
|
-
} catch {
|
|
39
|
-
throw httpError(400, "invalid_json");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function readRaw(req, maxBytes) {
|
|
44
|
-
const chunks = [];
|
|
45
|
-
let size = 0;
|
|
46
|
-
for await (const chunk of req) {
|
|
47
|
-
size += chunk.length;
|
|
48
|
-
if (size > maxBytes) throw httpError(413, "payload_too_large");
|
|
49
|
-
chunks.push(chunk);
|
|
50
|
-
}
|
|
51
|
-
return Buffer.concat(chunks);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function sendFile(res, filePath, mimeType) {
|
|
55
|
-
setCors(res);
|
|
56
|
-
res.writeHead(200, {
|
|
57
|
-
"Content-Type": mimeType || contentType(filePath),
|
|
58
|
-
"Cache-Control": "private, max-age=3600"
|
|
59
|
-
});
|
|
60
|
-
createReadStream(filePath).pipe(res);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function contentType(filePath) {
|
|
64
|
-
const types = {
|
|
65
|
-
".html": "text/html; charset=utf-8",
|
|
66
|
-
".css": "text/css; charset=utf-8",
|
|
67
|
-
".js": "text/javascript; charset=utf-8",
|
|
68
|
-
".json": "application/json; charset=utf-8",
|
|
69
|
-
".svg": "image/svg+xml"
|
|
70
|
-
};
|
|
71
|
-
return types[extname(filePath)] || "application/octet-stream";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function shouldBundleSourceStyles(pathname, filePath) {
|
|
75
|
-
return pathname === "/styles.css" && filePath === join(ROOT_DIR, "public", "styles.css");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function staticAssetPathname(pathname) {
|
|
79
|
-
return pathname === "/app" || pathname.startsWith("/app/")
|
|
80
|
-
? pathname.slice(4) || "/"
|
|
81
|
-
: pathname;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export async function serveStatic(req, res, pathname) {
|
|
85
|
-
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
86
|
-
sendEmpty(res, 405);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const staticPathname = staticAssetPathname(pathname);
|
|
91
|
-
const requested = staticPathname === "/" ? "/index.html" : staticPathname;
|
|
92
|
-
const filePath = resolvePath(PUBLIC_DIR, `.${decodeURIComponent(requested)}`);
|
|
93
|
-
if (!filePath.startsWith(PUBLIC_DIR)) {
|
|
94
|
-
sendEmpty(res, 403);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const data = shouldBundleSourceStyles(staticPathname, filePath)
|
|
100
|
-
? Buffer.from((await bundleCss({ entryPoint: filePath, outfile: filePath })).cssFile.contents)
|
|
101
|
-
: await readFile(filePath);
|
|
102
|
-
res.writeHead(200, {
|
|
103
|
-
"Content-Type": contentType(filePath),
|
|
104
|
-
"Cache-Control": "no-store"
|
|
105
|
-
});
|
|
106
|
-
if (req.method !== "HEAD") res.end(data);
|
|
107
|
-
else res.end();
|
|
108
|
-
} catch {
|
|
109
|
-
if (pathname !== "/") {
|
|
110
|
-
await serveStatic(req, res, "/");
|
|
111
|
-
} else {
|
|
112
|
-
sendEmpty(res, 404);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
package/src/core/migrate.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* migrate.js — one-time startup migration of per-board SQLite files from
|
|
3
|
-
* in-repo .orbit/ directories into the central DATA_DIR/boards/<slug>/ store.
|
|
4
|
-
*
|
|
5
|
-
* Called during board.js startup, before ensureSchemaForEveryBoard, so that
|
|
6
|
-
* by the time per-board connections are opened they are already at their
|
|
7
|
-
* canonical central location.
|
|
8
|
-
*
|
|
9
|
-
* If a migration fails the registry row is NOT updated and Orbit continues
|
|
10
|
-
* to serve from the old in-repo path. A structured warning with agent-actionable
|
|
11
|
-
* manual instructions is printed to stderr.
|
|
12
|
-
*/
|
|
13
|
-
import { existsSync, mkdirSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
14
|
-
import { dirname, join } from "node:path";
|
|
15
|
-
import { closeConnection, getRegistry, openConnection } from "./db.js";
|
|
16
|
-
import { BOARDS_DIR } from "./paths.js";
|
|
17
|
-
import { listBoards } from "./registry.js";
|
|
18
|
-
|
|
19
|
-
/** Normalize to forward slashes for consistent cross-platform prefix checks. */
|
|
20
|
-
const fwd = (p) => p.replace(/\\/g, "/");
|
|
21
|
-
|
|
22
|
-
/** True when db_path already lives inside the canonical BOARDS_DIR. */
|
|
23
|
-
function isCentral(dbPath) {
|
|
24
|
-
return fwd(dbPath).startsWith(fwd(BOARDS_DIR) + "/");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Canonical central path for a given slug. */
|
|
28
|
-
function centralPathFor(slug) {
|
|
29
|
-
return join(BOARDS_DIR, slug, "board.db");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Delete a file if it exists; silently ignore errors. */
|
|
33
|
-
function tryUnlink(p) {
|
|
34
|
-
try { if (existsSync(p)) unlinkSync(p); } catch { /* ignore */ }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Walk upward from startDir, removing each directory that is now empty.
|
|
39
|
-
* Stops at stopDir (inclusive) or the first non-empty directory.
|
|
40
|
-
* This cleans up empty .orbit/ trees left behind after moving the db out.
|
|
41
|
-
*/
|
|
42
|
-
function pruneEmptyAncestors(startDir, stopDir) {
|
|
43
|
-
const stopFwd = fwd(stopDir);
|
|
44
|
-
let cur = fwd(startDir);
|
|
45
|
-
while (cur.startsWith(stopFwd)) {
|
|
46
|
-
try {
|
|
47
|
-
if (!existsSync(cur)) {
|
|
48
|
-
/* already gone, climb */
|
|
49
|
-
} else if (readdirSync(cur).length > 0) {
|
|
50
|
-
break; // non-empty — stop here
|
|
51
|
-
} else {
|
|
52
|
-
// rmSync({recursive:true, force:true}) works correctly for both files and
|
|
53
|
-
// empty directories across Node versions and platforms. We've already
|
|
54
|
-
// confirmed the directory is empty so nothing is over-deleted.
|
|
55
|
-
rmSync(cur, { recursive: true, force: true });
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
if (cur === stopFwd) break; // reached the limit
|
|
61
|
-
const parent = fwd(dirname(cur));
|
|
62
|
-
if (parent === cur) break; // filesystem root
|
|
63
|
-
cur = parent;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* For a db path inside a repo's .orbit directory, return the .orbit parent.
|
|
69
|
-
* /repo/.orbit/board.db → /repo/.orbit
|
|
70
|
-
* /repo/.orbit/boards/x/board.db → /repo/.orbit
|
|
71
|
-
* Returns null if ".orbit" is not found in the path.
|
|
72
|
-
*/
|
|
73
|
-
function orbitParentDir(dbPath) {
|
|
74
|
-
const parts = fwd(dbPath).split("/");
|
|
75
|
-
const idx = parts.lastIndexOf(".orbit");
|
|
76
|
-
if (idx < 0) return null;
|
|
77
|
-
return parts.slice(0, idx + 1).join("/");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function sqlLiteral(v) {
|
|
81
|
-
return `'${String(v).replace(/'/g, "''")}'`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Attempt to migrate one board from in-repo storage to central storage.
|
|
86
|
-
* Returns a result object with status: 'migrated' | 'failed' | 'missing_source'.
|
|
87
|
-
*/
|
|
88
|
-
function migrateOneBoard(boardRow) {
|
|
89
|
-
const { id, slug, db_path: oldPath } = boardRow;
|
|
90
|
-
|
|
91
|
-
if (!existsSync(oldPath)) {
|
|
92
|
-
// Source file is gone; ensureSchemaForEveryBoard will prune the stale row.
|
|
93
|
-
return { status: "missing_source", board: boardRow, oldPath };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const newPath = centralPathFor(slug);
|
|
97
|
-
mkdirSync(dirname(newPath), { recursive: true });
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
// Safety: if the target path is already registered to a *different* board id,
|
|
101
|
-
// skip rather than overwrite live data (guards against corrupted registries with
|
|
102
|
-
// duplicate slugs — cannot happen through normal API use but is defensive).
|
|
103
|
-
const existingOwner = getRegistry().prepare("SELECT id FROM boards WHERE db_path = ?").get(newPath);
|
|
104
|
-
if (existingOwner && existingOwner.id !== id) {
|
|
105
|
-
return {
|
|
106
|
-
status: "failed",
|
|
107
|
-
board: boardRow,
|
|
108
|
-
oldPath,
|
|
109
|
-
newPath,
|
|
110
|
-
error: new Error(
|
|
111
|
-
`central path ${newPath} is already registered to board ${existingOwner.id} — skipping to avoid data loss`
|
|
112
|
-
)
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Remove any stale central copy left by a previous partial migration.
|
|
117
|
-
if (existsSync(newPath)) tryUnlink(newPath);
|
|
118
|
-
|
|
119
|
-
// VACUUM INTO creates a clean, WAL-flushed copy. It captures only committed
|
|
120
|
-
// data and produces a single-file database (no -wal/-shm at the destination).
|
|
121
|
-
const db = openConnection(oldPath);
|
|
122
|
-
db.exec(`VACUUM INTO ${sqlLiteral(newPath)};`);
|
|
123
|
-
closeConnection(oldPath);
|
|
124
|
-
|
|
125
|
-
// Update the registry before deleting the source so the canonical pointer is
|
|
126
|
-
// always valid: if the process dies between these two steps the old file still
|
|
127
|
-
// exists and the next run will redo the migration cleanly.
|
|
128
|
-
getRegistry()
|
|
129
|
-
.prepare("UPDATE boards SET db_path = ?, updated_at = ? WHERE id = ?")
|
|
130
|
-
.run(newPath, new Date().toISOString(), id);
|
|
131
|
-
|
|
132
|
-
// Remove source files (main db + WAL + SHM + journal).
|
|
133
|
-
tryUnlink(oldPath);
|
|
134
|
-
tryUnlink(oldPath + "-wal");
|
|
135
|
-
tryUnlink(oldPath + "-shm");
|
|
136
|
-
tryUnlink(oldPath + "-journal");
|
|
137
|
-
|
|
138
|
-
// Prune now-empty .orbit/ directories from the repo tree.
|
|
139
|
-
const orbitDir = orbitParentDir(oldPath);
|
|
140
|
-
if (orbitDir) pruneEmptyAncestors(dirname(oldPath), orbitDir);
|
|
141
|
-
|
|
142
|
-
return { status: "migrated", board: boardRow, oldPath, newPath };
|
|
143
|
-
} catch (error) {
|
|
144
|
-
// Ensure no lingering open connection or partial central file on failure.
|
|
145
|
-
try { closeConnection(oldPath); } catch { /* ignore */ }
|
|
146
|
-
tryUnlink(newPath);
|
|
147
|
-
return { status: "failed", board: boardRow, oldPath, newPath, error };
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Migrate all boards whose db_path lives outside DATA_DIR into central storage.
|
|
153
|
-
* Called at server/MCP startup before any per-board connections are opened.
|
|
154
|
-
* Prints a concise summary for migrated boards; prints agent-actionable
|
|
155
|
-
* instructions for any failures so a human or AI can complete the migration.
|
|
156
|
-
*/
|
|
157
|
-
export function migrateInRepoBoards() {
|
|
158
|
-
const pending = listBoards().filter((row) => !isCentral(row.db_path));
|
|
159
|
-
if (pending.length === 0) return;
|
|
160
|
-
|
|
161
|
-
const results = pending.map(migrateOneBoard);
|
|
162
|
-
const migrated = results.filter((r) => r.status === "migrated");
|
|
163
|
-
const failed = results.filter((r) => r.status === "failed");
|
|
164
|
-
|
|
165
|
-
if (migrated.length > 0) {
|
|
166
|
-
const n = migrated.length;
|
|
167
|
-
console.log(`[orbit] Migrated ${n} board${n > 1 ? "s" : ""} to central storage (${BOARDS_DIR}):`);
|
|
168
|
-
for (const r of migrated) {
|
|
169
|
-
console.log(` ✓ ${r.board.slug}`);
|
|
170
|
-
console.log(` from: ${r.oldPath}`);
|
|
171
|
-
console.log(` to: ${r.newPath}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (failed.length > 0) {
|
|
176
|
-
const sep = " │";
|
|
177
|
-
const n = failed.length;
|
|
178
|
-
console.error(`\n[orbit] ⚠ Could not auto-migrate ${n} board${n > 1 ? "s" : ""} to central storage.`);
|
|
179
|
-
console.error(`${sep} These boards continue to work from their current paths.`);
|
|
180
|
-
console.error(`${sep} To complete the migration manually, run the commands below and`);
|
|
181
|
-
console.error(`${sep} restart orbit run:\n`);
|
|
182
|
-
for (const r of failed) {
|
|
183
|
-
const slash = process.platform === "win32" ? "\\" : "/";
|
|
184
|
-
const destDir = dirname(r.newPath).replace(/\//g, slash);
|
|
185
|
-
const src = r.oldPath.replace(/\//g, slash);
|
|
186
|
-
const dest = r.newPath.replace(/\//g, slash);
|
|
187
|
-
console.error(`${sep} Board: ${r.board.slug} (error: ${r.error?.message ?? r.error})`);
|
|
188
|
-
if (process.platform === "win32") {
|
|
189
|
-
console.error(`${sep} md "${destDir}"`);
|
|
190
|
-
console.error(`${sep} copy "${src}" "${dest}"`);
|
|
191
|
-
} else {
|
|
192
|
-
console.error(`${sep} mkdir -p '${destDir}'`);
|
|
193
|
-
console.error(`${sep} cp '${src}' '${dest}'`);
|
|
194
|
-
}
|
|
195
|
-
console.error(sep);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
package/src/core/paths.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
|
|
8
|
-
export const ROOT_DIR = resolve(__dirname, "..", "..");
|
|
9
|
-
|
|
10
|
-
const _distFull = join(ROOT_DIR, "dist", "full");
|
|
11
|
-
const _publicDir = join(ROOT_DIR, "public");
|
|
12
|
-
|
|
13
|
-
/** Where static assets (HTML/JS/CSS) are served from.
|
|
14
|
-
*
|
|
15
|
-
* public/ — editable source; `pnpm dev` bundles @starlove/ui imports on the fly.
|
|
16
|
-
* dist/full — prebuilt, hashed bundles from `pnpm build`; what Docker/npm ship.
|
|
17
|
-
*
|
|
18
|
-
* Auto (default): public/ when it exists, else dist/full. Repo dev therefore
|
|
19
|
-
* always sees live CSS/JS edits. Docker and other dist-only installs fall back
|
|
20
|
-
* to the bundle without needing an env var.
|
|
21
|
-
*
|
|
22
|
-
* Override: ORBIT_USE_DIST=1 force dist/full, ORBIT_USE_DIST=0 force public/. */
|
|
23
|
-
function resolvePublicDir() {
|
|
24
|
-
const distExists = existsSync(_distFull);
|
|
25
|
-
const publicExists = existsSync(_publicDir);
|
|
26
|
-
const pref = process.env.ORBIT_USE_DIST;
|
|
27
|
-
|
|
28
|
-
if (pref === "1" && distExists) return _distFull;
|
|
29
|
-
if (pref === "0" && publicExists) return _publicDir;
|
|
30
|
-
if (publicExists) return _publicDir;
|
|
31
|
-
if (distExists) return _distFull;
|
|
32
|
-
return _publicDir;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const PUBLIC_DIR = resolvePublicDir();
|
|
36
|
-
export const MCP_SERVER_PATH = join(ROOT_DIR, "src", "mcp-server.js");
|
|
37
|
-
/** Repo root for default board placement; cwd unless PROJECT_ROOT is set. */
|
|
38
|
-
export const PROJECT_ROOT = process.env.PROJECT_ROOT ? resolve(process.env.PROJECT_ROOT) : resolve(process.cwd());
|
|
39
|
-
// Store registry + exports in ~/.orbit so global installs don't write into the package dir.
|
|
40
|
-
export const DATA_DIR = process.env.DATA_DIR || join(homedir(), ".orbit");
|
|
41
|
-
/** Central registry of all boards (metadata + paths to per-board SQLite files). */
|
|
42
|
-
export const REGISTRY_DB_PATH = join(DATA_DIR, "registry.db");
|
|
43
|
-
/** Central directory where every board's SQLite file lives: DATA_DIR/boards/<slug>/board.db */
|
|
44
|
-
export const BOARDS_DIR = join(DATA_DIR, "boards");
|
|
45
|
-
export const EXPORT_DIR = join(DATA_DIR, "exports");
|
|
46
|
-
export const BACKUP_DIR = join(DATA_DIR, "backups");
|
|
47
|
-
/** Central directory for dispatch run artifacts: DATA_DIR/dispatch-runs/<board-slug>/<run-id>/ */
|
|
48
|
-
export const DISPATCH_RUNS_DIR = join(DATA_DIR, "dispatch-runs");
|
|
49
|
-
export const PORT = Number(process.env.PORT || 13701);
|
|
50
|
-
|
|
51
|
-
mkdirSync(DATA_DIR, { recursive: true });
|
|
52
|
-
mkdirSync(BOARDS_DIR, { recursive: true });
|
|
53
|
-
mkdirSync(EXPORT_DIR, { recursive: true });
|
|
54
|
-
mkdirSync(BACKUP_DIR, { recursive: true });
|
|
55
|
-
mkdirSync(DISPATCH_RUNS_DIR, { recursive: true });
|