@lizard-build/sdk 0.1.41 → 0.1.43

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 CHANGED
@@ -1352,9 +1352,15 @@ interface VolumeInfo {
1352
1352
  status: string;
1353
1353
  attachedTo?: string | null;
1354
1354
  createdAt: number;
1355
+ /**
1356
+ * Whether `sizeGb` is actually enforced as a hard limit on the volume's node.
1357
+ * Returned by {@link Volume.resize}; `false` means the size was recorded but the
1358
+ * node cannot enforce it, so writes past it are not refused.
1359
+ */
1360
+ sizeEnforced?: boolean;
1355
1361
  }
1356
1362
  interface CreateVolumeOpts extends ConnectionOpts {
1357
- /** Whole GB, default 5. New volumes allow 1–50 GB unless the server config sets another maximum. Existing volumes are never resized. */
1363
+ /** Whole GB, default 5. New volumes allow 1–50 GB unless the server config sets another maximum. Creating never changes an existing volume's size; use `Volume.resize` for that. */
1358
1364
  sizeGb?: number;
1359
1365
  /**
1360
1366
  * Region to place the volume in, e.g. `'us-east-1'`. A volume is node-local, so
@@ -1399,8 +1405,8 @@ declare class Volume {
1399
1405
  * exist yet. This is the reason a volume's name is its key: an agent that wants
1400
1406
  * "the scratch disk for this task" no longer has to store an id between runs.
1401
1407
  *
1402
- * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1403
- * and never resizes one that is already there.
1408
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
1409
+ * so an existing volume keeps its size. Call {@link Volume.resize} to change it.
1404
1410
  */
1405
1411
  static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
1406
1412
  /** Look a volume up by name or by id. */
@@ -1408,7 +1414,26 @@ declare class Volume {
1408
1414
  static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
1409
1415
  /** Delete a volume by name or by id, without constructing one first. */
1410
1416
  static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
1417
+ /**
1418
+ * Resize a volume, by name or by id, to `sizeGb`.
1419
+ *
1420
+ * The change is in place and online: the size is a quota, so no data is copied,
1421
+ * it completes in well under a second, and a sandbox that has the volume mounted
1422
+ * keeps running and sees the new size immediately. Both growing and shrinking are
1423
+ * allowed, but a shrink must leave at least 10% of the new size free — otherwise
1424
+ * it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
1425
+ *
1426
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1427
+ * `volume_resize_in_progress`, `volume_not_provisioned`,
1428
+ * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1429
+ * the size is left unchanged).
1430
+ *
1431
+ * @returns The volume's updated info, including `sizeEnforced`.
1432
+ */
1433
+ static resize(projectId: string, nameOrId: string, sizeGb: number, opts?: ConnectionOpts): Promise<VolumeInfo>;
1411
1434
  getInfo(projectId: string): Promise<VolumeInfo>;
1435
+ /** Resize this volume in place — see the static {@link Volume.resize}. */
1436
+ resize(projectId: string, sizeGb: number): Promise<VolumeInfo>;
1412
1437
  delete(projectId: string): Promise<void>;
1413
1438
  }
1414
1439
 
@@ -1508,6 +1533,7 @@ declare class Lizard {
1508
1533
  getOrCreate: (name: string, opts?: CreateVolumeOpts) => Promise<Volume>;
1509
1534
  get: (nameOrId: string, opts?: ConnectionOpts) => Promise<Volume>;
1510
1535
  list: (opts?: ConnectionOpts) => Promise<VolumeInfo[]>;
1536
+ resize: (nameOrId: string, sizeGb: number, opts?: ConnectionOpts) => Promise<VolumeInfo>;
1511
1537
  delete: (nameOrId: string, opts?: ConnectionOpts) => Promise<void>;
1512
1538
  };
1513
1539
  /**
@@ -1557,6 +1583,13 @@ interface Account {
1557
1583
  declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise<string>;
1558
1584
 
1559
1585
  declare class LizardError extends Error {
1586
+ /** HTTP status of the failed API call, when the error came from one. */
1587
+ status?: number;
1588
+ /**
1589
+ * The server's machine-readable error code, when it sent one — e.g.
1590
+ * `'volume_too_full_to_shrink'`. Branch on this rather than on `message`.
1591
+ */
1592
+ code?: string;
1560
1593
  constructor(message: string);
1561
1594
  }
1562
1595
  /** Config was saved, but one or more deploy/restart actions failed. Never retry blindly. */
package/dist/index.d.ts CHANGED
@@ -1352,9 +1352,15 @@ interface VolumeInfo {
1352
1352
  status: string;
1353
1353
  attachedTo?: string | null;
1354
1354
  createdAt: number;
1355
+ /**
1356
+ * Whether `sizeGb` is actually enforced as a hard limit on the volume's node.
1357
+ * Returned by {@link Volume.resize}; `false` means the size was recorded but the
1358
+ * node cannot enforce it, so writes past it are not refused.
1359
+ */
1360
+ sizeEnforced?: boolean;
1355
1361
  }
