@indexable/sdk 0.0.2 → 0.0.4

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexable/sdk-linux-x64-gnu",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "os": ["linux"],
5
5
  "cpu": ["x64"],
6
6
  "main": "ix-sdk.linux-x64-gnu.node",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indexable/sdk",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  "typecheck": "bunx tsc --noEmit"
14
14
  },
15
15
  "optionalDependencies": {
16
- "@indexable/sdk-linux-x64-gnu": "0.0.2"
16
+ "@indexable/sdk-linux-x64-gnu": "0.0.4"
17
17
  },
18
18
  "devDependencies": {
19
19
  "bun-types": "latest"
package/src/index.test.ts CHANGED
@@ -38,11 +38,15 @@ describe("Ix", () => {
38
38
 
39
39
  describe("Vm lifecycle", () => {
40
40
  test(
41
- "spawn → getters → stop",
41
+ "spawn → ready → getters → stop",
42
42
  async () => {
43
43
  const ix = new Ix();
44
- const vm = track(await ix.spawn(IMAGE, { name: "sdk-lifecycle" }));
44
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-lifecycle" }));
45
45
 
46
+ // ID is available immediately (client-generated).
47
+ expect(vm.id).toMatch(/^vm_/);
48
+
49
+ await vm.ready();
46
50
  expect(vm.id).toBeTruthy();
47
51
  expect(vm.image).toBe(IMAGE);
48
52
  expect(vm.ipv6).toBeTruthy();
@@ -60,7 +64,7 @@ describe("Vm filesystem", () => {
60
64
  "writeFile → readFile → listDir",
61
65
  async () => {
62
66
  const ix = new Ix();
63
- const vm = track(await ix.spawn(IMAGE, { name: "sdk-fs" }));
67
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-fs" }));
64
68
 
65
69
  const content = `hello from sdk test ${Date.now()}`;
66
70
  await vm.writeFile("/tmp/sdk-test.txt", content);
@@ -76,12 +80,71 @@ describe("Vm filesystem", () => {
76
80
  );
77
81
  });
78
82
 
83
+ describe("Vm exec", () => {
84
+ test(
85
+ "exec → capture stdout and exit code",
86
+ async () => {
87
+ const ix = new Ix();
88
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-exec" }));
89
+
90
+ const result = await vm.exec(["echo", "hello from exec"]);
91
+ expect(result.exitCode).toBe(0);
92
+ expect(result.stdout).toContain("hello from exec");
93
+ expect(result.stderr).toBe("");
94
+ },
95
+ TIMEOUT_MS,
96
+ );
97
+
98
+ test(
99
+ "exec → nonzero exit code",
100
+ async () => {
101
+ const ix = new Ix();
102
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-exec-fail" }));
103
+
104
+ const result = await vm.exec(["sh", "-c", "exit 42"]);
105
+ expect(result.exitCode).toBe(42);
106
+ },
107
+ TIMEOUT_MS,
108
+ );
109
+
110
+ test(
111
+ "exec → capture stderr",
112
+ async () => {
113
+ const ix = new Ix();
114
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-exec-stderr" }));
115
+
116
+ const result = await vm.exec([
117
+ "sh",
118
+ "-c",
119
+ "echo err >&2; echo out",
120
+ ]);
121
+ expect(result.exitCode).toBe(0);
122
+ expect(result.stdout).toContain("out");
123
+ expect(result.stderr).toContain("err");
124
+ },
125
+ TIMEOUT_MS,
126
+ );
127
+
128
+ test(
129
+ "exec → working directory",
130
+ async () => {
131
+ const ix = new Ix();
132
+ const vm = track(ix.spawn(IMAGE, { name: "sdk-exec-cwd" }));
133
+
134
+ const result = await vm.exec(["pwd"], "/tmp");
135
+ expect(result.exitCode).toBe(0);
136
+ expect(result.stdout.trim()).toBe("/tmp");
137
+ },
138
+ TIMEOUT_MS,
139
+ );
140
+ });
141
+
79
142
  describe("Vm version control", () => {
80
143
  test(
81
144
  "fork → write on fork → merge back",
82
145
  async () => {
83
146
  const ix = new Ix();
84
- const main = track(await ix.spawn(IMAGE, { name: "sdk-vcs-main" }));
147
+ const main = track(ix.spawn(IMAGE, { name: "sdk-vcs-main" }));
85
148
 
86
149
  await main.writeFile("/tmp/file-a.txt", "from main");
87
150
 
package/src/index.ts CHANGED
@@ -7,17 +7,9 @@
7
7
  * import Ix from "@indexable/sdk"
8
8
  *
9
9
  * const ix = new Ix()
10
- * const vm = await ix.spawn("ubuntu:24.04")
11
- * console.log(vm.ipv6)
12
- *
13
- * await vm.writeFile("/tmp/hi", "hello")
14
- * const content = await vm.readFile("/tmp/hi")
15
- *
16
- * const fork = await vm.fork("experiment")
17
- * await fork.writeFile("/tmp/new", "from fork")
18
- * await vm.merge(fork)
19
- *
20
- * await vm.stop()
10
+ * const vm = ix.spawn("ubuntu:24.04") // sync — no await
11
+ * console.log(vm.id) // "vm_06e9..." (available immediately)
12
+ * await vm.writeFile("/tmp/hi", "hello") // first await creates the VM on server
21
13
  * ```
22
14
  */
23
15
 
@@ -71,27 +63,46 @@ export interface SecretEntry {
71
63
  createdAt: string;
72
64
  }
73
65
 
66
+ /** Output from executing a command inside a VM. */
67
+ export interface ExecResult {
68
+ /** Process exit code (0 = success). */
69
+ exitCode: number;
70
+ /** Captured stdout. */
71
+ stdout: string;
72
+ /** Captured stderr. */
73
+ stderr: string;
74
+ }
75
+
74
76
  export interface ConnectOptions {
75
77
  server?: string;
76
78
  token?: string;
77
79
  }
78
80
 
79
- /** A virtual machine. */
81
+ /** A virtual machine handle. Getters return `null` until the first awaited operation. */
80
82
  export interface Vm {
81
- /** VM identifier (e.g. "vm_06e9...") */
83
+ /** VM identifier — always available, even before the VM is created on the server. */
82
84
  readonly id: string;
83
- readonly name: string;
84
- readonly image: string;
85
- readonly status: string;
86
- readonly ipv6: string;
85
+ readonly name: string | null;
86
+ readonly image: string | null;
87
+ readonly status: string | null;
88
+ readonly ipv6: string | null;
87
89
  readonly ipv4: string | null;
88
90
  readonly subdomain: string | null;
91
+ readonly startedAt: string | null;
92
+ readonly stoppedAt: string | null;
93
+ readonly failureReason: string | null;
94
+
95
+ /** Explicitly trigger VM creation when you need metadata before any operation. */
96
+ ready(): Promise<void>;
89
97
 
90
98
  stop(): Promise<void>;
91
99
  start(): Promise<void>;
92
100
  restart(): Promise<void>;
93
101
  delete(): Promise<void>;
94
102
 
103
+ /** Execute a command inside the VM and return its output. */
104
+ exec(command: string[], workingDir?: string): Promise<ExecResult>;
105
+
95
106
  writeFile(path: string, content: string): Promise<void>;
96
107
  readFile(path: string): Promise<string>;
97
108
  listDir(path: string): Promise<DirEntry[]>;
@@ -116,8 +127,8 @@ export class Ix {
116
127
  this.inner = new native.Ix(options ?? undefined);
117
128
  }
118
129
 
119
- /** Create a VM from an OCI image. */
120
- async spawn(image: string, options?: SpawnOptions): Promise<Vm> {
130
+ /** Create a VM handle from an OCI image. Returns immediately (sync). */
131
+ spawn(image: string, options?: SpawnOptions): Vm {
121
132
  return this.inner.spawn(image, options ?? undefined);
122
133
  }
123
134