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 CHANGED
@@ -1,12 +1,86 @@
1
- # smolmachines
1
+ # smol (Node SDK)
2
2
 
3
- **Name reserved.** This is a pre-release placeholder (`0.0.1`).
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
- The official **smolmachines** SDK — embed isolated microVM sandboxes directly in
6
- your code, running locally (embedded) or on the smolfleet cloud — ships in the
7
- `0.1.0`+ release.
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
- - Project: https://github.com/smol-machines/smol
10
- - Site: https://smolmachines.com
11
+ Run the **same code** against the local embedded engine or the smolfleet **cloud** —
12
+ the backend is chosen by `ConnectOptions`:
11
13
 
12
- Apache-2.0.
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
@@ -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();
@@ -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
+ }