1356
1362
  interface CreateVolumeOpts extends ConnectionOpts {
1357
- /** Whole GB, default 5. New volumes allow 1–50 GB unless the server config sets another maximum. Existing volumes are never resized. */
1363
+ /** Whole GB, default 5. New volumes allow 1–50 GB unless the server config sets another maximum. Creating never changes an existing volume's size; use `Volume.resize` for that. */
1358
1364
  sizeGb?: number;
1359
1365
  /**
1360
1366
  * Region to place the volume in, e.g. `'us-east-1'`. A volume is node-local, so
@@ -1399,8 +1405,8 @@ declare class Volume {
1399
1405
  * exist yet. This is the reason a volume's name is its key: an agent that wants
1400
1406
  * "the scratch disk for this task" no longer has to store an id between runs.
1401
1407
  *
1402
- * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1403
- * and never resizes one that is already there.
1408
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
1409
+ * so an existing volume keeps its size. Call {@link Volume.resize} to change it.
1404
1410
  */
1405
1411
  static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise<Volume>;
1406
1412
  /** Look a volume up by name or by id. */
@@ -1408,7 +1414,26 @@ declare class Volume {
1408
1414
  static list(projectId: string, opts?: ConnectionOpts): Promise<VolumeInfo[]>;
1409
1415
  /** Delete a volume by name or by id, without constructing one first. */
1410
1416
  static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise<void>;
1417
+ /**
1418
+ * Resize a volume, by name or by id, to `sizeGb`.
1419
+ *
1420
+ * The change is in place and online: the size is a quota, so no data is copied,
1421
+ * it completes in well under a second, and a sandbox that has the volume mounted
1422
+ * keeps running and sees the new size immediately. Both growing and shrinking are
1423
+ * allowed, but a shrink must leave at least 10% of the new size free — otherwise
1424
+ * it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
1425
+ *
1426
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1427
+ * `volume_resize_in_progress`, `volume_not_provisioned`,
1428
+ * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1429
+ * the size is left unchanged).
1430
+ *
1431
+ * @returns The volume's updated info, including `sizeEnforced`.
1432
+ */
1433
+ static resize(projectId: string, nameOrId: string, sizeGb: number, opts?: ConnectionOpts): Promise<VolumeInfo>;
1411
1434
  getInfo(projectId: string): Promise<VolumeInfo>;
1435
+ /** Resize this volume in place — see the static {@link Volume.resize}. */
1436
+ resize(projectId: string, sizeGb: number): Promise<VolumeInfo>;
1412
1437
  delete(projectId: string): Promise<void>;
1413
1438
  }
1414
1439
 
@@ -1508,6 +1533,7 @@ declare class Lizard {
1508
1533
  getOrCreate: (name: string, opts?: CreateVolumeOpts) => Promise<Volume>;
1509
1534
  get: (nameOrId: string, opts?: ConnectionOpts) => Promise<Volume>;
1510
1535
  list: (opts?: ConnectionOpts) => Promise<VolumeInfo[]>;
1536
+ resize: (nameOrId: string, sizeGb: number, opts?: ConnectionOpts) => Promise<VolumeInfo>;
1511
1537
  delete: (nameOrId: string, opts?: ConnectionOpts) => Promise<void>;
1512
1538
  };
1513
1539
  /**
@@ -1557,6 +1583,13 @@ interface Account {
1557
1583
  declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise<string>;
1558
1584
 
1559
1585
  declare class LizardError extends Error {
1586
+ /** HTTP status of the failed API call, when the error came from one. */
1587
+ status?: number;
1588
+ /**
1589
+ * The server's machine-readable error code, when it sent one — e.g.
1590
+ * `'volume_too_full_to_shrink'`. Branch on this rather than on `message`.
1591
+ */
1592
+ code?: string;
1560
1593
  constructor(message: string);
1561
1594
  }
1562
1595
  /** Config was saved, but one or more deploy/restart actions failed. Never retry blindly. */
package/dist/index.js CHANGED
@@ -112,17 +112,23 @@ var TimeoutError = class extends LizardError {
112
112
  };
