@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.
- package/LICENSE +24 -0
- package/README.md +175 -0
- package/docs/api/README.md +55 -0
- package/docs/api/session.md +143 -0
- package/docs/api/types.md +120 -0
- package/docs/architecture/overview.md +218 -0
- package/docs/cpp-usage.md +60 -0
- package/docs/development.md +105 -0
- package/docs/getting-started.md +127 -0
- package/docs/guides/comms.md +70 -0
- package/docs/guides/environments.md +80 -0
- package/docs/guides/history.md +44 -0
- package/docs/guides/interactive-input.md +48 -0
- package/docs/guides/interrupting.md +45 -0
- package/docs/guides/playground.md +33 -0
- package/docs/guides/sessions-lifecycle.md +70 -0
- package/docs/kernels.md +117 -0
- package/docs/protocol.md +135 -0
- package/docs/releasing.md +73 -0
- package/docs/troubleshooting.md +110 -0
- package/lib/execution/execution-queue.d.ts +20 -0
- package/lib/execution/execution-queue.js +256 -0
- package/lib/handlers/display-handler.d.ts +7 -0
- package/lib/handlers/display-handler.js +10 -0
- package/lib/handlers/error-handler.d.ts +7 -0
- package/lib/handlers/error-handler.js +8 -0
- package/lib/handlers/result-handler.d.ts +7 -0
- package/lib/handlers/result-handler.js +10 -0
- package/lib/handlers/stream-handler.d.ts +7 -0
- package/lib/handlers/stream-handler.js +8 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +5 -0
- package/lib/messaging/message-parser.d.ts +6 -0
- package/lib/messaging/message-parser.js +33 -0
- package/lib/messaging/message-router.d.ts +14 -0
- package/lib/messaging/message-router.js +41 -0
- package/lib/middleware/index.d.ts +5 -0
- package/lib/middleware/index.js +5 -0
- package/lib/middleware/middleware-chain.d.ts +7 -0
- package/lib/middleware/middleware-chain.js +14 -0
- package/lib/middleware/middleware.d.ts +5 -0
- package/lib/middleware/middleware.js +2 -0
- package/lib/middleware/plugins/logging-plugin.d.ts +6 -0
- package/lib/middleware/plugins/logging-plugin.js +9 -0
- package/lib/middleware/plugins/metrics-plugin.d.ts +8 -0
- package/lib/middleware/plugins/metrics-plugin.js +13 -0
- package/lib/session/comm.d.ts +39 -0
- package/lib/session/comm.js +58 -0
- package/lib/session/native-paths.d.ts +48 -0
- package/lib/session/native-paths.js +108 -0
- package/lib/session/session-manager.d.ts +229 -0
- package/lib/session/session-manager.js +842 -0
- package/lib/session/supervisor-client.d.ts +36 -0
- package/lib/session/supervisor-client.js +147 -0
- package/lib/types/engine.d.ts +269 -0
- package/lib/types/engine.js +2 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/index.js +3 -0
- package/lib/types/messages.d.ts +68 -0
- package/lib/types/messages.js +2 -0
- package/lib/utils/logger.d.ts +12 -0
- package/lib/utils/logger.js +58 -0
- package/lib/utils/network.d.ts +11 -0
- package/lib/utils/network.js +50 -0
- package/package.json +57 -0
- package/packages/hera/DESCRIPTION +29 -0
- package/packages/hera/LICENSE +2 -0
- package/packages/hera/LICENSE.md +21 -0
- package/packages/hera/NAMESPACE +32 -0
- package/packages/hera/NEWS.md +7 -0
- package/packages/hera/R/cell_options.R +13 -0
- package/packages/hera/R/comm.R +228 -0
- package/packages/hera/R/completion.R +54 -0
- package/packages/hera/R/execute.R +199 -0
- package/packages/hera/R/inspect.R +73 -0
- package/packages/hera/R/log.R +14 -0
- package/packages/hera/R/mime_bundle.R +65 -0
- package/packages/hera/R/routines.R +86 -0
- package/packages/hera/R/utils.R +32 -0
- package/packages/hera/R/zzz.R +128 -0
- package/packages/hera/man/Comm.Rd +179 -0
- package/packages/hera/man/CommManager.Rd +215 -0
- package/packages/hera/man/View.Rd +22 -0
- package/packages/hera/man/cell_options.Rd +20 -0
- package/packages/hera/man/clear_output.Rd +23 -0
- package/packages/hera/man/complete.Rd +23 -0
- package/packages/hera/man/display_data.Rd +22 -0
- package/packages/hera/man/is_elara.Rd +18 -0
- package/packages/hera/man/mime_bundle.Rd +25 -0
- package/packages/hera/man/mime_types.Rd +22 -0
- package/packages/hera/man/reexports.Rd +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Kariuki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The bundled R package "hera" (packages/hera) is a separate work with its own
|
|
24
|
+
copyright holders and MIT license; see packages/hera/LICENSE.
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Jovian
|
|
2
|
+
|
|
3
|
+
[](https://github.com/damurka/jovian/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Jovian runs **R** and **Python** as supervised Jupyter kernels that you can drive from Node.js and Electron. Every session is its own operating-system process with its own embedded interpreter, so one session blocking on a long call (a Shiny app, a slow loop) never starves another, and a kernel that crashes takes only its own session with it.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { SessionManager } from '@damurka/jovian';
|
|
9
|
+
|
|
10
|
+
const manager = new SessionManager();
|
|
11
|
+
|
|
12
|
+
const r = await manager.createSession({ kernelType: 'r', rHome: process.env.R_HOME, workingDirectory: '/projects/analysis' });
|
|
13
|
+
const result = await r.execute('x <- 1:10; mean(x)');
|
|
14
|
+
console.log(result.success, result.output);
|
|
15
|
+
|
|
16
|
+
const py = await manager.createSession({ kernelType: 'python', pythonHome: '/path/to/python' });
|
|
17
|
+
console.log((await py.execute('sum(range(1, 11))')).success);
|
|
18
|
+
|
|
19
|
+
await manager.stopAll();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The pieces are named after moons of Jupiter:
|
|
23
|
+
|
|
24
|
+
| Name | Role |
|
|
25
|
+
|---|---|
|
|
26
|
+
| **Jovian** | This npm package: a TypeScript client (`lib/`) over the native binaries below. |
|
|
27
|
+
| **Adrastea** | Language-neutral Jupyter kernel framework — wire protocol, ZMQ transport, request loop, the abstract `Interpreter` interface (`native/`, a static library). |
|
|
28
|
+
| **Elara** | The R kernel: embeds R on top of Adrastea (`elara` / `elara.exe`). |
|
|
29
|
+
| **Carpo** | The Python kernel: embeds CPython on top of Adrastea the same way (`carpo` / `carpo.exe`). |
|
|
30
|
+
| **Themisto** | The kernel supervisor: spawns and monitors one kernel process per session and re-exposes sessions over HTTP + WebSocket (`themisto` / `themisto.exe`). |
|
|
31
|
+
| [hera](packages/hera) | The R companion package loaded inside every Elara session (execution, completion, inspection, comms). |
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install @damurka/jovian
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The package ships **prebuilt** `themisto`, `elara` and `carpo` binaries — no compiler, CMake or vcpkg — for **Windows x64** (`win32-x64`), **Linux x64** (`linux-x64`) and **macOS on Apple Silicon** (`darwin-arm64`); Intel Macs and Linux on ARM are not published yet, so build from source there and point `JOVIAN_NATIVE_DIR` at `dist/native/Release`. npm installs the matching `@damurka/jovian-<os>-<cpu>` package automatically as an optional dependency, so do not install with `--omit=optional` / `--no-optional`. Node.js ≥ 22.4 is required (ES modules; global `fetch` and `WebSocket`).
|
|
40
|
+
|
|
41
|
+
What you must already have on the machine:
|
|
42
|
+
|
|
43
|
+
- **R** (4.2 or newer; a build with a shared library, which the CRAN/Posit binaries and distribution packages are) for R sessions. Pass its location as `rHome` (`R RHOME` prints it). The `hera` R package that every R session needs ships inside the npm package and is installed into R on a session's first start, which needs the `remotes` package and `hera`'s CRAN dependencies (`cli`, `evaluate`, `glue`, `IRdisplay`, `jsonlite`, `R6`, `repr`, `rlang`):
|
|
44
|
+
|
|
45
|
+
```r
|
|
46
|
+
install.packages(c("remotes", "cli", "evaluate", "glue", "IRdisplay", "jsonlite", "R6", "repr", "rlang"))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- **Python 3** with its shared library (optional, for Python sessions); pass `pythonHome` (`python3 -c "import sys; print(sys.prefix)"`).
|
|
50
|
+
- **Linux:** `libuuid` (`libuuid1`, present on nearly every system) and a glibc at least as new as the one the binaries were built against (Ubuntu 24.04's, 2.39). On an older distribution, [build from source](#requirements).
|
|
51
|
+
- **macOS:** 14 or newer.
|
|
52
|
+
- **Windows:** the [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist) (x64, 2015–2022) — the binaries use the dynamic C++ runtime; most machines already have it.
|
|
53
|
+
|
|
54
|
+
The rest of this README is for building Jovian from source.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **Two kernels, one API.** R (Elara) and Python (Carpo), chosen per session with `kernelType`.
|
|
59
|
+
- **Execution** with streaming stdout/stderr, `execute_result`, rich `display_data` (R plots arrive as `image/png`), `update_display_data`, `clear_output`, and structured errors with tracebacks.
|
|
60
|
+
- **Interactive input.** `input()` (Python) and `readline()` (R) round-trip over the Jupyter stdin channel; see [the input guide](docs/guides/interactive-input.md).
|
|
61
|
+
- **Real interrupt.** `session.interrupt()` breaks running R and Python code (a sleeping call, a busy loop), not just the queue; the kernel keeps running afterwards. See [interrupting](docs/guides/interrupting.md).
|
|
62
|
+
- **Working directory.** `workingDirectory` is where the kernel process starts (`getwd()` / `os.getcwd()`); it survives restarts.
|
|
63
|
+
- **Protocol requests as methods.** `complete`, `inspect`, `isComplete`, `kernelInfo`, `commInfo`, `queryKernelHistory` — real Jupyter messages, answered by the kernel. See [API reference](docs/api/README.md).
|
|
64
|
+
- **Comms.** Open a comm to a kernel-side target, or receive kernel-initiated ones with `session.on('comm', ...)`. See [comms](docs/guides/comms.md).
|
|
65
|
+
- **`user_expressions` and `stop_on_error`.** Evaluate expressions after a cell succeeds; abort everything queued behind a failing cell.
|
|
66
|
+
- **Protocol-driven lifecycle.** `stop()` sends the kernel a real `shutdown_request` (`restart: false`), `restart()` one with `restart: true`; the kernel's `shutdown_reply` surfaces as an event and an orderly exit is never reported as a crash.
|
|
67
|
+
- **Crash detection in milliseconds.** The supervisor watches the OS process handle, with the ZMQ heartbeat as a backstop for a kernel that is alive but stuck. A crashed session can be `restart()`ed in place under the same session id.
|
|
68
|
+
- **Restart with different options.** Switch R or Python installations on restart; unchanged options are kept ([details](docs/guides/sessions-lifecycle.md)).
|
|
69
|
+
- **A browser playground** (Next.js) for exercising live sessions — see [`tools/playground`](tools/playground/README.md).
|
|
70
|
+
- **Standard Jupyter launch mode.** `elara` and `carpo` can also be started directly by `jupyter lab` / `jupyter console` via a generated kernelspec (`npm run jupyter:kernelspec`), no supervisor involved.
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
*Building from source. To use the published package, see [Install](#install).*
|
|
75
|
+
|
|
76
|
+
Jovian builds C++ (Adrastea, Elara, Carpo, Themisto) and TypeScript. R and Python are **runtime** dependencies of the kernels, not build-time ones: Elara and Carpo load R's and Python's shared libraries dynamically when a session starts, so the binaries build without either installed (R's headers are still needed to compile Elara).
|
|
77
|
+
|
|
78
|
+
### All platforms
|
|
79
|
+
|
|
80
|
+
| Requirement | Notes |
|
|
81
|
+
|---|---|
|
|
82
|
+
| **Git** | To clone the repo and vcpkg. |
|
|
83
|
+
| **CMake ≥ 3.24** | Declared in `CMakeLists.txt`. The local build uses CMake 4.3.1 (the copy bundled with Visual Studio 2026). |
|
|
84
|
+
| **A C++23-capable compiler** | `CMAKE_CXX_STANDARD 23` is required. See the platform sections for what is actually verified. |
|
|
85
|
+
| **vcpkg** with `VCPKG_ROOT` set | Dependencies come from the manifest in `vcpkg.json` (pinned `builtin-baseline`): `nlohmann-json`, `cppzmq`, `zeromq`, `openssl`, `gtest`, `cpp-httplib`, `ixwebsocket`. Clone [microsoft/vcpkg](https://github.com/microsoft/vcpkg) and run its bootstrap script (`bootstrap-vcpkg.bat` / `bootstrap-vcpkg.sh`); CMake picks the manifest up through the toolchain file `$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake`. |
|
|
86
|
+
| **Node.js** (recent LTS) | The library is ES modules compiled with `tsc`; the test files are `.ts` and are run directly with `node --test`, which needs a Node with built-in TypeScript type stripping (unflagged since Node 22.18 / 23.6). Developed on Node 24; CI uses the current LTS. Use the official build from nodejs.org: some distribution packages (Ubuntu's `nodejs`) omit type stripping and fail with `ERR_UNKNOWN_FILE_EXTENSION` on `.ts` files. Global `fetch` and `WebSocket` are used by the client. |
|
|
87
|
+
| **R** (to run R sessions, and its headers to build Elara) | Developed against R 4.6.0; CI uses the latest release. On Windows, R ≥ 4.2 is needed for `readline()` to work over the stdin channel (older R still starts, but `readline()` cannot be answered). |
|
|
88
|
+
| **The R package `hera`** and its dependencies | Every R session needs it. Its `Imports` (from `packages/hera/DESCRIPTION`) are `cli`, `evaluate`, `glue`, `IRdisplay`, `jsonlite`, `R6`, `repr`, `rlang`, `tools`, `utils`. CI installs them with `r-lib/actions/setup-r-dependencies` using `packages: local::packages/hera`; locally, install those packages from CRAN and run **`npm run hera:install`** (`R CMD INSTALL packages/hera`), or pass `heraSrcPath` so Elara installs it on first start (needs the `remotes` package; `heraSrcPath` has no default). Use `hera` >= 0.6.0.9001: earlier versions work but only show the output of one long-running R expression when it ends, not as it is produced — re-run `npm run hera:install` after pulling. |
|
|
89
|
+
| **Python** (optional) | Only needed to run Python sessions and to run `CarpoTest`. Carpo does not include Python's headers or link Python at build time. A CPython 3 installation with its shared library: `python3NN.dll` on Windows, `libpython3.*.so` on Linux, `libpython3.*.dylib` on macOS. |
|
|
90
|
+
|
|
91
|
+
### Windows
|
|
92
|
+
|
|
93
|
+
- **Visual Studio with the “Desktop development with C++” workload** (MSVC, Windows SDK, and the bundled CMake). The repository is built and tested here with **Visual Studio 2026 (version 18), MSVC toolset v145**, generator `Visual Studio 18 2026`. CI builds on the GitHub `windows-latest` runner. Earlier Visual Studio versions have not been tried; the build needs MSVC's `/std:c++latest` C++23 mode.
|
|
94
|
+
- `CMakePresets.json` also defines Ninja + `cl.exe` presets (`x64-debug`, `x64-release`, `x86-*`); they require `VCPKG_ROOT` and `VSINSTALLDIR` (open a *Developer PowerShell / Command Prompt*, or configure from Visual Studio).
|
|
95
|
+
- vcpkg installs the `x64-windows` triplet (dynamic DLLs), which is why `dist/native/Release/` contains ZeroMQ/OpenSSL DLLs next to the executables.
|
|
96
|
+
- R's DLL lives in `<R_HOME>\bin\x64`; pass it as `rPath` if it is not on `PATH`. You do **not** need Rtools.
|
|
97
|
+
- Note that `npm run compile` uses `%VCPKG_ROOT%` (cmd.exe syntax); use `npm run build` from any shell.
|
|
98
|
+
|
|
99
|
+
### Linux
|
|
100
|
+
|
|
101
|
+
- A C++23 compiler, `cmake`, and vcpkg's own prerequisites (see the vcpkg docs). CI installs one extra package: **`uuid-dev`** (`sudo apt-get install uuid-dev`), because the GUID code calls `uuid_generate()` from libuuid.
|
|
102
|
+
- R **must have been built with a shared library** (`--enable-R-shlib`; distribution packages and the CRAN/Posit binaries are). Elara `dlopen`s `$R_HOME/lib/libR.so`.
|
|
103
|
+
- CI runs on `ubuntu-latest`. The full suite (native, unit, integration) was also run on **Ubuntu 26.04 under WSL**; notes for Debian/Ubuntu:
|
|
104
|
+
- `cmake/FindR.cmake` finds Debian's split R headers (`R.h` in `/usr/share/R/include`) by asking `R CMD config --cppflags`, so `r-base-dev` is enough.
|
|
105
|
+
- Carpo looks for libpython in `lib/`, `lib64/` and `lib/<arch>-linux-gnu/`, so a distribution Python works with `PYTHONHOME=/usr` (`sudo apt install python3`; add `python3-venv` to run CarpoTest's venv test).
|
|
106
|
+
- If `hera` fails to install with `undefined symbol: SETLENGTH`, the apt `r-cran-*` packages were built for a different R ABI: hide the site library (`R_LIBS_SITE=/nonexistent`) and install `hera`'s dependencies from CRAN into a private `R_LIBS_USER`, as CI does.
|
|
107
|
+
- The distribution's packaged Node.js has no TypeScript type stripping; use the official build from nodejs.org to run the `.ts` tests.
|
|
108
|
+
- macOS is covered only by CI.
|
|
109
|
+
|
|
110
|
+
### macOS
|
|
111
|
+
|
|
112
|
+
- A C++23-capable Apple clang (Xcode or the Command Line Tools); the build links the CoreFoundation framework. CI runs on `macos-latest` using the runner's preinstalled toolchain and installs nothing extra.
|
|
113
|
+
- R must provide `libR.dylib` under `$R_HOME/lib` (CRAN's framework build does).
|
|
114
|
+
|
|
115
|
+
### Environment variables
|
|
116
|
+
|
|
117
|
+
| Variable | Used by | Meaning |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `VCPKG_ROOT` | build | vcpkg checkout; used by `npm run build` and the CMake presets. |
|
|
120
|
+
| `R_HOME` | runtime, tests, examples | R installation to use when `rHome` is not passed. `R RHOME` is used as a fallback by the tests and the playground. |
|
|
121
|
+
| `R_PATH`, `R_LIBS` | examples, playground | Passed as `rPath` / `rLibs`. |
|
|
122
|
+
| `PYTHONHOME` | runtime, tests | Python installation prefix when `pythonHome` is not passed. |
|
|
123
|
+
| `JOVIAN_NATIVE_DIR` | `lib/` | Directory holding `themisto`, `elara` and `carpo`. Default: the installed `@damurka/jovian-<os>-<cpu>` package, else `dist/native/Release` in a source checkout. Use it to run against a *copy* of the binaries (Windows will not let you overwrite a running `.exe`). |
|
|
124
|
+
| `ELARA_HERA_SRC` | Elara | Set for you from the `heraSrcPath` option: where Elara installs `hera` from if it is missing or older than the source. |
|
|
125
|
+
|
|
126
|
+
## Building and testing
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
npm install --legacy-peer-deps # CI uses `npm ci --legacy-peer-deps` (typescript and the eslint plugin disagree on peer versions)
|
|
130
|
+
npm run build # native (elara + carpo + themisto, Release) into dist/native, then TypeScript into dist/lib
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`npm run build` configures with `VCPKG_ROOT`'s toolchain file when the variable is set. The individual steps are `npm run build:native` (only `cmake --build`, after a configure) and `npm run build:lib` (`tsc --build`).
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm test # native ctest + TypeScript unit + integration tests (scripts/test.js)
|
|
137
|
+
npm run test:unit # TypeScript unit tests only (fake WebSocket, no processes)
|
|
138
|
+
npm run test:integration # end-to-end: real themisto + real R / Python kernels
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The TypeScript tests import the **compiled** library from `dist/`, so run `npm run build:lib` after changing anything in `lib/`. The integration tests skip themselves when the native binaries are not built, and the Python ones skip when no Python or no `carpo` is available. More in [docs/development.md](docs/development.md).
|
|
142
|
+
|
|
143
|
+
Other scripts: `npm run hera:install` (install/update the `hera` R package from `packages/hera`), `npm run dev` (TypeScript watch mode), `npm run clean` (removes `dist/`), `npm run format`, `npm run lint`, `npm run jupyter:kernelspec` (write `kernel.json` files for `elara` and `carpo`; `-- --only=r` / `--only=python` for one).
|
|
144
|
+
|
|
145
|
+
## Playground
|
|
146
|
+
|
|
147
|
+
A browser UI (Next.js) for driving real R and Python sessions, with completion (`Tab`) and inspection (`Shift+Tab`, double-click in the output):
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm run playground:install # once — the playground has its own node_modules
|
|
151
|
+
npm run playground # http://127.0.0.1:4173
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
See [`tools/playground/README.md`](tools/playground/README.md).
|
|
155
|
+
|
|
156
|
+
## Documentation
|
|
157
|
+
|
|
158
|
+
| | |
|
|
159
|
+
|---|---|
|
|
160
|
+
| [Getting started](docs/getting-started.md) | Prerequisites → build → your first R and Python session. |
|
|
161
|
+
| [Architecture](docs/architecture/overview.md) | Components, process and thread model, message flows. |
|
|
162
|
+
| [Protocol](docs/protocol.md) | Jupyter messages supported, Themisto's HTTP API and WebSocket frames. |
|
|
163
|
+
| [API reference](docs/api/README.md) | `SessionManager`, `Session`, `Comm`, options and result types. |
|
|
164
|
+
| Guides | [Interactive input](docs/guides/interactive-input.md) · [Interrupting](docs/guides/interrupting.md) · [Comms](docs/guides/comms.md) · [History](docs/guides/history.md) · [Session lifecycle](docs/guides/sessions-lifecycle.md) · [R and Python environments](docs/guides/environments.md) · [Playground](docs/guides/playground.md) |
|
|
165
|
+
| [Kernels](docs/kernels.md) | How Elara and Carpo work, and where they differ. |
|
|
166
|
+
| [Development](docs/development.md) | Repo layout, build system, test layers, CI, debugging. |
|
|
167
|
+
| [Releasing](docs/releasing.md) | How the npm packages are built and published. |
|
|
168
|
+
| [Troubleshooting](docs/troubleshooting.md) | Real error messages and what causes them. |
|
|
169
|
+
| [C++ usage](docs/cpp-usage.md) | Using Adrastea from C++; writing a new language kernel. |
|
|
170
|
+
|
|
171
|
+
Runnable examples are in [`examples/`](examples): [`basic/simple-execute.js`](examples/basic/simple-execute.js) (one session) and [`advanced/two-sessions.js`](examples/advanced/two-sessions.js) (two concurrent sessions, one blocking).
|
|
172
|
+
|
|
173
|
+
## Status
|
|
174
|
+
|
|
175
|
+
Both kernels support execute, streaming output, display data, errors, completion, inspection, `is_complete`, kernel info, history, comms, interactive input, real interrupt, and session restart / crash recovery. Elara additionally has a standard Jupyter connection-file launch mode alongside the Themisto-supervised one; Carpo's kernelspec is generated too but has not been exercised against a real `jupyter lab` / `jupyter console`. Carpo supports pointing a session at a venv's `site-packages`. Interrupt was verified on Windows; its POSIX implementation (`pthread_kill` of SIGINT to the interpreter thread) is covered by the same tests but relies on CI to run them on Linux and macOS.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# TypeScript API reference
|
|
2
|
+
|
|
3
|
+
The `@damurka/jovian` package (ES modules) exports:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import {
|
|
7
|
+
SessionManager, Session, Comm, // classes
|
|
8
|
+
type EngineOptions, type ExecutionOptions, type ExecutionResult, type JupyterMessage,
|
|
9
|
+
// …every type in ./types (see types.md), plus the middleware classes
|
|
10
|
+
} from '@damurka/jovian';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Page | Contents |
|
|
14
|
+
|---|---|
|
|
15
|
+
| **This page** | `SessionManager`, events, errors and timeouts |
|
|
16
|
+
| [`session.md`](session.md) | `Session` — every method, property and event; `Comm` |
|
|
17
|
+
| [`types.md`](types.md) | `EngineOptions`, `ExecutionOptions`, `ExecutionResult`, reply content types, message types |
|
|
18
|
+
|
|
19
|
+
## `SessionManager`
|
|
20
|
+
|
|
21
|
+
Creates sessions and owns the one shared supervisor process (`themisto`) they all talk to. The supervisor is spawned lazily on the first `createSession()` and killed by `stopAll()` / `killAll()` (and, as a safety net, when the Node process exits).
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const manager = new SessionManager();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Member | Description |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `createSession(options?: EngineOptions): Promise<Session>` | Spawns a kernel process (per `options.kernelType`), waits until it has registered and the session's WebSocket is ready, and resolves with the `Session`. Rejects with the supervisor's message if the kernel could not start: `workingDirectory does not exist or is not a directory: …`, `no kernel executable is configured for kernelType 'python' …`, or — when the kernel process itself failed (R or Python not found, …) — `Kernel process exited before it could register -- check its stderr output for the actual error.` The *actual* R/Python error is printed on stderr with an `[elara]` / `[carpo]` prefix (see [Troubleshooting](../troubleshooting.md)). |
|
|
30
|
+
| `stopAll(): Promise<void>` | Gracefully `stop()`s every session, then kills the supervisor. Call this before your process exits — **without it a script hangs**, because the supervisor's pipes keep Node's event loop alive. |
|
|
31
|
+
| `killAll(): void` | Immediately force-closes every session and kills the supervisor. Use it as the fallback when `stopAll()` is racing a timeout (a kernel stuck inside `shiny::runApp()` cannot process a shutdown until that call returns). |
|
|
32
|
+
|
|
33
|
+
The kernel binaries are looked up in this order: `$JOVIAN_NATIVE_DIR`; the installed `@damurka/jovian-<os>-<cpu>` package (its `bin/` directory); `dist/native/Release` in a source checkout. If none has `themisto[.exe]`, `createSession()` rejects with an error naming what was expected (`jovian: the kernel binaries were not found. Expected the '@damurka/jovian-…' package …`, or, on a platform without a prebuilt package, the list of supported ones). When the library runs from an installed package and `heraSrcPath` is not given, it defaults to the copy of `hera` shipped in the package.
|
|
34
|
+
|
|
35
|
+
## Events
|
|
36
|
+
|
|
37
|
+
`Session` is an `EventEmitter`. Everything the kernel sends is available as events — the full list is in [`session.md`](session.md#events). The two you must know about:
|
|
38
|
+
|
|
39
|
+
- **Always attach an `'error'` listener.** `Session` re-emits every R/Python error message as Node's special `'error'` event (with the error message string), and internal failures as an `Error` instance. Node **throws** if `'error'` is emitted with no listener, which kills the process. The error is also in the execution's `ExecutionResult` — the listener can be a no-op.
|
|
40
|
+
- `'exit'` means the kernel (or the connection to the supervisor) died unexpectedly.
|
|
41
|
+
|
|
42
|
+
## Errors and timeouts
|
|
43
|
+
|
|
44
|
+
| Situation | What you get |
|
|
45
|
+
|---|---|
|
|
46
|
+
| R/Python code raises an error | `execute()` **resolves** with `success: false`, the `error` message, and the `error` iopub message in `output` (with `ename`, `evalue`, `traceback`). It does not reject. |
|
|
47
|
+
| `execute()` exceeds its `timeout` (default **30 000 ms**; `0` = none) | Rejects with `Execution timed out after <n>ms`. The kernel is *not* interrupted — call `interrupt()` yourself if you want the code to stop. |
|
|
48
|
+
| A kernel waits for `input()` / `readline()` | The execution's timeout is cleared while it waits, so a slow human cannot trip it. |
|
|
49
|
+
| Session stopped, restarted, killed or crashed while an `execute()` is queued or running | Rejects with `Queue cleared`. |
|
|
50
|
+
| More than `queueSize` (default **100**) executions queued | Rejects with `Execution queue is full`. |
|
|
51
|
+
| Protocol requests (`complete()`, `kernelInfo()`, …) | Time out after **10 000 ms** by default (`session.request(…, { timeout })` to change), reject on an `error`/`aborted` reply, on a supervisor `requestError`, and with `Session stopped` / `Session process exited: …` / `Session is restarting` / `Session was killed` if the session goes away. |
|
|
52
|
+
| `interrupt()` | Never rejects: resolves `false` if unacknowledged within its timeout (default 5 000 ms). |
|
|
53
|
+
| `restart()` after `stop()`/`kill()` | Throws `Cannot restart session <id>: it was already stopped`. A stopped session is terminal — create a new one. |
|
|
54
|
+
|
|
55
|
+
Logging: pass `logger` (a `(level, message, data?) => void` function) to route library logs; without it every level, including `trace`/`debug`, is printed to the console. `enableLogging` adds a middleware that prints the first 100 characters of every raw message frame; `enableMetrics` prints a messages/second line per message.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# `Session` and `Comm`
|
|
2
|
+
|
|
3
|
+
A `Session` is one kernel process (R or Python) plus its WebSocket connection to the supervisor. You get one from [`SessionManager.createSession()`](README.md#sessionmanager); the constructor is not part of the public API.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
const session = await manager.createSession({ kernelType: 'python', pythonHome, workingDirectory: '/projects/a' });
|
|
7
|
+
session.on('error', () => {}); // required, see README#events
|
|
8
|
+
session.on('stdout', (t) => process.stdout.write(t));
|
|
9
|
+
const result = await session.execute('print("hi"); 1 + 1');
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Properties
|
|
13
|
+
|
|
14
|
+
| Property | Type | Description |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `info` | `{ sessionId, httpBase, wsBase }` | Where this session lives on the supervisor. `httpBase` + `/sessions/<sessionId>` is the supervisor's own view of it — `status()` reads exactly that. |
|
|
17
|
+
| `options` | `EngineOptions` | What the session is running with: the creation options, updated by any `restart(options)` that changed them. |
|
|
18
|
+
| `executionState` | `'busy'` \| `'idle'` \| `'starting'` \| `undefined` | The kernel's latest iopub `status`. Tracked per request, so an interrupt handled *during* an execution (its own busy/idle pair) does not flip the state to idle while the execution is still running. `undefined` until the first status. |
|
|
19
|
+
|
|
20
|
+
## Running code
|
|
21
|
+
|
|
22
|
+
### `execute(code, options?): Promise<ExecutionResult>`
|
|
23
|
+
|
|
24
|
+
Runs `code`. Calls are queued and sent to the kernel **one at a time** (a kernel runs one thing at a time anyway); each resolves when the kernel's `execute_reply` arrives. See [`ExecutionOptions` / `ExecutionResult`](types.md#execution). Notable options:
|
|
25
|
+
|
|
26
|
+
- `timeout` — ms, default 30 000, `0` for none. When it fires, `execute()` rejects with `Execution timed out after <n>ms (the kernel was interrupted)` **and the kernel is interrupted** (so it does not keep running code nobody is waiting for, which would block everything queued behind it).
|
|
27
|
+
- `interruptOnTimeout: false` — on a timeout, only reject: leave the kernel running (the message then ends after `ms`, without the parenthesis). The default is `true`.
|
|
28
|
+
- `allowStdin: true` — let `input()` / `readline()` ask for input; see [Interactive input](../guides/interactive-input.md).
|
|
29
|
+
- `stopOnError: true` — if this execution fails, every `execute()` still **queued behind it** is skipped: it resolves with `success: false`, `aborted: true`, `status: 'aborted'`, empty `output`, and never reaches the kernel. Without it a failure does not affect the queue.
|
|
30
|
+
- `userExpressions: { name: 'expr' }` — evaluated in the kernel after the code, only if it succeeded; results in `result.userExpressions[name]`, each `{ status: 'ok', data, metadata }` or `{ status: 'error', ename, evalue, traceback }`.
|
|
31
|
+
- `silent`, `storeHistory` — passed through to the kernel (`silent` suppresses `execute_input`, the execution count and history).
|
|
32
|
+
|
|
33
|
+
`ExecutionResult.output` holds every iopub message the execution produced (`stream`, `execute_result`, `display_data`, `update_display_data`, `clear_output`, `error`), in arrival order. It does **not** contain `status` messages.
|
|
34
|
+
|
|
35
|
+
### `interrupt(options?: { timeout?: number }): Promise<boolean>`
|
|
36
|
+
|
|
37
|
+
Sends `interrupt_request` on the control channel. Resolves `true` if the kernel acknowledged (`interrupt_reply` with `status: 'ok'`), `false` if not within `timeout` (default 5 000 ms) or the session is gone — it **never rejects**. The interrupt is real: the kernel services its control channel on a watcher thread while code runs, so it is answered immediately even mid-execution and running code is broken out of like Ctrl-C (R: an interrupt condition; Python: `KeyboardInterrupt`). The interrupted `execute()` resolves with `success: false`; the kernel stays usable. Interrupting an idle kernel does nothing. See [Interrupting](../guides/interrupting.md) for limits.
|
|
38
|
+
|
|
39
|
+
### `sendInputReply(value: string): void`
|
|
40
|
+
|
|
41
|
+
Answers a pending `'input_request'`. Fire-and-forget; what comes back is the running execution finishing.
|
|
42
|
+
|
|
43
|
+
### `createShiny(options: ShinyAppOptions): Promise<ShinyAppHandle>`
|
|
44
|
+
|
|
45
|
+
R only. Runs `shiny::runApp()` in the session (with `timeout: 0`) and resolves once the port accepts connections; the returned `done` promise resolves when the app stops. The session is busy for as long as the app runs.
|
|
46
|
+
|
|
47
|
+
## Protocol requests
|
|
48
|
+
|
|
49
|
+
Each is a real Jupyter request answered by the kernel; each rejects on an `error`/`aborted` status, on timeout (10 s), or if the session ends. They are queued **behind a running execution** on the kernel's main thread (only `interrupt()` is serviced during one).
|
|
50
|
+
|
|
51
|
+
| Method | Sends | Resolves with |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| `complete(code, cursorPos = code.length)` | `complete_request` | `{ status, matches: string[], cursor_start, cursor_end, metadata }` |
|
|
54
|
+
| `inspect(code, cursorPos = code.length, detailLevel = 0)` | `inspect_request` | `{ status, found, data: { 'text/plain'?, 'text/html'? }, metadata }` |
|
|
55
|
+
| `isComplete(code)` | `is_complete_request` | `{ status: 'complete' \| 'incomplete' \| 'invalid' \| 'unknown', indent? }` |
|
|
56
|
+
| `kernelInfo()` | `kernel_info_request` | `{ protocol_version, implementation, implementation_version, language_info: { name, version, … }, banner, … }` |
|
|
57
|
+
| `commInfo(targetName?)` | `comm_info_request` | `{ status, comms: { [commId]: { target_name } } }` |
|
|
58
|
+
| `queryKernelHistory(options?)` | `history_request` | `KernelHistoryEntry[]` — see [History](../guides/history.md) |
|
|
59
|
+
| `request<T>(msgType, content?, { timeout? })` | any whitelisted request/reply pair | the reply `content` |
|
|
60
|
+
|
|
61
|
+
`request()` is the generic form behind the methods above. It accepts the request types in the [whitelist](../protocol.md#client--themisto); the reply type is derived by replacing `_request` with `_reply`. It is not for `execute_request`, `input_reply` or `shutdown_request`.
|
|
62
|
+
|
|
63
|
+
## Comms
|
|
64
|
+
|
|
65
|
+
A *comm* is a named message stream between the client and a target registered inside the kernel (R: `hera::CommManager$register_comm_target(name, callback)`). Carpo has no way to register targets yet, so comms are an R feature today. See [the guide](../guides/comms.md).
|
|
66
|
+
|
|
67
|
+
| Method | Description |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `openComm(targetName, data?): Promise<Comm>` | Opens a comm and returns a `Comm` object. If the kernel has no such target it answers with a `comm_close`, so the returned comm emits `'close'`. |
|
|
70
|
+
| `commOpen(targetName, data?, commId?)` | Low-level: sends `comm_open`; resolves `{ commId, msgId }`. |
|
|
71
|
+
| `commMsg(commId, data?)` / `commClose(commId, data?)` | Low-level: send `comm_msg` / `comm_close`; resolve with the msg id they were sent under. |
|
|
72
|
+
|
|
73
|
+
**`Comm`** (`EventEmitter`)
|
|
74
|
+
|
|
75
|
+
| Member | Description |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `id`, `targetName`, `closed` | Identity and state. |
|
|
78
|
+
| `send(data?): Promise<string>` | Sends a `comm_msg`. Rejects if the comm is closed. |
|
|
79
|
+
| `close(data?): Promise<void>` | Sends `comm_close` (no-op if already closed) and emits `'close'`. |
|
|
80
|
+
| event `'message'` `(data)` | The kernel sent a `comm_msg` on this comm. |
|
|
81
|
+
| event `'close'` `(data)` | Closed by either side, **or** because the kernel restarted, exited, or the session stopped — then `data.reason` is `'kernel restarted'`, `'kernel exited'`, `'session stopped'` or `'connection lost'`. |
|
|
82
|
+
|
|
83
|
+
Comms the **kernel** opens arrive as the session's `'comm'` event: `session.on('comm', (comm, data) => …)` where `data` is the `comm_open`'s payload.
|
|
84
|
+
|
|
85
|
+
## History
|
|
86
|
+
|
|
87
|
+
| Method | Description |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `getHistory(): ExecutionHistoryEntry[]` | This session object's own transcript: `{ code, executionCount?, time, messages, truncated? }` per `execute()`, output included. In memory, capped at the latest **200** entries; within an entry the stream (stdout/stderr) text is bounded to the newest **~500 000 characters** (older stream messages are dropped and `truncated` is `true`; results, display data and errors are kept in full). Survives `restart()`, gone with the `Session` object. It is the live array — copy it if you need a snapshot. |
|
|
90
|
+
| `queryKernelHistory(options?)` | What the *kernel* remembers running (inputs only; lost when the kernel restarts). See [History](../guides/history.md) for the difference. |
|
|
91
|
+
|
|
92
|
+
## Status
|
|
93
|
+
|
|
94
|
+
### `status(): Promise<SessionStatusInfo>`
|
|
95
|
+
|
|
96
|
+
What the supervisor knows about this session's kernel process right now — a plain `GET {httpBase}/sessions/{sessionId}`, so it needs no kernel round trip:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const s = await session.status();
|
|
100
|
+
// { sessionId, status: 'ready', kernelType: 'r', workingDirectory: '...', pid: 4242,
|
|
101
|
+
// memoryBytes: 88000000, heartbeat: { hasPong: true, rttMs: 0.6, sinceLastPongMs: 42, misses: 0 } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`heartbeat` is the supervisor's ZMQ ping to the kernel (about ten a second): `rttMs` is the round trip of the last answered ping, `sinceLastPongMs` how long ago that was, `misses` how many pings in a row went unanswered (`0` = healthy). The kernel answers pings from a thread of its own, separate from the one that runs code, so **the heartbeat stays live while the kernel is busy** — unlike `kernelInfo()`, which would wait for the running cell to finish. Use it as a liveness signal ("is it alive?"), not a responsiveness one ("is it free?" — that is `executionState`). `heartbeat` is `null` for a session with no client (e.g. one that has been stopped); `hasPong` is `false` until the first ping has been answered. Rejects if the supervisor cannot be reached or no longer knows the session (a stopped session is removed).
|
|
105
|
+
|
|
106
|
+
## Lifecycle
|
|
107
|
+
|
|
108
|
+
### `restart(options?: Partial<EngineOptions>): Promise<void>`
|
|
109
|
+
|
|
110
|
+
Replaces the kernel process under the **same session id** — recovers a crashed session, or resets a healthy one (Jupyter's "Restart Kernel"). Queued and running `execute()` calls reject with `Queue cleared`; pending requests reject with `Session is restarting`; open comms emit `'close'` with reason `'kernel restarted'`.
|
|
111
|
+
|
|
112
|
+
`options`, if given, are **merged** with the session's current options (`{ ...session.options, ...options }`) before being sent, so switching only `rHome` keeps `workingDirectory`, `rLibs`, …; `session.options` is updated. To *clear* a field, pass `undefined` or an empty string. The old kernel receives `shutdown_request{restart: true}` — you can observe the kernel's `shutdown_reply` (`restart: true`) as an event before `'restarted'` fires. Throws if the session was already stopped or killed.
|
|
113
|
+
|
|
114
|
+
### `stop(): Promise<void>`
|
|
115
|
+
|
|
116
|
+
Sends the kernel `shutdown_request{restart: false}` through the supervisor, waits for the process to exit (force-killed after ~2 s if it does not), waits up to 250 ms for the `shutdown_reply` to be observable, closes the socket and emits `'stopped'`. Idempotent. Pending work rejects (`Queue cleared`, `Session stopped`).
|
|
117
|
+
|
|
118
|
+
### `kill(): void`
|
|
119
|
+
|
|
120
|
+
Closes the local socket and rejects pending work **without** telling the supervisor — only for cleanup on the way out; the kernel process is not stopped by this alone (`SessionManager.killAll()` also kills the supervisor, which takes the kernels with it).
|
|
121
|
+
|
|
122
|
+
### `ready(): Promise<void>`
|
|
123
|
+
|
|
124
|
+
Resolves once the session's WebSocket is open and bound. `execute()` and the request methods await it for you.
|
|
125
|
+
|
|
126
|
+
## Events
|
|
127
|
+
|
|
128
|
+
All are Node `EventEmitter` events on the `Session`.
|
|
129
|
+
|
|
130
|
+
| Event | Arguments | When |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `'message'` | `JupyterMessage` | Every message from the kernel (iopub, shell, control, stdin). |
|
|
133
|
+
| `'*'` | `(msgType, content)` | Same, as a catch-all. |
|
|
134
|
+
| *`<msg_type>`* — e.g. `'status'`, `'execute_reply'`, `'shutdown_reply'`, `'input_request'`, `'comm_msg'`, `'display_data'`, `'update_display_data'`, `'clear_output'` | `content` | Every message also fires an event named after its `msg_type`. |
|
|
135
|
+
| `'stdout'`, `'stderr'` | `text` | Streamed output (`stream` messages by `name`). |
|
|
136
|
+
| `'result'` | `text` | The `text/plain` of each `execute_result` / `display_data`. |
|
|
137
|
+
| `'input_request'` | `{ prompt, password }` | The kernel is blocked waiting for input; answer with `sendInputReply()`. |
|
|
138
|
+
| `'comm'` | `(comm: Comm, data)` | The kernel opened a comm. |
|
|
139
|
+
| `'error'` | `string` (the error's `evalue`) or `Error` | A code error in the kernel, or an internal failure handling a message. **Requires a listener.** |
|
|
140
|
+
| `'exit'` | `{ reason }` | Kernel died unexpectedly (or the supervisor connection dropped). The session is left recoverable via `restart()`. Not emitted for `stop()` / `restart()`. |
|
|
141
|
+
| `'restarted'` | — | `restart()` finished. |
|
|
142
|
+
| `'stopped'` | — | `stop()` finished. |
|
|
143
|
+
| `'requestError'` | `{ id, error }` | The supervisor refused a fire-and-forget request (a `comm_*` message) — e.g. session not found. |
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Types
|
|
2
|
+
|
|
3
|
+
Everything here is exported from `jovian` (`lib/types/`). Snake_case fields (`cursor_pos`, `execution_state`, …) are Jupyter's wire names, passed through unchanged; camelCase fields are Jovian's.
|
|
4
|
+
|
|
5
|
+
## `EngineOptions`
|
|
6
|
+
|
|
7
|
+
Passed to `SessionManager.createSession()`; also `Partial<EngineOptions>` to `Session.restart()`.
|
|
8
|
+
|
|
9
|
+
| Field | Type | Default | Meaning |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| `kernelType` | `'r'` \| `'python'` | `'r'` | Which kernel the session runs. Selects which of the fields below apply and which executable (`elara` / `carpo`) is spawned. |
|
|
12
|
+
| `rHome` | string | — | R installation (`R_HOME`). Required to run R sessions unless `R_HOME` is discoverable by the kernel's environment — pass it explicitly. |
|
|
13
|
+
| `rPath` | string | `<rHome>/bin/x64` (Windows) | Directory containing `R.dll`; put on the kernel's `PATH`. |
|
|
14
|
+
| `rLibs` | string | — | Extra library path (`R_LIBS`, `R_LIBS_USER`, and `R_LIBS_SITE` on Windows); where `hera` is looked up and installed. |
|
|
15
|
+
| `pandocPath` | string | — | Directory with a `pandoc` binary, for bundled R installs that do not ship it on `PATH` (`RSTUDIO_PANDOC`). |
|
|
16
|
+
| `heraSrcPath` | string | — | Source directory of the `hera` package. If set, Elara installs it (`remotes::install_local`, needs `remotes`) when it is missing or older than the source. **There is no built-in default**: if `hera` is not installed and this is unset, R code cannot run (see [Troubleshooting](../troubleshooting.md#hera-is-not-installed)). |
|
|
17
|
+
| `pythonHome` | string | — | Python installation prefix (`PYTHONHOME`). Its shared library is loaded from here. |
|
|
18
|
+
| `pythonPath` | string | — | Extra `PYTHONPATH`. |
|
|
19
|
+
| `venvPath` | string | — | A venv whose `site-packages` is added to `sys.path`. `pythonHome` must still point at the *base* install. |
|
|
20
|
+
| `workingDirectory` | string | supervisor's cwd | Directory the kernel process starts in (`getwd()` / `os.getcwd()`); relative paths resolve against it. Must exist, or `createSession()` rejects with `workingDirectory does not exist or is not a directory: <path>`. Kept across `restart()`. |
|
|
21
|
+
| `queueSize` | number | 100 | Max queued `execute()` calls. |
|
|
22
|
+
| `enableLogging` | boolean | false | Log the first 100 characters of every raw message frame. |
|
|
23
|
+
| `enableMetrics` | boolean | false | Print a messages/second line per message. |
|
|
24
|
+
| `logger` | `(level, message, data?) => void` | console | Receives the library's logs (`trace` … `error`). |
|
|
25
|
+
|
|
26
|
+
## Execution
|
|
27
|
+
|
|
28
|
+
### `ExecutionOptions`
|
|
29
|
+
|
|
30
|
+
| Field | Type | Default | Meaning |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| `timeout` | number (ms) | 30 000 | `0` = none. Cleared while the kernel waits for input. When it fires the call rejects and, unless `interruptOnTimeout` is `false`, the kernel is interrupted too. |
|
|
33
|
+
| `interruptOnTimeout` | boolean | true | On a timeout, also send the kernel an interrupt (the rejection message then ends `(the kernel was interrupted)`). Set `false` to leave it running. |
|
|
34
|
+
| `allowStdin` | boolean | false | Permit `input()` / `readline()`. |
|
|
35
|
+
| `stopOnError` | boolean | false | On failure, skip everything queued behind this call (they resolve `aborted`). Also forwarded to the kernel as `stop_on_error`. |
|
|
36
|
+
| `userExpressions` | `Record<string, string>` | — | Expressions evaluated after the code, if it succeeded. |
|
|
37
|
+
| `silent` | boolean | false | No `execute_input`, no execution count, no history. |
|
|
38
|
+
| `storeHistory` | boolean | true | Record in the kernel's history (ignored when `silent`). |
|
|
39
|
+
|
|
40
|
+
### `ExecutionResult`
|
|
41
|
+
|
|
42
|
+
| Field | Type | Meaning |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| `success` | boolean | `true` iff the kernel's reply status was `ok`. |
|
|
45
|
+
| `output` | `JupyterMessage[]` | Every `stream` / `execute_result` / `display_data` / `update_display_data` / `clear_output` / `error` message the execution produced. |
|
|
46
|
+
| `error` | `Error?` | For a failed execution: the message; for an aborted one: `Execution aborted: an earlier execution failed with stopOnError`. |
|
|
47
|
+
| `executionCount` | number? | From the reply; absent for a silent execution. |
|
|
48
|
+
| `status` | `'ok'` \| `'error'` \| `'aborted'`? | The reply's own status. |
|
|
49
|
+
| `aborted` | `true`? | Present when skipped because of an earlier `stopOnError` failure, or when the kernel replied `aborted`. |
|
|
50
|
+
| `userExpressions` | `Record<string, UserExpressionResult>`? | Present when `userExpressions` were requested and evaluated. |
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
type UserExpressionResult =
|
|
54
|
+
| { status: 'ok'; data: Record<string, any>; metadata: Record<string, any> }
|
|
55
|
+
| { status: 'error'; ename: string; evalue: string; traceback: string[] };
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `ExecutionHistoryEntry`
|
|
59
|
+
|
|
60
|
+
`{ code: string; executionCount?: number; time: number; messages: JupyterMessage[]; truncated?: boolean }` — one entry of `Session.getHistory()`. `truncated` is `true` when the execution printed so much that the oldest stream (stdout/stderr) messages were dropped: an entry keeps the newest ~500 000 characters of stream text and everything else in full.
|
|
61
|
+
|
|
62
|
+
### `ShinyAppOptions` / `ShinyAppHandle`
|
|
63
|
+
|
|
64
|
+
`ShinyAppOptions`: `appDir` (required), `port` (default: a free port), `host` (`'127.0.0.1'`), `launchBrowser` (false), `readyTimeout` (10 000 ms), `env` (`Record<string,string>`, applied with `Sys.setenv()` before launch). `ShinyAppHandle`: `{ host, port, url, done: Promise<ExecutionResult> }` — `done` resolves when the app stops.
|
|
65
|
+
|
|
66
|
+
## Session status
|
|
67
|
+
|
|
68
|
+
Returned by `Session.status()`.
|
|
69
|
+
|
|
70
|
+
### `SessionStatusInfo`
|
|
71
|
+
|
|
72
|
+
`{ sessionId: string; status: 'starting' | 'ready' | 'stopped' | 'crashed'; kernelType: 'r' | 'python'; workingDirectory: string; pid: number; memoryBytes: number | null; heartbeat: HeartbeatInfo | null }` — `pid` is `0` when the kernel process is not running; `memoryBytes` is `null` where it can't be read; `workingDirectory` is empty when the session was created without one.
|
|
73
|
+
|
|
74
|
+
### `HeartbeatInfo`
|
|
75
|
+
|
|
76
|
+
`{ hasPong: boolean; rttMs: number; sinceLastPongMs: number; misses: number }` — the supervisor's view of the kernel's heartbeat channel. `hasPong` is `false` until the first ping is answered (`rttMs` and `sinceLastPongMs` are then meaningless); `rttMs` is the round trip of the last answered ping in ms; `sinceLastPongMs` how long ago it arrived; `misses` the number of consecutive unanswered pings. Kept fresh by a kernel thread that is independent of the one running code, so it keeps answering while a cell runs.
|
|
77
|
+
|
|
78
|
+
## Protocol reply contents
|
|
79
|
+
|
|
80
|
+
| Type | Fields |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `CompleteReplyContent` | `status`, `matches: string[]`, `cursor_start`, `cursor_end`, `metadata` |
|
|
83
|
+
| `InspectReplyContent` | `status`, `found: boolean`, `data`, `metadata` |
|
|
84
|
+
| `IsCompleteReplyContent` | `status: 'complete' \| 'incomplete' \| 'invalid' \| 'unknown'`, `indent?` |
|
|
85
|
+
| `KernelInfoReplyContent` | `status`, `protocol_version`, `implementation`, `implementation_version`, `language_info: { name, version, mimetype, file_extension, pygments_lexer?, codemirror_mode?, nbconvert_exporter? }`, `banner`, `help_links?` |
|
|
86
|
+
| `CommInfoReplyContent` | `status`, `comms: Record<string, { target_name: string }>` |
|
|
87
|
+
| `InterruptReplyContent` | `status` |
|
|
88
|
+
| `ShutdownReplyContent` | `status`, `restart: boolean` — the payload of the `'shutdown_reply'` event |
|
|
89
|
+
|
|
90
|
+
### History
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface KernelHistoryOptions {
|
|
94
|
+
histAccessType?: 'tail' | 'range' | 'search'; // default 'tail'
|
|
95
|
+
output?: boolean; // default false (the kernel records no output today)
|
|
96
|
+
raw?: boolean; // default true
|
|
97
|
+
n?: number; // 'tail'/'search'; default 100
|
|
98
|
+
session?: number; start?: number; stop?: number; // 'range'
|
|
99
|
+
pattern?: string; unique?: boolean; // 'search' (glob: * ?)
|
|
100
|
+
}
|
|
101
|
+
type KernelHistoryEntry = [session: number, lineNumber: number, input: string | [input: string, output: string]];
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Messages
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
interface JupyterMessage<T = any> {
|
|
108
|
+
topic: string; // iopub: 'kernel_core.<kernel id>.<msg_type>'; shell/control: the msg_type
|
|
109
|
+
msgType: string;
|
|
110
|
+
channel: 'iopub' | 'shell' | 'stdin' | 'control';
|
|
111
|
+
parentMsgId: string; // the id of the request that caused it ('' for unsolicited messages)
|
|
112
|
+
content: T;
|
|
113
|
+
timestamp: number; // client receive time, ms
|
|
114
|
+
raw: string; // the raw frame text
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Typed `content` interfaces: `StreamContent` (`name: 'stdout'|'stderr'`, `text`), `ExecuteResultContent`, `DisplayDataContent`, `UpdateDisplayDataContent` (`transient?: { display_id? }`), `ClearOutputContent` (`wait`), `ErrorContent` (`ename`, `evalue`, `traceback`), `StatusContent` (`execution_state: 'busy' | 'idle' | 'starting'`), `ExecuteInputContent` (`code`, `execution_count`), `InputRequestContent` (`prompt`, `password`), `CommOpenContent` / `CommMsgContent` / `CommCloseContent`.
|
|
119
|
+
|
|
120
|
+
`ExecutionState` = `'busy' | 'idle' | 'starting'`. `LogLevel` = `'trace' | 'debug' | 'info' | 'warn' | 'error'`.
|