@usestatemachines/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +158 -18
  2. package/agents/SKILL.md +18 -8
  3. package/agents/index.js +6 -6
  4. package/agents/llms.txt +22 -9
  5. package/agents/prompt.md +2 -2
  6. package/esm/extensions/apps.d.ts +11 -1
  7. package/esm/extensions/apps.js +16 -0
  8. package/esm/extensions/client.d.ts +47 -4
  9. package/esm/extensions/client.js +107 -12
  10. package/esm/extensions/errors.d.ts +26 -1
  11. package/esm/extensions/errors.js +34 -1
  12. package/esm/extensions/index.d.ts +4 -4
  13. package/esm/extensions/index.js +1 -1
  14. package/esm/extensions/running-environment.d.ts +20 -3
  15. package/esm/extensions/running-environment.js +33 -2
  16. package/esm/extensions/wait.d.ts +13 -4
  17. package/esm/extensions/wait.js +38 -13
  18. package/esm/funcs/environments-list.js +1 -0
  19. package/esm/funcs/environments-resume.d.ts +11 -0
  20. package/esm/funcs/environments-resume.js +92 -0
  21. package/esm/funcs/environments-suspend.d.ts +11 -0
  22. package/esm/funcs/environments-suspend.js +92 -0
  23. package/esm/funcs/get-environment-exchange.d.ts +11 -0
  24. package/esm/funcs/get-environment-exchange.js +96 -0
  25. package/esm/funcs/get-environment-position.d.ts +11 -0
  26. package/esm/funcs/get-environment-position.js +92 -0
  27. package/esm/funcs/list-environment-history.d.ts +11 -0
  28. package/esm/funcs/list-environment-history.js +97 -0
  29. package/esm/funcs/snapshots-capture.d.ts +11 -0
  30. package/esm/funcs/snapshots-capture.js +94 -0
  31. package/esm/funcs/snapshots-delete.d.ts +11 -0
  32. package/esm/funcs/snapshots-delete.js +92 -0
  33. package/esm/funcs/snapshots-get.d.ts +11 -0
  34. package/esm/funcs/snapshots-get.js +92 -0
  35. package/esm/funcs/snapshots-list.d.ts +11 -0
  36. package/esm/funcs/snapshots-list.js +98 -0
  37. package/esm/funcs/snapshots-update.d.ts +11 -0
  38. package/esm/funcs/snapshots-update.js +93 -0
  39. package/esm/lib/config.d.ts +2 -2
  40. package/esm/lib/config.js +2 -2
  41. package/esm/models/app-instance-snapshot.d.ts +15 -0
  42. package/esm/models/app-instance-snapshot.js +19 -0
  43. package/esm/models/create-environment-input.d.ts +6 -2
  44. package/esm/models/create-environment-input.js +3 -1
  45. package/esm/models/environment-app.d.ts +11 -0
  46. package/esm/models/environment-app.js +13 -0
  47. package/esm/models/environment-exchange.d.ts +116 -0
  48. package/esm/models/environment-exchange.js +126 -0
  49. package/esm/models/environment-position.d.ts +25 -0
  50. package/esm/models/environment-position.js +32 -0
  51. package/esm/models/environment-snapshot.d.ts +40 -0
  52. package/esm/models/environment-snapshot.js +42 -0
  53. package/esm/models/environment.d.ts +5 -0
  54. package/esm/models/environment.js +5 -0
  55. package/esm/models/error-response.d.ts +9 -0
  56. package/esm/models/error-response.js +9 -0
  57. package/esm/models/history-body.d.ts +28 -0
  58. package/esm/models/history-body.js +33 -0
  59. package/esm/models/history-cause.d.ts +48 -0
  60. package/esm/models/history-cause.js +63 -0
  61. package/esm/models/history-position.d.ts +11 -0
  62. package/esm/models/history-position.js +15 -0
  63. package/esm/models/history-transaction.d.ts +16 -0
  64. package/esm/models/history-transaction.js +20 -0
  65. package/esm/models/index.d.ts +8 -0
  66. package/esm/models/index.js +8 -0
  67. package/esm/models/operations/capture-snapshot.d.ts +41 -0
  68. package/esm/models/operations/capture-snapshot.js +36 -0
  69. package/esm/models/operations/create-environment-definition.d.ts +92 -10
  70. package/esm/models/operations/create-environment-definition.js +63 -4
  71. package/esm/models/operations/delete-snapshot.d.ts +23 -0
  72. package/esm/models/operations/delete-snapshot.js +20 -0
  73. package/esm/models/operations/get-environment-exchange.d.ts +25 -0
  74. package/esm/models/operations/get-environment-exchange.js +21 -0
  75. package/esm/models/operations/get-environment-position.d.ts +23 -0
  76. package/esm/models/operations/get-environment-position.js +20 -0
  77. package/esm/models/operations/get-snapshot.d.ts +23 -0
  78. package/esm/models/operations/get-snapshot.js +20 -0
  79. package/esm/models/operations/index.d.ts +10 -0
  80. package/esm/models/operations/index.js +10 -0
  81. package/esm/models/operations/list-environment-history.d.ts +28 -0
  82. package/esm/models/operations/list-environment-history.js +24 -0
  83. package/esm/models/operations/list-environments.d.ts +2 -0
  84. package/esm/models/operations/list-environments.js +1 -0
  85. package/esm/models/operations/list-snapshots.d.ts +30 -0
  86. package/esm/models/operations/list-snapshots.js +25 -0
  87. package/esm/models/operations/resume-environment.d.ts +23 -0
  88. package/esm/models/operations/resume-environment.js +20 -0
  89. package/esm/models/operations/stop-environment.d.ts +1 -1
  90. package/esm/models/operations/suspend-environment.d.ts +23 -0
  91. package/esm/models/operations/suspend-environment.js +20 -0
  92. package/esm/models/operations/update-snapshot.d.ts +37 -0
  93. package/esm/models/operations/update-snapshot.js +29 -0
  94. package/esm/models/release.d.ts +11 -0
  95. package/esm/models/release.js +10 -0
  96. package/esm/sdk/environments.d.ts +2 -0
  97. package/esm/sdk/environments.js +8 -0
  98. package/esm/sdk/sdk.d.ts +6 -0
  99. package/esm/sdk/sdk.js +17 -0
  100. package/esm/sdk/snapshots.d.ts +9 -0
  101. package/esm/sdk/snapshots.js +27 -0
  102. package/esm/types/discriminated-union.d.ts +24 -0
  103. package/esm/types/discriminated-union.js +60 -0
  104. package/esm/types/smart-union.js +2 -1
  105. package/openapi.json +2473 -242
  106. package/package.json +11 -11
