@damurka/jovian 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.
Files changed (91) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +175 -0
  3. package/docs/api/README.md +55 -0
  4. package/docs/api/session.md +143 -0
  5. package/docs/api/types.md +120 -0
  6. package/docs/architecture/overview.md +218 -0
  7. package/docs/cpp-usage.md +60 -0
  8. package/docs/development.md +105 -0
  9. package/docs/getting-started.md +127 -0
  10. package/docs/guides/comms.md +70 -0
  11. package/docs/guides/environments.md +80 -0
  12. package/docs/guides/history.md +44 -0
  13. package/docs/guides/interactive-input.md +48 -0
  14. package/docs/guides/interrupting.md +45 -0
  15. package/docs/guides/playground.md +33 -0
  16. package/docs/guides/sessions-lifecycle.md +70 -0
  17. package/docs/kernels.md +117 -0
  18. package/docs/protocol.md +135 -0
  19. package/docs/releasing.md +73 -0
  20. package/docs/troubleshooting.md +110 -0
  21. package/lib/execution/execution-queue.d.ts +20 -0
  22. package/lib/execution/execution-queue.js +256 -0
  23. package/lib/handlers/display-handler.d.ts +7 -0
  24. package/lib/handlers/display-handler.js +10 -0
  25. package/lib/handlers/error-handler.d.ts +7 -0
  26. package/lib/handlers/error-handler.js +8 -0
  27. package/lib/handlers/result-handler.d.ts +7 -0
  28. package/lib/handlers/result-handler.js +10 -0
  29. package/lib/handlers/stream-handler.d.ts +7 -0
  30. package/lib/handlers/stream-handler.js +8 -0
  31. package/lib/index.d.ts +7 -0
  32. package/lib/index.js +5 -0
  33. package/lib/messaging/message-parser.d.ts +6 -0
  34. package/lib/messaging/message-parser.js +33 -0
  35. package/lib/messaging/message-router.d.ts +14 -0
  36. package/lib/messaging/message-router.js +41 -0
  37. package/lib/middleware/index.d.ts +5 -0
  38. package/lib/middleware/index.js +5 -0
  39. package/lib/middleware/middleware-chain.d.ts +7 -0
  40. package/lib/middleware/middleware-chain.js +14 -0
  41. package/lib/middleware/middleware.d.ts +5 -0
  42. package/lib/middleware/middleware.js +2 -0
  43. package/lib/middleware/plugins/logging-plugin.d.ts +6 -0
  44. package/lib/middleware/plugins/logging-plugin.js +9 -0
  45. package/lib/middleware/plugins/metrics-plugin.d.ts +8 -0
  46. package/lib/middleware/plugins/metrics-plugin.js +13 -0
  47. package/lib/session/comm.d.ts +39 -0
  48. package/lib/session/comm.js +58 -0
  49. package/lib/session/native-paths.d.ts +48 -0
  50. package/lib/session/native-paths.js +108 -0
  51. package/lib/session/session-manager.d.ts +229 -0
  52. package/lib/session/session-manager.js +842 -0
  53. package/lib/session/supervisor-client.d.ts +36 -0
  54. package/lib/session/supervisor-client.js +147 -0
  55. package/lib/types/engine.d.ts +269 -0
  56. package/lib/types/engine.js +2 -0
  57. package/lib/types/index.d.ts +3 -0
  58. package/lib/types/index.js +3 -0
  59. package/lib/types/messages.d.ts +68 -0
  60. package/lib/types/messages.js +2 -0
  61. package/lib/utils/logger.d.ts +12 -0
  62. package/lib/utils/logger.js +58 -0
  63. package/lib/utils/network.d.ts +11 -0
  64. package/lib/utils/network.js +50 -0
  65. package/package.json +57 -0
  66. package/packages/hera/DESCRIPTION +29 -0
  67. package/packages/hera/LICENSE +2 -0
  68. package/packages/hera/LICENSE.md +21 -0
  69. package/packages/hera/NAMESPACE +32 -0
  70. package/packages/hera/NEWS.md +7 -0
  71. package/packages/hera/R/cell_options.R +13 -0
  72. package/packages/hera/R/comm.R +228 -0
  73. package/packages/hera/R/completion.R +54 -0
  74. package/packages/hera/R/execute.R +199 -0
  75. package/packages/hera/R/inspect.R +73 -0
  76. package/packages/hera/R/log.R +14 -0
  77. package/packages/hera/R/mime_bundle.R +65 -0
  78. package/packages/hera/R/routines.R +86 -0
  79. package/packages/hera/R/utils.R +32 -0
  80. package/packages/hera/R/zzz.R +128 -0
  81. package/packages/hera/man/Comm.Rd +179 -0
  82. package/packages/hera/man/CommManager.Rd +215 -0
  83. package/packages/hera/man/View.Rd +22 -0
  84. package/packages/hera/man/cell_options.Rd +20 -0
  85. package/packages/hera/man/clear_output.Rd +23 -0
  86. package/packages/hera/man/complete.Rd +23 -0
  87. package/packages/hera/man/display_data.Rd +22 -0
  88. package/packages/hera/man/is_elara.Rd +18 -0
  89. package/packages/hera/man/mime_bundle.Rd +25 -0
  90. package/packages/hera/man/mime_types.Rd +22 -0
  91. package/packages/hera/man/reexports.Rd +16 -0
