nebula-notebook 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/README.md +222 -0
- package/bin/nebula-notebook.js +33 -0
- package/dist/assets/index-C1h_sArD.css +32 -0
- package/dist/assets/index-CDSTBon8.js +658 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +73 -0
- package/node-server/dist/app.d.ts +5 -0
- package/node-server/dist/app.js +38 -0
- package/node-server/dist/auth/auth-middleware.d.ts +24 -0
- package/node-server/dist/auth/auth-middleware.js +276 -0
- package/node-server/dist/auth/auth-service.d.ts +84 -0
- package/node-server/dist/auth/auth-service.js +265 -0
- package/node-server/dist/auth/index.d.ts +3 -0
- package/node-server/dist/auth/index.js +8 -0
- package/node-server/dist/cluster/client-registration.d.ts +43 -0
- package/node-server/dist/cluster/client-registration.js +217 -0
- package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
- package/node-server/dist/cluster/cluster-secret.js +90 -0
- package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
- package/node-server/dist/cluster/kernel-proxy.js +361 -0
- package/node-server/dist/cluster/server-registry.d.ts +109 -0
- package/node-server/dist/cluster/server-registry.js +217 -0
- package/node-server/dist/config/output-limits.d.ts +7 -0
- package/node-server/dist/config/output-limits.js +10 -0
- package/node-server/dist/discovery/discovery-service.d.ts +198 -0
- package/node-server/dist/discovery/discovery-service.js +811 -0
- package/node-server/dist/discovery/index.d.ts +5 -0
- package/node-server/dist/discovery/index.js +21 -0
- package/node-server/dist/discovery/types.d.ts +48 -0
- package/node-server/dist/discovery/types.js +24 -0
- package/node-server/dist/fs/fs-service.d.ts +218 -0
- package/node-server/dist/fs/fs-service.js +1422 -0
- package/node-server/dist/fs/index.d.ts +5 -0
- package/node-server/dist/fs/index.js +21 -0
- package/node-server/dist/fs/types.d.ts +132 -0
- package/node-server/dist/fs/types.js +5 -0
- package/node-server/dist/index.d.ts +13 -0
- package/node-server/dist/index.js +556 -0
- package/node-server/dist/kernel/default-kernel.d.ts +5 -0
- package/node-server/dist/kernel/default-kernel.js +138 -0
- package/node-server/dist/kernel/index.d.ts +7 -0
- package/node-server/dist/kernel/index.js +23 -0
- package/node-server/dist/kernel/kernel-service.d.ts +290 -0
- package/node-server/dist/kernel/kernel-service.js +1714 -0
- package/node-server/dist/kernel/kernelspec.d.ts +29 -0
- package/node-server/dist/kernel/kernelspec.js +210 -0
- package/node-server/dist/kernel/session-store.d.ts +87 -0
- package/node-server/dist/kernel/session-store.js +303 -0
- package/node-server/dist/kernel/types.d.ts +143 -0
- package/node-server/dist/kernel/types.js +17 -0
- package/node-server/dist/llm/index.d.ts +5 -0
- package/node-server/dist/llm/index.js +21 -0
- package/node-server/dist/llm/llm-service.d.ts +77 -0
- package/node-server/dist/llm/llm-service.js +454 -0
- package/node-server/dist/llm/types.d.ts +40 -0
- package/node-server/dist/llm/types.js +15 -0
- package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
- package/node-server/dist/notebook/cell-metadata.js +76 -0
- package/node-server/dist/notebook/headless-handler.d.ts +127 -0
- package/node-server/dist/notebook/headless-handler.js +1530 -0
- package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
- package/node-server/dist/notebook/notebook-websocket.js +103 -0
- package/node-server/dist/notebook/operation-router.d.ts +115 -0
- package/node-server/dist/notebook/operation-router.js +641 -0
- package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
- package/node-server/dist/notebook/undoRedoManager.js +558 -0
- package/node-server/dist/output/display-data.d.ts +14 -0
- package/node-server/dist/output/display-data.js +134 -0
- package/node-server/dist/resources/resource-service.d.ts +69 -0
- package/node-server/dist/resources/resource-service.js +363 -0
- package/node-server/dist/routes/auth.d.ts +5 -0
- package/node-server/dist/routes/auth.js +61 -0
- package/node-server/dist/routes/cluster.d.ts +7 -0
- package/node-server/dist/routes/cluster.js +94 -0
- package/node-server/dist/routes/fs.d.ts +7 -0
- package/node-server/dist/routes/fs.js +392 -0
- package/node-server/dist/routes/kernel.d.ts +13 -0
- package/node-server/dist/routes/kernel.js +637 -0
- package/node-server/dist/routes/llm.d.ts +8 -0
- package/node-server/dist/routes/llm.js +105 -0
- package/node-server/dist/routes/notebook.d.ts +10 -0
- package/node-server/dist/routes/notebook.js +335 -0
- package/node-server/dist/routes/python.d.ts +8 -0
- package/node-server/dist/routes/python.js +187 -0
- package/node-server/dist/routes/resources.d.ts +7 -0
- package/node-server/dist/routes/resources.js +77 -0
- package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
- package/node-server/dist/scripts/show-auth-qr.js +81 -0
- package/node-server/dist/terminal/pty-manager.d.ts +100 -0
- package/node-server/dist/terminal/pty-manager.js +246 -0
- package/node-server/dist/terminal/server.d.ts +19 -0
- package/node-server/dist/terminal/server.js +254 -0
- package/node-server/dist/terminal/types.d.ts +50 -0
- package/node-server/dist/terminal/types.js +9 -0
- package/node-server/package.json +45 -0
- package/package.json +99 -0
- package/scripts/postinstall.cjs +26 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernelspec Discovery
|
|
3
|
+
*
|
|
4
|
+
* Discovers Jupyter kernelspecs from standard locations.
|
|
5
|
+
* Similar to jupyter_client.kernelspec.find_kernel_specs()
|
|
6
|
+
*/
|
|
7
|
+
import { KernelSpec } from './types';
|
|
8
|
+
export type { KernelSpec } from './types';
|
|
9
|
+
/**
|
|
10
|
+
* Standard kernelspec search paths
|
|
11
|
+
*/
|
|
12
|
+
export declare function getKernelSearchPaths(): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Discover all available kernelspecs on the system
|
|
15
|
+
* Uses 60-second in-memory cache to avoid repeated disk I/O
|
|
16
|
+
*/
|
|
17
|
+
export declare function discoverKernelSpecs(forceRefresh?: boolean): KernelSpec[];
|
|
18
|
+
/**
|
|
19
|
+
* Invalidate the kernelspec cache (call after installing a new kernel)
|
|
20
|
+
*/
|
|
21
|
+
export declare function invalidateKernelspecCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Get a specific kernelspec by name
|
|
24
|
+
*/
|
|
25
|
+
export declare function getKernelSpec(name: string): KernelSpec | null;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a kernelspec exists
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasKernelSpec(name: string): boolean;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Kernelspec Discovery
|
|
4
|
+
*
|
|
5
|
+
* Discovers Jupyter kernelspecs from standard locations.
|
|
6
|
+
* Similar to jupyter_client.kernelspec.find_kernel_specs()
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.getKernelSearchPaths = getKernelSearchPaths;
|
|
43
|
+
exports.discoverKernelSpecs = discoverKernelSpecs;
|
|
44
|
+
exports.invalidateKernelspecCache = invalidateKernelspecCache;
|
|
45
|
+
exports.getKernelSpec = getKernelSpec;
|
|
46
|
+
exports.hasKernelSpec = hasKernelSpec;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
// In-memory cache for kernelspecs (avoids repeated disk I/O)
|
|
51
|
+
let kernelspecCache = null;
|
|
52
|
+
let kernelspecCacheTime = 0;
|
|
53
|
+
const KERNELSPEC_CACHE_TTL_MS = 60 * 1000; // 60 seconds
|
|
54
|
+
/**
|
|
55
|
+
* Standard kernelspec search paths
|
|
56
|
+
*/
|
|
57
|
+
function getKernelSearchPaths() {
|
|
58
|
+
const paths = [];
|
|
59
|
+
const home = os.homedir();
|
|
60
|
+
// User-local paths
|
|
61
|
+
if (process.platform === 'darwin') {
|
|
62
|
+
paths.push(path.join(home, 'Library', 'Jupyter', 'kernels'));
|
|
63
|
+
}
|
|
64
|
+
else if (process.platform === 'win32') {
|
|
65
|
+
paths.push(path.join(home, 'AppData', 'Roaming', 'jupyter', 'kernels'));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
paths.push(path.join(home, '.local', 'share', 'jupyter', 'kernels'));
|
|
69
|
+
}
|
|
70
|
+
// System paths
|
|
71
|
+
if (process.platform !== 'win32') {
|
|
72
|
+
paths.push('/usr/local/share/jupyter/kernels');
|
|
73
|
+
paths.push('/usr/share/jupyter/kernels');
|
|
74
|
+
}
|
|
75
|
+
// Conda paths
|
|
76
|
+
const condaPrefix = process.env.CONDA_PREFIX;
|
|
77
|
+
if (condaPrefix) {
|
|
78
|
+
paths.push(path.join(condaPrefix, 'share', 'jupyter', 'kernels'));
|
|
79
|
+
}
|
|
80
|
+
// JUPYTER_PATH environment variable
|
|
81
|
+
const jupyterPath = process.env.JUPYTER_PATH;
|
|
82
|
+
if (jupyterPath) {
|
|
83
|
+
for (const p of jupyterPath.split(path.delimiter)) {
|
|
84
|
+
if (p) {
|
|
85
|
+
paths.push(path.join(p, 'kernels'));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Homebrew Python paths (macOS)
|
|
90
|
+
if (process.platform === 'darwin') {
|
|
91
|
+
const brewPaths = [
|
|
92
|
+
'/opt/homebrew/share/jupyter/kernels',
|
|
93
|
+
'/usr/local/opt/python/Frameworks/Python.framework/Versions/Current/share/jupyter/kernels',
|
|
94
|
+
];
|
|
95
|
+
paths.push(...brewPaths);
|
|
96
|
+
}
|
|
97
|
+
// pyenv paths
|
|
98
|
+
const pyenvRoot = process.env.PYENV_ROOT || path.join(home, '.pyenv');
|
|
99
|
+
if (fs.existsSync(pyenvRoot)) {
|
|
100
|
+
const versionsDir = path.join(pyenvRoot, 'versions');
|
|
101
|
+
if (fs.existsSync(versionsDir)) {
|
|
102
|
+
try {
|
|
103
|
+
const versions = fs.readdirSync(versionsDir);
|
|
104
|
+
for (const version of versions) {
|
|
105
|
+
const kernelPath = path.join(versionsDir, version, 'share', 'jupyter', 'kernels');
|
|
106
|
+
paths.push(kernelPath);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore read errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return paths;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Read a kernel.json file and return the kernelspec
|
|
118
|
+
*/
|
|
119
|
+
function readKernelSpec(kernelDir) {
|
|
120
|
+
const kernelJsonPath = path.join(kernelDir, 'kernel.json');
|
|
121
|
+
if (!fs.existsSync(kernelJsonPath)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const content = fs.readFileSync(kernelJsonPath, 'utf-8');
|
|
126
|
+
const spec = JSON.parse(content);
|
|
127
|
+
return {
|
|
128
|
+
name: path.basename(kernelDir),
|
|
129
|
+
displayName: spec.display_name || path.basename(kernelDir),
|
|
130
|
+
language: spec.language || 'python',
|
|
131
|
+
path: kernelDir,
|
|
132
|
+
argv: spec.argv,
|
|
133
|
+
env: spec.env,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.error(`Error reading kernelspec from ${kernelDir}:`, err);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Perform actual kernelspec discovery (disk I/O)
|
|
143
|
+
*/
|
|
144
|
+
function performKernelspecDiscovery() {
|
|
145
|
+
const specs = [];
|
|
146
|
+
const seenNames = new Set();
|
|
147
|
+
const searchPaths = getKernelSearchPaths();
|
|
148
|
+
for (const searchPath of searchPaths) {
|
|
149
|
+
if (!fs.existsSync(searchPath)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const entries = fs.readdirSync(searchPath, { withFileTypes: true });
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (!entry.isDirectory()) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const kernelName = entry.name;
|
|
159
|
+
if (seenNames.has(kernelName)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const kernelDir = path.join(searchPath, kernelName);
|
|
163
|
+
const spec = readKernelSpec(kernelDir);
|
|
164
|
+
if (spec) {
|
|
165
|
+
specs.push(spec);
|
|
166
|
+
seenNames.add(kernelName);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
// Ignore errors reading directories
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return specs;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Discover all available kernelspecs on the system
|
|
178
|
+
* Uses 60-second in-memory cache to avoid repeated disk I/O
|
|
179
|
+
*/
|
|
180
|
+
function discoverKernelSpecs(forceRefresh = false) {
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
// Return from cache if valid
|
|
183
|
+
if (!forceRefresh && kernelspecCache && (now - kernelspecCacheTime) < KERNELSPEC_CACHE_TTL_MS) {
|
|
184
|
+
return kernelspecCache;
|
|
185
|
+
}
|
|
186
|
+
// Perform discovery and update cache
|
|
187
|
+
kernelspecCache = performKernelspecDiscovery();
|
|
188
|
+
kernelspecCacheTime = now;
|
|
189
|
+
return kernelspecCache;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Invalidate the kernelspec cache (call after installing a new kernel)
|
|
193
|
+
*/
|
|
194
|
+
function invalidateKernelspecCache() {
|
|
195
|
+
kernelspecCache = null;
|
|
196
|
+
kernelspecCacheTime = 0;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get a specific kernelspec by name
|
|
200
|
+
*/
|
|
201
|
+
function getKernelSpec(name) {
|
|
202
|
+
const specs = discoverKernelSpecs();
|
|
203
|
+
return specs.find(s => s.name === name) || null;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Check if a kernelspec exists
|
|
207
|
+
*/
|
|
208
|
+
function hasKernelSpec(name) {
|
|
209
|
+
return getKernelSpec(name) !== null;
|
|
210
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Store
|
|
3
|
+
*
|
|
4
|
+
* SQLite-based persistence for kernel sessions.
|
|
5
|
+
* Enables recovery after server restarts.
|
|
6
|
+
*/
|
|
7
|
+
import { PersistedSession } from './types';
|
|
8
|
+
export declare class SessionStore {
|
|
9
|
+
private db;
|
|
10
|
+
constructor(dbPath?: string);
|
|
11
|
+
/**
|
|
12
|
+
* Create tables if they don't exist
|
|
13
|
+
*/
|
|
14
|
+
private initDb;
|
|
15
|
+
/**
|
|
16
|
+
* Insert or update a session
|
|
17
|
+
*/
|
|
18
|
+
saveSession(session: PersistedSession): void;
|
|
19
|
+
/**
|
|
20
|
+
* Get a session by ID
|
|
21
|
+
*/
|
|
22
|
+
getSession(sessionId: string): PersistedSession | null;
|
|
23
|
+
/**
|
|
24
|
+
* Get all active sessions
|
|
25
|
+
*/
|
|
26
|
+
getActiveSessions(): PersistedSession[];
|
|
27
|
+
/**
|
|
28
|
+
* Get orphaned sessions (from previous server run)
|
|
29
|
+
*/
|
|
30
|
+
getOrphanedSessions(serverId?: string): PersistedSession[];
|
|
31
|
+
/**
|
|
32
|
+
* Update heartbeat timestamp
|
|
33
|
+
*/
|
|
34
|
+
updateHeartbeat(sessionId: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Update session status
|
|
37
|
+
*/
|
|
38
|
+
updateStatus(sessionId: string, status: 'active' | 'orphaned' | 'terminated'): void;
|
|
39
|
+
/**
|
|
40
|
+
* Delete a session
|
|
41
|
+
*/
|
|
42
|
+
deleteSession(sessionId: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Mark all active sessions as orphaned (called on startup)
|
|
45
|
+
*/
|
|
46
|
+
markAllOrphaned(serverId?: string, serverInstanceId?: string): number;
|
|
47
|
+
/**
|
|
48
|
+
* Get session by file path
|
|
49
|
+
*/
|
|
50
|
+
getSessionByFile(filePath: string): PersistedSession | null;
|
|
51
|
+
/**
|
|
52
|
+
* Cleanup old terminated/orphaned sessions
|
|
53
|
+
*/
|
|
54
|
+
cleanupOldSessions(maxAgeHours?: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* Get dead sessions (orphaned or terminated) - candidates for cleanup
|
|
57
|
+
*/
|
|
58
|
+
getDeadSessions(serverId?: string): PersistedSession[];
|
|
59
|
+
/**
|
|
60
|
+
* Delete specific sessions by ID
|
|
61
|
+
*/
|
|
62
|
+
deleteSessions(sessionIds: string[]): number;
|
|
63
|
+
/**
|
|
64
|
+
* Save kernel preference for a notebook file.
|
|
65
|
+
*/
|
|
66
|
+
saveNotebookKernelPreference(filePath: string, kernelName: string, serverId?: string | null): void;
|
|
67
|
+
/**
|
|
68
|
+
* Get kernel preference for a notebook file.
|
|
69
|
+
*/
|
|
70
|
+
getNotebookKernelPreference(filePath: string): {
|
|
71
|
+
kernelName: string;
|
|
72
|
+
serverId: string | null;
|
|
73
|
+
updatedAt: number;
|
|
74
|
+
} | null;
|
|
75
|
+
/**
|
|
76
|
+
* Delete kernel preference for a notebook file.
|
|
77
|
+
*/
|
|
78
|
+
deleteNotebookKernelPreference(filePath: string): void;
|
|
79
|
+
/**
|
|
80
|
+
* Close database connection
|
|
81
|
+
*/
|
|
82
|
+
close(): void;
|
|
83
|
+
/**
|
|
84
|
+
* Convert database row to PersistedSession
|
|
85
|
+
*/
|
|
86
|
+
private rowToSession;
|
|
87
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Session Store
|
|
4
|
+
*
|
|
5
|
+
* SQLite-based persistence for kernel sessions.
|
|
6
|
+
* Enables recovery after server restarts.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.SessionStore = void 0;
|
|
46
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
class SessionStore {
|
|
49
|
+
db;
|
|
50
|
+
constructor(dbPath) {
|
|
51
|
+
const resolvedPath = dbPath || process.env.NEBULA_SESSIONS_DB_PATH || path.join(__dirname, '../../sessions.db');
|
|
52
|
+
this.db = new better_sqlite3_1.default(resolvedPath);
|
|
53
|
+
this.initDb();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create tables if they don't exist
|
|
57
|
+
*/
|
|
58
|
+
initDb() {
|
|
59
|
+
this.db.exec(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
61
|
+
session_id TEXT PRIMARY KEY,
|
|
62
|
+
kernel_name TEXT NOT NULL,
|
|
63
|
+
file_path TEXT,
|
|
64
|
+
kernel_pid INTEGER,
|
|
65
|
+
server_id TEXT,
|
|
66
|
+
server_instance_id TEXT,
|
|
67
|
+
kernel_start_time TEXT,
|
|
68
|
+
status TEXT DEFAULT 'active',
|
|
69
|
+
created_at REAL NOT NULL,
|
|
70
|
+
last_heartbeat REAL NOT NULL,
|
|
71
|
+
connection_file TEXT,
|
|
72
|
+
connection_config TEXT
|
|
73
|
+
)
|
|
74
|
+
`);
|
|
75
|
+
this.db.exec(`
|
|
76
|
+
CREATE TABLE IF NOT EXISTS notebook_kernel_prefs (
|
|
77
|
+
file_path TEXT PRIMARY KEY,
|
|
78
|
+
kernel_name TEXT NOT NULL,
|
|
79
|
+
server_id TEXT,
|
|
80
|
+
updated_at REAL NOT NULL
|
|
81
|
+
)
|
|
82
|
+
`);
|
|
83
|
+
// Migration: add connection_config column if it doesn't exist
|
|
84
|
+
try {
|
|
85
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN connection_config TEXT');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Column already exists
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN server_id TEXT');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Column already exists
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN server_instance_id TEXT');
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Column already exists
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
this.db.exec('ALTER TABLE sessions ADD COLUMN kernel_start_time TEXT');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Column already exists
|
|
107
|
+
}
|
|
108
|
+
this.db.exec(`
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_status
|
|
110
|
+
ON sessions(status)
|
|
111
|
+
`);
|
|
112
|
+
this.db.exec(`
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_file_path
|
|
114
|
+
ON sessions(file_path) WHERE file_path IS NOT NULL
|
|
115
|
+
`);
|
|
116
|
+
this.db.exec(`
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_notebook_kernel_prefs_updated_at
|
|
118
|
+
ON notebook_kernel_prefs(updated_at)
|
|
119
|
+
`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Insert or update a session
|
|
123
|
+
*/
|
|
124
|
+
saveSession(session) {
|
|
125
|
+
const stmt = this.db.prepare(`
|
|
126
|
+
INSERT OR REPLACE INTO sessions
|
|
127
|
+
(session_id, kernel_name, file_path, kernel_pid,
|
|
128
|
+
server_id, server_instance_id, kernel_start_time,
|
|
129
|
+
status, created_at, last_heartbeat,
|
|
130
|
+
connection_file, connection_config)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
132
|
+
`);
|
|
133
|
+
stmt.run(session.sessionId, session.kernelName, session.filePath, session.kernelPid, session.serverId ?? null, session.serverInstanceId ?? null, session.kernelStartTime ?? null, session.status, session.createdAt, session.lastHeartbeat, session.connectionFile, session.connectionConfig);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Get a session by ID
|
|
137
|
+
*/
|
|
138
|
+
getSession(sessionId) {
|
|
139
|
+
const stmt = this.db.prepare('SELECT * FROM sessions WHERE session_id = ?');
|
|
140
|
+
const row = stmt.get(sessionId);
|
|
141
|
+
if (row) {
|
|
142
|
+
return this.rowToSession(row);
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get all active sessions
|
|
148
|
+
*/
|
|
149
|
+
getActiveSessions() {
|
|
150
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'active'");
|
|
151
|
+
const rows = stmt.all();
|
|
152
|
+
return rows.map(row => this.rowToSession(row));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get orphaned sessions (from previous server run)
|
|
156
|
+
*/
|
|
157
|
+
getOrphanedSessions(serverId) {
|
|
158
|
+
if (serverId) {
|
|
159
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'orphaned' AND (server_id = ? OR server_id IS NULL)");
|
|
160
|
+
const rows = stmt.all(serverId);
|
|
161
|
+
return rows.map(row => this.rowToSession(row));
|
|
162
|
+
}
|
|
163
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'orphaned'");
|
|
164
|
+
const rows = stmt.all();
|
|
165
|
+
return rows.map(row => this.rowToSession(row));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update heartbeat timestamp
|
|
169
|
+
*/
|
|
170
|
+
updateHeartbeat(sessionId) {
|
|
171
|
+
const stmt = this.db.prepare('UPDATE sessions SET last_heartbeat = ? WHERE session_id = ?');
|
|
172
|
+
stmt.run(Date.now() / 1000, sessionId);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Update session status
|
|
176
|
+
*/
|
|
177
|
+
updateStatus(sessionId, status) {
|
|
178
|
+
const stmt = this.db.prepare('UPDATE sessions SET status = ? WHERE session_id = ?');
|
|
179
|
+
stmt.run(status, sessionId);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Delete a session
|
|
183
|
+
*/
|
|
184
|
+
deleteSession(sessionId) {
|
|
185
|
+
const stmt = this.db.prepare('DELETE FROM sessions WHERE session_id = ?');
|
|
186
|
+
stmt.run(sessionId);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Mark all active sessions as orphaned (called on startup)
|
|
190
|
+
*/
|
|
191
|
+
markAllOrphaned(serverId, serverInstanceId) {
|
|
192
|
+
if (serverId && serverInstanceId) {
|
|
193
|
+
const stmt = this.db.prepare("UPDATE sessions SET status = 'orphaned' WHERE status = 'active' AND (server_id = ? OR server_id IS NULL) AND (server_instance_id IS NULL OR server_instance_id != ?)");
|
|
194
|
+
const result = stmt.run(serverId, serverInstanceId);
|
|
195
|
+
return result.changes;
|
|
196
|
+
}
|
|
197
|
+
const stmt = this.db.prepare("UPDATE sessions SET status = 'orphaned' WHERE status = 'active'");
|
|
198
|
+
const result = stmt.run();
|
|
199
|
+
return result.changes;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get session by file path
|
|
203
|
+
*/
|
|
204
|
+
getSessionByFile(filePath) {
|
|
205
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE file_path = ? AND status = 'active'");
|
|
206
|
+
const row = stmt.get(filePath);
|
|
207
|
+
if (row) {
|
|
208
|
+
return this.rowToSession(row);
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Cleanup old terminated/orphaned sessions
|
|
214
|
+
*/
|
|
215
|
+
cleanupOldSessions(maxAgeHours = 24) {
|
|
216
|
+
const cutoff = (Date.now() / 1000) - (maxAgeHours * 3600);
|
|
217
|
+
const stmt = this.db.prepare("DELETE FROM sessions WHERE status IN ('terminated', 'orphaned') AND last_heartbeat < ?");
|
|
218
|
+
const result = stmt.run(cutoff);
|
|
219
|
+
return result.changes;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get dead sessions (orphaned or terminated) - candidates for cleanup
|
|
223
|
+
*/
|
|
224
|
+
getDeadSessions(serverId) {
|
|
225
|
+
if (serverId) {
|
|
226
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE status IN ('orphaned', 'terminated') AND (server_id = ? OR server_id IS NULL)");
|
|
227
|
+
const rows = stmt.all(serverId);
|
|
228
|
+
return rows.map(row => this.rowToSession(row));
|
|
229
|
+
}
|
|
230
|
+
const stmt = this.db.prepare("SELECT * FROM sessions WHERE status IN ('orphaned', 'terminated')");
|
|
231
|
+
const rows = stmt.all();
|
|
232
|
+
return rows.map(row => this.rowToSession(row));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Delete specific sessions by ID
|
|
236
|
+
*/
|
|
237
|
+
deleteSessions(sessionIds) {
|
|
238
|
+
if (sessionIds.length === 0)
|
|
239
|
+
return 0;
|
|
240
|
+
const placeholders = sessionIds.map(() => '?').join(',');
|
|
241
|
+
const stmt = this.db.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`);
|
|
242
|
+
const result = stmt.run(...sessionIds);
|
|
243
|
+
return result.changes;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Save kernel preference for a notebook file.
|
|
247
|
+
*/
|
|
248
|
+
saveNotebookKernelPreference(filePath, kernelName, serverId) {
|
|
249
|
+
const stmt = this.db.prepare(`
|
|
250
|
+
INSERT OR REPLACE INTO notebook_kernel_prefs
|
|
251
|
+
(file_path, kernel_name, server_id, updated_at)
|
|
252
|
+
VALUES (?, ?, ?, ?)
|
|
253
|
+
`);
|
|
254
|
+
stmt.run(filePath, kernelName, serverId ?? null, Date.now() / 1000);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get kernel preference for a notebook file.
|
|
258
|
+
*/
|
|
259
|
+
getNotebookKernelPreference(filePath) {
|
|
260
|
+
const stmt = this.db.prepare('SELECT kernel_name, server_id, updated_at FROM notebook_kernel_prefs WHERE file_path = ?');
|
|
261
|
+
const row = stmt.get(filePath);
|
|
262
|
+
if (!row)
|
|
263
|
+
return null;
|
|
264
|
+
return {
|
|
265
|
+
kernelName: row.kernel_name,
|
|
266
|
+
serverId: row.server_id ?? null,
|
|
267
|
+
updatedAt: row.updated_at,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Delete kernel preference for a notebook file.
|
|
272
|
+
*/
|
|
273
|
+
deleteNotebookKernelPreference(filePath) {
|
|
274
|
+
const stmt = this.db.prepare('DELETE FROM notebook_kernel_prefs WHERE file_path = ?');
|
|
275
|
+
stmt.run(filePath);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Close database connection
|
|
279
|
+
*/
|
|
280
|
+
close() {
|
|
281
|
+
this.db.close();
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Convert database row to PersistedSession
|
|
285
|
+
*/
|
|
286
|
+
rowToSession(row) {
|
|
287
|
+
return {
|
|
288
|
+
sessionId: row.session_id,
|
|
289
|
+
kernelName: row.kernel_name,
|
|
290
|
+
filePath: row.file_path,
|
|
291
|
+
kernelPid: row.kernel_pid,
|
|
292
|
+
serverId: row.server_id,
|
|
293
|
+
serverInstanceId: row.server_instance_id,
|
|
294
|
+
kernelStartTime: row.kernel_start_time,
|
|
295
|
+
status: row.status,
|
|
296
|
+
createdAt: row.created_at,
|
|
297
|
+
lastHeartbeat: row.last_heartbeat,
|
|
298
|
+
connectionFile: row.connection_file,
|
|
299
|
+
connectionConfig: row.connection_config,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
exports.SessionStore = SessionStore;
|