package/agents/llms.txt CHANGED
@@ -1,48 +1,61 @@
1
1
  # State Machines
2
2
 
3
- > State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Start, connect to and stop them with the TypeScript SDK `@usestatemachines/sdk`.
3
+ > State Machines runs disposable environments of replica SaaS apps, such as Salesforce, for tests and coding agents. Start, connect to, suspend, resume and stop them with the TypeScript SDK `@usestatemachines/sdk`.
4
4
 
5
5
  Install with `npm install @usestatemachines/sdk`. It is ESM only and runs on Node.js 20.20 or later (22 or 24 recommended) and Cloudflare Workers, not browsers. TypeScript needs `lib: ["DOM"]` or `@types/node`. `new StateMachines()` reads the API key from `STATE_API_KEY` and doesn't read `.env` files; in Cloudflare Workers, pass `apiKey` explicitly. Never print the key. The installed `node_modules/@usestatemachines/sdk/README.md` matches your version: read it before relying on memory.
6
6
 
7
7
  ## Concepts
8
8
 
9
9
  - Environment: one disposable run of one or more apps. `const env = await sm.environments.start({ apps: ['salesforce'] })` creates it, waits until it is `running` and returns a `RunningEnvironment`. If the environment fails or the wait times out or is aborted, `start()` stops the environment and rethrows the original error; an `EnvironmentFailedError` or `TimeoutError` carries `err.environmentId` and `err.stopped`. Always call `env.stop()` in a `finally`.
10
+ - Suspend and resume: `env.suspend()` ends the sandboxes and keeps the data; the lifetime pauses. Await `env.waitUntilSuspended()`, later `env.resume()` and `env.waitUntilReady()` to run again on the same data. `env.stop()` ends the environment and discards its data; a suspended environment still needs it.
10
11
  - Apps: a string is shorthand for `{ key: s, appId: s }`, nothing more. Use the object form, such as `{ key: 'crm', appId: 'salesforce' }`, for your own key or two instances of one app. `appId` names the app in the catalog (`sm.catalog.get()`); `key` is your name for it in this environment, and later calls take the key.
11
12
  - Actor: one of the app's users; requests run with that user's permissions. Salesforce actor IDs are its user IDs, such as `005…`, never a role name. `await env.actors('salesforce')` lists them in the release's order. connect() acts as the environment's declared default connection's actor, or else the first actor, the administrator, unless you pass `actorId`; pick a less-privileged actor to test permissions.
12
13
  - API profile: one API of the app, with its path and versions. Salesforce offers several (`rest`, `soap-partner`, `bulk2`, `oauth`) and has no default, so always pass `apiId`: `env.connect('salesforce', { apiId: 'rest' })`.
13
14
  - Connection: `connection.fetch` never sends your API key, never follows redirects and never retries. `connection.headers()` returns live tokens: treat them as secrets. Salesforce's SOAP profile (`soap-partner`) takes the actor token in the envelope instead: put `connection.actorToken()`, also a secret, in `SessionHeader/sessionId`; `headers()` then carries only `X-State-Environment-Token`.
14
15
  - Full control: `sm.environments.create(input)` returns one `Environment` before the apps boot, `sm.environments.waitUntilReady(environment.id)` waits, and `sm.environments.connect(environment.id, key, options)` connects. Nothing on this path stops the environment for you; use it to keep a failed environment for inspection.
16
+ - Snapshot: the saved data of a running environment's apps, taken per app instance. `const snapshot = await env.snapshot({ name: 'seeded' })` captures it and waits until it is `ready`. `sm.environments.start({ snapshotId: snapshot.id })` starts a new environment from it, in place of `apps`; each app keeps its key. Capturing needs `snapshots:create` and `snapshots:read`, restoring needs `snapshots:read`, `rename` needs `snapshots:create`, and `sm.snapshots.delete(id)` needs `snapshots:delete`, which keys created by members don't hold. Snapshots are never deleted automatically, not even failed or timed-out ones: `SnapshotFailedError` and `SnapshotTimeoutError` carry `err.snapshotId`. `sm.snapshots` also has `create`, `waitUntilReady`, `get`, `list` and `rename`.
15
17
  - Vendor clients: the gateway needs `X-State-Environment-Token` on every request. A client such as jsforce only sends it if you configure custom headers. Prefer `connection.fetch`; if you pass `headers()` to another client, send them only to `connection.baseUrl` and disable redirects.
16
- - Pagination: `environments.list`, `environments.events` and `workspaces.events` return one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
18
+ - Pagination: `environments.list`, `environments.events`, `workspaces.events` and `snapshots.list` return one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
17
19
 
18
20
  ## Statuses
19
21
 
20
22
  Enums are open: handle values you don't recognise.
21
23
 
22
- - Environment `status`: `queued`, `starting`, `running`, `stopping`, `stopped`, `failed`. `stopReason`: `requested`, `expired`, `app_failed`, `owner_lost`.
23
- - App `status`: `pending`, `starting`, `ready`, `stopping`, `stopped`, `failed`. A failed app carries `errorCode`.
24
+ - Environment `status`: `queued`, `starting`, `running`, `suspending`, `suspended`, `stopping`, `stopped`, `failed`. `stopReason`: `requested`, `expired`, `app_failed`, `owner_lost`. A suspended environment keeps its data and carries `suspendedAt`.
25
+ - App `status`: `pending`, `starting`, `ready`, `recovering`, `suspended`, `stopping`, `stopped`, `failed`. A failed app carries `errorCode`.
24
26
  - App `disposal`: `pending`, `complete`, `failed`, `unknown`.
25
- - `start()` and `waitUntilReady` throw `EnvironmentFailedError` if the environment becomes `failed`, `stopping` or `stopped`, and `TimeoutError` after 10 minutes by default (`readyTimeoutMs` for `start()`, `timeoutMs` for `waitUntilReady`). `start()` then stops the environment; `waitUntilReady` never does.
27
+ - Snapshot `status`: `capturing`, `ready`, `failed`. A failed snapshot carries `errorCode`: `capture-failed`, `pause-failed` or `pause-lost` when the apps could not be held paused for the capture, or `capture-interrupted` when the API restarted mid-capture.
28
+ - `start()` and `waitUntilReady` throw `EnvironmentFailedError` if the environment becomes `failed`, `suspending`, `suspended`, `stopping` or `stopped`, and `TimeoutError` after 10 minutes by default (`readyTimeoutMs` for `start()`, `timeoutMs` for `waitUntilReady`). `start()` then stops the environment; `waitUntilReady` never does.
29
+ - `waitUntilSuspended` resolves when the environment is `suspended`, and throws `EnvironmentFailedError` if it ends instead.
26
30
  - `waitUntilStopped` resolves when the environment is `stopped` or `failed` and every app's disposal is `complete`. It throws `DisposalIncompleteError` if a disposal is `failed` or `unknown`, and `TimeoutError` if disposal stays `pending`.