113
113
  async function handleApiError(res) {
114
114
  let message;
115
+ let code;
115
116
  try {
116
117
  const body = await res.json();
117
118
  message = body.error ?? res.statusText;
119
+ if (typeof body.code === "string") code = body.code;
118
120
  } catch {
119
121
  message = res.statusText;
120
122
  }
121
- if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
122
- if (res.status === 404) throw new NotFoundError(message);
123
- if (res.status === 409) throw new ConflictError(message);
124
- if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
125
- throw new LizardError(`API error ${res.status}: ${message}`);
123
+ let err;
124
+ if (res.status === 401 || res.status === 403) err = new AuthenticationError(message);
125
+ else if (res.status === 404) err = new NotFoundError(message);
126
+ else if (res.status === 409) err = new ConflictError(message);
127
+ else if (res.status === 408 || res.status === 504) err = new TimeoutError(message);
128
+ else err = new LizardError(`API error ${res.status}: ${message}`);
129
+ err.status = res.status;
130
+ err.code = code;
131
+ throw err;
126
132
  }
127
133
 
128
134
  // src/platform/client.ts
@@ -1565,8 +1571,8 @@ var Volume = class _Volume {
1565
1571
  * exist yet. This is the reason a volume's name is its key: an agent that wants
1566
1572
  * "the scratch disk for this task" no longer has to store an id between runs.
1567
1573
  *
1568
- * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1569
- * and never resizes one that is already there.
1574
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
1575
+ * so an existing volume keeps its size. Call {@link Volume.resize} to change it.
1570
1576
  */
1571
1577
  static async getOrCreate(projectId, name, opts) {
1572
1578
  const config = new ConnectionConfig(opts);
@@ -1606,6 +1612,32 @@ var Volume = class _Volume {
1606
1612
  });
1607
1613
  if (!res.ok) await handleApiError(res);
1608
1614
  }
