beads-enhanced-ui 0.1.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/LICENSE +22 -0
- package/README.md +95 -0
- package/app/index.html +49 -0
- package/app/main.bundle.js +999 -0
- package/app/main.bundle.js.map +7 -0
- package/app/protocol.js +216 -0
- package/app/styles.css +2342 -0
- package/bin/bdui.js +19 -0
- package/package.json +90 -0
- package/server/app.js +110 -0
- package/server/bd.js +227 -0
- package/server/cli/commands.js +203 -0
- package/server/cli/daemon.js +271 -0
- package/server/cli/index.js +135 -0
- package/server/cli/open.js +139 -0
- package/server/cli/usage.js +27 -0
- package/server/config.js +36 -0
- package/server/db.js +154 -0
- package/server/index.js +76 -0
- package/server/list-adapters.js +264 -0
- package/server/logging.js +23 -0
- package/server/registry-watcher.js +200 -0
- package/server/subscriptions.js +299 -0
- package/server/validators.js +113 -0
- package/server/watcher.js +139 -0
- package/server/ws.js +1363 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { resolveWorkspaceDatabase } from './db.js';
|
|
4
|
+
import { debug } from './logging.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Watch the resolved workspace database target and invoke a callback after a
|
|
8
|
+
* debounce window.
|
|
9
|
+
*
|
|
10
|
+
* For SQLite workspaces this watches the DB file's parent directory and filters
|
|
11
|
+
* by file name. For non-SQLite backends (for example Dolt), this watches the
|
|
12
|
+
* workspace `.beads` directory.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} root_dir - Project root directory (starting point for resolution).
|
|
15
|
+
* @param {() => void} onChange - Called when changes are detected.
|
|
16
|
+
* @param {{ debounce_ms?: number, cooldown_ms?: number, explicit_db?: string }} [options]
|
|
17
|
+
* @returns {{ close: () => void, rebind: (opts?: { root_dir?: string, explicit_db?: string }) => void, path: string }}
|
|
18
|
+
*/
|
|
19
|
+
export function watchDb(root_dir, onChange, options = {}) {
|
|
20
|
+
const debounce_ms = options.debounce_ms ?? 250;
|
|
21
|
+
const cooldown_ms = options.cooldown_ms ?? 1000;
|
|
22
|
+
const log = debug('watcher');
|
|
23
|
+
|
|
24
|
+
/** @type {ReturnType<typeof setTimeout> | undefined} */
|
|
25
|
+
let timer;
|
|
26
|
+
/** @type {fs.FSWatcher | undefined} */
|
|
27
|
+
let watcher;
|
|
28
|
+
let cooldown_until = 0;
|
|
29
|
+
let current_path = '';
|
|
30
|
+
let current_dir = '';
|
|
31
|
+
let current_file = '';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Schedule the debounced onChange callback.
|
|
35
|
+
*/
|
|
36
|
+
const schedule = () => {
|
|
37
|
+
if (timer) {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
timer = setTimeout(() => {
|
|
41
|
+
onChange();
|
|
42
|
+
cooldown_until = Date.now() + cooldown_ms;
|
|
43
|
+
}, debounce_ms);
|
|
44
|
+
timer.unref();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Attach a watcher to the directory containing the resolved DB path.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} base_dir
|
|
51
|
+
* @param {string | undefined} explicit_db
|
|
52
|
+
*/
|
|
53
|
+
const bind = (base_dir, explicit_db) => {
|
|
54
|
+
const resolved = resolveWorkspaceDatabase({ cwd: base_dir, explicit_db });
|
|
55
|
+
current_path = resolved.path;
|
|
56
|
+
if (pathIsDirectory(current_path)) {
|
|
57
|
+
current_dir = current_path;
|
|
58
|
+
current_file = '';
|
|
59
|
+
} else {
|
|
60
|
+
current_dir = path.dirname(current_path);
|
|
61
|
+
current_file = path.basename(current_path);
|
|
62
|
+
}
|
|
63
|
+
if (!resolved.exists) {
|
|
64
|
+
log(
|
|
65
|
+
'resolved workspace database missing: %s – Hint: set --db, export BEADS_DB, or run `bd init` in your workspace.',
|
|
66
|
+
current_path
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// (Re)create watcher
|
|
71
|
+
try {
|
|
72
|
+
watcher = fs.watch(
|
|
73
|
+
current_dir,
|
|
74
|
+
{ persistent: true },
|
|
75
|
+
(event_type, filename) => {
|
|
76
|
+
if (current_file && filename && String(filename) !== current_file) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (event_type === 'change' || event_type === 'rename') {
|
|
80
|
+
if (Date.now() < cooldown_until) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
log('fs %s %s', event_type, filename || '');
|
|
84
|
+
schedule();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
log('unable to watch directory %s %o', current_dir, err);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// initial bind
|
|
94
|
+
bind(root_dir, options.explicit_db);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
get path() {
|
|
98
|
+
return current_path;
|
|
99
|
+
},
|
|
100
|
+
close() {
|
|
101
|
+
if (timer) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
timer = undefined;
|
|
104
|
+
}
|
|
105
|
+
watcher?.close();
|
|
106
|
+
},
|
|
107
|
+
/**
|
|
108
|
+
* Re-resolve and reattach watcher when root_dir or explicit_db changes.
|
|
109
|
+
*
|
|
110
|
+
* @param {{ root_dir?: string, explicit_db?: string }} [opts]
|
|
111
|
+
*/
|
|
112
|
+
rebind(opts = {}) {
|
|
113
|
+
const next_root = opts.root_dir ? String(opts.root_dir) : root_dir;
|
|
114
|
+
const next_explicit = opts.explicit_db ?? options.explicit_db;
|
|
115
|
+
const next_resolved = resolveWorkspaceDatabase({
|
|
116
|
+
cwd: next_root,
|
|
117
|
+
explicit_db: next_explicit
|
|
118
|
+
});
|
|
119
|
+
const next_path = next_resolved.path;
|
|
120
|
+
if (next_path !== current_path) {
|
|
121
|
+
// swap watcher
|
|
122
|
+
watcher?.close();
|
|
123
|
+
cooldown_until = 0;
|
|
124
|
+
bind(next_root, next_explicit);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} file_path
|
|
132
|
+
*/
|
|
133
|
+
function pathIsDirectory(file_path) {
|
|
134
|
+
try {
|
|
135
|
+
return fs.statSync(file_path).isDirectory();
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|