27
31
 
28
32
  ## Limits
29
33
 
30
34
  - Live environments are capped per organization: `(await sm.credential.get()).limits.liveEnvironments`.
31
- - `lifetimeMinutes` defaults to 15 minutes, or the deployment maximum if that is lower. Both are in `(await sm.credential.get()).limits.environmentLifetime`. Lifetime counts from the start, so boot uses part of it; at the end the environment stops with `stopReason: 'expired'`.
35
+ - `lifetimeMinutes` defaults to 15 minutes, or the deployment maximum if that is lower. Both are in `(await sm.credential.get()).limits.environmentLifetime`. Lifetime counts from the start, so boot uses part of it, and pauses while suspended; at the end the environment stops with `stopReason: 'expired'`.
36
+ - A suspended environment doesn't count toward the live cap; resuming it does.
32
37
  - Always call `env.stop()` in a `finally`. In CI, then await `env.waitUntilStopped()`.
33
38
 
34
39
  ## Errors and retries
35
40
 
36
- A failed API call throws `StateMachinesError` with `status`, `code`, `requestId` and `retryAfter`. The SDK retries `502`, `503`, `504`, connection errors and attempt timeouts itself, within a 10-minute retry budget (the last attempt can end later); `create` retries reuse one idempotency key. It retries nothing else: not `429`, other `4xx` or other `5xx`. `err.stopped === false` with `err.stopError` means the stop wasn't confirmed; `sm.environments.stop(err.environmentId)` is safe to repeat. Without `stopError`, you passed your own `idempotencyKey`, so `start()` left the environment running because another `start()` with that key may be using it; stop it only once nothing else needs it. Every error from `create`, including the create step of `start()`, carries `err.idempotencyKey`: `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same input returns the environment the first call created. Invalid `apps` fail before any request as a `StateMachinesError` (`status` null, `cause` a `TypeError`; don't replay it) with one of: `Pass at least one app.`, `App key "<key>" is used twice.` or `"<s>" can't be used as an app key; pass { key, appId } instead.`
41
+ A failed API call throws `StateMachinesError` with `status`, `code`, `requestId` and `retryAfter`. The SDK retries `502`, `503`, `504`, connection errors and attempt timeouts itself, within a 10-minute retry budget (the last attempt can end later); `create` retries reuse one idempotency key. It retries nothing else: not `429`, other `4xx` or other `5xx`. The one exception is snapshot capture, which retries `429` `snapshot-limit` for up to 60 seconds with one idempotency key. `err.stopped === false` with `err.stopError` means the stop wasn't confirmed; `sm.environments.stop(err.environmentId)` is safe to repeat. Without `stopError`, you passed your own `idempotencyKey`, so `start()` left the environment running because another `start()` with that key may be using it; stop it only once nothing else needs it. Every error from `create`, including the create step of `start()`, carries `err.idempotencyKey`: `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same input returns the environment the first call created. Invalid `apps` fail before any request as a `StateMachinesError` (`status` null, `cause` a `TypeError`; don't replay it) with one of: `Pass at least one app.`, `App key "<key>" is used twice.` or `"<s>" can't be used as an app key; pass { key, appId } instead.`
37
42
 
38
43
  | Code | Status | What to do |
39
44
  | --- | --- | --- |
40
- | `environment-limit` | 429 | Not retried. Stop a live environment (`sm.environments.list({ status: 'live' })`), then start again. |
45
+ | `environment-limit` | 429 | Not retried. Suspend or stop a live environment (`sm.environments.list({ status: 'live' })`), then start or resume again. |
46
+ | `not-ready` | 409 | Only a running environment whose apps are all ready can be suspended. Wait for it, or stop it. |
47
+ | `not-suspended` | 409 | Only a suspended environment can resume. After `suspend()`, await `waitUntilSuspended()` first. |
48
+ | `suspend-in-progress` | 409 | The environment is suspending. Await `waitUntilSuspended()`, then stop it. |
41
49
  | `lifetime-too-long` | 422 | Lower `lifetimeMinutes` to at most `limits.environmentLifetime.maximumMinutes`. |
42
50
  | `release-unavailable` | 422 | Unknown or retired app or release. Check `appId` and `appReleaseId` against `sm.catalog.get()`. |
43
51
  | `release-unavailable` | 503 | This deployment doesn't serve the release. Retried within the budget; omit `appReleaseId` to use the default release, or contact support with the `requestId`. A failed app can carry it as `errorCode`. |