@@ -0,0 +1,41 @@
1
+ import { MessageParser } from './message-parser.js';
2
+ export class MessageRouter {
3
+ handlers;
4
+ emitter;
5
+ constructor(emitter) {
6
+ this.emitter = emitter;
7
+ this.handlers = new Map();
8
+ }
9
+ async route(rawMessage) {
10
+ const message = MessageParser.parse(rawMessage);
11
+ // Emit raw message event
12
+ this.emitter.emit('*', message.msgType, message.content);
13
+ this.emitter.emit('message', message);
14
+ // Route to specific handler (dispatch on the plain msg_type, e.g.
15
+ // "stream" / "execute_result" — `topic` is a kernel-namespaced
16
+ // string like "kernel_core.<id>.stream" and isn't matched here)
17
+ const handler = this.findHandler(message.msgType);
18
+ if (handler) {
19
+ await handler.handle(message, this.emitter);
20
+ }
21
+ // Always emit msg_type-specific event
22
+ this.emitter.emit(message.msgType, message.content);
23
+ }
24
+ findHandler(msgType) {
25
+ // Exact match
26
+ if (this.handlers.has(msgType)) {
27
+ return this.handlers.get(msgType);
28
+ }
29
+ // Prefix match (e.g., "stream" matches "stream.stdout")
30
+ for (const [key, handler] of this.handlers) {
31
+ if (msgType.startsWith(key)) {
32
+ return handler;
33
+ }
34
+ }
35
+ return undefined;
36
+ }
37
+ registerHandler(msgType, handler) {
38
+ this.handlers.set(msgType, handler);
39
+ }
40
+ }
41
+ //# sourceMappingURL=message-router.js.map
@@ -0,0 +1,5 @@
1
+ export * from './middleware.js';
2
+ export * from './middleware-chain.js';
3
+ export * from './plugins/logging-plugin.js';
4
+ export * from './plugins/metrics-plugin.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ export * from './middleware.js';
2
+ export * from './middleware-chain.js';
3
+ export * from './plugins/logging-plugin.js';
4
+ export * from './plugins/metrics-plugin.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import type { Middleware } from './middleware.js';
2
+ export declare class MiddlewareChain {
3
+ private middlewares;
4
+ use(middleware: Middleware): void;
5
+ process(message: string): Promise<string>;
6
+ }
7
+ //# sourceMappingURL=middleware-chain.d.ts.map
@@ -0,0 +1,14 @@
1
+ export class MiddlewareChain {
2
+ middlewares = [];
3
+ use(middleware) {
4
+ this.middlewares.push(middleware);
5
+ }
6
+ async process(message) {
7
+ let result = message;
8
+ for (const middleware of this.middlewares) {
9
+ result = await middleware.process(result);
10
+ }
11
+ return result;
12
+ }
13
+ }
14
+ //# sourceMappingURL=middleware-chain.js.map
@@ -0,0 +1,5 @@
1
+ export interface Middleware {
2
+ name: string;
3
+ process(message: string): Promise<string> | string;
4
+ }
5
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1,6 @@
1
+ import type { Middleware } from '../middleware.js';
2
+ export declare class LoggingMiddleware implements Middleware {
3
+ name: string;
4
+ process(message: string): string;
5
+ }
6
+ //# sourceMappingURL=logging-plugin.d.ts.map
@@ -0,0 +1,9 @@
1
+ export class LoggingMiddleware {
2
+ name = 'logging';
3
+ process(message) {
4
+ const timestamp = new Date().toISOString();
5
+ console.log(`[${timestamp}] Message: ${message.substring(0, 100)}...`);
6
+ return message;
7
+ }
8
+ }
9
+ //# sourceMappingURL=logging-plugin.js.map
@@ -0,0 +1,8 @@
1
+ import type { Middleware } from '../middleware.js';
2
+ export declare class MetricsMiddleware implements Middleware {
3
+ name: string;
4
+ private messageCount;
5
+ private startTime;
6
+ process(message: string): string;
7
+ }
8
+ //# sourceMappingURL=metrics-plugin.d.ts.map
@@ -0,0 +1,13 @@
1
+ export class MetricsMiddleware {
2
+ name = 'metrics';
3
+ messageCount = 0;
4
+ startTime = Date.now();
5
+ process(message) {
6
+ this.messageCount++;
7
+ const elapsed = (Date.now() - this.startTime) / 1000;
8
+ const rate = this.messageCount / elapsed;
9
+ console.log(`Messages/sec: ${rate.toFixed(2)}`);
10
+ return message;
11
+ }
12
+ }
13
+ //# sourceMappingURL=metrics-plugin.js.map
@@ -0,0 +1,39 @@
1
+ import { EventEmitter } from 'events';
2
+ /**
3
+ * The parts of a Session a Comm needs -- kept as an interface so this file
4
+ * doesn't import session-manager (which imports it).
5
+ */
6
+ export interface CommTransport {
7
+ commMsg(commId: string, data: Record<string, unknown>): Promise<string>;
8
+ commClose(commId: string, data: Record<string, unknown>): Promise<string>;
9
+ }
10
+ /**
11
+ * One open comm: a named, bidirectional message channel between this client
12
+ * and a target registered inside the kernel (in R, `hera::CommManager$
13
+ * register_comm_target()`).
14
+ *
15
+ * Get one either from `session.openComm(target)` (client-initiated) or from
16
+ * the session's `'comm'` event (kernel-initiated, e.g. R calling
17
+ * `CommManager$new_comm()` then `$open()`).
18
+ *
19
+ * Events: `'message'` (data) for every comm_msg the kernel sends over it and
20
+ * `'close'` (data) once it is closed by either side -- or because the kernel
21
+ * restarted or the session ended, in which case `data.reason` says so.
22
+ */
23
+ export declare class Comm extends EventEmitter {
24
+ readonly id: string;
25
+ readonly targetName: string;
26
+ private readonly transport;
27
+ private isClosed;
28
+ constructor(id: string, targetName: string, transport: CommTransport);
29
+ get closed(): boolean;
30
+ /** Sends `data` to the kernel-side handler. Rejects if the comm is closed. */
31
+ send(data?: Record<string, unknown>): Promise<string>;
32
+ /** Closes the comm (the kernel is told; no-op if it is already closed). */
33
+ close(data?: Record<string, unknown>): Promise<void>;
34
+ /** @internal Called by Session for the kernel's own comm_msg / comm_close. */
35
+ receiveMessage(data: Record<string, unknown>): void;
36
+ /** @internal */
37
+ receiveClose(data: Record<string, unknown>): void;
38
+ }
39
+ //# sourceMappingURL=comm.d.ts.map
@@ -0,0 +1,58 @@
1
+ import { EventEmitter } from 'events';
2
+ /**
3
+ * One open comm: a named, bidirectional message channel between this client
4
+ * and a target registered inside the kernel (in R, `hera::CommManager$
5
+ * register_comm_target()`).
6
+ *
7
+ * Get one either from `session.openComm(target)` (client-initiated) or from
8
+ * the session's `'comm'` event (kernel-initiated, e.g. R calling
9
+ * `CommManager$new_comm()` then `$open()`).
10
+ *
11
+ * Events: `'message'` (data) for every comm_msg the kernel sends over it and
12
+ * `'close'` (data) once it is closed by either side -- or because the kernel
13
+ * restarted or the session ended, in which case `data.reason` says so.
14
+ */
15
+ export class Comm extends EventEmitter {
16
+ id;
17
+ targetName;
18
+ transport;
19
+ isClosed = false;
20
+ constructor(id, targetName, transport) {
21
+ super();
22
+ this.id = id;
23
+ this.targetName = targetName;
24
+ this.transport = transport;
25
+ }
26
+ get closed() {
27
+ return this.isClosed;
28
+ }
29
+ /** Sends `data` to the kernel-side handler. Rejects if the comm is closed. */
30
+ send(data = {}) {
31
+ if (this.isClosed) {
32
+ return Promise.reject(new Error(`Comm ${this.id} (${this.targetName}) is closed`));
33
+ }
34
+ return this.transport.commMsg(this.id, data);
35
+ }
36
+ /** Closes the comm (the kernel is told; no-op if it is already closed). */
37
+ async close(data = {}) {
38
+ if (this.isClosed) {
39
+ return;
40
+ }
41
+ this.isClosed = true;
42
+ await this.transport.commClose(this.id, data);
43
+ this.emit('close', data);
44
+ }
45
+ /** @internal Called by Session for the kernel's own comm_msg / comm_close. */
46
+ receiveMessage(data) {
47
+ this.emit('message', data);
48
+ }
49
+ /** @internal */
50
+ receiveClose(data) {
51
+ if (this.isClosed) {
52
+ return;
53
+ }
54
+ this.isClosed = true;
55
+ this.emit('close', data);
56
+ }
57
+ }
58
+ //# sourceMappingURL=comm.js.map
@@ -0,0 +1,48 @@
1
+ /** The npm scope the packages are published under (also set in scripts/release.mjs; a test keeps them in step). */
2
+ export declare const PACKAGE_SCOPE = "@damurka";
3
+ export declare const PACKAGE_NAME = "@damurka/jovian";
4
+ /**
5
+ * The prebuilt kernels (themisto, elara, carpo) ship in one small package per
6
+ * platform -- `@scope/jovian-<os>-<cpu>` -- that npm installs alongside
7
+ * the main package only where it matches (each declares `os`/`cpu`). These are
8
+ * the platforms that get a package.
9
+ */
10
+ export declare const SUPPORTED_PLATFORMS: readonly ['win32-x64', 'linux-x64', 'darwin-arm64'];
11
+ export declare function platformPackageName(platform?: string, arch?: string): string | undefined;
12
+ export interface NativeLocation {
13
+ /** Directory holding themisto, elara and carpo. */
14
+ dir: string;
15
+ source: 'JOVIAN_NATIVE_DIR' | 'platform package' | 'source build';
16
+ }
17
+ export interface NativeLookup {
18
+ env: Record<string, string | undefined>;
19
+ platform: string;
20
+ arch: string;
21
+ exists: (path: string) => boolean;
22
+ /** Absolute path of a package's package.json, or undefined if it is not installed. */
23
+ resolvePackageJson: (packageName: string) => string | undefined;
24
+ /** dist/native/Release of a source checkout. */
25
+ sourceBuildDir: string;
26
+ }
27
+ /**
28
+ * Where the kernels are, in order of precedence:
29
+ * 1. JOVIAN_NATIVE_DIR -- an explicit directory (a copy of the binaries, an
30
+ * app that bundles them elsewhere);
31
+ * 2. the installed `@scope/jovian-<os>-<cpu>` package for this machine;
32
+ * 3. a source checkout's dist/native/Release (development).
33
+ */
34
+ export declare function locateNativeDirectory(lookup?: NativeLookup): NativeLocation;
35
+ /**
36
+ * npm does not reliably keep the executable bit on files in a tarball, so make
37
+ * sure the kernels can be run (POSIX only; a no-op on Windows).
38
+ */
39
+ export declare function ensureExecutable(dir: string, platform?: string): void;
40
+ /**
41
+ * The copy of the 'hera' R package that ships inside the npm package, used to
42
+ * install/refresh it in R on a session's first start when the caller gave no
43
+ * heraSrcPath. Only when running from an installed package (under
44
+ * node_modules): a source checkout leaves hera alone so development and CI
45
+ * control which one is loaded.
46
+ */
47
+ export declare function bundledHeraSource(baseDir?: string, exists?: (path: string) => boolean): string | undefined;
48
+ //# sourceMappingURL=native-paths.d.ts.map
@@ -0,0 +1,108 @@
1
+ import { chmodSync, existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { dirname, join, sep } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ /** The npm scope the packages are published under (also set in scripts/release.mjs; a test keeps them in step). */
6
+ export const PACKAGE_SCOPE = '@damurka';
7
+ export const PACKAGE_NAME = `${PACKAGE_SCOPE}/jovian`;
8
+ /**
9
+ * The prebuilt kernels (themisto, elara, carpo) ship in one small package per
10
+ * platform -- `@scope/jovian-<os>-<cpu>` -- that npm installs alongside
11
+ * the main package only where it matches (each declares `os`/`cpu`). These are
12
+ * the platforms that get a package.
13
+ */
14
+ export const SUPPORTED_PLATFORMS = ['win32-x64', 'linux-x64', 'darwin-arm64'];
15
+ export function platformPackageName(platform = process.platform, arch = process.arch) {
16
+ const key = `${platform}-${arch}`;
17
+ return SUPPORTED_PLATFORMS.includes(key) ? `${PACKAGE_NAME}-${key}` : undefined;
18
+ }
19
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
20
+ function defaultLookup() {
21
+ const require = createRequire(import.meta.url);
22
+ return {
23
+ env: process.env,
24
+ platform: process.platform,
25
+ arch: process.arch,
26
+ exists: existsSync,
27
+ resolvePackageJson: (name) => {
28
+ try {
29
+ return require.resolve(`${name}/package.json`);
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ },
35
+ // dist/lib/session -> dist/native/Release
36
+ sourceBuildDir: join(moduleDir, '../../native/Release')
37
+ };
38
+ }
39
+ /**
40
+ * Where the kernels are, in order of precedence:
41
+ * 1. JOVIAN_NATIVE_DIR -- an explicit directory (a copy of the binaries, an
42
+ * app that bundles them elsewhere);
43
+ * 2. the installed `@scope/jovian-<os>-<cpu>` package for this machine;
44
+ * 3. a source checkout's dist/native/Release (development).
45
+ */
46
+ export function locateNativeDirectory(lookup = defaultLookup()) {
47
+ const exe = lookup.platform === 'win32' ? 'themisto.exe' : 'themisto';
48
+ const has = (dir) => lookup.exists(join(dir, exe));
49
+ const override = lookup.env.JOVIAN_NATIVE_DIR;
50
+ if (override) {
51
+ if (!has(override)) {
52
+ throw new Error(`jovian: JOVIAN_NATIVE_DIR is set to '${override}' but there is no ${exe} in it.`);
53
+ }
54
+ return { dir: override, source: 'JOVIAN_NATIVE_DIR' };
55
+ }
56
+ const packageName = platformPackageName(lookup.platform, lookup.arch);
57
+ if (packageName) {
58
+ const packageJson = lookup.resolvePackageJson(packageName);
59
+ if (packageJson) {
60
+ const dir = join(dirname(packageJson), 'bin');
61
+ if (has(dir)) {
62
+ return { dir, source: 'platform package' };
63
+ }
64
+ }
65
+ }
66
+ if (has(lookup.sourceBuildDir)) {
67
+ return { dir: lookup.sourceBuildDir, source: 'source build' };
68
+ }
69
+ if (!packageName) {
70
+ throw new Error(`jovian: there are no prebuilt kernels for ${lookup.platform}-${lookup.arch} ` +
71
+ `(supported: ${SUPPORTED_PLATFORMS.join(', ')}). Build them from source and point ` +
72
+ `JOVIAN_NATIVE_DIR at the output directory.`);
73
+ }
74
+ throw new Error(`jovian: the kernel binaries were not found. Expected the '${packageName}' package ` +
75
+ `(installed automatically as an optional dependency -- reinstall without --omit=optional / ` +
76
+ `--no-optional), or a source build at ${lookup.sourceBuildDir}, or JOVIAN_NATIVE_DIR.`);
77
+ }
78
+ /**
79
+ * npm does not reliably keep the executable bit on files in a tarball, so make
80
+ * sure the kernels can be run (POSIX only; a no-op on Windows).
81
+ */
82
+ export function ensureExecutable(dir, platform = process.platform) {
83
+ if (platform === 'win32')
84
+ return;
85
+ for (const name of ['themisto', 'elara', 'carpo']) {
86
+ try {
87
+ chmodSync(join(dir, name), 0o755);
88
+ }
89
+ catch {
90
+ // Absent (carpo is optional) or not ours to change: running it will say so.
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * The copy of the 'hera' R package that ships inside the npm package, used to
96
+ * install/refresh it in R on a session's first start when the caller gave no
97
+ * heraSrcPath. Only when running from an installed package (under
98
+ * node_modules): a source checkout leaves hera alone so development and CI
99
+ * control which one is loaded.
100
+ */
101
+ export function bundledHeraSource(baseDir = moduleDir, exists = existsSync) {
102
+ if (!baseDir.split(sep).includes('node_modules'))
103
+ return undefined;
104
+ // <package>/lib/session -> <package>/packages/hera (the published layout)
105
+ const candidate = join(baseDir, '../../packages/hera');
106
+ return exists(join(candidate, 'DESCRIPTION')) ? candidate : undefined;
107
+ }
108
+ //# sourceMappingURL=native-paths.js.map
@@ -0,0 +1,229 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { CommInfoReplyContent, CompleteReplyContent, EngineOptions, ExecutionHistoryEntry, ExecutionOptions, ExecutionResult, InspectReplyContent, IsCompleteReplyContent, KernelHistoryEntry, KernelHistoryOptions, KernelInfoReplyContent, SessionStatusInfo, ShinyAppHandle, ShinyAppOptions } from '../types/index.js';
3
+ import type { ExecutionState } from '../types/messages.js';
4
+ import { SupervisorClient, type SessionConnectionInfo } from './supervisor-client.js';
5
+ import { Comm } from './comm.js';
6
+ export type { ShinyAppHandle };
7
+ export declare class Session extends EventEmitter {
8
+ private ws;
9
+ readonly info: SessionConnectionInfo;
10
+ private currentOptions;
11
+ private readonly supervisor;
12
+ private readonly logger;
13
+ private readonly router;
14
+ private readonly middleware;
15
+ private readonly queue;
16
+ /**
17
+ * The options this session is currently running with: what it was
18
+ * created with, updated by any `restart(options)` that changed them.
19
+ */
20
+ get options(): EngineOptions;
21
+ private readyPromise;
22
+ private stopped;
23
+ private readonly comms;
24
+ private readonly busyRequests;
25
+ private readonly pendingRequests;
26
+ private kernelExecutionState;
27
+ private readonly executionHistory;
28
+ private readonly executionHistoryByMsgId;
29
+ private readonly historyStreamChars;
30
+ constructor(info: SessionConnectionInfo, options: EngineOptions, supervisor: SupervisorClient);
31
+ /**
32
+ * (Re)establishes the WebSocket to this.info's session and resolves
33
+ * once it's ready. Used both by the constructor and by restart() --
34
+ * info.sessionId/httpBase/wsBase don't change across a restart
35
+ * (SessionRegistry::restartSession() replaces the kernel in place under
36
+ * the same id), so reconnecting to the exact same URL is enough to pick
37
+ * back up a session the supervisor just gave a fresh kernel.
38
+ */
39
+ private connect;
40
+ /**
41
+ * Replaces this session's kernel process in place, keeping the same
42
+ * session id -- recovers a crashed session (kernelExit/unexpected close
43
+ * leaves the Session object itself alive but every execute() rejecting
44
+ * forever otherwise), and doubles as Jupyter's "Restart Kernel" for a
45
+ * still-healthy one. Not available after an explicit stop()/kill(): at
46
+ * that point the caller's intent was to end the session, not reset it
47
+ * -- create a new one instead via SessionManager.createSession().
48
+ *
49
+ * `options`, if given, switches this session's R installation on the
50
+ * restart (rHome/rPath/etc) instead of reusing whatever it was created
51
+ * with -- e.g. flip from R 4.4 to R 4.6 on the fly, without closing
52
+ * this session and opening a new one (a different session id/WS URL)
53
+ * just to pick a different R.
54
+ */
55
+ restart(options?: Partial<EngineOptions>): Promise<void>;
56
+ private send;
57
+ private recordExecutionHistory;
58
+ private boundStreamHistory;
59
+ private handleFrame;
60
+ /** Resolves once this session's R interpreter has started. */
61
+ ready(): Promise<void>;
62
+ execute(code: string, options?: ExecutionOptions): Promise<ExecutionResult>;
63
+ /**
64
+ * Answers a pending input_request -- this session emits one (see the
65
+ * 'input_request' event, content: {prompt, password}) whenever the
66
+ * kernel calls input()/readline()/scan() during an execute() that was
67
+ * given { allowStdin: true }, and genuinely blocks its single execution
68
+ * thread until this arrives (ServerZmqImpl::sendStdin() in
69
+ * native/src/adrastea/transport/server/server_zmq_impl.cpp does a real,
70
+ * untimed ZMQ recv underneath). Fire-and-forget like interrupt(): the
71
+ * reply that eventually unblocks the kernel surfaces through the
72
+ * *execute_request's own* execute_reply/stream messages, not through a
73
+ * reply to this call.
74
+ */
75
+ sendInputReply(value: string): void;
76
+ /**
77
+ * This session's own local record of every execute() call it has made
78
+ * and what each one produced (code + every iopub message), for as long
79
+ * as this Session object has been alive. Purely in-memory and
80
+ * process-local -- gone if the process holding this Session restarts,
81
+ * same as the Session object itself. Useful for e.g. rebuilding a UI's
82
+ * transcript after some *other* thing (not this process) reconnects to
83
+ * it, or for inspecting what actually ran without threading your own
84
+ * bookkeeping through every execute() call site.
85
+ *
86
+ * Not the same thing as queryKernelHistory(): this is this session's
87
+ * own bookkeeping (full fidelity -- includes actual output, which the
88
+ * kernel's own history manager doesn't track), while that one asks the
89
+ * *kernel itself* what it remembers running (input code only,
90
+ * authoritative even if some other client executed it, but capped by
91
+ * this process's own historical view of it, and lost across a kernel
92
+ * restart the same as the kernel's own memory of it is).
93
+ */
94
+ getHistory(): ExecutionHistoryEntry[];
95
+ /**
96
+ * Sends a real Jupyter history_request and resolves with the kernel's
97
+ * own history_reply (KernelCore::historyRequest() ->
98
+ * HistoryManager::processRequest(), native/src/adrastea/core/history/)
99
+ * -- the kernel's own authoritative record of what it has executed,
100
+ * independent of which client (or how many, over how many reconnects)
101
+ * actually ran it. Defaults to the 100 most recent executions ('tail').
102
+ * See KernelHistoryOptions' own doc comment for the other access modes,
103
+ * and getHistory()'s doc comment for how this differs from that.
104
+ */
105
+ queryKernelHistory(options?: KernelHistoryOptions): Promise<KernelHistoryEntry[]>;
106
+ /**
107
+ * Sends interrupt_request over the control channel and resolves true if
108
+ * the kernel acknowledged it (interrupt_reply, status ok), false if it
109
+ * didn't within `options.timeout` (default 5s) or the session is gone --
110
+ * never rejects, since a caller typically fires this from a "stop"
111
+ * button and has nothing useful to do with an error.
112
+ *
113
+ * A real interrupt: the kernel services its control channel on a
114
+ * separate thread while code runs, so this is answered immediately even
115
+ * mid-execution, and the running code is broken out of exactly as Ctrl-C
116
+ * would (R: an interrupt condition; Python: KeyboardInterrupt). The
117
+ * interrupted execute() resolves with success: false. Interrupting an
118
+ * idle kernel does nothing. Limits: code blocked inside a native call
119
+ * that never returns to the interpreter (a long C extension call, a
120
+ * blocking socket read) is only interrupted once it does, and a kernel
121
+ * waiting on an input() / readline() reply must be answered (or its
122
+ * execute() timed out) first.
123
+ */
124
+ interrupt(options?: {
125
+ timeout?: number;
126
+ }): Promise<boolean>;
127
+ /**
128
+ * The latest iopub `status` the kernel reported ('busy' while it is
129
+ * handling a request, 'idle' between them) -- undefined until the first
130
+ * one arrives. Also available as the 'status' event.
131
+ */
132
+ get executionState(): ExecutionState | undefined;
133
+ /**
134
+ * Sends any of the Jupyter requests that have a plain request/reply
135
+ * shape and resolves with the kernel's reply content: complete_request,
136
+ * inspect_request, is_complete_request, kernel_info_request,
137
+ * history_request, comm_info_request (shell) and interrupt_request
138
+ * (control) -- the typed methods below (complete(), inspect(), ...) are
139
+ * this with the right message type and content filled in. Rejects on a
140
+ * reply whose status is 'error'/'aborted', when the supervisor refuses
141
+ * the request, on timeout, or if the session goes away first.
142
+ *
143
+ * Deliberately not for execute_request (use execute(): it owns the
144
+ * queue/timeout/stdin semantics), input_reply (sendInputReply()) or
145
+ * shutdown_request (stop()/restart()) -- the supervisor rejects those.
146
+ */
147
+ request<T = any>(msgType: string, content?: Record<string, unknown>, options?: {
148
+ timeout?: number;
149
+ }): Promise<T>;
150
+ private trackExecutionState;
151
+ private routeComm;
152
+ private closeAllComms;
153
+ private settleRequest;
154
+ private rejectPendingRequests;
155
+ private watchFor;
156
+ /**
157
+ * What the supervisor knows about this session's kernel process right
158
+ * now: lifecycle status, pid, memory, working directory and the
159
+ * heartbeat (round-trip time of the last ping, missed pings). The
160
+ * heartbeat is answered by a kernel thread separate from the one that
161
+ * runs code, so it stays live while the kernel is busy -- unlike
162
+ * kernelInfo(), which would wait for the running code to finish.
163
+ */
164
+ status(): Promise<SessionStatusInfo>;
165
+ /** complete_request: completions for the code at `cursorPos` (default: the end of `code`). */
166
+ complete(code: string, cursorPos?: number): Promise<CompleteReplyContent>;
167
+ /** inspect_request: documentation/details for the symbol at `cursorPos` (default: the end of `code`). */
168
+ inspect(code: string, cursorPos?: number, detailLevel?: 0 | 1): Promise<InspectReplyContent>;
169
+ /**
170
+ * is_complete_request: whether `code` is a complete statement, needs more
171
+ * lines ('incomplete', with an `indent` hint when the kernel has one),
172
+ * or can never parse ('invalid') -- what a console needs to decide
173
+ * between "run it" and "keep prompting".
174
+ */
175
+ isComplete(code: string): Promise<IsCompleteReplyContent>;
176
+ /** kernel_info_request: what the kernel is -- implementation, language and its version, protocol version, banner. */
177
+ kernelInfo(): Promise<KernelInfoReplyContent>;
178
+ /** comm_info_request: the comms currently open in the kernel, optionally only those for one target. */
179
+ commInfo(targetName?: string): Promise<CommInfoReplyContent>;
180
+ private sendComm;
181
+ /** Opens a comm to a kernel-side `targetName`; resolves with its comm id and the msg id it was sent under. */
182
+ commOpen(targetName: string, data?: Record<string, unknown>, commId?: string): Promise<{
183
+ commId: string;
184
+ msgId: string;
185
+ }>;
186
+ /**
187
+ * Opens a comm to a kernel-side `targetName` and returns it as a Comm
188
+ * object (send()/close(), 'message'/'close' events). If the kernel has
189
+ * no such target it answers with a comm_close, so the returned comm
190
+ * emits 'close' shortly after. Kernel-initiated comms arrive as this
191
+ * session's 'comm' event instead: `session.on('comm', (comm, data) => ...)`.
192
+ */
193
+ openComm(targetName: string, data?: Record<string, unknown>): Promise<Comm>;
194
+ /** Sends `data` over an open comm. */
195
+ commMsg(commId: string, data?: Record<string, unknown>): Promise<string>;
196
+ /** Closes a comm. */
197
+ commClose(commId: string, data?: Record<string, unknown>): Promise<string>;
198
+ /**
199
+ * Launches a Shiny app in this session's R process and resolves once
200
+ * it's actually accepting connections. shiny::runApp() blocks the R
201
+ * session for as long as the app runs, so -- unlike execute() --
202
+ * resolving here does not mean the app is done; that's what the
203
+ * returned `done` promise is for.
204
+ */
205
+ createShiny(options: ShinyAppOptions): Promise<ShinyAppHandle>;
206
+ /** Stops the R session and waits for its process to exit. */
207
+ stop(): Promise<void>;
208
+ /** Skips the graceful shutdown protocol -- only for cleanup on the way out. */
209
+ kill(): void;
210
+ }
211
+ export declare class SessionManager {
212
+ private readonly supervisor;
213
+ private readonly sessions;
214
+ private exitHandlerRegistered;
215
+ /** Creates a new R session in its own OS process and waits for it to be ready. */
216
+ createSession(options?: EngineOptions): Promise<Session>;
217
+ /** Gracefully stops every session managed by this instance. */
218
+ stopAll(): Promise<void>;
219
+ /**
220
+ * Forcibly terminates every session. Prefer stopAll(), but a session
221
+ * whose R interpreter is blocked in a long-running call (e.g.
222
+ * shiny::runApp()) can't process a graceful shutdown until that call
223
+ * returns -- callers wanting a bounded-time exit (e.g. a Ctrl+C
224
+ * handler) should race stopAll() against a timeout and fall back to this.
225
+ */
226
+ killAll(): void;
227
+ private registerExitHandler;
228
+ }
229
+ //# sourceMappingURL=session-manager.d.ts.map