smolmachines 0.0.1 → 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 +82 -8
- package/dist/assets.d.ts +14 -0
- package/dist/assets.js +45 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +53 -0
- package/dist/generated/smolfleet.d.ts +2015 -0
- package/dist/generated/smolfleet.js +6 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +28 -0
- package/dist/machine.d.ts +53 -0
- package/dist/machine.js +100 -0
- package/dist/native.d.ts +86 -0
- package/dist/native.js +20 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.js +478 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.js +4 -0
- package/package.json +63 -6
- package/index.js +0 -6
package/README.md
CHANGED
|
@@ -1,12 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# smol (Node SDK)
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
Embed isolated **microVM sandboxes** directly in your Node.js code — no server to
|
|
4
|
+
run. The smolvm engine is linked in-process via a native addon.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
> **Supported platforms** (native *local* transport): macOS **Apple Silicon**, and
|
|
7
|
+
> **Linux x64/arm64 with glibc ≥ 2.34** (RHEL 9, Ubuntu 22.04+, Debian 12, Amazon
|
|
8
|
+
> Linux 2023). The **cloud** transport works anywhere the package installs.
|
|
9
|
+
> Not yet prebuilt: macOS Intel, and Linux with glibc < 2.34.
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
Run the **same code** against the local embedded engine or the smolfleet **cloud** —
|
|
12
|
+
the backend is chosen by `ConnectOptions`:
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
```ts
|
|
15
|
+
// Local (embedded, default) — no server, no config:
|
|
16
|
+
const local = await Machine.create({ resources: { cpus: 2, memoryMb: 1024 } });
|
|
17
|
+
|
|
18
|
+
// Cloud (smolfleet) — pass an API key, or set SMOL_CLOUD_TOKEN (e.g. via `smol login`):
|
|
19
|
+
const cloud = await Machine.create(
|
|
20
|
+
{ image: 'python:3.12' },
|
|
21
|
+
{ target: 'cloud', apiKey: process.env.SMOL_CLOUD_TOKEN },
|
|
22
|
+
);
|
|
23
|
+
const res = await cloud.exec(['python', '-c', 'print(40 + 2)']);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Cloud-only gaps (`run`, `execStream`, `pullImage`, `listImages`) throw `NotSupportedError`;
|
|
27
|
+
the common surface (create/exec/files/state/stop/delete) is identical on both.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install smolmachines
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Node.js ≥ 18 on a host the engine supports (macOS Apple Silicon, or Linux
|
|
36
|
+
with KVM).
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Machine } from 'smolmachines';
|
|
42
|
+
|
|
43
|
+
const m = await Machine.create({ resources: { cpus: 2, memoryMb: 1024 } });
|
|
44
|
+
try {
|
|
45
|
+
// Run a command in a container image
|
|
46
|
+
const res = await m.run('python:3.12', ['python', '-c', 'print(2 ** 10)']);
|
|
47
|
+
res.assertSuccess();
|
|
48
|
+
console.log(res.stdout); // "1024\n"
|
|
49
|
+
|
|
50
|
+
// Or exec directly in the VM, move files in/out
|
|
51
|
+
await m.writeFile('/tmp/hello.txt', 'hi');
|
|
52
|
+
const back = await m.readFile('/tmp/hello.txt');
|
|
53
|
+
console.log(back.toString()); // "hi"
|
|
54
|
+
} finally {
|
|
55
|
+
await m.delete();
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
- `Machine.create(config?, conn?)` — create and start a machine.
|
|
62
|
+
- `machine.exec(command, opts?)` / `machine.run(image, command, opts?)` → `ExecResult`.
|
|
63
|
+
- `machine.execStream(command, opts?)` → `AsyncGenerator<ExecEvent>`.
|
|
64
|
+
- `machine.readFile(path)` / `machine.writeFile(path, data, mode?)`.
|
|
65
|
+
- `machine.pullImage(image)` / `machine.listImages()`.
|
|
66
|
+
- `machine.stop()` / `machine.delete()` / `await machine.state()` → `"running" | "stopped"`.
|
|
67
|
+
|
|
68
|
+
Errors are typed: `SmolError` (with `.code`), `ExecutionError`, `NotSupportedError`, `InvalidConfigError`.
|
|
69
|
+
|
|
70
|
+
## Building from source
|
|
71
|
+
|
|
72
|
+
This package's native core lives alongside it (Rust, `src/*.rs`) and links the
|
|
73
|
+
sibling `smolvm` repo's engine + `libkrun`. From this directory:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install
|
|
77
|
+
npm run build # napi build (native) + tsc (types) + bundle
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The native build needs the Rust toolchain, `@napi-rs/cli`, and `libkrun` available
|
|
81
|
+
in the `smolvm` repo's `lib/` (this package expects the `smolvm` repo checked out
|
|
82
|
+
three levels up).
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
Apache-2.0
|
package/dist/assets.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Auto-wiring for bundled native assets.
|
|
2
|
+
*
|
|
3
|
+
* Points the engine at the package's bundled, signed boot helper and hypervisor
|
|
4
|
+
* libraries so the SDK works on a plain `node` with no manual env setup:
|
|
5
|
+
* - SMOLVM_BOOT_BINARY → bundled `smol-vmm` helper (handles `_boot-vm`; on
|
|
6
|
+
* macOS codesigned with `com.apple.security.hypervisor`, so the user's
|
|
7
|
+
* `node` needs no entitlement).
|
|
8
|
+
* - SMOLVM_LIB_DIR → the dir holding libkrun/libkrunfw.
|
|
9
|
+
*
|
|
10
|
+
* A user-provided value always wins. Exposed as a function (and self-invoked)
|
|
11
|
+
* so it runs reliably regardless of import elision/ordering — `native.ts` calls
|
|
12
|
+
* it before loading the addon.
|
|
13
|
+
*/
|
|
14
|
+
export declare function wireBundledAssets(): void;
|
package/dist/assets.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Auto-wiring for bundled native assets.
|
|
3
|
+
*
|
|
4
|
+
* Points the engine at the package's bundled, signed boot helper and hypervisor
|
|
5
|
+
* libraries so the SDK works on a plain `node` with no manual env setup:
|
|
6
|
+
* - SMOLVM_BOOT_BINARY → bundled `smol-vmm` helper (handles `_boot-vm`; on
|
|
7
|
+
* macOS codesigned with `com.apple.security.hypervisor`, so the user's
|
|
8
|
+
* `node` needs no entitlement).
|
|
9
|
+
* - SMOLVM_LIB_DIR → the dir holding libkrun/libkrunfw.
|
|
10
|
+
*
|
|
11
|
+
* A user-provided value always wins. Exposed as a function (and self-invoked)
|
|
12
|
+
* so it runs reliably regardless of import elision/ordering — `native.ts` calls
|
|
13
|
+
* it before loading the addon.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.wireBundledAssets = wireBundledAssets;
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
let wired = false;
|
|
20
|
+
function wireBundledAssets() {
|
|
21
|
+
if (wired)
|
|
22
|
+
return;
|
|
23
|
+
wired = true;
|
|
24
|
+
const platformArch = `${process.platform}-${process.arch}`;
|
|
25
|
+
const helperName = process.platform === 'win32' ? 'smol-vmm.exe' : 'smol-vmm';
|
|
26
|
+
// `__dirname` is the package root from source (tsx) and `dist/` when built —
|
|
27
|
+
// check both layouts.
|
|
28
|
+
const candidates = [
|
|
29
|
+
(0, node_path_1.join)(__dirname, 'native', platformArch),
|
|
30
|
+
(0, node_path_1.join)(__dirname, '..', 'native', platformArch),
|
|
31
|
+
];
|
|
32
|
+
for (const nativeDir of candidates) {
|
|
33
|
+
if (!(0, node_fs_1.existsSync)(nativeDir))
|
|
34
|
+
continue;
|
|
35
|
+
const helper = (0, node_path_1.join)(nativeDir, helperName);
|
|
36
|
+
if (!process.env.SMOLVM_BOOT_BINARY && (0, node_fs_1.existsSync)(helper)) {
|
|
37
|
+
process.env.SMOLVM_BOOT_BINARY = helper;
|
|
38
|
+
}
|
|
39
|
+
if (!process.env.SMOLVM_LIB_DIR) {
|
|
40
|
+
process.env.SMOLVM_LIB_DIR = nativeDir;
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
wireBundledAssets();
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Typed errors for the `smol` SDK.
|
|
2
|
+
*
|
|
3
|
+
* The native addon reports errors as `Error` objects whose message is
|
|
4
|
+
* prefixed with a bracketed code, e.g. `"[KVM_UNAVAILABLE] …"`. We parse that
|
|
5
|
+
* back into a typed hierarchy so callers can branch on `err.code` /`instanceof`.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SmolError extends Error {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
constructor(code: string, message: string);
|
|
10
|
+
}
|
|
11
|
+
/** The active backend can't serve this operation (e.g. volumes on local). */
|
|
12
|
+
export declare class NotSupportedError extends SmolError {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/** A required configuration value is missing or invalid (a usage error). */
|
|
16
|
+
export declare class InvalidConfigError extends SmolError {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
/** A command ran but exited non-zero (raised by `ExecResult.assertSuccess()`). */
|
|
20
|
+
export declare class ExecutionError extends SmolError {
|
|
21
|
+
readonly exitCode: number;
|
|
22
|
+
readonly stdout: string;
|
|
23
|
+
readonly stderr: string;
|
|
24
|
+
constructor(exitCode: number, stdout: string, stderr: string);
|
|
25
|
+
}
|
|
26
|
+
/** Convert any error thrown by the native addon into a typed `SmolError`. */
|
|
27
|
+
export declare function wrapNativeError(err: unknown): SmolError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Typed errors for the `smol` SDK.
|
|
3
|
+
*
|
|
4
|
+
* The native addon reports errors as `Error` objects whose message is
|
|
5
|
+
* prefixed with a bracketed code, e.g. `"[KVM_UNAVAILABLE] …"`. We parse that
|
|
6
|
+
* back into a typed hierarchy so callers can branch on `err.code` /`instanceof`.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ExecutionError = exports.InvalidConfigError = exports.NotSupportedError = exports.SmolError = void 0;
|
|
10
|
+
exports.wrapNativeError = wrapNativeError;
|
|
11
|
+
class SmolError extends Error {
|
|
12
|
+
constructor(code, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = new.target.name;
|
|
15
|
+
this.code = code;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.SmolError = SmolError;
|
|
19
|
+
/** The active backend can't serve this operation (e.g. volumes on local). */
|
|
20
|
+
class NotSupportedError extends SmolError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super('NOT_SUPPORTED', message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.NotSupportedError = NotSupportedError;
|
|
26
|
+
/** A required configuration value is missing or invalid (a usage error). */
|
|
27
|
+
class InvalidConfigError extends SmolError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super('INVALID_CONFIG', message);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.InvalidConfigError = InvalidConfigError;
|
|
33
|
+
/** A command ran but exited non-zero (raised by `ExecResult.assertSuccess()`). */
|
|
34
|
+
class ExecutionError extends SmolError {
|
|
35
|
+
constructor(exitCode, stdout, stderr) {
|
|
36
|
+
super('COMMAND_FAILED', `Command exited with code ${exitCode}`);
|
|
37
|
+
this.exitCode = exitCode;
|
|
38
|
+
this.stdout = stdout;
|
|
39
|
+
this.stderr = stderr;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.ExecutionError = ExecutionError;
|
|
43
|
+
const BRACKETED = /^\[([A-Z_]+)\]\s*(.*)$/s;
|
|
44
|
+
/** Convert any error thrown by the native addon into a typed `SmolError`. */
|
|
45
|
+
function wrapNativeError(err) {
|
|
46
|
+
if (err instanceof SmolError)
|
|
47
|
+
return err;
|
|
48
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
+
const m = BRACKETED.exec(message);
|
|
50
|
+
if (m)
|
|
51
|
+
return new SmolError(m[1], m[2]);
|
|
52
|
+
return new SmolError('SMOLVM_ERROR', message);
|
|
53
|
+
}
|