44
- | `connection-unavailable` | 409 | The app stopped or the environment is no longer `running`. If it stopped or expired, start a new one. (Before revealing any token, connect() throws a `TypeError` for an app that isn't ready, an unknown actor, or a missing `apiId`.) |
52
+ | `connection-unavailable` | 409 | The app stopped or the environment is no longer `running`. If it is suspended, resume it; if it stopped or expired, start a new one. (Before revealing any token, connect() throws a `TypeError` for an app that isn't ready, an unknown actor, or a missing `apiId`.) |
45
53
  | `idempotency-conflict` | 409 | The idempotency key was used with a different input. Replay with the exact same input. |
54
+ | `capture-in-progress` | 409 | This environment is already capturing a snapshot; one runs at a time. Wait for it, then capture again. |
55
+ | `snapshot-limit` | 429 | Older APIs only. `capture` and `env.snapshot()` retry it for up to 60 seconds; if it still fails, wait and capture again. |
56
+ | `not-ready` | 409 | Capture: the environment isn't `running` or an app isn't `ready`. Restore: the snapshot isn't `ready` yet; `await sm.snapshots.waitUntilReady(id)`. Delete: the snapshot is still `capturing`. |
57
+ | `version-conflict` | 409 | `rename` sent a stale `expectedVersion`, or the snapshot was deleted. `get` it: if `deletedAt` is set it's gone, otherwise retry with its `version`. |
58
+ | `release-unavailable` | 422 | On a restore: no release on this deployment matches the snapshot's apps. Start from `apps`, seed again and capture a new snapshot. |
46
59
  | `request-timeout` | 503 | Retried automatically. If `create` still fails, replay it with `err.idempotencyKey`. |
47
60
 
48
61
  ## Quickstart
package/agents/prompt.md CHANGED
@@ -19,8 +19,8 @@ try {
19
19
  }
20
20
  ```
21
21
 
22
- 4. Adapt it to my task. Log `await sm.catalog.get()` to find each app's ID and the actors and API profiles its release offers; don't guess IDs. Start only the apps the task needs; a string such as `'salesforce'` means `{ key: 'salesforce', appId: 'salesforce' }`. Salesforce always needs an `apiId`. `connect()` acts as the declared default connection's actor, or else the app's first actor, the administrator; to act as a specific user, pass an `actorId` from `await env.actors('salesforce')`.
23
- 5. Always call `env.stop()` in a `finally`. In CI, then await `env.waitUntilStopped()` so the run fails if cleanup isn't confirmed. If `start()` fails after creating the environment, it stops it; an `EnvironmentFailedError` or `TimeoutError` carries `err.environmentId` and `err.stopped`. If the create itself fails, call `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same `input`; never retry without the key, which can start a second environment.
22
+ 4. Adapt it to my task. Log `await sm.catalog.get()` to find each app's ID and the actors and API profiles its release offers; don't guess IDs. Start only the apps the task needs; a string such as `'salesforce'` means `{ key: 'salesforce', appId: 'salesforce' }`. Salesforce always needs an `apiId`. `connect()` acts as the declared default connection's actor, or else the app's first actor, the administrator; to act as a specific user, pass an `actorId` from `await env.actors('salesforce')`. To reuse seeded data, `await env.snapshot()` once, then `sm.environments.start({ snapshotId })`.
23
+ 5. Always call `env.stop()` in a `finally`; it ends the environment and discards its data. In CI, then await `env.waitUntilStopped()` so the run fails if cleanup isn't confirmed. If `start()` fails after creating the environment, it stops it; an `EnvironmentFailedError` or `TimeoutError` carries `err.environmentId` and `err.stopped`. If the create itself fails, call `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same `input`; never retry without the key, which can start a second environment.
24
24
  6. Never print, log or commit keys or tokens, including `connection.headers()` and `connection.actorToken()`, which return live tokens.
25
25
 
26
26
  Read more: `node_modules/@usestatemachines/sdk/README.md`, https://state-web.usestatemachines.workers.dev/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk
@@ -1,8 +1,18 @@
1
1
  import type { CreateEnvironmentInput, CreateEnvironmentInputApp } from '../models/create-environment-input.js';
2
2
  /** A string is shorthand for `{ key: <the string>, appId: <the string> }`. */
3
3
  export type AppInput = string | CreateEnvironmentInputApp;
4
- export interface EnvironmentInput extends Omit<CreateEnvironmentInput, 'apps'> {
4
+ type Common = Omit<CreateEnvironmentInput, 'apps' | 'snapshotId' | 'snapshotAppInstanceKeys'>;
5
+ export interface EnvironmentInput extends Common {
5
6
  /** 1 to 10 apps with unique keys. */
6
7
  apps: AppInput[];
7
8
  }
9
+ /** Starts from a snapshot: each app keeps the key it had when captured. */
10
+ export interface SnapshotEnvironmentInput extends Common {
11
+ snapshotId: string;
12
+ /** Keys of the snapshot's app instances to restore. Defaults to all. */
13
+ snapshotAppInstanceKeys?: string[];
14
+ }
8
15
  export declare function expandApps(input: EnvironmentInput): CreateEnvironmentInput;
16
+ export type EnvironmentSource = EnvironmentInput | SnapshotEnvironmentInput | CreateEnvironmentInput;
17
+ export declare function expandSource(input: EnvironmentSource): CreateEnvironmentInput;
18
+ export {};
@@ -21,3 +21,19 @@ export function expandApps(input) {
21
21
  }
22
22
  return { ...input, apps };
23
23
  }
24
+ export function expandSource(input) {
25
+ const { apps, ...rest } = input;
26
+ if (rest.snapshotId === undefined) {
27
+ if (rest.snapshotAppInstanceKeys !== undefined) {
28
+ throw new TypeError('Pass snapshotAppInstanceKeys with snapshotId.');
29
+ }
30
+ if (apps === undefined) {
31
+ throw new TypeError('Pass apps or snapshotId.');
32
+ }
33
+ return expandApps({ ...rest, apps });
34
+ }
35
+ if (apps !== undefined) {
36
+ throw new TypeError('Pass apps or snapshotId, not both.');
37
+ }
38
+ return rest;
39
+ }
@@ -4,13 +4,17 @@ import type { Connection as ConnectionDetails } from '../models/connection.js';
4
4
  import type { ConnectionSecret } from '../models/connection-secret.js';
5
5
  import type { Credential } from '../models/credential.js';
6
6
  import type { Environment } from '../models/environment.js';
7
+ import type { EnvironmentSnapshot } from '../models/environment-snapshot.js';
7
8
  import type { EventPage } from '../models/event-page.js';
9
+ import type { CaptureSnapshotRequestBody } from '../models/operations/capture-snapshot.js';
8
10
  import type { ListEnvironmentsRequest } from '../models/operations/list-environments.js';
11
+ import type { ListSnapshotsRequest } from '../models/operations/list-snapshots.js';
9
12
  import type { ListWorkspacesRequest } from '../models/operations/list-workspaces.js';
13
+ import type { UpdateSnapshotRequestBody } from '../models/operations/update-snapshot.js';
10
14
  import type { Release } from '../models/release.js';
11
15
  import type { ReleaseActor } from '../models/release-actor.js';
12
16
  import type { Workspace } from '../models/workspace.js';
13
- import { type EnvironmentInput } from './apps.js';
17
+ import { type EnvironmentSource } from './apps.js';
14
18
  import { Connection } from './connection.js';
15
19
  import { RunningEnvironment } from './running-environment.js';
16
20
  import { type WaitOptions } from './wait.js';
@@ -44,6 +48,12 @@ export interface StartOptions extends CreateOptions {
44
48
  /** Deadline for the whole wait for readiness. Defaults to 10 minutes. */
45
49
  readyTimeoutMs?: number;
46
50
  }
51
+ export type CaptureInput = Omit<CaptureSnapshotRequestBody, 'environmentId'>;
52
+ /** `readyTimeoutMs` bounds the wait for `ready`, as in `start()`. */
53
+ export type CaptureOptions = StartOptions;
54
+ export type ListSnapshotsQuery = Omit<ListSnapshotsRequest, 'workspaceId'> & {
55
+ workspaceId?: string;
56
+ };
47
57
  export interface ConnectOptions extends CallOptions {
48
58
  /** Defaults to the actor of the environment's declared default connection, else the app's first actor. */
49
59
  actorId?: string;
@@ -76,13 +86,21 @@ export declare class StateMachines {
76
86
  * environment (unless the caller passed its own `idempotencyKey`), then rethrows the
77
87
  * error with `environmentId`, `stopped` and `stopError`.
78
88
  */
79
- start: (input: EnvironmentInput, options?: StartOptions) => Promise<RunningEnvironment>;
89
+ start: (input: EnvironmentSource, options?: StartOptions) => Promise<RunningEnvironment>;
80
90
  /**
81
91
  * A string app is shorthand for `{ key: <the string>, appId: <the string> }`. Every
82
92
  * failure carries the `idempotencyKey`; replaying the create with it is safe.
83
93
  */
84
- create: (input: EnvironmentInput, options?: CreateOptions) => Promise<Environment>;
94
+ create: (input: EnvironmentSource, options?: CreateOptions) => Promise<Environment>;
95
+ /** Ends the environment and discards its data. Safe to repeat. */
85
96
  stop: (environmentId: string, options?: CallOptions | undefined) => Promise<Environment>;
97
+ /**
98
+ * Suspends a running environment: its sandboxes end and its data stays for `resume`.
99
+ * Its lifetime is paused meanwhile. Use `stop` to end it.
100
+ */
101
+ suspend: (environmentId: string, options?: CallOptions | undefined) => Promise<Environment>;
102
+ /** Queues a suspended environment to run again on the data it was suspended with. */
103
+ resume: (environmentId: string, options?: CallOptions | undefined) => Promise<Environment>;
86
104
  events: (environmentId: string, query?: {
87
105
  limit?: number;
88
106
  cursor?: string;
@@ -91,8 +109,10 @@ export declare class StateMachines {
91
109
  actors: (environmentId: string, appKey: string, options?: CallOptions | undefined) => Promise<ReleaseActor[]>;
92
110
  getConnection: (environmentId: string, appKey: string, options?: CallOptions | undefined) => Promise<ConnectionDetails>;
93
111
  reveal: (environmentId: string, appKey: string, actorId: string, options?: CallOptions | undefined) => Promise<ConnectionSecret>;
94
- /** Resolves when the environment is running. Never stops it. */
112
+ /** Resolves when the environment is running. Never suspends or stops it. */
95
113
  waitUntilReady: (environmentId: string, options?: WaitOptions | undefined) => Promise<Environment>;
114
+ /** Resolves when the environment is suspended and can resume. */
115
+ waitUntilSuspended: (environmentId: string, options?: WaitOptions | undefined) => Promise<Environment>;
96
116
  /** Resolves when the environment ended and every sandbox is confirmed disposed. */
97
117
  waitUntilStopped: (environmentId: string, options?: WaitOptions | undefined) => Promise<Environment>;
98
118
  /**
@@ -102,6 +122,29 @@ export declare class StateMachines {
102
122
  */
103
123
  connect: (environmentId: string, appKey: string, options?: ConnectOptions) => Promise<Connection>;
104
124
  };
125
+ readonly snapshots: {
126
+ /** Returns the API's answer at once: it may still be `capturing`. */
127
+ create: (environmentId: string, input?: CaptureInput, options?: CreateOptions) => Promise<EnvironmentSnapshot>;
128
+ /**
129
+ * Creates a snapshot and waits until it's `ready`. Needs `snapshots:create` and
130
+ * `snapshots:read`. A failed or timed-out snapshot is never deleted: the error
131
+ * carries `snapshotId` and `idempotencyKey`.
132
+ */
133
+ capture: (environmentId: string, input?: CaptureInput, options?: StartOptions) => Promise<EnvironmentSnapshot>;
134
+ /** Resolves when the snapshot is `ready`. Never deletes it. */
135
+ waitUntilReady: (snapshotId: string, options?: WaitOptions | undefined) => Promise<EnvironmentSnapshot>;
136
+ /** Deleted snapshots too, with `deletedAt` set. */
137
+ get: (snapshotId: string, options?: CallOptions | undefined) => Promise<EnvironmentSnapshot>;
138
+ /** Newest first. Deleted snapshots are left out. */
139
+ list: (query?: ListSnapshotsQuery, options?: CallOptions | undefined) => Promise<Page<EnvironmentSnapshot>>;
140
+ /**
141
+ * `null` clears the name. A stale `expectedVersion`, or a deleted snapshot, is a 409
142
+ * `version-conflict`; so is a retry of a rename whose first answer was lost.
143
+ */
144
+ rename: (snapshotId: string, body: UpdateSnapshotRequestBody, options?: CallOptions | undefined) => Promise<EnvironmentSnapshot>;
145
+ /** Marks the snapshot deleted and returns it; safe to repeat. A capturing snapshot is a 409 `not-ready`. */
146
+ delete: (snapshotId: string, options?: CallOptions | undefined) => Promise<EnvironmentSnapshot>;
147
+ };
105
148
  }
106
149
  /**
107
150
  * Names the call a request belongs to. Never sent: the attempt hook removes it.
@@ -1,20 +1,14 @@
1
1
  import { SDK_METADATA } from '../lib/config.js';
2
2
  import { HTTPClient } from '../lib/http.js';
3
3
  import { StateMachines as Generated } from '../sdk/sdk.js';
4
- import { expandApps } from './apps.js';
4
+ import { expandSource } from './apps.js';
5
5
  import { Connection } from './connection.js';
6
- import { EnvironmentFailedError, StateMachinesError, TimeoutError, toStateMachinesError, } from './errors.js';
6
+ import { CallerAbortedError, EnvironmentFailedError, SnapshotFailedError, SnapshotTimeoutError, StateMachinesError, TimeoutError, toStateMachinesError, } from './errors.js';
7
7
  import { RunningEnvironment } from './running-environment.js';
8
8
  import { linkSignal } from './signal.js';
9
- import { defaultTimeoutMs, disposed, poll, ready, } from './wait.js';
9
+ import { backoff, defaultTimeoutMs, disposed, poll, pollUntil, ready, sleep, snapshotReady, suspended, } from './wait.js';
10
10
  const stopAfterFailureMs = 30000;
11
- /** Thrown from a hook so the generated retry loop stops, whatever the abort reason. */
12
- class CallerAbortedError extends Error {
13
- name = 'CallerAbortedError';
14
- constructor(reason) {
15
- super('The caller aborted the request.', { cause: reason });
16
- }
17
- }
11
+ const busyBudgetMs = 60000;
18
12
  export class StateMachines {
19
13
  #sdk;
20
14
  #fetch;
@@ -117,7 +111,7 @@ export class StateMachines {
117
111
  create: async (input, options = {}) => {
118
112
  const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
119
113
  try {
120
- const body = expandApps(input);
114
+ const body = expandSource(input);
121
115
  return await this.#call(options, async (call) => {
122
116
  const workspaceId = await this.#workspace(options.workspaceId, call.signal);
123
117
  const { data } = await this.#sdk.environments.create({ workspaceId, idempotencyKey, body }, call);
@@ -133,15 +127,25 @@ export class StateMachines {
133
127
  throw new StateMachinesError(error, { idempotencyKey });
134
128
  }
135
129
  },
130
+ /** Ends the environment and discards its data. Safe to repeat. */
136
131
  stop: (environmentId, options) => this.#unwrap(options, (call) => this.#sdk.environments.stop({ environmentId }, call)),
132
+ /**
133
+ * Suspends a running environment: its sandboxes end and its data stays for `resume`.
134
+ * Its lifetime is paused meanwhile. Use `stop` to end it.
135
+ */
136
+ suspend: (environmentId, options) => this.#unwrap(options, (call) => this.#sdk.environments.suspend({ environmentId }, call)),
137
+ /** Queues a suspended environment to run again on the data it was suspended with. */
138
+ resume: (environmentId, options) => this.#unwrap(options, (call) => this.#sdk.environments.resume({ environmentId }, call)),
137
139
  events: (environmentId, query = {}, options) => this.#call(options, (call) => this.#sdk.environments.events({ ...query, environmentId }, call)),
138
140
  /** The app's users, in the release's order. */
139
141
  actors: async (environmentId, appKey, options) => (await this.environments.getConnection(environmentId, appKey, options))
140
142
  .actors,
141
143
  getConnection: (environmentId, appKey, options) => this.#unwrap(options, (call) => this.#sdk.environments.getConnection({ environmentId, appKey }, call)),
142
144
  reveal: (environmentId, appKey, actorId, options) => this.#reveal(environmentId, appKey, { actorId }, options),
143
- /** Resolves when the environment is running. Never stops it. */
145
+ /** Resolves when the environment is running. Never suspends or stops it. */
144
146
  waitUntilReady: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), ready, options),
147
+ /** Resolves when the environment is suspended and can resume. */
148
+ waitUntilSuspended: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), suspended, options),
145
149
  /** Resolves when the environment ended and every sandbox is confirmed disposed. */
146
150
  waitUntilStopped: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), disposed, options),
147
151
  /**
@@ -163,6 +167,97 @@ export class StateMachines {
163
167
  return new Connection(details.baseUrl, api, secret, this.#fetch);
164
168
  },
165
169
  };
170
+ snapshots = {
171
+ /** Returns the API's answer at once: it may still be `capturing`. */
172
+ create: (environmentId, input = {}, options = {}) => this.#createSnapshot(environmentId, input, options, 0),
173
+ /**
174
+ * Creates a snapshot and waits until it's `ready`. Needs `snapshots:create` and
175
+ * `snapshots:read`. A failed or timed-out snapshot is never deleted: the error
176
+ * carries `snapshotId` and `idempotencyKey`.
177
+ */
178
+ capture: async (environmentId, input = {}, options = {}) => {
179
+ const { readyTimeoutMs, ...createOptions } = options;
180
+ const { signal } = options;
181
+ const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
182
+ const timeoutMs = readyTimeoutMs ?? defaultTimeoutMs;
183
+ if (!(timeoutMs > 0)) {
184
+ throw new RangeError('readyTimeoutMs must be more than 0.');
185
+ }
186
+ const created = await this.#createSnapshot(environmentId, input, { ...createOptions, idempotencyKey }, busyBudgetMs);
187
+ try {
188
+ signal?.throwIfAborted();
189
+ if (snapshotReady(created)) {
190
+ return created;
191
+ }
192
+ return await this.snapshots.waitUntilReady(created.id, {
193
+ timeoutMs,
194
+ ...(signal ? { signal } : {}),
195
+ });
196
+ }
197
+ catch (error) {
198
+ const snapshotId = created.id;
199
+ if (signal?.aborted && Object.is(error, signal.reason)) {
200
+ throw Object.assign(new StateMachinesError(new CallerAbortedError(error), {
201
+ idempotencyKey,
202
+ }), { snapshotId });
203
+ }
204
+ if (error instanceof StateMachinesError ||
205
+ error instanceof SnapshotFailedError ||
206
+ error instanceof SnapshotTimeoutError) {
207
+ Object.assign(error, { snapshotId, idempotencyKey });
208
+ }
209
+ throw error;
210
+ }
211
+ },
212
+ /** Resolves when the snapshot is `ready`. Never deletes it. */
213
+ waitUntilReady: (snapshotId, options) => pollUntil((signal) => this.#unwrap({ signal }, (call) => this.#sdk.snapshots.get({ snapshotId }, { ...call, retries: { strategy: 'none' } })), snapshotReady, options, (timeoutMs, last) => new SnapshotTimeoutError(timeoutMs, last, snapshotId)),
214
+ /** Deleted snapshots too, with `deletedAt` set. */
215
+ get: (snapshotId, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.get({ snapshotId }, call)),
216
+ /** Newest first. Deleted snapshots are left out. */
217
+ list: (query = {}, options) => this.#call(options, async (call) => {
218
+ const { workspaceId, ...filters } = query;
219
+ const resolved = await this.#workspace(workspaceId, call.signal);
220
+ return this.#sdk.snapshots.list({ ...filters, workspaceId: resolved }, call);
221
+ }),
222
+ /**
223
+ * `null` clears the name. A stale `expectedVersion`, or a deleted snapshot, is a 409
224
+ * `version-conflict`; so is a retry of a rename whose first answer was lost.
225
+ */
226
+ rename: (snapshotId, body, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.update({ snapshotId, body }, call)),
227
+ /** Marks the snapshot deleted and returns it; safe to repeat. A capturing snapshot is a 409 `not-ready`. */
228
+ delete: (snapshotId, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.delete({ snapshotId }, call)),
229
+ };
230
+ async #createSnapshot(environmentId, input, options, busyMs) {
231
+ const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
232
+ try {
233
+ return await this.#call(options, async (call) => {
234
+ const workspaceId = await this.#workspace(options.workspaceId, call.signal);
235
+ const request = {
236
+ workspaceId,
237
+ idempotencyKey,
238
+ body: { ...input, environmentId },
239
+ };
240
+ const deadline = Date.now() + busyMs;
241
+ for (let attempt = 0;; attempt += 1) {
242
+ try {
243
+ return (await this.#sdk.snapshots.capture(request, call)).data;
244
+ }
245
+ catch (error) {
246
+ const busy = toStateMachinesError(error);
247
+ const waitMs = Math.max(busy.retryAfter ?? 0, backoff(attempt));
248
+ if (busy.code !== 'snapshot-limit' ||
249
+ Date.now() + waitMs >= deadline) {
250
+ throw error;
251
+ }
252
+ await sleep(waitMs, call.signal);
253
+ }
254
+ }
255
+ });
256
+ }
257
+ catch (error) {
258
+ throw new StateMachinesError(error, { idempotencyKey });
259
+ }
260
+ }
166
261
  #reveal(environmentId, appKey, body, options) {