1615
+ /**
1616
+ * Resize a volume, by name or by id, to `sizeGb`.
1617
+ *
1618
+ * The change is in place and online: the size is a quota, so no data is copied,
1619
+ * it completes in well under a second, and a sandbox that has the volume mounted
1620
+ * keeps running and sees the new size immediately. Both growing and shrinking are
1621
+ * allowed, but a shrink must leave at least 10% of the new size free — otherwise
1622
+ * it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
1623
+ *
1624
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1625
+ * `volume_resize_in_progress`, `volume_not_provisioned`,
1626
+ * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1627
+ * the size is left unchanged).
1628
+ *
1629
+ * @returns The volume's updated info, including `sizeEnforced`.
1630
+ */
1631
+ static async resize(projectId, nameOrId, sizeGb, opts) {
1632
+ const config = new ConnectionConfig(opts);
1633
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1634
+ method: "PATCH",
1635
+ headers: config.headers,
1636
+ body: JSON.stringify({ sizeGb })
1637
+ });
1638
+ if (!res.ok) await handleApiError(res);
1639
+ return res.json();
1640
+ }
1609
1641
  async getInfo(projectId) {
1610
1642
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1611
1643
  headers: this.config.headers
@@ -1613,6 +1645,13 @@ var Volume = class _Volume {
1613
1645
  if (!res.ok) await handleApiError(res);
1614
1646
  return res.json();
1615
1647
  }
1648
+ /** Resize this volume in place — see the static {@link Volume.resize}. */
1649
+ async resize(projectId, sizeGb) {
1650
+ return _Volume.resize(projectId, this.volumeId, sizeGb, {
1651
+ apiKey: this.config.apiKey,
1652
+ apiUrl: this.config.apiUrl
1653
+ });
1654
+ }
1616
1655
  async delete(projectId) {
1617
1656
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1618
1657
  method: "DELETE",
@@ -1646,6 +1685,7 @@ var Lizard = class {
1646
1685
  getOrCreate: async (name, opts) => Volume.getOrCreate(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
1647
1686
  get: async (nameOrId, opts) => Volume.get(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts }),
1648
1687
  list: async (opts) => Volume.list(await this.projectId(), { ...await this.connectionOpts(), ...opts }),
1688
+ resize: async (nameOrId, sizeGb, opts) => Volume.resize(await this.projectId(), nameOrId, sizeGb, { ...await this.connectionOpts(), ...opts }),
1649
1689
  delete: async (nameOrId, opts) => Volume.delete(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts })
1650
1690
  };
1651
1691
  this.config = new ConnectionConfig(opts);
package/dist/index.mjs CHANGED
@@ -61,17 +61,23 @@ var TimeoutError = class extends LizardError {
61
61
  };
62
62
  async function handleApiError(res) {
63
63
  let message;
64
+ let code;
64
65
  try {
65
66
  const body = await res.json();
66
67
  message = body.error ?? res.statusText;
68
+ if (typeof body.code === "string") code = body.code;
67
69
  } catch {
68
70
  message = res.statusText;
69
71
  }
70
- if (res.status === 401 || res.status === 403) throw new AuthenticationError(message);
71
- if (res.status === 404) throw new NotFoundError(message);
72
- if (res.status === 409) throw new ConflictError(message);
73
- if (res.status === 408 || res.status === 504) throw new TimeoutError(message);
74
- throw new LizardError(`API error ${res.status}: ${message}`);
72
+ let err;
73
+ if (res.status === 401 || res.status === 403) err = new AuthenticationError(message);
74
+ else if (res.status === 404) err = new NotFoundError(message);
75
+ else if (res.status === 409) err = new ConflictError(message);
76
+ else if (res.status === 408 || res.status === 504) err = new TimeoutError(message);
77
+ else err = new LizardError(`API error ${res.status}: ${message}`);
78
+ err.status = res.status;
79
+ err.code = code;
80
+ throw err;
75
81
  }
76
82
 
77
83
  // src/platform/client.ts
@@ -1514,8 +1520,8 @@ var Volume = class _Volume {
1514
1520
  * exist yet. This is the reason a volume's name is its key: an agent that wants
1515
1521
  * "the scratch disk for this task" no longer has to store an id between runs.
1516
1522
  *
1517
- * An existing volume is returned as-is — `sizeGb` applies only to a fresh create
1518
- * and never resizes one that is already there.
1523
+ * An existing volume is returned as-is — `sizeGb` applies only to a fresh create,
1524
+ * so an existing volume keeps its size. Call {@link Volume.resize} to change it.
1519
1525
  */
1520
1526
  static async getOrCreate(projectId, name, opts) {
1521
1527
  const config = new ConnectionConfig(opts);
@@ -1555,6 +1561,32 @@ var Volume = class _Volume {
1555
1561
  });
1556
1562
  if (!res.ok) await handleApiError(res);
1557
1563
  }
1564
+ /**
1565
+ * Resize a volume, by name or by id, to `sizeGb`.
1566
+ *
1567
+ * The change is in place and online: the size is a quota, so no data is copied,
1568
+ * it completes in well under a second, and a sandbox that has the volume mounted
1569
+ * keeps running and sees the new size immediately. Both growing and shrinking are
1570
+ * allowed, but a shrink must leave at least 10% of the new size free — otherwise
1571
+ * it throws `ConflictError` with `code: 'volume_too_full_to_shrink'`.
1572
+ *
1573
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1574
+ * `volume_resize_in_progress`, `volume_not_provisioned`,
1575
+ * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1576
+ * the size is left unchanged).
1577
+ *
1578
+ * @returns The volume's updated info, including `sizeEnforced`.
1579
+ */
1580
+ static async resize(projectId, nameOrId, sizeGb, opts) {
1581
+ const config = new ConnectionConfig(opts);
1582
+ const res = await fetch(`${config.apiUrl}/api/projects/${projectId}/volumes/${encodeURIComponent(nameOrId)}`, {
1583
+ method: "PATCH",
1584
+ headers: config.headers,
1585
+ body: JSON.stringify({ sizeGb })
1586
+ });
1587
+ if (!res.ok) await handleApiError(res);
1588
+ return res.json();
1589
+ }
1558
1590
  async getInfo(projectId) {
1559
1591
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1560
1592
  headers: this.config.headers
@@ -1562,6 +1594,13 @@ var Volume = class _Volume {
1562
1594
  if (!res.ok) await handleApiError(res);
1563
1595
  return res.json();
1564
1596
  }
1597
+ /** Resize this volume in place — see the static {@link Volume.resize}. */
1598
+ async resize(projectId, sizeGb) {
1599
+ return _Volume.resize(projectId, this.volumeId, sizeGb, {
1600
+ apiKey: this.config.apiKey,
1601
+ apiUrl: this.config.apiUrl
1602
+ });
1603
+ }
1565
1604
  async delete(projectId) {
1566
1605
  const res = await fetch(`${this.config.apiUrl}/api/projects/${projectId}/volumes/${this.volumeId}`, {
1567
1606
  method: "DELETE",
@@ -1595,6 +1634,7 @@ var Lizard = class {
1595
1634
  getOrCreate: async (name, opts) => Volume.getOrCreate(await this.projectId(), name, { ...await this.connectionOpts(), ...opts }),
1596
1635
  get: async (nameOrId, opts) => Volume.get(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts }),
1597
1636
  list: async (opts) => Volume.list(await this.projectId(), { ...await this.connectionOpts(), ...opts }),
1637
+ resize: async (nameOrId, sizeGb, opts) => Volume.resize(await this.projectId(), nameOrId, sizeGb, { ...await this.connectionOpts(), ...opts }),
1598
1638
  delete: async (nameOrId, opts) => Volume.delete(await this.projectId(), nameOrId, { ...await this.connectionOpts(), ...opts })
1599
1639
  };
1600
1640
  this.config = new ConnectionConfig(opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "description": "TypeScript SDK for Lizard: Kubernetes sandboxes, code execution and cloud apps.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",