@capsulos/provider-lambda 0.1.0 → 0.1.2

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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type LambdaMicrovmsClientConfig } from '@aws-sdk/client-lambda-microvms';
2
- import { type CapsuleHandle, type CapsuleInfo, type CapsuleSpec, type Connection, type Provider } from '@capsulos/sdk';
2
+ import { type CapsuleHandle, type CapsuleInfo, type CapsuleSpec, type Connection, type CreateOptions, type HeadersOptions, type Provider } from '@capsulos/sdk';
3
3
  export interface LambdaProviderOptions {
4
4
  region: string;
5
5
  /** AWS account id, used to build image ARNs from template names. */
@@ -26,14 +26,33 @@ export declare class LambdaProvider implements Provider {
26
26
  constructor(opts: LambdaProviderOptions);
27
27
  /** Templates are MicrovmImages; the API wants the full ARN (a plain name fails). */
28
28
  imageArn(template: string): string;
29
- create(spec: CapsuleSpec): Promise<CapsuleHandle>;
29
+ create(spec: CapsuleSpec, opts?: CreateOptions): Promise<CapsuleHandle>;
30
30
  get(id: string): Promise<CapsuleInfo | null>;
31
31
  /** All microVMs of one template (the API lists per image). */
32
32
  list(template?: string): Promise<CapsuleInfo[]>;
33
33
  terminate(id: string): Promise<void>;
34
34
  connect(id: string): Promise<Connection>;
35
+ /**
36
+ * Did this call's RunMicrovm launch the VM, or did its clientToken hand
37
+ * back one launched earlier (idempotency/recovery)? The API does not say,
38
+ * so: no clientToken → ours; otherwise ours when the VM started after we
39
+ * asked (with a minute of slack for clock skew). Unknown → not ours, which
40
+ * errs on the side of leaving a VM alive.
41
+ */
42
+ private launchedBy;
43
+ /** A Connection from an endpoint already in hand (get() returns it): no extra GetMicrovm. */
44
+ connectionFor(id: string, endpoint: string): Connection;
35
45
  /** Headers to reach another port of the capsule through the same endpoint. */
36
- portHeaders(id: string, port: number): Promise<Record<string, string>>;
46
+ portHeaders(id: string, port: number, opts?: HeadersOptions): Promise<Record<string, string>>;
47
+ /** Forget the cached endpoint token: the next request mints a new one. */
48
+ invalidateToken(id: string): void;
49
+ private infoOf;
37
50
  private token;
51
+ /**
52
+ * Poll until the VM runs. A SUSPENDED VM (a clientToken that recovered an
53
+ * idle one) counts as alive: the first request auto-resumes it. Timeout and
54
+ * abort throw a CapsuleError that names the VM (capsuleId) for the caller
55
+ * to clean up; create() does that itself.
56
+ */
38
57
  private waitForRunning;
39
58
  }
package/dist/index.js CHANGED
@@ -8,7 +8,8 @@
8
8
  // - endpoint tokens expire after at most 60 min: cached in memory, re-minted
9
9
  // - other ports are addressed with the X-aws-proxy-port header
10
10
  // - the idle policy suspends and the first request auto-resumes (~0.8 s)
11
- // - clientToken is both idempotency and recovery (same token → same VM)
11
+ // - clientToken is both idempotency and recovery (same token → same VM);
12
+ // a recovered VM may be SUSPENDED, which create() accepts as alive
12
13
  import { LambdaMicrovmsClient, RunMicrovmCommand, GetMicrovmCommand, ListMicrovmsCommand, TerminateMicrovmCommand, CreateMicrovmAuthTokenCommand, } from '@aws-sdk/client-lambda-microvms';
13
14
  import { CapsuleError, } from '@capsulos/sdk';
14
15
  const CAPSULOD_PORT = 8080;
@@ -46,7 +47,8 @@ export class LambdaProvider {
46
47
  return template;
47
48
  return `arn:aws:lambda:${this.opts.region}:${this.opts.accountId}:microvm-image:${template}`;
48
49
  }
49
- async create(spec) {
50
+ async create(spec, opts = {}) {
51
+ const t0 = Date.now();
50
52
  const ingress = this.opts.ingressConnectorArn ??
51
53
  `arn:aws:lambda:${this.opts.region}:aws:network-connector:aws-network-connector:ALL_INGRESS`;
52
54
  const res = await this.client.send(new RunMicrovmCommand({
@@ -70,21 +72,35 @@ export class LambdaProvider {
70
72
  }));
71
73
  if (!res.microvmId)
72
74
  throw new CapsuleError('RunMicrovm returned no microvmId');
73
- const info = await this.waitForRunning(res.microvmId);
74
- return { ...info, connection: await this.connect(res.microvmId) };
75
+ const id = res.microvmId;
76
+ let info;
77
+ try {
78
+ info = await this.waitForRunning(id, opts);
79
+ }
80
+ catch (err) {
81
+ // Never leave a VM running without an owner: the caller gets an error,
82
+ // not a capsule, so the VM goes too (the error still names it). Unless
83
+ // the clientToken recovered a VM someone else launched: that one has
84
+ // an owner, so it stays and the error says so.
85
+ if (err instanceof CapsuleError && err.capsuleId === id) {
86
+ if (await this.launchedBy(id, t0, spec.clientToken)) {
87
+ await this.terminate(id).catch(() => undefined);
88
+ throw new CapsuleError(`${err.message}; terminated it`, err.status, { capsuleId: id });
89
+ }
90
+ throw new CapsuleError(`${err.message}; left it running (recovered through clientToken, it has another owner)`, err.status, {
91
+ capsuleId: id,
92
+ });
93
+ }
94
+ throw err;
95
+ }
96
+ if (!info.endpoint)
97
+ throw new CapsuleError(`microVM ${id} is ${info.state} but has no endpoint`, undefined, { capsuleId: id });
98
+ return { ...info, connection: this.connectionFor(id, info.endpoint) };
75
99
  }
76
100
  async get(id) {
77
101
  try {
78
102
  const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
79
- return {
80
- id,
81
- provider: this.name,
82
- state: toState(vm.state),
83
- template: vm.imageArn ?? null,
84
- createdAt: vm.startedAt ?? null,
85
- terminatedAt: vm.terminatedAt ?? null,
86
- stateReason: vm.stateReason ?? null,
87
- };
103
+ return this.infoOf(id, vm);
88
104
  }
89
105
  catch (err) {
90
106
  if (err instanceof Error && err.name === 'ResourceNotFoundException')
@@ -103,11 +119,13 @@ export class LambdaProvider {
103
119
  for (const item of page.items ?? []) {
104
120
  if (!item.microvmId)
105
121
  continue;
122
+ // The list item carries no endpoint, terminatedAt or stateReason: get() does.
106
123
  out.push({
107
124
  id: item.microvmId,
108
125
  provider: this.name,
109
126
  state: toState(item.state),
110
127
  template: item.imageArn ?? null,
128
+ templateVersion: item.imageVersion ?? null,
111
129
  createdAt: item.startedAt ?? null,
112
130
  });
113
131
  }
@@ -131,19 +149,59 @@ export class LambdaProvider {
131
149
  async connect(id) {
132
150
  const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
133
151
  if (!vm.endpoint)
134
- throw new CapsuleError(`microVM ${id} has no endpoint yet (state ${vm.state})`);
152
+ throw new CapsuleError(`microVM ${id} has no endpoint yet (state ${vm.state})`, undefined, { capsuleId: id });
153
+ return this.connectionFor(id, vm.endpoint);
154
+ }
155
+ /**
156
+ * Did this call's RunMicrovm launch the VM, or did its clientToken hand
157
+ * back one launched earlier (idempotency/recovery)? The API does not say,
158
+ * so: no clientToken → ours; otherwise ours when the VM started after we
159
+ * asked (with a minute of slack for clock skew). Unknown → not ours, which
160
+ * errs on the side of leaving a VM alive.
161
+ */
162
+ async launchedBy(id, t0, clientToken) {
163
+ if (!clientToken)
164
+ return true;
165
+ try {
166
+ const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
167
+ return vm.startedAt !== undefined && vm.startedAt.getTime() >= t0 - 60_000;
168
+ }
169
+ catch {
170
+ return false;
171
+ }
172
+ }
173
+ /** A Connection from an endpoint already in hand (get() returns it): no extra GetMicrovm. */
174
+ connectionFor(id, endpoint) {
135
175
  return {
136
- baseUrl: `https://${vm.endpoint}`,
137
- headers: async () => ({ 'X-aws-proxy-auth': await this.token(id) }),
176
+ baseUrl: endpoint.startsWith('https://') ? endpoint : `https://${endpoint}`,
177
+ headers: async (opts) => ({ 'X-aws-proxy-auth': await this.token(id, opts?.fresh) }),
138
178
  };
139
179
  }
140
180
  /** Headers to reach another port of the capsule through the same endpoint. */
141
- async portHeaders(id, port) {
142
- return { 'X-aws-proxy-auth': await this.token(id), 'X-aws-proxy-port': String(port) };
181
+ async portHeaders(id, port, opts) {
182
+ return { 'X-aws-proxy-auth': await this.token(id, opts?.fresh), 'X-aws-proxy-port': String(port) };
183
+ }
184
+ /** Forget the cached endpoint token: the next request mints a new one. */
185
+ invalidateToken(id) {
186
+ this.tokens.delete(id);
143
187
  }
144
- async token(id) {
188
+ infoOf(id, vm) {
189
+ return {
190
+ id,
191
+ provider: this.name,
192
+ state: toState(vm.state),
193
+ template: vm.imageArn ?? null,
194
+ templateVersion: vm.imageVersion ?? null,
195
+ createdAt: vm.startedAt ?? null,
196
+ terminatedAt: vm.terminatedAt ?? null,
197
+ stateReason: vm.stateReason ?? null,
198
+ endpoint: vm.endpoint ? `https://${vm.endpoint}` : null,
199
+ maxDurationSeconds: vm.maximumDurationInSeconds ?? null,
200
+ };
201
+ }
202
+ async token(id, fresh = false) {
145
203
  const cached = this.tokens.get(id);
146
- if (cached && cached.expiresAt > Date.now())
204
+ if (cached && cached.expiresAt > Date.now() && !fresh)
147
205
  return cached.value;
148
206
  const ttl = Math.min(this.opts.tokenTtlMinutes ?? 60, 60);
149
207
  const ports = new Set([CAPSULOD_PORT, ...(this.opts.allowedPorts ?? [])]);
@@ -159,18 +217,27 @@ export class LambdaProvider {
159
217
  this.tokens.set(id, { value, expiresAt: Date.now() + (ttl - 5) * 60_000 });
160
218
  return value;
161
219
  }
162
- async waitForRunning(id, timeoutMs = 120_000) {
220
+ /**
221
+ * Poll until the VM runs. A SUSPENDED VM (a clientToken that recovered an
222
+ * idle one) counts as alive: the first request auto-resumes it. Timeout and
223
+ * abort throw a CapsuleError that names the VM (capsuleId) for the caller
224
+ * to clean up; create() does that itself.
225
+ */
226
+ async waitForRunning(id, opts = {}) {
227
+ const timeoutMs = opts.timeoutMs ?? 120_000;
163
228
  const t0 = Date.now();
164
229
  for (;;) {
165
- const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
166
- if (vm.state === 'RUNNING') {
167
- return { id, provider: this.name, state: 'running', template: vm.imageArn ?? null, createdAt: vm.startedAt ?? null };
168
- }
230
+ if (opts.signal?.aborted)
231
+ throw new CapsuleError(`creation of microVM ${id} aborted`, undefined, { capsuleId: id });
232
+ const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }), { abortSignal: opts.signal });
233
+ if (vm.state === 'RUNNING' || vm.state === 'SUSPENDED')
234
+ return this.infoOf(id, vm);
169
235
  if (vm.state === 'TERMINATED' || vm.state === 'TERMINATING') {
170
- throw new CapsuleError(`microVM ${id} died while starting: ${vm.stateReason ?? vm.state}`);
236
+ throw new CapsuleError(`microVM ${id} died while starting: ${vm.stateReason ?? vm.state}`, undefined, { capsuleId: id });
237
+ }
238
+ if (Date.now() - t0 > timeoutMs) {
239
+ throw new CapsuleError(`microVM ${id} did not run within ${timeoutMs}ms (state ${vm.state})`, undefined, { capsuleId: id });
171
240
  }
172
- if (Date.now() - t0 > timeoutMs)
173
- throw new CapsuleError(`timeout waiting for microVM ${id} to run`);
174
241
  await new Promise((r) => setTimeout(r, 400));
175
242
  }
176
243
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,yEAAyE;AACzE,EAAE;AACF,8DAA8D;AAC9D,gFAAgF;AAChF,8EAA8E;AAC9E,+EAA+E;AAC/E,iEAAiE;AACjE,2EAA2E;AAC3E,2EAA2E;AAC3E,uEAAuE;AACvE,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,uBAAuB,EACvB,6BAA6B,GAE9B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EACL,YAAY,GASb,MAAM,eAAe,CAAC;AAqBvB,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B,SAAS,OAAO,CAAC,CAAqB;IACpC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,aAAa,CAAC;QACnB,KAAK,YAAY;YACf,OAAO,YAAY,CAAC;QACtB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,cAAc;IAKI;IAJpB,IAAI,GAAG,QAAQ,CAAC;IACR,MAAM,CAAuB;IAC7B,MAAM,GAAG,IAAI,GAAG,EAAgD,CAAC;IAElF,YAA6B,IAA2B;QAA3B,SAAI,GAAJ,IAAI,CAAuB;QACtD,IAAI,CAAC,MAAM,GAAG,IAAI,oBAAoB,CAAC;YACrC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/D,CAAC,CAAC;IACL,CAAC;IAED,oFAAoF;IACpF,QAAQ,CAAC,QAAgB;QACvB,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC;YAAE,OAAO,QAAQ,CAAC;QACjD,OAAO,kBAAkB,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,kBAAkB,QAAQ,EAAE,CAAC;IAC/F,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAiB,EAAE,OAAsB,EAAE;QACtD,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,OAAO,GACX,IAAI,CAAC,IAAI,CAAC,mBAAmB;YAC7B,kBAAkB,IAAI,CAAC,IAAI,CAAC,MAAM,0DAA0D,CAAC;QAC/F,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAChC,IAAI,iBAAiB,CAAC;YACpB,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC7C,wBAAwB,EAAE,CAAC,OAAO,CAAC;YACnC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,uBAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvF,GAAG,CAAC,IAAI,CAAC,IAAI;gBACX,CAAC,CAAC;oBACE,UAAU,EAAE;wBACV,sBAAsB,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB;wBACrD,wBAAwB,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB;wBACvD,iBAAiB,EAAE,IAAI;qBACxB;iBACF;gBACH,CAAC,CAAC,EAAE,CAAC;YACP,wBAAwB,EAAE,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,kBAAkB,IAAI,CAAC,GAAG,IAAI;YAC7F,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,+EAA+E;YAC/E,cAAc,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;SAC7F,CAAC,CACH,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,SAAS;YAAE,MAAM,IAAI,YAAY,CAAC,kCAAkC,CAAC,CAAC;QAC/E,MAAM,EAAE,GAAG,GAAG,CAAC,SAAS,CAAC;QACzB,IAAI,IAAiB,CAAC;QACtB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uEAAuE;YACvE,uEAAuE;YACvE,qEAAqE;YACrE,+CAA+C;YAC/C,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,SAAS,KAAK,EAAE,EAAE,CAAC;gBACxD,IAAI,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;oBACpD,MAAM,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;oBAChD,MAAM,IAAI,YAAY,CAAC,GAAG,GAAG,CAAC,OAAO,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;gBACzF,CAAC;gBACD,MAAM,IAAI,YAAY,CAAC,GAAG,GAAG,CAAC,OAAO,yEAAyE,EAAE,GAAG,CAAC,MAAM,EAAE;oBAC1H,SAAS,EAAE,EAAE;iBACd,CAAC,CAAC;YACL,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,OAAO,IAAI,CAAC,KAAK,sBAAsB,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/H,OAAO,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;IACxE,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,EAAU;QAClB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACpF,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,2BAA2B;gBAAE,OAAO,IAAI,CAAC;YAClF,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,IAAI,CAAC,QAAiB;QAC1B,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,YAAY,CAAC,wEAAwE,CAAC,CAAC;QAChH,MAAM,GAAG,GAAkB,EAAE,CAAC;QAC9B,IAAI,SAA6B,CAAC;QAClC,GAAG,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACjC,IAAI,mBAAmB,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC,CACjF,CAAC;YACF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;gBACpC,IAAI,CAAC,IAAI,CAAC,SAAS;oBAAE,SAAS;gBAC9B,8EAA8E;gBAC9E,GAAG,CAAC,IAAI,CAAC;oBACP,EAAE,EAAE,IAAI,CAAC,SAAS;oBAClB,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;oBAC1B,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;oBAC/B,eAAe,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI;oBAC1C,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI;iBAClC,CAAC,CAAC;YACL,CAAC;YACD,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,CAAC,QAAQ,SAAS,EAAE;QACpB,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,EAAU;QACxB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,uBAAuB,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACjF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,2BAA2B;gBAAE,OAAO;YAC7E,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QACpF,IAAI,CAAC,EAAE,CAAC,QAAQ;YAAE,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,+BAA+B,EAAE,CAAC,KAAK,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;QAChI,OAAO,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,UAAU,CAAC,EAAU,EAAE,EAAU,EAAE,WAAoB;QACnE,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YACpF,OAAO,EAAE,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,6FAA6F;IAC7F,aAAa,CAAC,EAAU,EAAE,QAAgB;QACxC,OAAO;YACL,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,QAAQ,EAAE;YAC3E,OAAO,EAAE,KAAK,EAAE,IAAqB,EAAE,EAAE,CAAC,CAAC,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;SACtG,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,IAAY,EAAE,IAAqB;QAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,kBAAkB,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IACrG,CAAC;IAED,0EAA0E;IAC1E,eAAe,CAAC,EAAU;QACxB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAEO,MAAM,CAAC,EAAU,EAAE,EAAmL;QAC5M,OAAO;YACL,EAAE;YACF,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC;YACxB,QAAQ,EAAE,EAAE,CAAC,QAAQ,IAAI,IAAI;YAC7B,eAAe,EAAE,EAAE,CAAC,YAAY,IAAI,IAAI;YACxC,SAAS,EAAE,EAAE,CAAC,SAAS,IAAI,IAAI;YAC/B,YAAY,EAAE,EAAE,CAAC,YAAY,IAAI,IAAI;YACrC,WAAW,EAAE,EAAE,CAAC,WAAW,IAAI,IAAI;YACnC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI;YACvD,kBAAkB,EAAE,EAAE,CAAC,wBAAwB,IAAI,IAAI;SACxD,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,EAAU,EAAE,KAAK,GAAG,KAAK;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,IAAI,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK;YAAE,OAAO,MAAM,CAAC,KAAK,CAAC;QAC3E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAChC,IAAI,6BAA6B,CAAC;YAChC,iBAAiB,EAAE,EAAE;YACrB,mBAAmB,EAAE,GAAG;YACxB,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SACnD,CAAC,CACH,CAAC;QACF,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,kBAAkB,CAAC,CAAC;QAClD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,YAAY,CAAC,qDAAqD,CAAC,CAAC;QAC1F,iEAAiE;QACjE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,CAAC,CAAC;QAC3E,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;OAKG;IACK,KAAK,CAAC,cAAc,CAAC,EAAU,EAAE,OAAsB,EAAE;QAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,SAAS,CAAC;YACR,IAAI,IAAI,CAAC,MAAM,EAAE,OAAO;gBAAE,MAAM,IAAI,YAAY,CAAC,uBAAuB,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YACpH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAClH,IAAI,EAAE,CAAC,KAAK,KAAK,SAAS,IAAI,EAAE,CAAC,KAAK,KAAK,WAAW;gBAAE,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YACnF,IAAI,EAAE,CAAC,KAAK,KAAK,YAAY,IAAI,EAAE,CAAC,KAAK,KAAK,aAAa,EAAE,CAAC;gBAC5D,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,yBAAyB,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAC3H,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC;gBAChC,MAAM,IAAI,YAAY,CAAC,WAAW,EAAE,uBAAuB,SAAS,aAAa,EAAE,CAAC,KAAK,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;YAC9H,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capsulos/provider-lambda",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "capsulos provider for AWS Lambda MicroVMs: capsules on your own AWS account.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@aws-sdk/client-lambda-microvms": "^3.1158.0",
47
- "@capsulos/sdk": "0.1.0"
47
+ "@capsulos/sdk": "0.1.2"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@types/node": "^22.20.1",
@@ -0,0 +1,60 @@
1
+ // create(): what happens to the VM when the wait for RUNNING fails. The AWS
2
+ // client is stubbed: these tests are about ownership, not about the service.
3
+ import { describe, expect, it } from 'vitest';
4
+ import { CapsuleError } from '@capsulos/sdk';
5
+ import { LambdaProvider } from './index.js';
6
+
7
+ type Sent = { name: string; input: Record<string, unknown> };
8
+
9
+ function provider(vm: { state: string; startedAt?: Date }) {
10
+ const sent: Sent[] = [];
11
+ const p = new LambdaProvider({ region: 'us-east-1', accountId: '123' });
12
+ (p as unknown as { client: { send: (cmd: { constructor: { name: string }; input: Record<string, unknown> }) => Promise<unknown> } }).client = {
13
+ async send(cmd) {
14
+ sent.push({ name: cmd.constructor.name, input: cmd.input });
15
+ switch (cmd.constructor.name) {
16
+ case 'RunMicrovmCommand':
17
+ return { microvmId: 'mvm-1' };
18
+ case 'GetMicrovmCommand':
19
+ return { microvmId: 'mvm-1', state: vm.state, startedAt: vm.startedAt, endpoint: 'x.on.aws' };
20
+ case 'TerminateMicrovmCommand':
21
+ return {};
22
+ default:
23
+ throw new Error(`unexpected ${cmd.constructor.name}`);
24
+ }
25
+ },
26
+ };
27
+ return { p, sent, terminated: () => sent.filter((s) => s.name === 'TerminateMicrovmCommand').length };
28
+ }
29
+
30
+ describe('LambdaProvider.create on timeout', () => {
31
+ it('terminates a VM it launched itself and names it in the error', async () => {
32
+ const { p, terminated } = provider({ state: 'PENDING', startedAt: new Date() });
33
+ const err = await p.create({ template: 't' }, { timeoutMs: 1 }).catch((e: unknown) => e);
34
+ expect(err).toBeInstanceOf(CapsuleError);
35
+ expect((err as CapsuleError).capsuleId).toBe('mvm-1');
36
+ expect((err as CapsuleError).message).toMatch(/terminated it/);
37
+ expect(terminated()).toBe(1);
38
+ });
39
+
40
+ it('leaves alone a VM that the clientToken recovered from an earlier launch', async () => {
41
+ const { p, terminated } = provider({ state: 'PENDING', startedAt: new Date(Date.now() - 10 * 60_000) });
42
+ const err = await p.create({ template: 't', clientToken: 'row-1' }, { timeoutMs: 1 }).catch((e: unknown) => e);
43
+ expect((err as CapsuleError).message).toMatch(/left it running/);
44
+ expect((err as CapsuleError).capsuleId).toBe('mvm-1');
45
+ expect(terminated()).toBe(0);
46
+ });
47
+
48
+ it('with a clientToken, a VM that started after the call is still ours', async () => {
49
+ const { p, terminated } = provider({ state: 'PENDING', startedAt: new Date(Date.now() + 1000) });
50
+ await p.create({ template: 't', clientToken: 'row-1' }, { timeoutMs: 1 }).catch(() => undefined);
51
+ expect(terminated()).toBe(1);
52
+ });
53
+
54
+ it('accepts a SUSPENDED VM as alive', async () => {
55
+ const { p } = provider({ state: 'SUSPENDED', startedAt: new Date() });
56
+ const handle = await p.create({ template: 't', clientToken: 'row-1' });
57
+ expect(handle.state).toBe('suspended');
58
+ expect(handle.connection.baseUrl).toBe('https://x.on.aws');
59
+ });
60
+ });
package/src/index.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  // - endpoint tokens expire after at most 60 min: cached in memory, re-minted
9
9
  // - other ports are addressed with the X-aws-proxy-port header
10
10
  // - the idle policy suspends and the first request auto-resumes (~0.8 s)
11
- // - clientToken is both idempotency and recovery (same token → same VM)
11
+ // - clientToken is both idempotency and recovery (same token → same VM);
12
+ // a recovered VM may be SUSPENDED, which create() accepts as alive
12
13
  import {
13
14
  LambdaMicrovmsClient,
14
15
  RunMicrovmCommand,
@@ -25,6 +26,8 @@ import {
25
26
  type CapsuleSpec,
26
27
  type CapsuleState,
27
28
  type Connection,
29
+ type CreateOptions,
30
+ type HeadersOptions,
28
31
  type Provider,
29
32
  } from '@capsulos/sdk';
30
33
 
@@ -84,7 +87,8 @@ export class LambdaProvider implements Provider {
84
87
  return `arn:aws:lambda:${this.opts.region}:${this.opts.accountId}:microvm-image:${template}`;
85
88
  }
86
89
 
87
- async create(spec: CapsuleSpec): Promise<CapsuleHandle> {
90
+ async create(spec: CapsuleSpec, opts: CreateOptions = {}): Promise<CapsuleHandle> {
91
+ const t0 = Date.now();
88
92
  const ingress =
89
93
  this.opts.ingressConnectorArn ??
90
94
  `arn:aws:lambda:${this.opts.region}:aws:network-connector:aws-network-connector:ALL_INGRESS`;
@@ -110,22 +114,34 @@ export class LambdaProvider implements Provider {
110
114
  }),
111
115
  );
112
116
  if (!res.microvmId) throw new CapsuleError('RunMicrovm returned no microvmId');
113
- const info = await this.waitForRunning(res.microvmId);
114
- return { ...info, connection: await this.connect(res.microvmId) };
117
+ const id = res.microvmId;
118
+ let info: CapsuleInfo;
119
+ try {
120
+ info = await this.waitForRunning(id, opts);
121
+ } catch (err) {
122
+ // Never leave a VM running without an owner: the caller gets an error,
123
+ // not a capsule, so the VM goes too (the error still names it). Unless
124
+ // the clientToken recovered a VM someone else launched: that one has
125
+ // an owner, so it stays and the error says so.
126
+ if (err instanceof CapsuleError && err.capsuleId === id) {
127
+ if (await this.launchedBy(id, t0, spec.clientToken)) {
128
+ await this.terminate(id).catch(() => undefined);
129
+ throw new CapsuleError(`${err.message}; terminated it`, err.status, { capsuleId: id });
130
+ }
131
+ throw new CapsuleError(`${err.message}; left it running (recovered through clientToken, it has another owner)`, err.status, {
132
+ capsuleId: id,
133
+ });
134
+ }
135
+ throw err;
136
+ }
137
+ if (!info.endpoint) throw new CapsuleError(`microVM ${id} is ${info.state} but has no endpoint`, undefined, { capsuleId: id });
138
+ return { ...info, connection: this.connectionFor(id, info.endpoint) };
115
139
  }
116
140
 
117
141
  async get(id: string): Promise<CapsuleInfo | null> {
118
142
  try {
119
143
  const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
120
- return {
121
- id,
122
- provider: this.name,
123
- state: toState(vm.state),
124
- template: vm.imageArn ?? null,
125
- createdAt: vm.startedAt ?? null,
126
- terminatedAt: vm.terminatedAt ?? null,
127
- stateReason: vm.stateReason ?? null,
128
- };
144
+ return this.infoOf(id, vm);
129
145
  } catch (err) {
130
146
  if (err instanceof Error && err.name === 'ResourceNotFoundException') return null;
131
147
  throw err;
@@ -143,11 +159,13 @@ export class LambdaProvider implements Provider {
143
159
  );
144
160
  for (const item of page.items ?? []) {
145
161
  if (!item.microvmId) continue;
162
+ // The list item carries no endpoint, terminatedAt or stateReason: get() does.
146
163
  out.push({
147
164
  id: item.microvmId,
148
165
  provider: this.name,
149
166
  state: toState(item.state),
150
167
  template: item.imageArn ?? null,
168
+ templateVersion: item.imageVersion ?? null,
151
169
  createdAt: item.startedAt ?? null,
152
170
  });
153
171
  }
@@ -169,21 +187,63 @@ export class LambdaProvider implements Provider {
169
187
 
170
188
  async connect(id: string): Promise<Connection> {
171
189
  const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
172
- if (!vm.endpoint) throw new CapsuleError(`microVM ${id} has no endpoint yet (state ${vm.state})`);
190
+ if (!vm.endpoint) throw new CapsuleError(`microVM ${id} has no endpoint yet (state ${vm.state})`, undefined, { capsuleId: id });
191
+ return this.connectionFor(id, vm.endpoint);
192
+ }
193
+
194
+ /**
195
+ * Did this call's RunMicrovm launch the VM, or did its clientToken hand
196
+ * back one launched earlier (idempotency/recovery)? The API does not say,
197
+ * so: no clientToken → ours; otherwise ours when the VM started after we
198
+ * asked (with a minute of slack for clock skew). Unknown → not ours, which
199
+ * errs on the side of leaving a VM alive.
200
+ */
201
+ private async launchedBy(id: string, t0: number, clientToken?: string): Promise<boolean> {
202
+ if (!clientToken) return true;
203
+ try {
204
+ const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
205
+ return vm.startedAt !== undefined && vm.startedAt.getTime() >= t0 - 60_000;
206
+ } catch {
207
+ return false;
208
+ }
209
+ }
210
+
211
+ /** A Connection from an endpoint already in hand (get() returns it): no extra GetMicrovm. */
212
+ connectionFor(id: string, endpoint: string): Connection {
173
213
  return {
174
- baseUrl: `https://${vm.endpoint}`,
175
- headers: async () => ({ 'X-aws-proxy-auth': await this.token(id) }),
214
+ baseUrl: endpoint.startsWith('https://') ? endpoint : `https://${endpoint}`,
215
+ headers: async (opts?: HeadersOptions) => ({ 'X-aws-proxy-auth': await this.token(id, opts?.fresh) }),
176
216
  };
177
217
  }
178
218
 
179
219
  /** Headers to reach another port of the capsule through the same endpoint. */
180
- async portHeaders(id: string, port: number): Promise<Record<string, string>> {
181
- return { 'X-aws-proxy-auth': await this.token(id), 'X-aws-proxy-port': String(port) };
220
+ async portHeaders(id: string, port: number, opts?: HeadersOptions): Promise<Record<string, string>> {
221
+ return { 'X-aws-proxy-auth': await this.token(id, opts?.fresh), 'X-aws-proxy-port': String(port) };
222
+ }
223
+
224
+ /** Forget the cached endpoint token: the next request mints a new one. */
225
+ invalidateToken(id: string): void {
226
+ this.tokens.delete(id);
182
227
  }
183
228
 
184
- private async token(id: string): Promise<string> {
229
+ private infoOf(id: string, vm: { state?: string; imageArn?: string; imageVersion?: string; startedAt?: Date; terminatedAt?: Date; stateReason?: string; endpoint?: string; maximumDurationInSeconds?: number }): CapsuleInfo {
230
+ return {
231
+ id,
232
+ provider: this.name,
233
+ state: toState(vm.state),
234
+ template: vm.imageArn ?? null,
235
+ templateVersion: vm.imageVersion ?? null,
236
+ createdAt: vm.startedAt ?? null,
237
+ terminatedAt: vm.terminatedAt ?? null,
238
+ stateReason: vm.stateReason ?? null,
239
+ endpoint: vm.endpoint ? `https://${vm.endpoint}` : null,
240
+ maxDurationSeconds: vm.maximumDurationInSeconds ?? null,
241
+ };
242
+ }
243
+
244
+ private async token(id: string, fresh = false): Promise<string> {
185
245
  const cached = this.tokens.get(id);
186
- if (cached && cached.expiresAt > Date.now()) return cached.value;
246
+ if (cached && cached.expiresAt > Date.now() && !fresh) return cached.value;
187
247
  const ttl = Math.min(this.opts.tokenTtlMinutes ?? 60, 60);
188
248
  const ports = new Set([CAPSULOD_PORT, ...(this.opts.allowedPorts ?? [])]);
189
249
  const res = await this.client.send(
@@ -200,17 +260,25 @@ export class LambdaProvider implements Provider {
200
260
  return value;
201
261
  }
202
262
 
203
- private async waitForRunning(id: string, timeoutMs = 120_000): Promise<CapsuleInfo> {
263
+ /**
264
+ * Poll until the VM runs. A SUSPENDED VM (a clientToken that recovered an
265
+ * idle one) counts as alive: the first request auto-resumes it. Timeout and
266
+ * abort throw a CapsuleError that names the VM (capsuleId) for the caller
267
+ * to clean up; create() does that itself.
268
+ */
269
+ private async waitForRunning(id: string, opts: CreateOptions = {}): Promise<CapsuleInfo> {
270
+ const timeoutMs = opts.timeoutMs ?? 120_000;
204
271
  const t0 = Date.now();
205
272
  for (;;) {
206
- const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }));
207
- if (vm.state === 'RUNNING') {
208
- return { id, provider: this.name, state: 'running', template: vm.imageArn ?? null, createdAt: vm.startedAt ?? null };
209
- }
273
+ if (opts.signal?.aborted) throw new CapsuleError(`creation of microVM ${id} aborted`, undefined, { capsuleId: id });
274
+ const vm = await this.client.send(new GetMicrovmCommand({ microvmIdentifier: id }), { abortSignal: opts.signal });
275
+ if (vm.state === 'RUNNING' || vm.state === 'SUSPENDED') return this.infoOf(id, vm);
210
276
  if (vm.state === 'TERMINATED' || vm.state === 'TERMINATING') {
211
- throw new CapsuleError(`microVM ${id} died while starting: ${vm.stateReason ?? vm.state}`);
277
+ throw new CapsuleError(`microVM ${id} died while starting: ${vm.stateReason ?? vm.state}`, undefined, { capsuleId: id });
278
+ }
279
+ if (Date.now() - t0 > timeoutMs) {
280
+ throw new CapsuleError(`microVM ${id} did not run within ${timeoutMs}ms (state ${vm.state})`, undefined, { capsuleId: id });
212
281
  }
213
- if (Date.now() - t0 > timeoutMs) throw new CapsuleError(`timeout waiting for microVM ${id} to run`);
214
282
  await new Promise((r) => setTimeout(r, 400));
215
283
  }
216
284
  }