167
262
  return this.#unwrap(options, (call) => this.#sdk.environments.reveal({ environmentId, appKey, body }, call));
168
263
  }
@@ -1,4 +1,5 @@
1
1
  import type { Environment } from '../models/environment.js';
2
+ import type { EnvironmentSnapshot } from '../models/environment-snapshot.js';
2
3
  import type { Code } from '../models/error-response.js';
3
4
  /** What `environments.start()` adds to the error it rethrows after a failed wait. */
4
5
  export interface StartFailure {
@@ -30,6 +31,8 @@ export declare class StateMachinesError extends Error implements Details, StartF
30
31
  readonly environmentId?: string;
31
32
  readonly stopped?: boolean;
32
33
  readonly stopError?: unknown;
34
+ /** Set when `snapshots.capture()` fails after the snapshot was created. */
35
+ readonly snapshotId?: string;
33
36
  readonly status: number | null;
34
37
  readonly code: Code | null;
35
38
  readonly requestId: string | null;
@@ -38,7 +41,7 @@ export declare class StateMachinesError extends Error implements Details, StartF
38
41
  readonly retryAfter: number | null;
39
42
  /** Transient: a 502, 503 or 504, or the request never got a response. */
40
43
  readonly retryable: boolean;
41
- /** Set on `environments.create` failures; replay the create with it to recover. */
44
+ /** Set on create and capture failures. A replay with it returns the same result, so use it when the outcome is unknown. */
42
45
  readonly idempotencyKey?: string;
43
46
  /** Wraps any thrown value. Wrapping a `StateMachinesError` copies it, keeping its cause. */
44
47
  constructor(error: unknown, extra?: {
@@ -68,4 +71,26 @@ export declare class TimeoutError extends Error implements StartFailure {
68
71
  readonly stopError?: unknown;
69
72
  constructor(timeoutMs: number, environment: Environment | null);
70
73
  }
74
+ /** Thrown from a hook so the generated retry loop stops, whatever the abort reason. */
75
+ export declare class CallerAbortedError extends Error {
76
+ readonly name = "CallerAbortedError";
77
+ constructor(reason: unknown);
78
+ }
79
+ /** A snapshot that ended `failed`, or was deleted, while the SDK waited for `ready`. */
80
+ export declare class SnapshotFailedError extends Error {
81
+ readonly snapshot: EnvironmentSnapshot;
82
+ readonly name = "SnapshotFailedError";
83
+ readonly snapshotId: string;
84
+ readonly idempotencyKey?: string;
85
+ constructor(snapshot: EnvironmentSnapshot);
86
+ }
87
+ /** The wait for `ready` passed its deadline. The capture may still finish on the server. */
88
+ export declare class SnapshotTimeoutError extends Error {
89
+ readonly timeoutMs: number;
90
+ readonly snapshot: EnvironmentSnapshot | null;
91
+ readonly snapshotId: string;
92
+ readonly name = "SnapshotTimeoutError";
93
+ readonly idempotencyKey?: string;
94
+ constructor(timeoutMs: number, snapshot: EnvironmentSnapshot | null, snapshotId: string);
95
+ }
71
96
  export {};
@@ -17,7 +17,7 @@ export class StateMachinesError extends Error {
17
17
  retryAfter;
18
18
  /** Transient: a 502, 503 or 504, or the request never got a response. */
19
19
  retryable;
20
- /** Set on `environments.create` failures; replay the create with it to recover. */
20
+ /** Set on create and capture failures. A replay with it returns the same result, so use it when the outcome is unknown. */
21
21
  idempotencyKey;
22
22
  /** Wraps any thrown value. Wrapping a `StateMachinesError` copies it, keeping its cause. */
23
23
  constructor(error, extra = {}) {
@@ -128,3 +128,36 @@ export class TimeoutError extends Error {
128
128
  this.environment = environment;
129
129
  }
130
130
  }
131
+ /** Thrown from a hook so the generated retry loop stops, whatever the abort reason. */
132
+ export class CallerAbortedError extends Error {
133
+ name = 'CallerAbortedError';
134
+ constructor(reason) {
135
+ super('The caller aborted the request.', { cause: reason });
136
+ }
137
+ }
138
+ /** A snapshot that ended `failed`, or was deleted, while the SDK waited for `ready`. */
139
+ export class SnapshotFailedError extends Error {
140
+ snapshot;
141
+ name = 'SnapshotFailedError';
142
+ snapshotId;
143
+ constructor(snapshot) {
144
+ super(snapshot.status === 'failed'
145
+ ? `Snapshot ${snapshot.id} is failed (${snapshot.errorCode ?? 'no error code'}).`
146
+ : `Snapshot ${snapshot.id} was deleted.`);
147
+ this.snapshot = snapshot;
148
+ this.snapshotId = snapshot.id;
149
+ }
150
+ }
151
+ /** The wait for `ready` passed its deadline. The capture may still finish on the server. */
152
+ export class SnapshotTimeoutError extends Error {
153
+ timeoutMs;
154
+ snapshot;
155
+ snapshotId;
156
+ name = 'SnapshotTimeoutError';
157
+ constructor(timeoutMs, snapshot, snapshotId) {
158
+ super(`Timed out after ${timeoutMs} ms waiting for snapshot ${snapshotId}.`);
159
+ this.timeoutMs = timeoutMs;
160
+ this.snapshot = snapshot;
161
+ this.snapshotId = snapshotId;
162
+ }
163
+ }
@@ -1,13 +1,13 @@
1
1
  export type { Fetcher, HTTPClientOptions } from '../lib/http.js';
2
2
  export { HTTPClient } from '../lib/http.js';
3
3
  export { ConnectionError, ErrorResponse, HTTPClientError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, ResponseValidationError, SDKValidationError, StateMachinesHTTPError, UnexpectedClientError, UnexpectedResponseError, } from '../models/errors/index.js';
4
- export type { ApiProfile, App, Catalog, Connection as ConnectionDetails, ConnectionSecret, CreateEnvironmentInput, Credential, Environment, EnvironmentApp, Event, EventPage, Release, ReleaseActor, Workspace, } from '../models/index.js';
4
+ export type { ApiProfile, App, AppInstanceSnapshot, Catalog, Connection as ConnectionDetails, ConnectionSecret, CreateEnvironmentInput, Credential, Environment, EnvironmentApp, EnvironmentSnapshot, Event, EventPage, Release, ReleaseActor, Workspace, } from '../models/index.js';
5
5
  export { Code as ErrorCode, Disposal, EnvironmentStatus, StopReason, } from '../models/index.js';
6
6
  export { Status as EnvironmentListStatus } from '../models/operations/list-environments.js';
7
- export type { AppInput, EnvironmentInput } from './apps.js';
8
- export type { CallOptions, ConnectOptions, CreateOptions, ListEnvironmentsQuery, Page, StartOptions, StateMachinesOptions, } from './client.js';
7
+ export type { AppInput, EnvironmentInput, SnapshotEnvironmentInput, } from './apps.js';
8
+ export type { CallOptions, CaptureInput, CaptureOptions, ConnectOptions, CreateOptions, ListEnvironmentsQuery, ListSnapshotsQuery, Page, StartOptions, StateMachinesOptions, } from './client.js';
9
9
  export { StateMachines } from './client.js';
10
10
  export { Connection, formatActorHeader } from './connection.js';
11
- export { DisposalIncompleteError, EnvironmentFailedError, StateMachinesError, TimeoutError, } from './errors.js';
11
+ export { DisposalIncompleteError, EnvironmentFailedError, SnapshotFailedError, SnapshotTimeoutError, StateMachinesError, TimeoutError, } from './errors.js';
12
12
  export { RunningEnvironment } from './running-environment.js';
13
13
  export type { WaitOptions } from './wait.js';
@@ -4,5 +4,5 @@ export { Code as ErrorCode, Disposal, EnvironmentStatus, StopReason, } from '../
4
4
  export { Status as EnvironmentListStatus } from '../models/operations/list-environments.js';
5
5
  export { StateMachines } from './client.js';
6
6
  export { Connection, formatActorHeader } from './connection.js';
7
- export { DisposalIncompleteError, EnvironmentFailedError, StateMachinesError, TimeoutError, } from './errors.js';
7
+ export { DisposalIncompleteError, EnvironmentFailedError, SnapshotFailedError, SnapshotTimeoutError, StateMachinesError, TimeoutError, } from './errors.js';
8
8
  export { RunningEnvironment } from './running-environment.js';
@@ -1,9 +1,10 @@
1
1
  import type { Environment } from '../models/environment.js';
2
+ import type { EnvironmentSnapshot } from '../models/environment-snapshot.js';
2
3
  import type { ReleaseActor } from '../models/release-actor.js';
3
- import type { CallOptions, ConnectOptions, StateMachines } from './client.js';
4
+ import type { CallOptions, CaptureInput, CaptureOptions, ConnectOptions, StateMachines } from './client.js';
4
5
  import type { Connection } from './connection.js';
5
6
  import type { WaitOptions } from './wait.js';
6
- /** A started environment. It remains the handle after `stop()`. */
7
+ /** A started environment. It remains the handle after `suspend()` and `stop()`. */
7
8
  export declare class RunningEnvironment {
8
9
  #private;
9
10
  readonly id: string;
@@ -18,7 +19,23 @@ export declare class RunningEnvironment {
18
19
  connect(appKey: string, options?: ConnectOptions): Promise<Connection>;
19
20
  /** The app's users, in the release's order. */
20
21
  actors(appKey: string, options?: CallOptions): Promise<ReleaseActor[]>;
21
- /** Safe to call twice. Doesn't wait for disposal: use `waitUntilStopped()`. */
22
+ /** Captures a snapshot in the environment's own workspace and waits until it's `ready`. */
23
+ snapshot(input?: CaptureInput, options?: CaptureOptions): Promise<EnvironmentSnapshot>;
24
+ /**
25
+ * Ends the sandboxes and keeps the data for `resume()`. Doesn't wait: use
26
+ * `waitUntilSuspended()` before resuming.
27
+ */
28
+ suspend(options?: CallOptions): Promise<Environment>;
29
+ /** Resolves when the environment is suspended and can resume. */
30
+ waitUntilSuspended(options?: WaitOptions): Promise<Environment>;
31
+ /** Runs a suspended environment again on its data. Use `waitUntilReady()` before connecting. */
32
+ resume(options?: CallOptions): Promise<Environment>;
33
+ /** Resolves when the environment is running again. */
34
+ waitUntilReady(options?: WaitOptions): Promise<Environment>;
35
+ /**
36
+ * Ends the environment and discards its data. Safe to call twice. Doesn't wait for
37
+ * disposal: use `waitUntilStopped()`.
38
+ */
22
39
  stop(options?: CallOptions): Promise<Environment>;
23
40
  /** Resolves when the environment ended and every sandbox is confirmed disposed. */
24
41
  waitUntilStopped(options?: WaitOptions): Promise<Environment>;
@@ -1,13 +1,15 @@
1
- /** A started environment. It remains the handle after `stop()`. */
1
+ /** A started environment. It remains the handle after `suspend()` and `stop()`. */
2
2
  export class RunningEnvironment {
3
3
  id;
4
4
  /** The record when `start()` resolved. Use `sm.environments.get(id)` for fresh state. */
5
5
  environment;
6
6
  #environments;
7
+ #snapshots;
7
8
  constructor(sm, environment) {
8
9
  this.id = environment.id;
9
10
  this.environment = environment;
10
11
  this.#environments = sm.environments;
12
+ this.#snapshots = sm.snapshots;
11
13
  }
12
14
  /**
13
15
  * actorId defaults to the actor of the environment's declared default connection, or
@@ -21,7 +23,36 @@ export class RunningEnvironment {
21
23
  actors(appKey, options) {
22
24
  return this.#environments.actors(this.id, appKey, options);
23
25
  }
24
- /** Safe to call twice. Doesn't wait for disposal: use `waitUntilStopped()`. */
26
+ /** Captures a snapshot in the environment's own workspace and waits until it's `ready`. */
27
+ snapshot(input, options = {}) {
28
+ return this.#snapshots.capture(this.id, input, {
29
+ ...options,
30
+ workspaceId: options.workspaceId ?? this.environment.workspaceId,
31
+ });
32
+ }
33
+ /**
34
+ * Ends the sandboxes and keeps the data for `resume()`. Doesn't wait: use
35
+ * `waitUntilSuspended()` before resuming.
36
+ */
37
+ suspend(options) {
38
+ return this.#environments.suspend(this.id, options);
39
+ }
40
+ /** Resolves when the environment is suspended and can resume. */
41
+ waitUntilSuspended(options) {
42
+ return this.#environments.waitUntilSuspended(this.id, options);
43
+ }
44
+ /** Runs a suspended environment again on its data. Use `waitUntilReady()` before connecting. */
45
+ resume(options) {
46
+ return this.#environments.resume(this.id, options);
47
+ }
48
+ /** Resolves when the environment is running again. */
49
+ waitUntilReady(options) {
50
+ return this.#environments.waitUntilReady(this.id, options);
51
+ }
52
+ /**
53
+ * Ends the environment and discards its data. Safe to call twice. Doesn't wait for
54
+ * disposal: use `waitUntilStopped()`.
55
+ */
25
56
  stop(options) {
26
57
  return this.#environments.stop(this.id, options);
27
58
  }