@usestatemachines/sdk 0.1.1 → 0.3.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.
- package/README.md +265 -75
- package/agents/SKILL.md +33 -46
- package/agents/index.js +7 -7
- package/agents/llms.txt +25 -40
- package/agents/prompt.md +11 -34
- package/esm/extensions/apps.d.ts +18 -0
- package/esm/extensions/apps.js +39 -0
- package/esm/extensions/client.d.ts +66 -3
- package/esm/extensions/client.js +236 -35
- package/esm/extensions/errors.d.ts +50 -6
- package/esm/extensions/errors.js +37 -3
- package/esm/extensions/index.d.ts +5 -3
- package/esm/extensions/index.js +2 -1
- package/esm/extensions/running-environment.d.ts +32 -0
- package/esm/extensions/running-environment.js +44 -0
- package/esm/extensions/signal.d.ts +1 -2
- package/esm/extensions/signal.js +9 -4
- package/esm/extensions/wait.d.ts +11 -3
- package/esm/extensions/wait.js +23 -13
- package/esm/funcs/create-environment-definition.d.ts +11 -0
- package/esm/funcs/create-environment-definition.js +94 -0
- package/esm/funcs/environments-list.js +1 -0
- package/esm/funcs/snapshots-capture.d.ts +11 -0
- package/esm/funcs/snapshots-capture.js +94 -0
- package/esm/funcs/snapshots-delete.d.ts +11 -0
- package/esm/funcs/snapshots-delete.js +92 -0
- package/esm/funcs/snapshots-get.d.ts +11 -0
- package/esm/funcs/snapshots-get.js +92 -0
- package/esm/funcs/snapshots-list.d.ts +11 -0
- package/esm/funcs/snapshots-list.js +98 -0
- package/esm/funcs/snapshots-update.d.ts +11 -0
- package/esm/funcs/snapshots-update.js +93 -0
- package/esm/lib/config.d.ts +2 -2
- package/esm/lib/config.js +2 -2
- package/esm/models/app-instance-snapshot.d.ts +15 -0
- package/esm/models/app-instance-snapshot.js +19 -0
- package/esm/models/connection-secret.d.ts +16 -0
- package/esm/models/connection-secret.js +13 -0
- package/esm/models/connection.d.ts +14 -0
- package/esm/models/connection.js +14 -0
- package/esm/models/create-environment-input.d.ts +6 -2
- package/esm/models/create-environment-input.js +3 -1
- package/esm/models/environment-app.d.ts +1 -0
- package/esm/models/environment-app.js +1 -0
- package/esm/models/environment-snapshot.d.ts +39 -0
- package/esm/models/environment-snapshot.js +41 -0
- package/esm/models/environment.d.ts +1 -0
- package/esm/models/environment.js +1 -0
- package/esm/models/error-response.d.ts +5 -0
- package/esm/models/error-response.js +5 -0
- package/esm/models/index.d.ts +2 -0
- package/esm/models/index.js +2 -0
- package/esm/models/operations/capture-snapshot.d.ts +41 -0
- package/esm/models/operations/capture-snapshot.js +36 -0
- package/esm/models/operations/create-environment-definition.d.ts +143 -0
- package/esm/models/operations/create-environment-definition.js +90 -0
- package/esm/models/operations/delete-snapshot.d.ts +23 -0
- package/esm/models/operations/delete-snapshot.js +20 -0
- package/esm/models/operations/get-snapshot.d.ts +23 -0
- package/esm/models/operations/get-snapshot.js +20 -0
- package/esm/models/operations/index.d.ts +6 -0
- package/esm/models/operations/index.js +6 -0
- package/esm/models/operations/list-environments.d.ts +2 -0
- package/esm/models/operations/list-environments.js +1 -0
- package/esm/models/operations/list-snapshots.d.ts +30 -0
- package/esm/models/operations/list-snapshots.js +25 -0
- package/esm/models/operations/reveal-connection.d.ts +26 -4
- package/esm/models/operations/reveal-connection.js +28 -2
- package/esm/models/operations/update-snapshot.d.ts +37 -0
- package/esm/models/operations/update-snapshot.js +29 -0
- package/esm/sdk/sdk.d.ts +6 -1
- package/esm/sdk/sdk.js +10 -0
- package/esm/sdk/snapshots.d.ts +9 -0
- package/esm/sdk/snapshots.js +27 -0
- package/openapi.json +1471 -144
- package/package.json +10 -11
package/README.md
CHANGED
|
@@ -1,55 +1,200 @@
|
|
|
1
1
|
# @usestatemachines/sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for [State](https://usestatemachines.com):
|
|
4
|
-
|
|
3
|
+
TypeScript SDK for [State](https://usestatemachines.com): start disposable environments of replica
|
|
4
|
+
apps, connect to them, snapshot their data, and stop them.
|
|
5
5
|
|
|
6
6
|
- Node.js 20.20 or later (22 or 24 recommended), and Cloudflare Workers. ESM only.
|
|
7
7
|
- Retries transient failures safely, including `create`, which is idempotent.
|
|
8
8
|
|
|
9
|
-
## Install
|
|
9
|
+
## Install
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
npm install @usestatemachines/sdk
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
15
17
|
```ts
|
|
16
18
|
import { StateMachines } from '@usestatemachines/sdk';
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
+
const sm = new StateMachines(); // reads STATE_API_KEY
|
|
21
|
+
const env = await sm.environments.start({ apps: ['salesforce'] });
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const salesforce = await env.connect('salesforce', { apiId: 'rest' });
|
|
25
|
+
const res = await salesforce.fetch('/services/data/v67.0/limits');
|
|
26
|
+
console.log(res.status, await res.json());
|
|
27
|
+
} finally {
|
|
28
|
+
await env.stop();
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`start()` creates the environment, waits until it's `running`, and returns a `RunningEnvironment`.
|
|
33
|
+
It takes `{ readyTimeoutMs, signal }`: the wait gives up after `readyTimeoutMs` (more than 0; 10
|
|
34
|
+
minutes by default). A string app is shorthand for `{ key: s, appId: s }`; nothing else is inferred.
|
|
35
|
+
Salesforce offers several APIs, so pass `apiId`.
|
|
36
|
+
|
|
37
|
+
If the wait fails after the environment was created, `start()` stops the environment, then
|
|
38
|
+
rethrows the original error. The SDK's errors (`EnvironmentFailedError`, `TimeoutError` or a
|
|
39
|
+
`StateMachinesError`; check with `instanceof`) carry `environmentId` and `stopped`. `stopped` is
|
|
40
|
+
`true` when the stop request succeeded. When it's `false`, the stop wasn't confirmed:
|
|
41
|
+
`stopError` says why, and you should call `sm.environments.stop(err.environmentId)`, which is safe
|
|
42
|
+
to repeat. The exception is your own key, below.
|
|
43
|
+
|
|
44
|
+
When you abort the `signal` during the wait, `start()` still stops the environment. If the stop
|
|
45
|
+
succeeded, it rejects with your `signal.reason`, unchanged. If not, it rejects with a
|
|
46
|
+
`StateMachinesError` whose `cause` leads to your reason, carrying `environmentId`, `stopped` and
|
|
47
|
+
`stopError`, so you can still clean up.
|
|
48
|
+
|
|
49
|
+
If you pass your own `idempotencyKey`, `start()` doesn't stop the environment on failure: another
|
|
50
|
+
`start()` with the same key gets the same environment and may be using it. The error carries
|
|
51
|
+
`environmentId` with `stopped: false` and no `stopError`. Stop it only once no other `start()` with
|
|
52
|
+
that key needs it.
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
If the create step fails, including an abort, `start()` stops nothing and rejects with a
|
|
55
|
+
`StateMachinesError` that carries `idempotencyKey`. The API may still have created the
|
|
56
|
+
environment, so replay with that key (see
|
|
57
|
+
[Recovering a failed create](#recovering-a-failed-create)) and stop what it returns.
|
|
58
|
+
|
|
59
|
+
## Choosing an actor
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const env = await sm.environments.start({ apps: ['salesforce'] });
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// The app's users. connect() uses the default actor unless you pick one.
|
|
66
|
+
const actors = await env.actors('salesforce');
|
|
67
|
+
const rep = actors.find((actor) => actor.summary.startsWith('Sales Representative'));
|
|
68
|
+
|
|
69
|
+
if (!rep) {
|
|
70
|
+
throw new Error('No sales representative in this release.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Act with a sales rep's permissions to test what they can and can't do.
|
|
74
|
+
const salesforce = await env.connect('salesforce', { actorId: rep.id, apiId: 'rest' });
|
|
75
|
+
const res = await salesforce.fetch('/services/data/v67.0/sobjects/Account');
|
|
76
|
+
console.log(res.status);
|
|
77
|
+
} finally {
|
|
78
|
+
await env.stop();
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Actors are the app's users, such as Salesforce user IDs (`005…`), and every request runs with the
|
|
83
|
+
chosen actor's permissions. Without an `actorId`, `connect()` uses the environment's declared
|
|
84
|
+
default connection, if it has one, or else the first actor the app lists, the administrator, so
|
|
85
|
+
pick a less-privileged actor to test permission behaviour.
|
|
86
|
+
|
|
87
|
+
## Full control
|
|
88
|
+
|
|
89
|
+
Use `create`, `waitUntilReady` and `connect` directly when you want to run each step yourself, or
|
|
90
|
+
keep an environment up after a failed boot so you can inspect it. You don't need this path for
|
|
91
|
+
app keys, labels or `lifetimeMinutes`: `start()` takes the same input.
|
|
92
|
+
**Nothing is stopped automatically on this path**: stop the environment yourself, even when a step
|
|
93
|
+
throws.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
22
96
|
const environment = await sm.environments.create({
|
|
23
|
-
name: '
|
|
24
|
-
//
|
|
97
|
+
name: 'nightly',
|
|
98
|
+
// key is your own name for the app; later calls take it.
|
|
25
99
|
apps: [{ key: 'crm', appId: 'salesforce' }],
|
|
100
|
+
lifetimeMinutes: 30,
|
|
101
|
+
labels: { suite: 'nightly' },
|
|
26
102
|
});
|
|
27
103
|
|
|
28
104
|
try {
|
|
29
|
-
await sm.environments.waitUntilReady(environment.id);
|
|
105
|
+
await sm.environments.waitUntilReady(environment.id, { timeoutMs: 5 * 60_000 });
|
|
106
|
+
|
|
107
|
+
const crm = await sm.environments.connect(environment.id, 'crm', { apiId: 'rest' });
|
|
108
|
+
const res = await crm.fetch('/services/data/v67.0/limits');
|
|
109
|
+
console.log(res.status);
|
|
110
|
+
} finally {
|
|
111
|
+
await sm.environments.stop(environment.id);
|
|
112
|
+
// In CI: fail the run when cleanup can't be confirmed.
|
|
113
|
+
await sm.environments.waitUntilStopped(environment.id);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
30
116
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
117
|
+
1. `create(input)` returns one `Environment` as soon as the API accepts it, before the apps boot.
|
|
118
|
+
2. `waitUntilReady(id)` polls until it is `running`. It throws `EnvironmentFailedError` if the
|
|
119
|
+
environment becomes `failed`, `stopping` or `stopped`, and `TimeoutError` after `timeoutMs`
|
|
120
|
+
(default 10 minutes). It **never stops the environment**, even when it throws.
|
|
121
|
+
3. `connect(id, appKey, { apiId, actorId })` returns a `Connection` to one app (see
|
|
122
|
+
[Connections](#connections)).
|
|
123
|
+
4. `stop(id)` returns as soon as the API accepts it, and a second call also resolves.
|
|
124
|
+
`waitUntilStopped(id)` resolves only when the environment is `stopped` or `failed` **and**
|
|
125
|
+
every app's `disposal` is `complete`. If any disposal is `failed` or `unknown`, it throws
|
|
126
|
+
`DisposalIncompleteError` with the environment.
|
|
35
127
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
128
|
+
`lifetimeMinutes` counts from when the environment starts, so boot time uses part of it. When it
|
|
129
|
+
runs out, the environment stops with `stopReason: 'expired'`. The default and maximum are in
|
|
130
|
+
`(await sm.credential.get()).limits.environmentLifetime`.
|
|
39
131
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
132
|
+
Both waits accept `{ timeoutMs, signal }`. Aborting the `signal` rejects the wait with
|
|
133
|
+
`signal.reason`. They poll at growing intervals of about 1 to 5 seconds, with random jitter, and
|
|
134
|
+
keep polling through transient failures.
|
|
135
|
+
|
|
136
|
+
`start()` and `create()` take the same input, including `{ key, appId }` objects for a custom key
|
|
137
|
+
or two instances of one app.
|
|
138
|
+
|
|
139
|
+
## Snapshots
|
|
140
|
+
|
|
141
|
+
A snapshot saves the data of a running environment's apps. Seed one environment, snapshot it, and
|
|
142
|
+
start later environments from the snapshot instead of seeding each one.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const env = await sm.environments.start({ apps: ['salesforce'] });
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const salesforce = await env.connect('salesforce', { apiId: 'rest' });
|
|
149
|
+
await salesforce.fetch('/services/data/v67.0/sobjects/Account', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
body: JSON.stringify({ Name: 'Acme' }),
|
|
43
153
|
});
|
|
44
|
-
const response = await crm.fetch('/services/data/v67.0/limits');
|
|
45
154
|
|
|
46
|
-
|
|
155
|
+
// Waits until the snapshot is ready.
|
|
156
|
+
const snapshot = await env.snapshot({ name: 'seeded accounts' });
|
|
157
|
+
console.log(snapshot.id, snapshot.sizeBytes);
|
|
47
158
|
} finally {
|
|
48
|
-
|
|
49
|
-
await sm.environments.stop(environment.id);
|
|
159
|
+
await env.stop();
|
|
50
160
|
}
|
|
51
161
|
```
|
|
52
162
|
|
|
163
|
+
```ts
|
|
164
|
+
// Later, or in every CI job: start from the seeded state.
|
|
165
|
+
const restored = await sm.environments.start({ snapshotId: 'snp_…' });
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Pass `snapshotId` instead of `apps` to `start()` or `create()`. Each app keeps the key it had when
|
|
169
|
+
captured; `snapshotAppInstanceKeys` restores only some of them. The new environment's
|
|
170
|
+
`sourceEnvironmentSnapshotId` and each app's `sourceAppInstanceSnapshotId` name the source.
|
|
171
|
+
|
|
172
|
+
- A snapshot is taken per app instance (`consistency: 'per-app-instance'`), not at one instant
|
|
173
|
+
across apps. It can only be taken while the environment is `running` and each app is `ready`.
|
|
174
|
+
- `readyTimeoutMs` bounds the wait for `ready` (10 minutes by default), as in `start()`. While the
|
|
175
|
+
API is busy (429 `snapshot-limit`), the capture is retried with the same idempotency key for up
|
|
176
|
+
to 60 seconds.
|
|
177
|
+
- The SDK never deletes a snapshot for you, and every capture error thrown after the snapshot was
|
|
178
|
+
created carries `snapshotId`. A wait past `readyTimeoutMs` throws `SnapshotTimeoutError`, and an
|
|
179
|
+
abort after the create leaves the capture running on the server: `sm.snapshots.waitUntilReady(err.snapshotId)`
|
|
180
|
+
waits again. A failed capture throws `SnapshotFailedError` and stays failed: capture again.
|
|
181
|
+
- Capturing needs `snapshots:create` and `snapshots:read`, restoring needs `snapshots:read`,
|
|
182
|
+
renaming needs `snapshots:create`, and deleting needs `snapshots:delete`. Keys created by members don't hold `snapshots:delete`, so
|
|
183
|
+
`delete` returns 403 for them.
|
|
184
|
+
|
|
185
|
+
`sm.snapshots` has the individual steps:
|
|
186
|
+
|
|
187
|
+
- `create(environmentId, input)` returns the API's answer at once, possibly still `capturing`, and
|
|
188
|
+
doesn't retry `snapshot-limit`. It needs only `snapshots:create`. `waitUntilReady(id)` waits for
|
|
189
|
+
`ready`. `capture(environmentId, input, options)` does both; `env.snapshot()` calls it.
|
|
190
|
+
- `list({ sourceEnvironmentId, cursor, limit })` returns one page, newest first, without deleted
|
|
191
|
+
snapshots. `get(id)` also returns deleted ones, with `deletedAt` set.
|
|
192
|
+
- `rename(id, { name, expectedVersion })` needs the snapshot's current `version`. A stale one, or a
|
|
193
|
+
deleted snapshot, is 409 `version-conflict`, and so is a retried rename whose first answer was
|
|
194
|
+
lost: `get(id)` shows the current name and version. `name: null` clears the name.
|
|
195
|
+
- `delete(id)` marks the snapshot deleted and returns it. A snapshot that is still `capturing`
|
|
196
|
+
can't be deleted (409 `not-ready`).
|
|
197
|
+
|
|
53
198
|
## Authentication
|
|
54
199
|
|
|
55
200
|
`new StateMachines()` reads the API key from `STATE_API_KEY`, but only where a global `process`
|
|
@@ -74,7 +219,8 @@ export default {
|
|
|
74
219
|
};
|
|
75
220
|
```
|
|
76
221
|
|
|
77
|
-
Calls that need a workspace (`environments.list
|
|
222
|
+
Calls that need a workspace (`environments.list`, `environments.create`, `snapshots.list`,
|
|
223
|
+
`snapshots.create` and `snapshots.capture`) use, in order:
|
|
78
224
|
|
|
79
225
|
1. `workspaceId` passed to that call
|
|
80
226
|
2. `workspaceId` passed to the constructor
|
|
@@ -86,24 +232,6 @@ A user session that has no workspace can't supply one, so those calls throw and
|
|
|
86
232
|
Other constructor options: `serverURL`, `fetch` (a custom fetch implementation), `httpClient`
|
|
87
233
|
(copied, never changed; not combined with `fetch`) and `timeoutMs` (see below).
|
|
88
234
|
|
|
89
|
-
## Environment lifecycle
|
|
90
|
-
|
|
91
|
-
1. `create(input)` returns the environment as soon as the API accepts it, before the apps boot.
|
|
92
|
-
2. `waitUntilReady(id)` polls until it is `running`. It throws `EnvironmentFailedError` if the
|
|
93
|
-
environment becomes `failed`, `stopping` or `stopped`, and `TimeoutError` after `timeoutMs`
|
|
94
|
-
(default 10 minutes). It **never stops the environment**, even when it throws.
|
|
95
|
-
3. `connect(id, appKey, { actorId, apiId })` returns a `Connection` to one app (see
|
|
96
|
-
[Connections](#connections)).
|
|
97
|
-
4. `stop(id)` stops it. Always stop in a `finally`.
|
|
98
|
-
|
|
99
|
-
`lifetimeMinutes` counts from when the environment starts, so boot time uses part of it. When it
|
|
100
|
-
runs out, the environment stops with `stopReason: 'expired'`. The default and maximum are in
|
|
101
|
-
`(await sm.credential.get()).limits.environmentLifetime`.
|
|
102
|
-
|
|
103
|
-
Both waits accept `{ timeoutMs, signal }`. Aborting the `signal` rejects the wait with
|
|
104
|
-
`signal.reason`. They poll at growing intervals of about 1 to 5 seconds, with random jitter, and
|
|
105
|
-
keep polling through transient failures.
|
|
106
|
-
|
|
107
235
|
## Retries and timeouts
|
|
108
236
|
|
|
109
237
|
Every API call retries on its own, with growing, jittered delays of up to about 5 seconds between
|
|
@@ -114,6 +242,7 @@ attempts:
|
|
|
114
242
|
| `502`, `503`, `504` | Yes | The API is saturated (`503`), or Cloudflare can't reach it for a moment (`502`, `504`). |
|
|
115
243
|
| No response: connection error or attempt timeout | Yes | The request may never have arrived. |
|
|
116
244
|
| `429` (`environment-limit`) | No | A quota on live environments. Stop one, then create again. |
|
|
245
|
+
| `429` (`snapshot-limit`) | Only by `snapshots.capture()` and `env.snapshot()`, for up to 60 seconds | The API is running as many captures as it can. |
|
|
117
246
|
| Any other status: other `4xx`, and other `5xx` | No | Not retried by the SDK. |
|
|
118
247
|
|
|
119
248
|
- A `Retry-After` header is honoured, up to 5 seconds.
|
|
@@ -123,7 +252,8 @@ attempts:
|
|
|
123
252
|
- `timeoutMs` (constructor option, default 60 seconds) limits **each attempt**. A timed-out
|
|
124
253
|
attempt is retried with a fresh timeout.
|
|
125
254
|
- A `signal` passed to a call aborts the whole call, including retries. The call rejects with a
|
|
126
|
-
`StateMachinesError` whose `status` is `null`.
|
|
255
|
+
`StateMachinesError` whose `status` is `null`. The waits, and `start()` while it waits, reject
|
|
256
|
+
with `signal.reason` instead (see [Full control](#full-control)).
|
|
127
257
|
|
|
128
258
|
```ts
|
|
129
259
|
const environment = await sm.environments.get('env_123', {
|
|
@@ -160,21 +290,12 @@ async function createOnce() {
|
|
|
160
290
|
const environment = await createOnce();
|
|
161
291
|
```
|
|
162
292
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
- every app's `disposal` is `complete`.
|
|
170
|
-
|
|
171
|
-
If any app's disposal is `failed` or `unknown`, it throws `DisposalIncompleteError` with the
|
|
172
|
-
environment. We recommend it in CI, so a run fails when cleanup can't be confirmed:
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
await sm.environments.stop(environment.id);
|
|
176
|
-
await sm.environments.waitUntilStopped(environment.id);
|
|
177
|
-
```
|
|
293
|
+
`start()` makes the same guarantee for its create step: call
|
|
294
|
+
`sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same input. A key
|
|
295
|
+
only recovers a failed create. Don't reuse the key of a `start()` whose wait failed: the replay
|
|
296
|
+
returns that same environment, which `start()` stopped (or, with your own key, left running for
|
|
297
|
+
you to stop). And don't replay an error whose `cause`
|
|
298
|
+
is a `TypeError`: the input was invalid and nothing was sent.
|
|
178
299
|
|
|
179
300
|
## Errors
|
|
180
301
|
|
|
@@ -188,14 +309,36 @@ Every failed API call throws a `StateMachinesError`:
|
|
|
188
309
|
| `fields` | `Record<string, string[]> \| null` | Per-field validation messages. |
|
|
189
310
|
| `retryAfter` | `number \| null` | Milliseconds the API asked you to wait. |
|
|
190
311
|
| `retryable` | `boolean` | `true` for a `502`, `503` or `504`, a connection error or an attempt timeout. `false` for caller aborts, request validation errors and responses that aren't valid JSON. |
|
|
191
|
-
| `idempotencyKey` | `string \| undefined` | Set on `environments.create` failures. |
|
|
312
|
+
| `idempotencyKey` | `string \| undefined` | Set on `environments.create` failures, including the create step of `start()`, and on `snapshots.create` and `snapshots.capture` failures. |
|
|
192
313
|
|
|
193
|
-
`status` is `null` when no HTTP response was received,
|
|
314
|
+
`status` is `null` when no HTTP response was received, when a response wasn't valid JSON, and when
|
|
315
|
+
the SDK rejected the input before sending (its `cause` is then a `TypeError`).
|
|
194
316
|
Error messages never include response bodies. The waits throw their own classes:
|
|
195
317
|
`EnvironmentFailedError` and `DisposalIncompleteError` (both with `environment`) and
|
|
196
318
|
`TimeoutError` (with `timeoutMs` and the last `environment` seen, or `null`). Check errors with
|
|
197
319
|
`instanceof`, not `name`: a platform abort is also named `TimeoutError`.
|
|
198
320
|
|
|
321
|
+
An SDK error that `start()` rethrows after a failed wait also carries `environmentId`, `stopped`
|
|
322
|
+
and, when the stop failed, `stopError`. The stop gets 30 seconds and doesn't wait for disposal.
|
|
323
|
+
Invalid `apps` fail before any request, as a `StateMachinesError` with `status: null`, the
|
|
324
|
+
`idempotencyKey`, and one of these messages: `Pass at least one app.`,
|
|
325
|
+
`App key "<key>" is used twice.` or `"<s>" can't be used as an app key; pass { key, appId } instead.`
|
|
326
|
+
Starting from a snapshot fails the same way with `Pass apps or snapshotId.`,
|
|
327
|
+
`Pass apps or snapshotId, not both.` or `Pass snapshotAppInstanceKeys with snapshotId.`
|
|
328
|
+
|
|
329
|
+
Snapshot waits throw `SnapshotFailedError`, with the `snapshot` (its `errorCode` is
|
|
330
|
+
`capture-failed`, or `capture-interrupted` when the API restarted mid-capture), and
|
|
331
|
+
`SnapshotTimeoutError`, with `timeoutMs` and the last `snapshot` seen, or `null`. Both carry
|
|
332
|
+
`snapshotId`. From `capture`, they and any `StateMachinesError` thrown after the snapshot was
|
|
333
|
+
created also carry `snapshotId` and `idempotencyKey`.
|
|
334
|
+
|
|
335
|
+
| Code | Status | Meaning |
|
|
336
|
+
| --- | --- | --- |
|
|
337
|
+
| `snapshot-limit` | 429 | The API is running as many captures as it can. `capture` retries it for up to 60 seconds; `create` doesn't. |
|
|
338
|
+
| `not-ready` | 409 | Capture: the environment isn't `running` or an app isn't `ready`. Restore: the snapshot isn't `ready` yet. Delete: the snapshot is still `capturing`. |
|
|
339
|
+
| `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`. |
|
|
340
|
+
| `release-unavailable` | 422 | Restore: no release on this deployment matches the snapshot's apps. |
|
|
341
|
+
|
|
199
342
|
```ts
|
|
200
343
|
import { ErrorCode, StateMachinesError } from '@usestatemachines/sdk';
|
|
201
344
|
|
|
@@ -212,8 +355,8 @@ try {
|
|
|
212
355
|
|
|
213
356
|
## Pagination
|
|
214
357
|
|
|
215
|
-
`environments.list`, `environments.events` and `
|
|
216
|
-
`{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`:
|
|
358
|
+
`environments.list`, `environments.events`, `workspaces.events` and `snapshots.list` return one
|
|
359
|
+
page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`:
|
|
217
360
|
|
|
218
361
|
```ts
|
|
219
362
|
let cursor: string | undefined;
|
|
@@ -231,9 +374,16 @@ do {
|
|
|
231
374
|
|
|
232
375
|
## Connections
|
|
233
376
|
|
|
234
|
-
`connect(
|
|
235
|
-
|
|
236
|
-
|
|
377
|
+
`connect()` needs the app to be ready, which `start()` guarantees. It takes `{ apiId, actorId }`
|
|
378
|
+
and throws a `TypeError` before revealing any token when a choice is missing or wrong:
|
|
379
|
+
|
|
380
|
+
- `apiId` is required when the app has several API profiles, as Salesforce does:
|
|
381
|
+
`Choose one API for <appKey>: <ids>.` There is no default API.
|
|
382
|
+
- `actorId` defaults to the actor of the environment's declared default connection, or else the
|
|
383
|
+
app's first actor (for Salesforce, the administrator). One that isn't among the app's actors
|
|
384
|
+
throws `Unknown actor <id> for <appKey>. Choose one of: <ids>.`
|
|
385
|
+
|
|
386
|
+
It returns a `Connection` with `baseUrl`, `environmentId`, `appKey`, `actorId`, `api` and
|
|
237
387
|
`expiresAt`, and these ways to call the app:
|
|
238
388
|
|
|
239
389
|
- `headers()` returns `X-State-Environment-Token` plus the app's actor header, such as
|
|
@@ -258,10 +408,7 @@ release does), takes the actor token in the envelope's `SessionHeader/sessionId`
|
|
|
258
408
|
value you put in the envelope:
|
|
259
409
|
|
|
260
410
|
```ts
|
|
261
|
-
const soap = await
|
|
262
|
-
actorId: actor.id, // from getConnection, as in the quickstart
|
|
263
|
-
apiId: 'soap-partner',
|
|
264
|
-
});
|
|
411
|
+
const soap = await env.connect('salesforce', { apiId: 'soap-partner' });
|
|
265
412
|
const envelope = `<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com">
|
|
266
413
|
<env:Header><SessionHeader><sessionId>${soap.actorToken()}</sessionId></SessionHeader></env:Header>
|
|
267
414
|
<env:Body><undelete><ids>001000000000000AAA</ids></undelete></env:Body>
|
|
@@ -279,12 +426,9 @@ as jsforce only sends it if you configure custom headers. Prefer `Connection.fet
|
|
|
279
426
|
don't let the client follow redirects, which would forward the tokens to another host:
|
|
280
427
|
|
|
281
428
|
```ts
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
});
|
|
286
|
-
const response = await fetch(new URL('services/data/v67.0/limits', crm.baseUrl), {
|
|
287
|
-
headers: crm.headers(),
|
|
429
|
+
const salesforce = await env.connect('salesforce', { apiId: 'rest' });
|
|
430
|
+
const response = await fetch(new URL('services/data/v67.0/limits', salesforce.baseUrl), {
|
|
431
|
+
headers: salesforce.headers(),
|
|
288
432
|
redirect: 'manual',
|
|
289
433
|
});
|
|
290
434
|
```
|
|
@@ -305,6 +449,52 @@ TypeScript consumers need `lib: ["DOM"]` or `@types/node`, for the `fetch`, `Req
|
|
|
305
449
|
version.
|
|
306
450
|
- The API may add error codes and enum values, such as a new status or stop reason. Enums are
|
|
307
451
|
open: handle values you don't recognise.
|
|
452
|
+
- 0.3.0 adds the snapshot APIs and keeps every 0.2.0 call. An input with neither `apps` nor
|
|
453
|
+
`snapshotId` now fails with `Pass apps or snapshotId.` `Environment` and its apps gain
|
|
454
|
+
`sourceEnvironmentSnapshotId` and `sourceAppInstanceSnapshotId` (`null` unless restored), so
|
|
455
|
+
object literals typed as `Environment` need them.
|
|
456
|
+
- 0.2.0 adds APIs and keeps every 0.1.x call, with these deliberate changes:
|
|
457
|
+
- `connect()` without `actorId` now succeeds when an app has several actors, acting as the
|
|
458
|
+
default actor (for Salesforce, the administrator, with full access).
|
|
459
|
+
- An empty `actorId` (`''`) now throws `Unknown actor`, instead of acting as an app's only actor.
|
|
460
|
+
- `create()` rejects an empty `apps` list or a repeated app key before sending a request, as a
|
|
461
|
+
`StateMachinesError` with `status: null`.
|
|
462
|
+
- `connect()`'s error messages now name the app: `Unknown actor <id> for <appKey>. …` and
|
|
463
|
+
`Choose one API for <appKey>: …`.
|
|
464
|
+
|
|
465
|
+
## Known limitations
|
|
466
|
+
|
|
467
|
+
- Vendor clients such as jsforce don't send `X-State-Environment-Token` unless you configure
|
|
468
|
+
custom headers, so the gateway rejects them. Use `Connection.fetch`, or pass `headers()` as
|
|
469
|
+
shown in [Connections](#connections).
|
|
470
|
+
- There is no default API profile: pass `apiId` whenever an app has more than one.
|
|
471
|
+
- Without a declared default connection, the default actor is the first one the release lists;
|
|
472
|
+
releases don't declare a default actor yet.
|
|
473
|
+
- `await using` isn't supported. Stop the environment in `finally`.
|
|
474
|
+
|
|
475
|
+
## What's new in 0.3.0
|
|
476
|
+
|
|
477
|
+
- `sm.snapshots`: `capture`, `create`, `waitUntilReady`, `get`, `list`, `rename` and `delete`
|
|
478
|
+
(see [Snapshots](#snapshots)).
|
|
479
|
+
- `env.snapshot()` captures a running environment and waits until the snapshot is `ready`.
|
|
480
|
+
- `start({ snapshotId })` and `create({ snapshotId })` start an environment from a snapshot.
|
|
481
|
+
Environments carry `sourceEnvironmentSnapshotId`, and apps `sourceAppInstanceSnapshotId`.
|
|
482
|
+
- `environments.list({ snapshotId })` lists the environments started from a snapshot.
|
|
483
|
+
- `SnapshotFailedError` and `SnapshotTimeoutError`.
|
|
484
|
+
|
|
485
|
+
## What's new in 0.2.0
|
|
486
|
+
|
|
487
|
+
- `sm.environments.start()` creates an environment and waits until it's running. It returns a
|
|
488
|
+
`RunningEnvironment` with `connect`, `actors`, `stop` and `waitUntilStopped`.
|
|
489
|
+
- String apps: `apps: ['salesforce']` means `[{ key: 'salesforce', appId: 'salesforce' }]`, in
|
|
490
|
+
`start()` and `create()`.
|
|
491
|
+
- `actors(appKey)` lists an app's actors in the release's order.
|
|
492
|
+
- `actorId` is optional in `connect()`. It defaults to the environment's declared default
|
|
493
|
+
connection's actor, or else the app's first actor.
|
|
494
|
+
- When readiness fails, `start()` stops the environment before it rethrows.
|
|
495
|
+
- Cancelling a call now takes effect immediately (fixes 0.1.1). A `signal` used to reach an
|
|
496
|
+
in-flight request only when its attempt timeout (60 seconds by default) fired. An abort during
|
|
497
|
+
a retry delay still takes effect when the delay ends.
|
|
308
498
|
|
|
309
499
|
## License
|
|
310
500
|
|
package/agents/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: state-machines
|
|
3
|
-
description: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a sandboxed copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK -
|
|
3
|
+
description: Use when an agent or test needs a disposable replica of a SaaS app such as Salesforce, to run integration tests or try API calls against a sandboxed copy instead of a real org. Covers the @usestatemachines/sdk TypeScript SDK - starting environments, choosing an actor, snapshots, cleanup, and errors such as environment-limit and release-unavailable. Also use when the user mentions State Machines, STATE_API_KEY or @usestatemachines/sdk.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
Read `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it matches the installed version. If the package is missing, run `npm install @usestatemachines/sdk`.
|
|
@@ -8,83 +8,70 @@ Read `node_modules/@usestatemachines/sdk/README.md` before relying on memory; it
|
|
|
8
8
|
## Workflow
|
|
9
9
|
|
|
10
10
|
1. Check that `STATE_API_KEY` is set without printing it (`test -n "$STATE_API_KEY"`). If it isn't, ask the user to set it; the SDK doesn't read `.env` files.
|
|
11
|
-
2.
|
|
12
|
-
3. Open a `try` right after `
|
|
13
|
-
4. `
|
|
14
|
-
5.
|
|
15
|
-
6.
|
|
16
|
-
7. In `finally`, `await sm.environments.stop(environment.id)`. In CI, then await `sm.environments.waitUntilStopped(environment.id)`.
|
|
11
|
+
2. Start the environment with only the apps the task needs: `const env = await sm.environments.start({ apps: ['salesforce'] })`. Log `await sm.catalog.get()` for each app's ID and the actors and API profiles its release offers; don't guess IDs. A string app means `{ key: s, appId: s }`; use `{ key, appId }` for your own key. `start()` waits until the environment is `running`. If that fails, it stops the environment and rethrows; an `EnvironmentFailedError` or `TimeoutError` carries `err.environmentId` and `err.stopped`.
|
|
12
|
+
3. Open a `try` right after `start()` returns, and call `env.stop()` in its `finally`.
|
|
13
|
+
4. Connect through one API profile. Salesforce offers several and has no default, so pass `apiId`: `env.connect('salesforce', { apiId: 'rest' })`. This acts as the environment's declared default connection's actor, or else the app's first actor, the administrator. To act with another user's permissions, choose one from `await env.actors('salesforce')` and pass its `id` as `actorId`. Salesforce actor IDs are its user IDs, such as `005…`, never a role name, so read them instead of hard-coding one.
|
|
14
|
+
5. Call the app with the connection's `fetch`. A leading `/` means the root of `baseUrl`.
|
|
15
|
+
6. In `finally`, `await env.stop()`. In CI, then `await env.waitUntilStopped()`.
|
|
17
16
|
|
|
18
17
|
The whole flow, as the package ships it. It is ESM with top-level `await`: save it as `state-quickstart.mts` and run `npx tsx state-quickstart.mts`.
|
|
19
18
|
|
|
20
19
|
```ts
|
|
21
20
|
import { StateMachines } from '@usestatemachines/sdk';
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
// A failed create carries err.idempotencyKey: see "Recovering a failed create" in the README.
|
|
27
|
-
const environment = await sm.environments.create({
|
|
28
|
-
name: 'quickstart',
|
|
29
|
-
// The default Salesforce app; sm.catalog.get() lists every app.
|
|
30
|
-
apps: [{ key: 'crm', appId: 'salesforce' }],
|
|
31
|
-
});
|
|
22
|
+
const sm = new StateMachines(); // reads STATE_API_KEY
|
|
23
|
+
const env = await sm.environments.start({ apps: ['salesforce'] });
|
|
32
24
|
|
|
33
25
|
try {
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// The connection lists the actors; this acts as the first.
|
|
38
|
-
const { actors } = await sm.environments.getConnection(environment.id, 'crm');
|
|
39
|
-
const [actor] = actors;
|
|
40
|
-
|
|
41
|
-
if (!actor) {
|
|
42
|
-
throw new Error('The Salesforce app has no actors.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const crm = await sm.environments.connect(environment.id, 'crm', {
|
|
46
|
-
actorId: actor.id,
|
|
47
|
-
apiId: 'rest',
|
|
48
|
-
});
|
|
49
|
-
const response = await crm.fetch('/services/data/v67.0/limits');
|
|
50
|
-
|
|
51
|
-
console.log(response.status, await response.json());
|
|
26
|
+
const salesforce = await env.connect('salesforce', { apiId: 'rest' });
|
|
27
|
+
const res = await salesforce.fetch('/services/data/v67.0/limits');
|
|
28
|
+
console.log(res.status, await res.json());
|
|
52
29
|
} finally {
|
|
53
|
-
|
|
54
|
-
await sm.environments.stop(environment.id);
|
|
30
|
+
await env.stop();
|
|
55
31
|
}
|
|
56
32
|
```
|
|
57
33
|
|
|
58
34
|
## Rules
|
|
59
35
|
|
|
60
36
|
- Always stop in `finally`. `waitUntilStopped` confirms every app was disposed; use it in CI so a run fails when cleanup can't be confirmed.
|
|
61
|
-
-
|
|
37
|
+
- To keep a failed environment for inspection, use the full-control path: `sm.environments.create`, then `sm.environments.waitUntilReady(environment.id)`, then `sm.environments.connect(environment.id, key, options)`. Nothing on that path stops the environment for you.
|
|
38
|
+
- Lifetime counts from the start, including boot. It defaults to 15 minutes, or the deployment maximum if that is lower: `(await sm.credential.get()).limits.environmentLifetime`. Set `lifetimeMinutes` in `start` or `create` for longer runs.
|
|
62
39
|
- Live environments are capped per organization: `limits.liveEnvironments` from `sm.credential.get()`. Stop the ones you no longer need.
|
|
63
40
|
- Never print, log or commit `STATE_API_KEY`, tokens, or `headers()` or `actorToken()` output.
|
|
64
41
|
- Salesforce's SOAP profile (`soap-partner`) takes the actor token in the envelope, not a header: put `connection.actorToken()` in `SessionHeader/sessionId`. For it, `headers()` carries only `X-State-Environment-Token`.
|
|
65
42
|
- `connection.fetch` never sends your API key, never follows redirects and only reaches URLs under `baseUrl`.
|
|
66
43
|
- Runtimes: Node.js 20.20 or later (22 or 24 recommended), and Cloudflare Workers. ESM only. TypeScript needs `lib: ["DOM"]` or `@types/node`. Never ship an API key to a browser.
|
|
67
44
|
- In Cloudflare Workers, pass the key explicitly: `new StateMachines({ apiKey: env.STATE_API_KEY })`, and bound calls with `{ signal: AbortSignal.timeout(20_000) }`, since retries can take minutes.
|
|
68
|
-
- `environments.list`, `environments.events` and `
|
|
69
|
-
-
|
|
45
|
+
- `environments.list`, `environments.events`, `workspaces.events` and `snapshots.list` return one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`.
|
|
46
|
+
- To reuse seeded data, `await env.snapshot({ name })` once, then `sm.environments.start({ snapshotId })` in place of `apps`. Capturing needs `snapshots:create` and `snapshots:read`. Snapshots are never deleted automatically, not even failed ones.
|
|
47
|
+
- Check errors with `instanceof` (`StateMachinesError`, `EnvironmentFailedError`, `DisposalIncompleteError`, `TimeoutError`, `SnapshotFailedError`, `SnapshotTimeoutError`), not by `name`.
|
|
70
48
|
|
|
71
49
|
## Recovering a failed create
|
|
72
50
|
|
|
73
|
-
The SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If `create` still throws
|
|
51
|
+
The SDK already retries `502`, `503`, `504` and connection errors with one idempotency key. If `start()` or `create` still throws with `err.idempotencyKey`, the create failed: don't call it again as a new request, which can start a second environment. Call `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same `input`. Don't replay when `err.status` is `null` and `err.cause` is a `TypeError`: the input was invalid and nothing was sent; fix the input instead. A key only recovers a failed create: don't reuse the key of a `start()` whose wait failed, because the replay returns that stopped environment. With your own key, `start()` doesn't stop the environment on failure, because another `start()` with that key may be using it: the error carries `err.environmentId`, `err.stopped` is `false` and there is no `err.stopError`. Stop it only once nothing else needs it. It returns the environment the first call created, or creates it if that call never arrived. A different input fails with `idempotency-conflict`. Keys are kept for a day.
|
|
74
52
|
|
|
75
53
|
## Troubleshooting
|
|
76
54
|
|
|
77
55
|
| Symptom | Cause | Fix |
|
|
78
56
|
| --- | --- | --- |
|
|
79
|
-
| `release-unavailable`, 422 | Unknown or retired `appId` or `appReleaseId`. | Check the IDs against `sm.catalog.get()`. Omit `appReleaseId` for the default release. |
|
|
57
|
+
| `release-unavailable`, 422 | Unknown or retired `appId` or `appReleaseId`. A string app is used as the `appId`. | Check the IDs against `sm.catalog.get()`. Omit `appReleaseId` for the default release. |
|
|
80
58
|
| `release-unavailable`, 503, or a failed app with that `errorCode` | This deployment doesn't serve the release. The SDK retries a 503 within a 10-minute budget. | Omit `appReleaseId`. If it persists, report the `requestId`. Pass a `signal` to bound the wait. |
|
|
81
|
-
| `environment-limit`, 429 | The organization is at its live-environment cap. Not retried. | `sm.environments.list({ status: 'live' })`, stop one you no longer need, then
|
|
59
|
+
| `environment-limit`, 429 | The organization is at its live-environment cap. Not retried. | `sm.environments.list({ status: 'live' })`, stop one you no longer need, then start again. |
|
|
82
60
|
| `lifetime-too-long`, 422 | `lifetimeMinutes` is above the maximum. | Use at most `limits.environmentLifetime.maximumMinutes`. |
|
|
83
|
-
| `TypeError:
|
|
84
|
-
| `TypeError:
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
61
|
+
| `TypeError: Unknown actor <id> for <appKey>. Choose one of: <ids>.` | The `actorId` isn't one of the app's actors. No token was revealed. | Pass an `id` from `await env.actors(appKey)`, or omit `actorId` to act as the default actor. |
|
|
62
|
+
| `TypeError: Choose one API for <appKey>: <ids>.` | The app has several API profiles and `apiId` is missing or unknown. Salesforce always needs one. | Pass `apiId` from the listed IDs, such as `rest`. |
|
|
63
|
+
| `start()` rejects with `EnvironmentFailedError` or `TimeoutError` | The environment failed, or wasn't `running` within `readyTimeoutMs` (10 minutes by default). If `err.stopped` is `true`, `start()` already stopped it. | Read `err.environment` (`null` on a `TimeoutError` that saw no read): its `stopReason` and each app's `errorCode`. If `err.stopped` is `false`, read `err.stopError` and call `sm.environments.stop(err.environmentId)`. To keep a failed environment, use the full-control path. |
|
|
64
|
+
| `StateMachinesError` (`status` null) for invalid `apps`: `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.` | Empty list, duplicate key, or a string that isn't a valid key. No request was sent. | Pass one to ten apps with unique keys; use `{ key, appId }` when the ID isn't a valid key. |
|
|
65
|
+
| `TypeError: App ... is not ready to connect` | On the full-control path, connect() ran before the app was `ready`. | Await `waitUntilReady` first, or use `start()`. |
|
|
66
|
+
| `connection-unavailable`, 409 | The app stopped, or the environment is no longer `running`. | Check `sm.environments.get(env.id)`; if it stopped or expired, start a new one. |
|
|
67
|
+
| `EnvironmentFailedError` from `waitUntilReady` | On the full-control path, the environment became `failed`, `stopping` or `stopped`. | Read `err.environment.stopReason` and each app's `errorCode`. Still stop it in `finally`. |
|
|
87
68
|
| A vendor client such as jsforce is rejected by the gateway | The gateway needs `X-State-Environment-Token` on every request; jsforce only sends it with custom headers configured. | Prefer `connection.fetch`. If you pass `headers()` to another client, send them only to `baseUrl` and disable redirects. |
|
|
69
|
+
| `not-ready`, 409 | Capture: the environment isn't `running` or an app isn't `ready`. Restore: the snapshot isn't `ready` yet. Delete: the snapshot is still `capturing`. | Capture from the handle `start()` returns. Before a restore or delete, `await sm.snapshots.waitUntilReady(snapshotId)`. |
|
|
70
|
+
| `snapshot-limit`, 429 | The API is running as many captures as it can. `capture` and `env.snapshot()` already retried it for 60 seconds. | Wait, then capture again. |
|
|
71
|
+
| `SnapshotFailedError` | The capture failed. `err.snapshot.errorCode` is `capture-failed`, or `capture-interrupted` when the API restarted mid-capture. The snapshot is kept. | Capture again with a new idempotency key (the default); the old key returns the same failed snapshot. |
|
|
72
|
+
| `SnapshotTimeoutError` | The snapshot wasn't `ready` within `readyTimeoutMs`. The capture continues on the server. | `await sm.snapshots.waitUntilReady(err.snapshotId)`. |
|
|
73
|
+
| `forbidden`, 403, from `sm.snapshots.delete` | The key lacks `snapshots:delete`. Keys created by members never hold it. | Use a key whose creator holds `snapshots:delete`, or keep the snapshot. |
|
|
74
|
+
| `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. |
|
|
88
75
|
| `TypeError: Pass apiKey or set STATE_API_KEY.` | No key in the environment. | Ask the user to set `STATE_API_KEY`; in Workers, pass `apiKey`. |
|
|
89
76
|
|
|
90
77
|
More: https://state-web.usestatemachines.workers.dev/llms.txt and https://www.npmjs.com/package/@usestatemachines/sdk
|