@lizard-build/sdk 0.1.29 → 0.1.31
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.mts +97 -8
- package/dist/index.d.ts +97 -8
- package/dist/index.js +109 -9
- package/dist/index.mjs +108 -9
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -70,8 +70,12 @@ interface FsOpts {
|
|
|
70
70
|
interface FileInfo {
|
|
71
71
|
name: string;
|
|
72
72
|
path: string;
|
|
73
|
-
type: 'file' | 'dir';
|
|
73
|
+
type: 'file' | 'dir' | 'symlink';
|
|
74
74
|
size: number;
|
|
75
|
+
/** Permission bits as a string, e.g. `-rw-r--r--`. Absent on older sandboxes. */
|
|
76
|
+
mode?: string;
|
|
77
|
+
/** Last modification time, unix milliseconds. Absent on older sandboxes. */
|
|
78
|
+
modTime?: number;
|
|
75
79
|
}
|
|
76
80
|
/**
|
|
77
81
|
* Read and write files inside a Lizard sandbox microVM.
|
|
@@ -126,6 +130,35 @@ declare class Fs {
|
|
|
126
130
|
* Create a directory (and any missing parents) inside the microVM.
|
|
127
131
|
*/
|
|
128
132
|
makeDir(path: string, opts?: FsOpts): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Metadata for a single path — size, type, permissions, modification time.
|
|
135
|
+
*
|
|
136
|
+
* Saves listing a parent directory and filtering it just to answer "does this
|
|
137
|
+
* exist, and how big is it".
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* const info = await sandbox.fs.stat('/app/out.bin')
|
|
142
|
+
* console.log(info.size, info.type)
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Throws `NotFoundError` if the path does not exist. Sandboxes created before
|
|
146
|
+
* this shipped run a guest agent without it and throw `LizardError` (501) —
|
|
147
|
+
* recreate the sandbox to use it.
|
|
148
|
+
*/
|
|
149
|
+
stat(path: string, opts?: FsOpts): Promise<FileInfo>;
|
|
150
|
+
/**
|
|
151
|
+
* Move or rename a path. Creates the destination's parent directories.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* Sandboxes created before this shipped run a guest agent without it and throw
|
|
159
|
+
* `LizardError` (501) — recreate the sandbox to use it.
|
|
160
|
+
*/
|
|
161
|
+
move(from: string, to: string): Promise<void>;
|
|
129
162
|
private execInternal;
|
|
130
163
|
}
|
|
131
164
|
|
|
@@ -150,7 +183,14 @@ interface SandboxOpts extends ConnectionOpts {
|
|
|
150
183
|
metadata?: Record<string, string>;
|
|
151
184
|
envs?: Record<string, string>;
|
|
152
185
|
timeoutMs?: number;
|
|
186
|
+
/** Attach a persistent volume by id, mounted at `/data`. */
|
|
153
187
|
volumeId?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Attach a persistent volume by name, mounted at `/data`. A volume's name is its
|
|
190
|
+
* key inside a project, so this is usually what you want — see {@link Volume}.
|
|
191
|
+
* Requires the sandbox's project to be given as an exact `projectId`.
|
|
192
|
+
*/
|
|
193
|
+
volumeName?: string;
|
|
154
194
|
}
|
|
155
195
|
/**
|
|
156
196
|
* Low-level HTTP client for the Lizard sandbox API.
|
|
@@ -293,9 +333,16 @@ declare class Sandbox extends SandboxClient {
|
|
|
293
333
|
* nothing is written to disk, so a paused sandbox does not survive a host
|
|
294
334
|
* failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
|
|
295
335
|
*
|
|
296
|
-
* Pausing does not
|
|
297
|
-
*
|
|
298
|
-
*
|
|
336
|
+
* Pausing does not buy you time past the original deadline. Resuming pushes the
|
|
337
|
+
* expiry forward by however long the sandbox was paused — so a pause/resume cycle
|
|
338
|
+
* costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
|
|
339
|
+
* paused past its original `expiresAt` is deleted there, within a minute, and
|
|
340
|
+
* `resume()` then returns `false`.
|
|
341
|
+
*
|
|
342
|
+
* In other words: pause and come back *before* the original deadline and you lose
|
|
343
|
+
* nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
|
|
344
|
+
* at create time to opt out of expiry entirely, which is the only way to park a
|
|
345
|
+
* sandbox indefinitely.
|
|
299
346
|
*
|
|
300
347
|
* @returns `true` if paused successfully.
|
|
301
348
|
*/
|
|
@@ -303,7 +350,12 @@ declare class Sandbox extends SandboxClient {
|
|
|
303
350
|
/**
|
|
304
351
|
* Resume a paused sandbox by unfreezing its vCPUs.
|
|
305
352
|
*
|
|
306
|
-
*
|
|
353
|
+
* The expiry is pushed forward by the time spent paused, so the timeout measures
|
|
354
|
+
* running time rather than wall-clock — provided the original deadline had not
|
|
355
|
+
* already passed while paused. See {@link pause}.
|
|
356
|
+
*
|
|
357
|
+
* @returns `true` if resumed successfully, `false` if the sandbox no longer exists
|
|
358
|
+
* (including because it expired while paused).
|
|
307
359
|
*/
|
|
308
360
|
resume(opts?: ConnectionOpts): Promise<boolean>;
|
|
309
361
|
/**
|
|
@@ -780,6 +832,14 @@ declare class AuthenticationError extends LizardError {
|
|
|
780
832
|
declare class NotFoundError extends LizardError {
|
|
781
833
|
constructor(message?: string);
|
|
782
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The resource already exists — most often a volume whose name is already taken in
|
|
837
|
+
* the project. Volume names are the key inside a project, so `Volume.create` refuses
|
|
838
|
+
* to make a second one; catch this, or call `Volume.getOrCreate` instead.
|
|
839
|
+
*/
|
|
840
|
+
declare class ConflictError extends LizardError {
|
|
841
|
+
constructor(message?: string);
|
|
842
|
+
}
|
|
783
843
|
declare class TimeoutError extends LizardError {
|
|
784
844
|
constructor(message?: string);
|
|
785
845
|
}
|
|
@@ -950,19 +1010,48 @@ interface CreateVolumeOpts extends ConnectionOpts {
|
|
|
950
1010
|
}
|
|
951
1011
|
/**
|
|
952
1012
|
* A persistent volume that outlives sandboxes.
|
|
953
|
-
*
|
|
1013
|
+
*
|
|
1014
|
+
* A volume's **name** is its key inside a project: names are unique per project, and
|
|
1015
|
+
* every method here accepts either a name or the generated id wherever a volume is
|
|
1016
|
+
* addressed. Prefer the name — it is the thing you chose and can reconstruct, while
|
|
1017
|
+
* the id only exists after the first create.
|
|
1018
|
+
*
|
|
1019
|
+
* Names are slugs: lowercase letters, digits and dashes, starting and ending with a
|
|
1020
|
+
* letter or digit, up to 64 characters.
|
|
1021
|
+
*
|
|
1022
|
+
* Mount one to a sandbox via `Sandbox.create({ volumeName: 'my-data' })`.
|
|
954
1023
|
*/
|
|
955
1024
|
declare class Volume {
|
|
1025
|
+
/** The volume's generated id. Stable, but you rarely need it — address by name. */
|
|
956
1026
|
readonly volumeId: string;
|
|
1027
|
+
/** The volume's name: unique within its project, and usable anywhere `volumeId` is. */
|
|
1028
|
+
readonly name?: string;
|
|
957
1029
|
private readonly config;
|
|
958
1030
|
constructor(opts: {
|
|
959
1031
|
volumeId: string;
|
|
1032
|
+
name?: string;
|
|
960
1033
|
} & ConnectionOpts);
|
|
1034
|
+
/**
|
|
1035
|
+
* Create a volume. Throws `ConflictError` (409) if the project already has one with
|
|
1036
|
+
* this name — use {@link getOrCreate} when you want "make sure this exists" instead.
|
|
1037
|
+
*/
|
|
961
1038
|
static create(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
|
|
962
|
-
|
|
1039
|
+
/**
|
|
1040
|
+
* Return the project's volume with this name, creating it first if it does not
|
|
1041
|
+
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1042
|
+
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1043
|
+
*
|
|
1044
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1045
|
+
* and never resizes one that is already there.
|
|
1046
|
+
*/
|
|
1047
|
+
static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
|
|
1048
|
+
/** Look a volume up by name or by id. */
|
|
1049
|
+
static get(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<Volume>;
|
|
963
1050
|
static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
|
|
1051
|
+
/** Delete a volume by name or by id, without constructing one first. */
|
|
1052
|
+
static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
|
|
964
1053
|
getInfo(projectId: string): Promise<VolumeInfo>;
|
|
965
1054
|
delete(projectId: string): Promise<void>;
|
|
966
1055
|
}
|
|
967
1056
|
|
|
968
|
-
export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
|
|
1057
|
+
export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
|
package/dist/index.d.ts
CHANGED
|
@@ -70,8 +70,12 @@ interface FsOpts {
|
|
|
70
70
|
interface FileInfo {
|
|
71
71
|
name: string;
|
|
72
72
|
path: string;
|
|
73
|
-
type: 'file' | 'dir';
|
|
73
|
+
type: 'file' | 'dir' | 'symlink';
|
|
74
74
|
size: number;
|
|
75
|
+
/** Permission bits as a string, e.g. `-rw-r--r--`. Absent on older sandboxes. */
|
|
76
|
+
mode?: string;
|
|
77
|
+
/** Last modification time, unix milliseconds. Absent on older sandboxes. */
|
|
78
|
+
modTime?: number;
|
|
75
79
|
}
|
|
76
80
|
/**
|
|
77
81
|
* Read and write files inside a Lizard sandbox microVM.
|
|
@@ -126,6 +130,35 @@ declare class Fs {
|
|
|
126
130
|
* Create a directory (and any missing parents) inside the microVM.
|
|
127
131
|
*/
|
|
128
132
|
makeDir(path: string, opts?: FsOpts): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Metadata for a single path — size, type, permissions, modification time.
|
|
135
|
+
*
|
|
136
|
+
* Saves listing a parent directory and filtering it just to answer "does this
|
|
137
|
+
* exist, and how big is it".
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* const info = await sandbox.fs.stat('/app/out.bin')
|
|
142
|
+
* console.log(info.size, info.type)
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* Throws `NotFoundError` if the path does not exist. Sandboxes created before
|
|
146
|
+
* this shipped run a guest agent without it and throw `LizardError` (501) —
|
|
147
|
+
* recreate the sandbox to use it.
|
|
148
|
+
*/
|
|
149
|
+
stat(path: string, opts?: FsOpts): Promise<FileInfo>;
|
|
150
|
+
/**
|
|
151
|
+
* Move or rename a path. Creates the destination's parent directories.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* Sandboxes created before this shipped run a guest agent without it and throw
|
|
159
|
+
* `LizardError` (501) — recreate the sandbox to use it.
|
|
160
|
+
*/
|
|
161
|
+
move(from: string, to: string): Promise<void>;
|
|
129
162
|
private execInternal;
|
|
130
163
|
}
|
|
131
164
|
|
|
@@ -150,7 +183,14 @@ interface SandboxOpts extends ConnectionOpts {
|
|
|
150
183
|
metadata?: Record<string, string>;
|
|
151
184
|
envs?: Record<string, string>;
|
|
152
185
|
timeoutMs?: number;
|
|
186
|
+
/** Attach a persistent volume by id, mounted at `/data`. */
|
|
153
187
|
volumeId?: string;
|
|
188
|
+
/**
|
|
189
|
+
* Attach a persistent volume by name, mounted at `/data`. A volume's name is its
|
|
190
|
+
* key inside a project, so this is usually what you want — see {@link Volume}.
|
|
191
|
+
* Requires the sandbox's project to be given as an exact `projectId`.
|
|
192
|
+
*/
|
|
193
|
+
volumeName?: string;
|
|
154
194
|
}
|
|
155
195
|
/**
|
|
156
196
|
* Low-level HTTP client for the Lizard sandbox API.
|
|
@@ -293,9 +333,16 @@ declare class Sandbox extends SandboxClient {
|
|
|
293
333
|
* nothing is written to disk, so a paused sandbox does not survive a host
|
|
294
334
|
* failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
|
|
295
335
|
*
|
|
296
|
-
* Pausing does not
|
|
297
|
-
*
|
|
298
|
-
*
|
|
336
|
+
* Pausing does not buy you time past the original deadline. Resuming pushes the
|
|
337
|
+
* expiry forward by however long the sandbox was paused — so a pause/resume cycle
|
|
338
|
+
* costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
|
|
339
|
+
* paused past its original `expiresAt` is deleted there, within a minute, and
|
|
340
|
+
* `resume()` then returns `false`.
|
|
341
|
+
*
|
|
342
|
+
* In other words: pause and come back *before* the original deadline and you lose
|
|
343
|
+
* nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
|
|
344
|
+
* at create time to opt out of expiry entirely, which is the only way to park a
|
|
345
|
+
* sandbox indefinitely.
|
|
299
346
|
*
|
|
300
347
|
* @returns `true` if paused successfully.
|
|
301
348
|
*/
|
|
@@ -303,7 +350,12 @@ declare class Sandbox extends SandboxClient {
|
|
|
303
350
|
/**
|
|
304
351
|
* Resume a paused sandbox by unfreezing its vCPUs.
|
|
305
352
|
*
|
|
306
|
-
*
|
|
353
|
+
* The expiry is pushed forward by the time spent paused, so the timeout measures
|
|
354
|
+
* running time rather than wall-clock — provided the original deadline had not
|
|
355
|
+
* already passed while paused. See {@link pause}.
|
|
356
|
+
*
|
|
357
|
+
* @returns `true` if resumed successfully, `false` if the sandbox no longer exists
|
|
358
|
+
* (including because it expired while paused).
|
|
307
359
|
*/
|
|
308
360
|
resume(opts?: ConnectionOpts): Promise<boolean>;
|
|
309
361
|
/**
|
|
@@ -780,6 +832,14 @@ declare class AuthenticationError extends LizardError {
|
|
|
780
832
|
declare class NotFoundError extends LizardError {
|
|
781
833
|
constructor(message?: string);
|
|
782
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The resource already exists — most often a volume whose name is already taken in
|
|
837
|
+
* the project. Volume names are the key inside a project, so `Volume.create` refuses
|
|
838
|
+
* to make a second one; catch this, or call `Volume.getOrCreate` instead.
|
|
839
|
+
*/
|
|
840
|
+
declare class ConflictError extends LizardError {
|
|
841
|
+
constructor(message?: string);
|
|
842
|
+
}
|
|
783
843
|
declare class TimeoutError extends LizardError {
|
|
784
844
|
constructor(message?: string);
|
|
785
845
|
}
|
|
@@ -950,19 +1010,48 @@ interface CreateVolumeOpts extends ConnectionOpts {
|
|
|
950
1010
|
}
|
|
951
1011
|
/**
|
|
952
1012
|
* A persistent volume that outlives sandboxes.
|
|
953
|
-
*
|
|
1013
|
+
*
|
|
1014
|
+
* A volume's **name** is its key inside a project: names are unique per project, and
|
|
1015
|
+
* every method here accepts either a name or the generated id wherever a volume is
|
|
1016
|
+
* addressed. Prefer the name — it is the thing you chose and can reconstruct, while
|
|
1017
|
+
* the id only exists after the first create.
|
|
1018
|
+
*
|
|
1019
|
+
* Names are slugs: lowercase letters, digits and dashes, starting and ending with a
|
|
1020
|
+
* letter or digit, up to 64 characters.
|
|
1021
|
+
*
|
|
1022
|
+
* Mount one to a sandbox via `Sandbox.create({ volumeName: 'my-data' })`.
|
|
954
1023
|
*/
|
|
955
1024
|
declare class Volume {
|
|
1025
|
+
/** The volume's generated id. Stable, but you rarely need it — address by name. */
|
|
956
1026
|
readonly volumeId: string;
|
|
1027
|
+
/** The volume's name: unique within its project, and usable anywhere `volumeId` is. */
|
|
1028
|
+
readonly name?: string;
|
|
957
1029
|
private readonly config;
|
|
958
1030
|
constructor(opts: {
|
|
959
1031
|
volumeId: string;
|
|
1032
|
+
name?: string;
|
|
960
1033
|
} & ConnectionOpts);
|
|
1034
|
+
/**
|
|
1035
|
+
* Create a volume. Throws `ConflictError` (409) if the project already has one with
|
|
1036
|
+
* this name — use {@link getOrCreate} when you want "make sure this exists" instead.
|
|
1037
|
+
*/
|
|
961
1038
|
static create(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
|
|
962
|
-
|
|
1039
|
+
/**
|
|
1040
|
+
* Return the project's volume with this name, creating it first if it does not
|
|
1041
|
+
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1042
|
+
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1043
|
+
*
|
|
1044
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1045
|
+
* and never resizes one that is already there.
|
|
1046
|
+
*/
|
|
1047
|
+
static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
|
|
1048
|
+
/** Look a volume up by name or by id. */
|
|
1049
|
+
static get(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<Volume>;
|
|
963
1050
|
static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
|
|
1051
|
+
/** Delete a volume by name or by id, without constructing one first. */
|
|
1052
|
+
static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
|
|
964
1053
|
getInfo(projectId: string): Promise<VolumeInfo>;
|
|
965
1054
|
delete(projectId: string): Promise<void>;
|
|
966
1055
|
}
|
|
967
1056
|
|
|
968
|
-
export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
|
|
1057
|
+
export { type Addon, type AddonType, AuthenticationError, type CodeContext, CodeSandbox, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type DeployEvent, DeployHandle, type DomainInfo, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, NotFoundError, type ProcessOpts, type ProcessResult, type Project, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, type Service, type ServiceMetrics, type SetSecretOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AuthenticationError: () => AuthenticationError,
|
|
24
24
|
CodeSandbox: () => CodeSandbox,
|
|
25
|
+
ConflictError: () => ConflictError,
|
|
25
26
|
DeployHandle: () => DeployHandle,
|
|
26
27
|
Lizard: () => Lizard,
|
|
27
28
|
LizardError: () => LizardError,
|
|
@@ -74,6 +75,12 @@ var NotFoundError = class extends LizardError {
|
|
|
74
75
|
this.name = "NotFoundError";
|
|
75
76
|
}
|
|
76
77
|
};
|
|
78
|
+
var ConflictError = class extends LizardError {
|
|
79
|
+
constructor(message = "Resource already exists") {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "ConflictError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
77
84
|
var TimeoutError = class extends LizardError {
|
|
78
85
|
constructor(message = "Sandbox operation timed out") {
|
|
79
86
|
super(message);
|
|
@@ -90,6 +97,7 @@ async function handleApiError(res) {
|
|
|
90
97
|
}
|
|
91
98
|
if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
|
|
92
99
|
if (res.status === 404) throw new NotFoundError(message);
|
|
100
|
+
if (res.status === 409) throw new ConflictError(message);
|
|
93
101
|
if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
|
|
94
102
|
throw new LizardError(`API error ${res.status}: ${message}`);
|
|
95
103
|
}
|
|
@@ -253,6 +261,49 @@ var Fs = class {
|
|
|
253
261
|
async makeDir(path, opts) {
|
|
254
262
|
await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
|
|
255
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Metadata for a single path — size, type, permissions, modification time.
|
|
266
|
+
*
|
|
267
|
+
* Saves listing a parent directory and filtering it just to answer "does this
|
|
268
|
+
* exist, and how big is it".
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* const info = await sandbox.fs.stat('/app/out.bin')
|
|
273
|
+
* console.log(info.size, info.type)
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* Throws `NotFoundError` if the path does not exist. Sandboxes created before
|
|
277
|
+
* this shipped run a guest agent without it and throw `LizardError` (501) —
|
|
278
|
+
* recreate the sandbox to use it.
|
|
279
|
+
*/
|
|
280
|
+
async stat(path, opts) {
|
|
281
|
+
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/stat`);
|
|
282
|
+
url.searchParams.set("path", path);
|
|
283
|
+
if (opts?.user) url.searchParams.set("user", opts.user);
|
|
284
|
+
const res = await fetch(url.toString(), { headers: this.config.headers });
|
|
285
|
+
if (!res.ok) await handleApiError(res);
|
|
286
|
+
return res.json();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Move or rename a path. Creates the destination's parent directories.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```ts
|
|
293
|
+
* await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
|
|
294
|
+
* ```
|
|
295
|
+
*
|
|
296
|
+
* Sandboxes created before this shipped run a guest agent without it and throw
|
|
297
|
+
* `LizardError` (501) — recreate the sandbox to use it.
|
|
298
|
+
*/
|
|
299
|
+
async move(from, to) {
|
|
300
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/move`, {
|
|
301
|
+
method: "POST",
|
|
302
|
+
headers: this.config.headers,
|
|
303
|
+
body: JSON.stringify({ from, to })
|
|
304
|
+
});
|
|
305
|
+
if (!res.ok) await handleApiError(res);
|
|
306
|
+
}
|
|
256
307
|
async execInternal(cmd, user) {
|
|
257
308
|
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
|
|
258
309
|
method: "POST",
|
|
@@ -278,7 +329,8 @@ var SandboxClient = class {
|
|
|
278
329
|
projectId,
|
|
279
330
|
metadata: opts?.metadata,
|
|
280
331
|
envs: opts?.envs,
|
|
281
|
-
volumeId: opts?.volumeId
|
|
332
|
+
volumeId: opts?.volumeId,
|
|
333
|
+
volumeName: opts?.volumeName
|
|
282
334
|
})
|
|
283
335
|
});
|
|
284
336
|
if (!res.ok) await handleApiError(res);
|
|
@@ -407,9 +459,16 @@ var Sandbox = class extends SandboxClient {
|
|
|
407
459
|
* nothing is written to disk, so a paused sandbox does not survive a host
|
|
408
460
|
* failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
|
|
409
461
|
*
|
|
410
|
-
* Pausing does not
|
|
411
|
-
*
|
|
412
|
-
*
|
|
462
|
+
* Pausing does not buy you time past the original deadline. Resuming pushes the
|
|
463
|
+
* expiry forward by however long the sandbox was paused — so a pause/resume cycle
|
|
464
|
+
* costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
|
|
465
|
+
* paused past its original `expiresAt` is deleted there, within a minute, and
|
|
466
|
+
* `resume()` then returns `false`.
|
|
467
|
+
*
|
|
468
|
+
* In other words: pause and come back *before* the original deadline and you lose
|
|
469
|
+
* nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
|
|
470
|
+
* at create time to opt out of expiry entirely, which is the only way to park a
|
|
471
|
+
* sandbox indefinitely.
|
|
413
472
|
*
|
|
414
473
|
* @returns `true` if paused successfully.
|
|
415
474
|
*/
|
|
@@ -419,7 +478,12 @@ var Sandbox = class extends SandboxClient {
|
|
|
419
478
|
/**
|
|
420
479
|
* Resume a paused sandbox by unfreezing its vCPUs.
|
|
421
480
|
*
|
|
422
|
-
*
|
|
481
|
+
* The expiry is pushed forward by the time spent paused, so the timeout measures
|
|
482
|
+
* running time rather than wall-clock — provided the original deadline had not
|
|
483
|
+
* already passed while paused. See {@link pause}.
|
|
484
|
+
*
|
|
485
|
+
* @returns `true` if resumed successfully, `false` if the sandbox no longer exists
|
|
486
|
+
* (including because it expired while paused).
|
|
423
487
|
*/
|
|
424
488
|
async resume(opts) {
|
|
425
489
|
return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
@@ -1111,8 +1175,13 @@ async function* readLines(body) {
|
|
|
1111
1175
|
var Volume = class _Volume {
|
|
1112
1176
|
constructor(opts) {
|
|
1113
1177
|
this.volumeId = opts.volumeId;
|
|
1178
|
+
this.name = opts.name;
|
|
1114
1179
|
this.config = new ConnectionConfig(opts);
|
|
1115
1180
|
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Create a volume. Throws `ConflictError` (409) if the project already has one with
|
|
1183
|
+
* this name — use {@link getOrCreate} when you want "make sure this exists" instead.
|
|
1184
|
+
*/
|
|
1116
1185
|
static async create(projectId, name, opts) {
|
|
1117
1186
|
const config = new ConnectionConfig(opts);
|
|
1118
1187
|
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
|
|
@@ -1122,15 +1191,36 @@ var Volume = class _Volume {
|
|
|
1122
1191
|
});
|
|
1123
1192
|
if (!res.ok) await handleApiError(res);
|
|
1124
1193
|
const vol = await res.json();
|
|
1125
|
-
return new _Volume({ volumeId: vol.id, ...opts });
|
|
1194
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1126
1195
|
}
|
|
1127
|
-
|
|
1196
|
+
/**
|
|
1197
|
+
* Return the project's volume with this name, creating it first if it does not
|
|
1198
|
+
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1199
|
+
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1200
|
+
*
|
|
1201
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1202
|
+
* and never resizes one that is already there.
|
|
1203
|
+
*/
|
|
1204
|
+
static async getOrCreate(projectId, name, opts) {
|
|
1128
1205
|
const config = new ConnectionConfig(opts);
|
|
1129
|
-
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes
|
|
1206
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
|
|
1207
|
+
method: "POST",
|
|
1208
|
+
headers: config.headers,
|
|
1209
|
+
body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, getOrCreate: true })
|
|
1210
|
+
});
|
|
1211
|
+
if (!res.ok) await handleApiError(res);
|
|
1212
|
+
const vol = await res.json();
|
|
1213
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1214
|
+
}
|
|
1215
|
+
/** Look a volume up by name or by id. */
|
|
1216
|
+
static async get(projectId, nameOrId, opts) {
|
|
1217
|
+
const config = new ConnectionConfig(opts);
|
|
1218
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1130
1219
|
headers: config.headers
|
|
1131
1220
|
});
|
|
1132
1221
|
if (!res.ok) await handleApiError(res);
|
|
1133
|
-
|
|
1222
|
+
const vol = await res.json();
|
|
1223
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1134
1224
|
}
|
|
1135
1225
|
static async list(projectId, opts) {
|
|
1136
1226
|
const config = new ConnectionConfig(opts);
|
|
@@ -1140,6 +1230,15 @@ var Volume = class _Volume {
|
|
|
1140
1230
|
if (!res.ok) await handleApiError(res);
|
|
1141
1231
|
return res.json();
|
|
1142
1232
|
}
|
|
1233
|
+
/** Delete a volume by name or by id, without constructing one first. */
|
|
1234
|
+
static async delete(projectId, nameOrId, opts) {
|
|
1235
|
+
const config = new ConnectionConfig(opts);
|
|
1236
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1237
|
+
method: "DELETE",
|
|
1238
|
+
headers: config.headers
|
|
1239
|
+
});
|
|
1240
|
+
if (!res.ok) await handleApiError(res);
|
|
1241
|
+
}
|
|
1143
1242
|
async getInfo(projectId) {
|
|
1144
1243
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1145
1244
|
headers: this.config.headers
|
|
@@ -1159,6 +1258,7 @@ var Volume = class _Volume {
|
|
|
1159
1258
|
0 && (module.exports = {
|
|
1160
1259
|
AuthenticationError,
|
|
1161
1260
|
CodeSandbox,
|
|
1261
|
+
ConflictError,
|
|
1162
1262
|
DeployHandle,
|
|
1163
1263
|
Lizard,
|
|
1164
1264
|
LizardError,
|
package/dist/index.mjs
CHANGED
|
@@ -39,6 +39,12 @@ var NotFoundError = class extends LizardError {
|
|
|
39
39
|
this.name = "NotFoundError";
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
var ConflictError = class extends LizardError {
|
|
43
|
+
constructor(message = "Resource already exists") {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "ConflictError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
42
48
|
var TimeoutError = class extends LizardError {
|
|
43
49
|
constructor(message = "Sandbox operation timed out") {
|
|
44
50
|
super(message);
|
|
@@ -55,6 +61,7 @@ async function handleApiError(res) {
|
|
|
55
61
|
}
|
|
56
62
|
if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
|
|
57
63
|
if (res.status === 404) throw new NotFoundError(message);
|
|
64
|
+
if (res.status === 409) throw new ConflictError(message);
|
|
58
65
|
if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
|
|
59
66
|
throw new LizardError(`API error ${res.status}: ${message}`);
|
|
60
67
|
}
|
|
@@ -218,6 +225,49 @@ var Fs = class {
|
|
|
218
225
|
async makeDir(path, opts) {
|
|
219
226
|
await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
|
|
220
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Metadata for a single path — size, type, permissions, modification time.
|
|
230
|
+
*
|
|
231
|
+
* Saves listing a parent directory and filtering it just to answer "does this
|
|
232
|
+
* exist, and how big is it".
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```ts
|
|
236
|
+
* const info = await sandbox.fs.stat('/app/out.bin')
|
|
237
|
+
* console.log(info.size, info.type)
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* Throws `NotFoundError` if the path does not exist. Sandboxes created before
|
|
241
|
+
* this shipped run a guest agent without it and throw `LizardError` (501) —
|
|
242
|
+
* recreate the sandbox to use it.
|
|
243
|
+
*/
|
|
244
|
+
async stat(path, opts) {
|
|
245
|
+
const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/stat`);
|
|
246
|
+
url.searchParams.set("path", path);
|
|
247
|
+
if (opts?.user) url.searchParams.set("user", opts.user);
|
|
248
|
+
const res = await fetch(url.toString(), { headers: this.config.headers });
|
|
249
|
+
if (!res.ok) await handleApiError(res);
|
|
250
|
+
return res.json();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Move or rename a path. Creates the destination's parent directories.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
|
|
258
|
+
* ```
|
|
259
|
+
*
|
|
260
|
+
* Sandboxes created before this shipped run a guest agent without it and throw
|
|
261
|
+
* `LizardError` (501) — recreate the sandbox to use it.
|
|
262
|
+
*/
|
|
263
|
+
async move(from, to) {
|
|
264
|
+
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/move`, {
|
|
265
|
+
method: "POST",
|
|
266
|
+
headers: this.config.headers,
|
|
267
|
+
body: JSON.stringify({ from, to })
|
|
268
|
+
});
|
|
269
|
+
if (!res.ok) await handleApiError(res);
|
|
270
|
+
}
|
|
221
271
|
async execInternal(cmd, user) {
|
|
222
272
|
const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
|
|
223
273
|
method: "POST",
|
|
@@ -243,7 +293,8 @@ var SandboxClient = class {
|
|
|
243
293
|
projectId,
|
|
244
294
|
metadata: opts?.metadata,
|
|
245
295
|
envs: opts?.envs,
|
|
246
|
-
volumeId: opts?.volumeId
|
|
296
|
+
volumeId: opts?.volumeId,
|
|
297
|
+
volumeName: opts?.volumeName
|
|
247
298
|
})
|
|
248
299
|
});
|
|
249
300
|
if (!res.ok) await handleApiError(res);
|
|
@@ -372,9 +423,16 @@ var Sandbox = class extends SandboxClient {
|
|
|
372
423
|
* nothing is written to disk, so a paused sandbox does not survive a host
|
|
373
424
|
* failure. Resume with `sandbox.resume()` or `Sandbox.connect(id)`.
|
|
374
425
|
*
|
|
375
|
-
* Pausing does not
|
|
376
|
-
*
|
|
377
|
-
*
|
|
426
|
+
* Pausing does not buy you time past the original deadline. Resuming pushes the
|
|
427
|
+
* expiry forward by however long the sandbox was paused — so a pause/resume cycle
|
|
428
|
+
* costs no runtime — but that adjustment only happens on `resume()`. A sandbox left
|
|
429
|
+
* paused past its original `expiresAt` is deleted there, within a minute, and
|
|
430
|
+
* `resume()` then returns `false`.
|
|
431
|
+
*
|
|
432
|
+
* In other words: pause and come back *before* the original deadline and you lose
|
|
433
|
+
* nothing; leave it paused across the deadline and it is gone. Pass `timeoutMs: 0`
|
|
434
|
+
* at create time to opt out of expiry entirely, which is the only way to park a
|
|
435
|
+
* sandbox indefinitely.
|
|
378
436
|
*
|
|
379
437
|
* @returns `true` if paused successfully.
|
|
380
438
|
*/
|
|
@@ -384,7 +442,12 @@ var Sandbox = class extends SandboxClient {
|
|
|
384
442
|
/**
|
|
385
443
|
* Resume a paused sandbox by unfreezing its vCPUs.
|
|
386
444
|
*
|
|
387
|
-
*
|
|
445
|
+
* The expiry is pushed forward by the time spent paused, so the timeout measures
|
|
446
|
+
* running time rather than wall-clock — provided the original deadline had not
|
|
447
|
+
* already passed while paused. See {@link pause}.
|
|
448
|
+
*
|
|
449
|
+
* @returns `true` if resumed successfully, `false` if the sandbox no longer exists
|
|
450
|
+
* (including because it expired while paused).
|
|
388
451
|
*/
|
|
389
452
|
async resume(opts) {
|
|
390
453
|
return SandboxClient.resumeSandbox(this.sandboxId, this.resolveOpts(opts));
|
|
@@ -1076,8 +1139,13 @@ async function* readLines(body) {
|
|
|
1076
1139
|
var Volume = class _Volume {
|
|
1077
1140
|
constructor(opts) {
|
|
1078
1141
|
this.volumeId = opts.volumeId;
|
|
1142
|
+
this.name = opts.name;
|
|
1079
1143
|
this.config = new ConnectionConfig(opts);
|
|
1080
1144
|
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Create a volume. Throws `ConflictError` (409) if the project already has one with
|
|
1147
|
+
* this name — use {@link getOrCreate} when you want "make sure this exists" instead.
|
|
1148
|
+
*/
|
|
1081
1149
|
static async create(projectId, name, opts) {
|
|
1082
1150
|
const config = new ConnectionConfig(opts);
|
|
1083
1151
|
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
|
|
@@ -1087,15 +1155,36 @@ var Volume = class _Volume {
|
|
|
1087
1155
|
});
|
|
1088
1156
|
if (!res.ok) await handleApiError(res);
|
|
1089
1157
|
const vol = await res.json();
|
|
1090
|
-
return new _Volume({ volumeId: vol.id, ...opts });
|
|
1158
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1091
1159
|
}
|
|
1092
|
-
|
|
1160
|
+
/**
|
|
1161
|
+
* Return the project's volume with this name, creating it first if it does not
|
|
1162
|
+
* exist yet. This is the reason a volume's name is its key: an agent that wants
|
|
1163
|
+
* "the scratch disk for this task" no longer has to store an id between runs.
|
|
1164
|
+
*
|
|
1165
|
+
* An existing volume is returned as-is — `sizeGb` applies only to a fresh create
|
|
1166
|
+
* and never resizes one that is already there.
|
|
1167
|
+
*/
|
|
1168
|
+
static async getOrCreate(projectId, name, opts) {
|
|
1093
1169
|
const config = new ConnectionConfig(opts);
|
|
1094
|
-
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes
|
|
1170
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes`, {
|
|
1171
|
+
method: "POST",
|
|
1172
|
+
headers: config.headers,
|
|
1173
|
+
body: JSON.stringify({ name, sizeGb: opts?.sizeGb ?? 5, getOrCreate: true })
|
|
1174
|
+
});
|
|
1175
|
+
if (!res.ok) await handleApiError(res);
|
|
1176
|
+
const vol = await res.json();
|
|
1177
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1178
|
+
}
|
|
1179
|
+
/** Look a volume up by name or by id. */
|
|
1180
|
+
static async get(projectId, nameOrId, opts) {
|
|
1181
|
+
const config = new ConnectionConfig(opts);
|
|
1182
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1095
1183
|
headers: config.headers
|
|
1096
1184
|
});
|
|
1097
1185
|
if (!res.ok) await handleApiError(res);
|
|
1098
|
-
|
|
1186
|
+
const vol = await res.json();
|
|
1187
|
+
return new _Volume({ volumeId: vol.id, name: vol.name, ...opts });
|
|
1099
1188
|
}
|
|
1100
1189
|
static async list(projectId, opts) {
|
|
1101
1190
|
const config = new ConnectionConfig(opts);
|
|
@@ -1105,6 +1194,15 @@ var Volume = class _Volume {
|
|
|
1105
1194
|
if (!res.ok) await handleApiError(res);
|
|
1106
1195
|
return res.json();
|
|
1107
1196
|
}
|
|
1197
|
+
/** Delete a volume by name or by id, without constructing one first. */
|
|
1198
|
+
static async delete(projectId, nameOrId, opts) {
|
|
1199
|
+
const config = new ConnectionConfig(opts);
|
|
1200
|
+
const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
|
|
1201
|
+
method: "DELETE",
|
|
1202
|
+
headers: config.headers
|
|
1203
|
+
});
|
|
1204
|
+
if (!res.ok) await handleApiError(res);
|
|
1205
|
+
}
|
|
1108
1206
|
async getInfo(projectId) {
|
|
1109
1207
|
const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
|
|
1110
1208
|
headers: this.config.headers
|
|
@@ -1123,6 +1221,7 @@ var Volume = class _Volume {
|
|
|
1123
1221
|
export {
|
|
1124
1222
|
AuthenticationError,
|
|
1125
1223
|
CodeSandbox,
|
|
1224
|
+
ConflictError,
|
|
1126
1225
|
DeployHandle,
|
|
1127
1226
|
Lizard,
|
|
1128
1227
|
LizardError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lizard-build/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Firecracker microVM sandboxes for AI agents — boot Linux in milliseconds, run code, write files, expose ports over HTTPS, then pause a session with memory intact and resume it later. TypeScript SDK for Lizard Sandboxes (lizard.build).",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|