@usestatemachines/sdk 0.4.0 → 0.6.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 (270) hide show
  1. package/README.md +177 -470
  2. package/agents/SKILL.md +38 -47
  3. package/agents/index.js +7 -7
  4. package/agents/llms.txt +49 -45
  5. package/agents/prompt.md +11 -11
  6. package/esm/extensions/client.d.ts +105 -116
  7. package/esm/extensions/client.js +162 -309
  8. package/esm/extensions/credentials.d.ts +13 -0
  9. package/esm/extensions/credentials.js +73 -0
  10. package/esm/extensions/environment.d.ts +25 -0
  11. package/esm/extensions/environment.js +1 -0
  12. package/esm/extensions/errors.d.ts +42 -75
  13. package/esm/extensions/errors.js +125 -110
  14. package/esm/extensions/index.d.ts +6 -11
  15. package/esm/extensions/index.js +2 -7
  16. package/esm/extensions/wait.d.ts +17 -18
  17. package/esm/extensions/wait.js +60 -56
  18. package/esm/extensions/yaml.d.ts +6 -0
  19. package/esm/extensions/yaml.js +137 -0
  20. package/esm/funcs/{catalog-release.d.ts → apps-list.d.ts} +1 -1
  21. package/esm/funcs/apps-list.js +91 -0
  22. package/esm/funcs/{credential-get.d.ts → apps-versions.d.ts} +1 -1
  23. package/esm/funcs/{workspaces-events.js → apps-versions.js} +6 -7
  24. package/esm/funcs/{environments-get-connection.d.ts → audit-events-list.d.ts} +1 -1
  25. package/esm/funcs/audit-events-list.js +93 -0
  26. package/esm/funcs/{workspaces-events.d.ts → environments-connect.d.ts} +1 -1
  27. package/esm/funcs/{environments-reveal.js → environments-connect.js} +7 -6
  28. package/esm/funcs/environments-create.d.ts +2 -1
  29. package/esm/funcs/environments-create.js +5 -10
  30. package/esm/funcs/{environments-events.d.ts → environments-delete.d.ts} +1 -1
  31. package/esm/funcs/{environments-stop.js → environments-delete.js} +8 -6
  32. package/esm/funcs/environments-get.d.ts +2 -1
  33. package/esm/funcs/environments-get.js +2 -1
  34. package/esm/funcs/environments-list.d.ts +1 -1
  35. package/esm/funcs/environments-list.js +11 -16
  36. package/esm/funcs/environments-pause.d.ts +12 -0
  37. package/esm/funcs/{environments-suspend.js → environments-pause.js} +7 -5
  38. package/esm/funcs/environments-resume.d.ts +2 -1
  39. package/esm/funcs/environments-resume.js +3 -1
  40. package/esm/funcs/environments-update.d.ts +12 -0
  41. package/esm/funcs/{get-environment-position.js → environments-update.js} +10 -8
  42. package/esm/funcs/me.d.ts +11 -0
  43. package/esm/funcs/{catalog-get.js → me.js} +5 -5
  44. package/esm/funcs/{catalog-get.d.ts → requests-body.d.ts} +1 -1
  45. package/esm/funcs/{environments-get-connection.js → requests-body.js} +8 -8
  46. package/esm/funcs/requests-get.d.ts +12 -0
  47. package/esm/funcs/{catalog-release.js → requests-get.js} +7 -6
  48. package/esm/funcs/{environments-stop.d.ts → requests-list.d.ts} +1 -1
  49. package/esm/funcs/{list-environment-history.js → requests-list.js} +15 -12
  50. package/esm/funcs/requests-stats.d.ts +12 -0
  51. package/esm/funcs/{environments-events.js → requests-stats.js} +15 -14
  52. package/esm/funcs/snapshots-create.d.ts +12 -0
  53. package/esm/funcs/{snapshots-capture.js → snapshots-create.js} +6 -11
  54. package/esm/funcs/snapshots-delete.d.ts +2 -1
  55. package/esm/funcs/snapshots-delete.js +2 -1
  56. package/esm/funcs/snapshots-get.d.ts +2 -1
  57. package/esm/funcs/snapshots-get.js +2 -1
  58. package/esm/funcs/snapshots-list.d.ts +1 -1
  59. package/esm/funcs/snapshots-list.js +9 -13
  60. package/esm/funcs/snapshots-update.d.ts +2 -1
  61. package/esm/funcs/snapshots-update.js +2 -1
  62. package/esm/funcs/workspaces-get.d.ts +2 -1
  63. package/esm/funcs/workspaces-get.js +2 -1
  64. package/esm/funcs/workspaces-list.d.ts +1 -1
  65. package/esm/funcs/workspaces-list.js +3 -1
  66. package/esm/lib/config.d.ts +3 -3
  67. package/esm/lib/config.js +3 -3
  68. package/esm/models/actor.d.ts +2 -3
  69. package/esm/models/actor.js +2 -3
  70. package/esm/models/{api-profile.d.ts → api.d.ts} +4 -4
  71. package/esm/models/{api-profile.js → api.js} +4 -5
  72. package/esm/models/app.d.ts +9 -1
  73. package/esm/models/app.js +7 -1
  74. package/esm/models/{event.d.ts → audit-event.d.ts} +6 -6
  75. package/esm/models/{event.js → audit-event.js} +7 -6
  76. package/esm/models/body-preview.d.ts +31 -0
  77. package/esm/models/body-preview.js +34 -0
  78. package/esm/models/create-environment.d.ts +51 -0
  79. package/esm/models/create-environment.js +35 -0
  80. package/esm/models/credentials.d.ts +31 -0
  81. package/esm/models/credentials.js +31 -0
  82. package/esm/models/environment-failure.d.ts +22 -0
  83. package/esm/models/environment-failure.js +25 -0
  84. package/esm/models/environment-status.d.ts +14 -0
  85. package/esm/models/environment-status.js +15 -0
  86. package/esm/models/environment.d.ts +27 -27
  87. package/esm/models/environment.js +28 -27
  88. package/esm/models/error.d.ts +38 -0
  89. package/esm/models/error.js +39 -0
  90. package/esm/models/errors/{error-response.d.ts → error.d.ts} +5 -5
  91. package/esm/models/errors/{error-response.js → error.js} +4 -4
  92. package/esm/models/errors/index.d.ts +1 -1
  93. package/esm/models/errors/index.js +1 -1
  94. package/esm/models/index.d.ts +17 -22
  95. package/esm/models/index.js +17 -22
  96. package/esm/models/me.d.ts +66 -0
  97. package/esm/models/me.js +75 -0
  98. package/esm/models/operations/create-credentials.d.ts +25 -0
  99. package/esm/models/operations/create-credentials.js +20 -0
  100. package/esm/models/operations/create-environment.d.ts +14 -0
  101. package/esm/models/operations/create-environment.js +18 -0
  102. package/esm/models/operations/create-snapshot.d.ts +25 -0
  103. package/esm/models/operations/create-snapshot.js +25 -0
  104. package/esm/models/operations/delete-environment.d.ts +13 -0
  105. package/esm/models/operations/delete-environment.js +17 -0
  106. package/esm/models/operations/delete-snapshot.d.ts +0 -12
  107. package/esm/models/operations/delete-snapshot.js +0 -9
  108. package/esm/models/operations/get-environment.d.ts +0 -12
  109. package/esm/models/operations/get-environment.js +0 -9
  110. package/esm/models/operations/get-request-body.d.ts +21 -0
  111. package/esm/models/operations/get-request-body.js +18 -0
  112. package/esm/models/operations/get-request-stats.d.ts +51 -0
  113. package/esm/models/operations/get-request-stats.js +40 -0
  114. package/esm/models/operations/get-request.d.ts +11 -0
  115. package/esm/models/operations/get-request.js +11 -0
  116. package/esm/models/operations/get-snapshot.d.ts +0 -12
  117. package/esm/models/operations/get-snapshot.js +0 -9
  118. package/esm/models/operations/get-workspace.d.ts +0 -12
  119. package/esm/models/operations/get-workspace.js +0 -9
  120. package/esm/models/operations/index.d.ts +13 -15
  121. package/esm/models/operations/index.js +13 -15
  122. package/esm/models/operations/list-apps.d.ts +26 -0
  123. package/esm/models/operations/list-apps.js +23 -0
  124. package/esm/models/operations/list-audit-events.d.ts +30 -0
  125. package/esm/models/operations/list-audit-events.js +25 -0
  126. package/esm/models/operations/list-environments.d.ts +6 -21
  127. package/esm/models/operations/list-environments.js +3 -16
  128. package/esm/models/operations/list-requests.d.ts +68 -0
  129. package/esm/models/operations/list-requests.js +53 -0
  130. package/esm/models/operations/list-snapshots.d.ts +8 -6
  131. package/esm/models/operations/list-snapshots.js +4 -3
  132. package/esm/models/operations/list-versions.d.ts +28 -0
  133. package/esm/models/operations/list-versions.js +24 -0
  134. package/esm/models/operations/list-workspaces.d.ts +8 -4
  135. package/esm/models/operations/list-workspaces.js +7 -4
  136. package/esm/models/operations/pause-environment.d.ts +13 -0
  137. package/esm/models/operations/pause-environment.js +17 -0
  138. package/esm/models/operations/resume-environment.d.ts +2 -12
  139. package/esm/models/operations/resume-environment.js +8 -11
  140. package/esm/models/operations/update-environment.d.ts +29 -0
  141. package/esm/models/operations/update-environment.js +20 -0
  142. package/esm/models/operations/update-snapshot.d.ts +0 -14
  143. package/esm/models/operations/update-snapshot.js +0 -10
  144. package/esm/models/permission.d.ts +17 -0
  145. package/esm/models/permission.js +18 -0
  146. package/esm/models/principal.d.ts +20 -0
  147. package/esm/models/principal.js +24 -0
  148. package/esm/models/request-detail.d.ts +39 -0
  149. package/esm/models/request-detail.js +38 -0
  150. package/esm/models/request-stats.d.ts +26 -0
  151. package/esm/models/request-stats.js +31 -0
  152. package/esm/models/request.d.ts +30 -0
  153. package/esm/models/request.js +34 -0
  154. package/esm/models/snapshot-status.d.ts +13 -0
  155. package/esm/models/snapshot-status.js +14 -0
  156. package/esm/models/snapshot.d.ts +47 -0
  157. package/esm/models/snapshot.js +54 -0
  158. package/esm/models/version.d.ts +27 -0
  159. package/esm/models/version.js +31 -0
  160. package/esm/models/workspace.d.ts +2 -4
  161. package/esm/models/workspace.js +2 -4
  162. package/esm/sdk/apps.d.ts +6 -0
  163. package/esm/sdk/apps.js +15 -0
  164. package/esm/sdk/audit-events.d.ts +5 -0
  165. package/esm/sdk/audit-events.js +11 -0
  166. package/esm/sdk/environments.d.ts +8 -9
  167. package/esm/sdk/environments.js +19 -23
  168. package/esm/sdk/requests.d.ts +9 -0
  169. package/esm/sdk/requests.js +23 -0
  170. package/esm/sdk/sdk.d.ts +11 -11
  171. package/esm/sdk/sdk.js +16 -23
  172. package/esm/sdk/snapshots.d.ts +6 -5
  173. package/esm/sdk/snapshots.js +7 -7
  174. package/esm/sdk/workspaces.d.ts +1 -2
  175. package/esm/sdk/workspaces.js +0 -4
  176. package/esm/types/smart-union.js +1 -2
  177. package/openapi.json +1630 -2380
  178. package/package.json +15 -13
  179. package/esm/extensions/apps.d.ts +0 -18
  180. package/esm/extensions/apps.js +0 -39
  181. package/esm/extensions/connection.d.ts +0 -42
  182. package/esm/extensions/connection.js +0 -133
  183. package/esm/extensions/running-environment.d.ts +0 -46
  184. package/esm/extensions/running-environment.js +0 -66
  185. package/esm/funcs/create-environment-definition.d.ts +0 -11
  186. package/esm/funcs/create-environment-definition.js +0 -94
  187. package/esm/funcs/credential-get.js +0 -76
  188. package/esm/funcs/environments-reveal.d.ts +0 -11
  189. package/esm/funcs/environments-suspend.d.ts +0 -11
  190. package/esm/funcs/get-environment-exchange.d.ts +0 -11
  191. package/esm/funcs/get-environment-exchange.js +0 -96
  192. package/esm/funcs/get-environment-position.d.ts +0 -11
  193. package/esm/funcs/list-environment-history.d.ts +0 -11
  194. package/esm/funcs/snapshots-capture.d.ts +0 -11
  195. package/esm/models/app-instance-snapshot.d.ts +0 -15
  196. package/esm/models/app-instance-snapshot.js +0 -19
  197. package/esm/models/catalog.d.ts +0 -12
  198. package/esm/models/catalog.js +0 -15
  199. package/esm/models/connection-secret.d.ts +0 -30
  200. package/esm/models/connection-secret.js +0 -31
  201. package/esm/models/connection.d.ts +0 -40
  202. package/esm/models/connection.js +0 -44
  203. package/esm/models/create-environment-input.d.ts +0 -39
  204. package/esm/models/create-environment-input.js +0 -25
  205. package/esm/models/credential-kind.d.ts +0 -9
  206. package/esm/models/credential-kind.js +0 -10
  207. package/esm/models/credential.d.ts +0 -48
  208. package/esm/models/credential.js +0 -44
  209. package/esm/models/environment-app.d.ts +0 -53
  210. package/esm/models/environment-app.js +0 -57
  211. package/esm/models/environment-exchange.d.ts +0 -116
  212. package/esm/models/environment-exchange.js +0 -126
  213. package/esm/models/environment-lifetime.d.ts +0 -10
  214. package/esm/models/environment-lifetime.js +0 -14
  215. package/esm/models/environment-position.d.ts +0 -25
  216. package/esm/models/environment-position.js +0 -32
  217. package/esm/models/environment-snapshot.d.ts +0 -40
  218. package/esm/models/environment-snapshot.js +0 -42
  219. package/esm/models/error-response.d.ts +0 -58
  220. package/esm/models/error-response.js +0 -60
  221. package/esm/models/event-page.d.ts +0 -11
  222. package/esm/models/event-page.js +0 -16
  223. package/esm/models/history-body.d.ts +0 -28
  224. package/esm/models/history-body.js +0 -33
  225. package/esm/models/history-cause.d.ts +0 -48
  226. package/esm/models/history-cause.js +0 -63
  227. package/esm/models/history-position.d.ts +0 -11
  228. package/esm/models/history-position.js +0 -15
  229. package/esm/models/history-transaction.d.ts +0 -16
  230. package/esm/models/history-transaction.js +0 -20
  231. package/esm/models/operations/capture-snapshot.d.ts +0 -41
  232. package/esm/models/operations/capture-snapshot.js +0 -36
  233. package/esm/models/operations/create-environment-definition.d.ts +0 -225
  234. package/esm/models/operations/create-environment-definition.js +0 -149
  235. package/esm/models/operations/create-environments.d.ts +0 -27
  236. package/esm/models/operations/create-environments.js +0 -27
  237. package/esm/models/operations/get-catalog.d.ts +0 -13
  238. package/esm/models/operations/get-catalog.js +0 -13
  239. package/esm/models/operations/get-connection.d.ts +0 -25
  240. package/esm/models/operations/get-connection.js +0 -21
  241. package/esm/models/operations/get-credential.d.ts +0 -13
  242. package/esm/models/operations/get-credential.js +0 -13
  243. package/esm/models/operations/get-environment-exchange.d.ts +0 -25
  244. package/esm/models/operations/get-environment-exchange.js +0 -21
  245. package/esm/models/operations/get-environment-position.d.ts +0 -23
  246. package/esm/models/operations/get-environment-position.js +0 -20
  247. package/esm/models/operations/get-release.d.ts +0 -23
  248. package/esm/models/operations/get-release.js +0 -20
  249. package/esm/models/operations/list-environment-events.d.ts +0 -15
  250. package/esm/models/operations/list-environment-events.js +0 -13
  251. package/esm/models/operations/list-environment-history.d.ts +0 -28
  252. package/esm/models/operations/list-environment-history.js +0 -24
  253. package/esm/models/operations/list-workspace-events.d.ts +0 -15
  254. package/esm/models/operations/list-workspace-events.js +0 -13
  255. package/esm/models/operations/reveal-connection.d.ts +0 -59
  256. package/esm/models/operations/reveal-connection.js +0 -55
  257. package/esm/models/operations/stop-environment.d.ts +0 -23
  258. package/esm/models/operations/stop-environment.js +0 -20
  259. package/esm/models/operations/suspend-environment.d.ts +0 -23
  260. package/esm/models/operations/suspend-environment.js +0 -20
  261. package/esm/models/release-actor.d.ts +0 -11
  262. package/esm/models/release-actor.js +0 -15
  263. package/esm/models/release.d.ts +0 -37
  264. package/esm/models/release.js +0 -39
  265. package/esm/sdk/catalog.d.ts +0 -6
  266. package/esm/sdk/catalog.js +0 -15
  267. package/esm/sdk/credential.d.ts +0 -5
  268. package/esm/sdk/credential.js +0 -11
  269. package/esm/types/discriminated-union.d.ts +0 -24
  270. package/esm/types/discriminated-union.js +0 -60
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @usestatemachines/sdk
2
2
 
3
- TypeScript SDK for [State](https://usestatemachines.com): start disposable environments of replica
4
- apps, connect to them, snapshot their data, suspend and resume them, and stop them.
5
-
6
- - Node.js 20.20 or later (22 or 24 recommended), and Cloudflare Workers. ESM only.
7
- - Retries transient failures safely, including `create`, which is idempotent.
3
+ TypeScript SDK for [State Machines](https://usestatemachines.com): create disposable environments
4
+ of replica apps, connect to them, take snapshots of their data, pause and resume them, and delete
5
+ them.
8
6
 
9
7
  ## Install
10
8
 
@@ -17,534 +15,243 @@ npm install @usestatemachines/sdk
17
15
  ```ts
18
16
  import { StateMachines } from '@usestatemachines/sdk';
19
17
 
20
- const sm = new StateMachines(); // reads STATE_API_KEY
21
- const env = await sm.environments.start({ apps: ['salesforce'] });
18
+ const sm = new StateMachines(); // reads STATEMACHINES_API_KEY
19
+ const env = await sm.environments.create({ apps: ['salesforce'] });
22
20
 
23
21
  try {
24
- const salesforce = await env.connect('salesforce', { apiId: 'rest' });
22
+ const salesforce = await env.connect('salesforce');
25
23
  const res = await salesforce.fetch('/services/data/v67.0/limits');
26
24
  console.log(res.status, await res.json());
27
25
  } finally {
28
- await env.stop();
26
+ await env.delete();
29
27
  }
30
28
  ```
31
29
 
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
- `env.stop()` ends the environment and discards its data. If the wait fails after the environment
38
- was created, `start()` stops the environment, then rethrows the original error. The SDK's errors
39
- (`EnvironmentFailedError`, `TimeoutError` or a `StateMachinesError`; check with `instanceof`)
40
- carry `environmentId` and `stopped`. `stopped` is `true` when the stop request succeeded. When
41
- it's `false`, the stop wasn't confirmed: `stopError` says why, and you should call
42
- `sm.environments.stop(err.environmentId)`, which is safe to repeat. The exception is your own key,
43
- below.
44
-
45
- When you abort the `signal` during the wait, `start()` still stops the environment. If the stop
46
- succeeded, it rejects with your `signal.reason`, unchanged. If not, it rejects with a
47
- `StateMachinesError` whose `cause` leads to your reason, carrying `environmentId`, `stopped` and
48
- `stopError`, so you can still clean up.
49
-
50
- If you pass your own `idempotencyKey`, `start()` doesn't stop the environment on failure: another
51
- `start()` with the same key gets the same environment and may be using it. The error carries
52
- `environmentId` with `stopped: false` and no `stopError`. Stop it only once no other `start()` with
53
- that key needs it.
30
+ `create` waits until the environment is `running`. If an app fails to start, the server deletes
31
+ the environment. [Waiting](#waiting) lists what each method throws.
54
32
 
55
- If the create step fails, including an abort, `start()` stops nothing and rejects with a
56
- `StateMachinesError` that carries `idempotencyKey`. The API may still have created the
57
- environment, so replay with that key (see
58
- [Recovering a failed create](#recovering-a-failed-create)) and stop what it returns.
59
-
60
- ## Choosing an actor
33
+ ## The client
61
34
 
62
35
  ```ts
63
- const env = await sm.environments.start({ apps: ['salesforce'] });
64
-
65
- try {
66
- // The app's users. connect() uses the default actor unless you pick one.
67
- const actors = await env.actors('salesforce');
68
- const rep = actors.find((actor) => actor.summary.startsWith('Sales Representative'));
69
-
70
- if (!rep) {
71
- throw new Error('No sales representative in this release.');
72
- }
73
-
74
- // Act with a sales rep's permissions to test what they can and can't do.
75
- const salesforce = await env.connect('salesforce', { actorId: rep.id, apiId: 'rest' });
76
- const res = await salesforce.fetch('/services/data/v67.0/sobjects/Account');
77
- console.log(res.status);
78
- } finally {
79
- await env.stop();
80
- }
36
+ const sm = new StateMachines({
37
+ apiKey: process.env.STATEMACHINES_API_KEY, // the default where `process` exists
38
+ baseUrl: 'https://api.usestatemachines.com', // the default
39
+ workspaceId: 'ws_...', // for member tokens; an API key has its own workspace
40
+ fetch, // defaults to the global fetch
41
+ attemptTimeoutMs: 60_000, // per HTTP attempt; a retry gets a fresh one
42
+ });
81
43
  ```
82
44
 
83
- Actors are the app's users, such as Salesforce user IDs (`005…`), and every request runs with the
84
- chosen actor's permissions. Without an `actorId`, `connect()` uses the environment's declared
85
- default connection, if it has one, or else the first actor the app lists, the administrator, so
86
- pick a less-privileged actor to test permission behaviour.
45
+ | Namespace | Methods |
46
+ | --- | --- |
47
+ | `sm` | `me()` |
48
+ | `sm.apps` | `list(query?)`, `versions(appId, query?)` |
49
+ | `sm.workspaces` | `list(query?)`, `get(id)` |
50
+ | `sm.environments` | `create(input, options?)`, `list(query?)`, `get(id)`, `update(id, { name?, labels? })`, `pause(id)`, `resume(id, options?)`, `delete(id)`, `wait(id, { status })`, `connect(id, app, { actorId? }?)` |
51
+ | `sm.snapshots` | `create({ environmentId, name? }, options?)`, `list(query?)`, `get(id)`, `update(id, { name })`, `delete(id)`, `wait(id, { status })` |
52
+ | `sm.requests` | `list(query)`, `get(id)`, `stats(query)`, `body(id, 'request' \| 'response')` |
53
+ | `sm.auditEvents` | `list(query?)` |
87
54
 
88
- ## Suspending and resuming
55
+ Every environment the SDK returns carries bound helpers: `env.connect(app)`, `env.pause()`,
56
+ `env.resume()`, `env.delete()`, `env.refresh()` and `env.snapshots.create({ name? })`. They are
57
+ not enumerable, so `JSON.stringify(env)` holds only the API's fields.
89
58
 
90
- Suspend an environment to end its sandboxes and keep its data, then resume it later on the same
91
- data:
92
-
93
- ```ts
94
- await env.suspend();
95
- await env.waitUntilSuspended();
96
- // Later, from the same handle or with sm.environments.resume(env.id):
97
- await env.resume();
98
- await env.waitUntilReady();
99
- ```
59
+ ## Apps
100
60
 
101
- - `env.suspend()` is `sm.environments.suspend(id)`. The environment reads `suspending`, then
102
- `suspended`, its apps read `suspended` and `suspendedAt` is set. Only a running environment
103
- whose apps are all ready can be suspended; anything else answers `409 not-ready`.
104
- - While suspended, the lifetime is paused: `expiresAt` moves out by the suspended time when it
105
- resumes, and the environment doesn't count toward the live-environment cap.
106
- - `resume()` queues it to run again; it answers `409 not-suspended` unless the environment is
107
- `suspended`, and `429 environment-limit` when the cap is reached. `waitUntilSuspended()` resolves
108
- on `suspended` and throws `EnvironmentFailedError` if the environment ends instead.
109
- - Suspending isn't cleanup: a suspended environment keeps its data until you stop it.
110
- `stop()` answers `409 suspend-in-progress` while it suspends.
111
-
112
- ## Full control
113
-
114
- Use `create`, `waitUntilReady` and `connect` directly when you want to run each step yourself, or
115
- keep an environment up after a failed boot so you can inspect it. You don't need this path for
116
- app keys, labels or `lifetimeMinutes`: `start()` takes the same input.
117
- **Nothing is stopped automatically on this path**: stop the environment yourself, even when a step
118
- throws.
61
+ A string names the app, and its name in the environment is the same string. Use the object form
62
+ for your own name, for two copies of one app, or for settings:
119
63
 
120
64
  ```ts
121
- const environment = await sm.environments.create({
122
- name: 'nightly',
123
- // key is your own name for the app; later calls take it.
124
- apps: [{ key: 'crm', appId: 'salesforce' }],
125
- lifetimeMinutes: 30,
126
- labels: { suite: 'nightly' },
65
+ await sm.environments.create({
66
+ name: 'Checkout tests',
67
+ labels: { team: 'crm' },
68
+ timeLimitMinutes: 30,
69
+ apps: [
70
+ 'salesforce',
71
+ {
72
+ app: 'salesforce',
73
+ name: 'partner-org',
74
+ settings: { settings: { soapApiLoginEnabled: false } },
75
+ },
76
+ ],
127
77
  });
78
+ ```
128
79
 
129
- try {
130
- await sm.environments.waitUntilReady(environment.id, { timeoutMs: 5 * 60_000 });
80
+ `settings` is passed to the app unchanged. Salesforce takes
81
+ `{ preset?: 'development', configure?, settings?: { soapApiLoginEnabled?: boolean } }`, where
82
+ `configure` can set the organization's `Name` and the administrator's `Username`. A refused value
83
+ fails the start with `failure.code: 'invalid_settings'`, and its `failure.message` names the
84
+ setting.
131
85
 
132
- const crm = await sm.environments.connect(environment.id, 'crm', { apiId: 'rest' });
133
- const res = await crm.fetch('/services/data/v67.0/limits');
134
- console.log(res.status);
135
- } finally {
136
- await sm.environments.stop(environment.id);
137
- // In CI: fail the run when cleanup can't be confirmed.
138
- await sm.environments.waitUntilStopped(environment.id);
139
- }
140
- ```
86
+ `sm.apps.list()` lists the apps. `sm.apps.versions(appId)` lists each version's APIs and actors.
87
+ A new environment runs each app's default version.
141
88
 
142
- 1. `create(input)` returns one `Environment` as soon as the API accepts it, before the apps boot.
143
- 2. `waitUntilReady(id)` polls until it is `running`. It throws `EnvironmentFailedError` if the
144
- environment becomes `failed`, `suspending`, `suspended`, `stopping` or `stopped`, and
145
- `TimeoutError` after `timeoutMs` (default 10 minutes). It **never stops the environment**,
146
- even when it throws.
147
- 3. `connect(id, appKey, { apiId, actorId })` returns a `Connection` to one app (see
148
- [Connections](#connections)).
149
- 4. `stop(id)` returns as soon as the API accepts it, and a second call also resolves.
150
- `waitUntilStopped(id)` resolves only when the environment is `stopped` or `failed` **and**
151
- every app's `disposal` is `complete`. If any disposal is `failed` or `unknown`, it throws
152
- `DisposalIncompleteError` with the environment.
153
-
154
- `lifetimeMinutes` counts from when the environment starts, so boot time uses part of it; time spent
155
- suspended doesn't count. When it runs out, the environment stops with `stopReason: 'expired'`. The
156
- default and maximum are in `(await sm.credential.get()).limits.environmentLifetime`.
157
-
158
- Both waits accept `{ timeoutMs, signal }`. Aborting the `signal` rejects the wait with
159
- `signal.reason`. They poll at growing intervals of about 1 to 5 seconds, with random jitter, and
160
- keep polling through transient failures.
161
-
162
- `start()` and `create()` take the same input, including `{ key, appId }` objects for a custom key
163
- or two instances of one app.
89
+ ## Credentials
164
90
 
165
- ## Snapshots
91
+ `env.connect(app, { actorId? })` returns the API's `Credentials`: `url`, `headers`, `token`,
92
+ `expiresAt` and `login`, plus `fetch(path, init?)`.
166
93
 
167
- A snapshot saves the data of a running environment's apps. Seed one environment, snapshot it, and
168
- start later environments from the snapshot instead of seeding each one.
94
+ - `fetch` joins `path` to `url`; a leading `/` means the root of the app. It adds `headers`
95
+ (`StateMachines-Environment-Token`), and puts `token` in the header of the API whose path
96
+ matches: `Authorization: Bearer` for Salesforce REST and Bulk 2.0.
97
+ - Salesforce SOAP takes the token in the envelope: put `credentials.token` in
98
+ `SessionHeader/sessionId` yourself.
99
+ - `fetch` never sends your API key, never follows redirects, never retries and only reaches paths
100
+ under `url`.
101
+ - `actorId` defaults to the app's default actor (for Salesforce, the administrator). Pick another
102
+ from the version's `actors` to act with that user's permissions.
103
+ - `login` holds what the vendor's own login call needs, when the version declares one for the
104
+ actor.
105
+ - `token` and `headers` are secrets. To use another client, such as jsforce, send `headers` and
106
+ the actor header only to `url`, with redirects off.
169
107
 
170
- ```ts
171
- const env = await sm.environments.start({ apps: ['salesforce'] });
108
+ Environments created with `tokenInUrl: true` carry the environment token in `url` instead, and
109
+ `headers` is empty.
172
110
 
173
- try {
174
- const salesforce = await env.connect('salesforce', { apiId: 'rest' });
175
- await salesforce.fetch('/services/data/v67.0/sobjects/Account', {
176
- method: 'POST',
177
- headers: { 'Content-Type': 'application/json' },
178
- body: JSON.stringify({ Name: 'Acme' }),
179
- });
180
-
181
- // Waits until the snapshot is ready.
182
- const snapshot = await env.snapshot({ name: 'seeded accounts' });
183
- console.log(snapshot.id, snapshot.sizeBytes);
184
- } finally {
185
- await env.stop();
186
- }
187
- ```
111
+ ## Pausing and resuming
188
112
 
189
- ```ts
190
- // Later, or in every CI job: start from the seeded state.
191
- const restored = await sm.environments.start({ snapshotId: 'snp_…' });
192
- ```
113
+ `env.pause()` takes effect when the API answers: the gateway refuses calls, the compute is
114
+ released and the data stays. The time limit stops counting. `env.resume()` boots the apps again
115
+ on the same data and waits until `running`. A paused or failed environment is deleted after one
116
+ day.
193
117
 
194
- Pass `snapshotId` instead of `apps` to `start()` or `create()`. Each app keeps the key it had when
195
- captured; `snapshotAppInstanceKeys` restores only some of them. The new environment's
196
- `sourceEnvironmentSnapshotId` and each app's `sourceAppInstanceSnapshotId` name the source.
197
-
198
- - A snapshot is taken per app instance (`consistency: 'app_instance'`), not at one instant
199
- across apps. It can only be taken while the environment is `running` and each app is `ready`.
200
- - `readyTimeoutMs` bounds the wait for `ready` (10 minutes by default), as in `start()`. While the
201
- API is busy (429 `snapshot-limit`), the capture is retried with the same idempotency key for up
202
- to 60 seconds.
203
- - The SDK never deletes a snapshot for you, and every capture error thrown after the snapshot was
204
- created carries `snapshotId`. A wait past `readyTimeoutMs` throws `SnapshotTimeoutError`, and an
205
- abort after the create leaves the capture running on the server: `sm.snapshots.waitUntilReady(err.snapshotId)`
206
- waits again. A failed capture throws `SnapshotFailedError` and stays failed: capture again.
207
- - Capturing needs `snapshots:create` and `snapshots:read`, restoring needs `snapshots:read`,
208
- renaming needs `snapshots:create`, and deleting needs `snapshots:delete`. Keys created by members don't hold `snapshots:delete`, so
209
- `delete` returns 403 for them.
210
-
211
- `sm.snapshots` has the individual steps:
212
-
213
- - `create(environmentId, input)` returns the API's answer at once, possibly still `capturing`, and
214
- doesn't retry `snapshot-limit`. It needs only `snapshots:create`. `waitUntilReady(id)` waits for
215
- `ready`. `capture(environmentId, input, options)` does both; `env.snapshot()` calls it.
216
- - `list({ sourceEnvironmentId, cursor, limit })` returns one page, newest first, without deleted
217
- snapshots. `get(id)` also returns deleted ones, with `deletedAt` set.
218
- - `rename(id, { name, expectedVersion })` needs the snapshot's current `version`. A stale one, or a
219
- deleted snapshot, is 409 `version-conflict`, and so is a retried rename whose first answer was
220
- lost: `get(id)` shows the current name and version. `name: null` clears the name.
221
- - `delete(id)` marks the snapshot deleted and returns it. A snapshot that is still `capturing`
222
- can't be deleted (409 `not-ready`).
223
-
224
- ## Authentication
225
-
226
- `new StateMachines()` reads the API key from `STATE_API_KEY`, but only where a global `process`
227
- exists. Without a key, the constructor throws a `TypeError`.
228
-
229
- In Cloudflare Workers, pass `apiKey` explicitly:
118
+ ## Snapshots
230
119
 
231
120
  ```ts
232
- import { StateMachines } from '@usestatemachines/sdk';
233
-
234
- export default {
235
- async fetch(_request: Request, env: { STATE_API_KEY: string }) {
236
- const sm = new StateMachines({ apiKey: env.STATE_API_KEY });
237
- // Bound the call: retries could otherwise hold the request for minutes.
238
- const { data } = await sm.environments.list(
239
- { status: 'live' },
240
- { signal: AbortSignal.timeout(20_000) },
241
- );
242
-
243
- return Response.json(data.map(({ id, status }) => ({ id, status })));
244
- },
245
- };
121
+ const seeded = await env.snapshots.create({ name: 'seeded' });
122
+ const copy = await sm.environments.create({ snapshotId: seeded.id });
246
123
  ```
247
124
 
248
- Calls that need a workspace (`environments.list`, `environments.create`, `snapshots.list`,
249
- `snapshots.create` and `snapshots.capture`) use, in order:
250
-
251
- 1. `workspaceId` passed to that call
252
- 2. `workspaceId` passed to the constructor
253
- 3. the workspace of the API key, read once from `sm.credential.get()` and cached
254
-
255
- A user session that has no workspace can't supply one, so those calls throw and ask for
256
- `workspaceId`.
125
+ A snapshot holds every app of a running environment at one moment, and waits until `ready`. A
126
+ pause or delete during the snapshot fails it with `failure.code: 'interrupted'`. One snapshot is
127
+ `saving` per environment at a time. `sm.snapshots.delete(id)` deletes a `ready` or `failed`
128
+ snapshot.
257
129
 
258
- Other constructor options: `serverURL`, `fetch` (a custom fetch implementation), `httpClient`
259
- (copied, never changed; not combined with `fetch`) and `timeoutMs` (see below).
130
+ ## Waiting
260
131
 
261
- ## Retries and timeouts
262
-
263
- Every API call retries on its own, with growing, jittered delays of up to about 5 seconds between
264
- attempts:
265
-
266
- | Response | Retried | Why |
132
+ | Method | Waits for | Throws |
267
133
  | --- | --- | --- |
268
- | `502`, `503`, `504` | Yes | The API is saturated (`503`), or Cloudflare can't reach it for a moment (`502`, `504`). |
269
- | No response: connection error or attempt timeout | Yes | The request may never have arrived. |
270
- | `429` (`environment-limit`) | No | A quota on live environments. Suspend or stop one, then create again. |
271
- | `429` (`snapshot-limit`) | Only by `snapshots.capture()` and `env.snapshot()`, for up to 60 seconds | Sent by older APIs when too many captures ran. The current API answers `409 capture-in-progress` instead. |
272
- | Any other status: other `4xx`, and other `5xx` | No | Not retried by the SDK. |
273
-
274
- - A `Retry-After` header is honoured, up to 5 seconds.
275
- - Retries have a 10-minute budget, checked after each failed attempt. It is not a deadline: the
276
- last delay and attempt can end later. Pass a `signal` to bound a call: an abort during a
277
- retry delay takes effect when the delay ends, at most about 5 seconds later.
278
- - `timeoutMs` (constructor option, default 60 seconds) limits **each attempt**. A timed-out
279
- attempt is retried with a fresh timeout.
280
- - A `signal` passed to a call aborts the whole call, including retries. The call rejects with a
281
- `StateMachinesError` whose `status` is `null`. The waits, and `start()` while it waits, reject
282
- with `signal.reason` instead (see [Full control](#full-control)).
283
-
284
- ```ts
285
- const environment = await sm.environments.get('env_123', {
286
- signal: AbortSignal.timeout(30_000),
287
- });
288
- ```
134
+ | `environments.create` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
135
+ | `environments.resume` | `running` | `EnvironmentFailedError` when the environment fails; `UnexpectedStatusError` on `paused` or `deleted` for another reason; `WaitTimeoutError` |
136
+ | `snapshots.create` | `ready` | `SnapshotFailedError` on `failed`; `UnexpectedStatusError` on `deleted` |
137
+ | `environments.wait(id, { status })`, `snapshots.wait(id, { status })` | `status` | the same rule: a status that cannot reach `status` without another call |
289
138
 
290
- ## Recovering a failed create
139
+ Pass `{ wait: false }` to `create`, `resume` or `snapshots.create` to get the API's answer at once.
140
+ `timeoutMs` bounds the wait, and `signal` aborts it. The option types document each default.
141
+ `pause` and `delete` do not wait: the status changes when the API answers.
291
142
 
292
- Every error thrown by `environments.create` carries `err.idempotencyKey`. Replaying the create
293
- with that key and the **same input** returns the environment the first call created, instead of
294
- creating a duplicate. If the first call never reached the API, the replay creates it. Replaying
295
- with a different input fails with `idempotency-conflict`. Keys are kept for a day.
143
+ ## YAML
296
144
 
297
145
  ```ts
298
- import { StateMachines, StateMachinesError } from '@usestatemachines/sdk';
299
-
300
- const sm = new StateMachines();
301
- const input = { name: 'nightly', apps: [{ key: 'crm', appId: 'salesforce' }] };
302
-
303
- async function createOnce() {
304
- try {
305
- return await sm.environments.create(input);
306
- } catch (error) {
307
- // Safe after any failure: the replay can't create a second environment.
308
- if (error instanceof StateMachinesError && error.idempotencyKey) {
309
- return sm.environments.create(input, { idempotencyKey: error.idempotencyKey });
310
- }
311
-
312
- throw error;
313
- }
314
- }
146
+ import { parseEnvironmentYaml } from '@usestatemachines/sdk';
315
147
 
316
- const environment = await createOnce();
148
+ const input = parseEnvironmentYaml(await readFile('environment.yaml', 'utf8'));
149
+ const env = await sm.environments.create(input);
317
150
  ```
318
151
 
319
- `start()` makes the same guarantee for its create step: call
320
- `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same input. A key
321
- only recovers a failed create. Don't reuse the key of a `start()` whose wait failed: the replay
322
- returns that same environment, which `start()` stopped (or, with your own key, left running for
323
- you to stop). And don't replay an error whose `cause`
324
- is a `TypeError`: the input was invalid and nothing was sent.
152
+ The document has the fields of `create`. A bad document throws `StateMachinesError` with
153
+ `code: 'invalid_request'`, `status: null` and `fields` keyed by dotted path. Anchors, aliases,
154
+ tags and duplicate keys are refused.
325
155
 
326
156
  ## Errors
327
157
 
328
- Every failed API call throws a `StateMachinesError`:
158
+ A failed call throws `StateMachinesError` with `code`, `status`, `message`, `requestId`, `fields`
159
+ and `retryAfterMs`. `status` is `null` when no response arrived. The message never holds a
160
+ response body.
329
161
 
330
- | Field | Type | Meaning |
162
+ | Class | Thrown when | Carries |
331
163
  | --- | --- | --- |
332
- | `status` | `number \| null` | HTTP status; `null` when there was no parsed response. |
333
- | `code` | `ErrorCode \| null` | API error code, such as `environment-limit`. |
334
- | `requestId` | `string \| null` | Include it when you contact support. |
335
- | `fields` | `Record<string, string[]> \| null` | Per-field validation messages. |
336
- | `retryAfter` | `number \| null` | Milliseconds the API asked you to wait. |
337
- | `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. |
338
- | `idempotencyKey` | `string \| undefined` | Set on `environments.create` failures, including the create step of `start()`, and on `snapshots.create` and `snapshots.capture` failures. |
339
-
340
- `status` is `null` when no HTTP response was received, when a response wasn't valid JSON, and when
341
- the SDK rejected the input before sending (its `cause` is then a `TypeError`).
342
- Error messages never include response bodies. The waits throw their own classes:
343
- `EnvironmentFailedError` and `DisposalIncompleteError` (both with `environment`) and
344
- `TimeoutError` (with `timeoutMs` and the last `environment` seen, or `null`). Check errors with
345
- `instanceof`, not `name`: a platform abort is also named `TimeoutError`.
346
-
347
- An SDK error that `start()` rethrows after a failed wait also carries `environmentId`, `stopped`
348
- and, when the stop failed, `stopError`. The stop gets 30 seconds and doesn't wait for disposal.
349
- Invalid `apps` fail before any request, as a `StateMachinesError` with `status: null`, the
350
- `idempotencyKey`, and one of these messages: `Pass at least one app.`,
351
- `App key "<key>" is used twice.` or `"<s>" can't be used as an app key; pass { key, appId } instead.`
352
- Starting from a snapshot fails the same way with `Pass apps or snapshotId.`,
353
- `Pass apps or snapshotId, not both.` or `Pass snapshotAppInstanceKeys with snapshotId.`
354
-
355
- Snapshot waits throw `SnapshotFailedError`, with the `snapshot` (its `errorCode` is
356
- `capture-failed`, or `capture-interrupted` when the API restarted mid-capture), and
357
- `SnapshotTimeoutError`, with `timeoutMs` and the last `snapshot` seen, or `null`. Both carry
358
- `snapshotId`. From `capture`, they and any `StateMachinesError` thrown after the snapshot was
359
- created also carry `snapshotId` and `idempotencyKey`.
164
+ | `StateMachinesError` | any failed call | `code`, `status`, `requestId`, `fields`, `retryAfterMs` |
165
+ | `EnvironmentFailedError` (a `StateMachinesError`) | a waited environment failed | `environment` |
166
+ | `SnapshotFailedError` (a `StateMachinesError`) | a waited snapshot failed | `snapshot` |
167
+ | `UnexpectedStatusError` | a waited resource cannot reach the status without another call | `resource`, `status` |
168
+ | `WaitTimeoutError` | a wait passed `timeoutMs` | `resource` (the last read), `timeoutMs` |
360
169
 
361
170
  | Code | Status | Meaning |
362
171
  | --- | --- | --- |
363
- | `capture-in-progress` | 409 | This environment is already capturing a snapshot; one runs at a time. Wait for it (`waitUntilReady`), then capture again. |
364
- | `snapshot-limit` | 429 | Older APIs only: too many captures were running. `capture` retries it for up to 60 seconds; `create` doesn't. |
365
- | `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`. |
366
- | `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`. |
367
- | `release-unavailable` | 422 | Restore: no release on this deployment matches the snapshot's apps. |
368
-
369
- ```ts
370
- import { ErrorCode, StateMachinesError } from '@usestatemachines/sdk';
371
-
372
- try {
373
- await sm.environments.create(input);
374
- } catch (error) {
375
- if (error instanceof StateMachinesError && error.code === ErrorCode.EnvironmentLimit) {
376
- console.error('Too many live environments; suspend or stop one first.');
377
- } else {
378
- throw error;
379
- }
380
- }
381
- ```
382
-
383
- ## Pagination
384
-
385
- `environments.list`, `environments.events`, `workspaces.events` and `snapshots.list` return one
386
- page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`:
387
-
388
- ```ts
389
- let cursor: string | undefined;
172
+ | `invalid_request` | 400 | A wrong field; see `fields`. |
173
+ | `unauthenticated` | 401 | A missing, expired or revoked key. |
174
+ | `forbidden` | 403 | The key lacks the permission. |
175
+ | `not_found` | 404 | No such resource in this workspace. |
176
+ | `invalid_status` | 409 | The status does not allow the call. |
177
+ | `snapshot_in_progress` | 409 | The environment has a snapshot in `saving`. |
178
+ | `idempotency_key_reused` | 409 | The same `idempotencyKey` with a different input. |
179
+ | `workspace_archived` | 409 | A write in an archived workspace. |
180
+ | `body_too_large` | 413 | The request body is over the API's size limit, which the message names. |
181
+ | `version_unavailable` | 422 | The app has no default version. |
182
+ | `environment_limit_reached` | 429 | Too many environments `starting` or `running`. |
183
+ | `internal_error` | 500 | An error on the server. |
184
+ | `unavailable` | 503 | Retried automatically. |
185
+
186
+ ### Errors from the app URL
187
+
188
+ `Credentials.fetch` returns the gateway's and the app's answers as responses, with the same JSON
189
+ error envelope, and never retries them. These codes come from State Machines, in front of the app:
390
190
 
391
- do {
392
- const page = await sm.environments.list({ status: 'live', cursor });
191
+ | Code | Status | Meaning |
192
+ | --- | --- | --- |
193
+ | `environment_not_running` | 409 | The environment is `paused`, `failed` or `deleted`. The body's `error.status` names the status. |
194
+ | `environment_starting` | 503 | The environment is still `starting`. The response carries `Retry-After`. |
195
+ | `environment_unavailable` | 503 | The app is restarting or did not answer. The response carries `Retry-After`. |
196
+ | `response_too_large` | 502 | The app's response is over the size limit of State Machines. |
197
+ | `capacity_exceeded` | 429 | The app has too many requests in flight. |
198
+ | `unsupported_protocol` | 501 | The app does not serve this API version or protocol. |
393
199
 
394
- for (const environment of page.data) {
395
- console.log(environment.id, environment.status);
396
- }
200
+ ## Retries and timeouts
397
201
 
398
- cursor = page.nextCursor ?? undefined;
399
- } while (cursor);
400
- ```
202
+ The SDK retries `502`, `503`, `504` and connection errors with backoff, until a deadline.
203
+ `create` and `snapshots.create` send one `Idempotency-Key` for every retry; pass your own
204
+ `idempotencyKey` to replay a call whose outcome you don't know. Each HTTP attempt times out after
205
+ `attemptTimeoutMs`.
401
206
 
402
- ## Connections
403
-
404
- `connect()` needs the app to be ready, which `start()` guarantees. It takes `{ apiId, actorId }`
405
- and throws a `TypeError` before revealing any token when a choice is missing or wrong:
406
-
407
- - `apiId` is required when the app has several API profiles, as Salesforce does:
408
- `Choose one API for <appKey>: <ids>.` There is no default API.
409
- - `actorId` defaults to the actor of the environment's declared default connection, or else the
410
- app's first actor (for Salesforce, the administrator). One that isn't among the app's actors
411
- throws `Unknown actor <id> for <appKey>. Choose one of: <ids>.`
412
-
413
- It returns a `Connection` with `baseUrl`, `environmentId`, `appKey`, `actorId`, `api` and
414
- `expiresAt`, and these ways to call the app:
415
-
416
- - `headers()` returns `X-State-Environment-Token` plus the app's actor header, such as
417
- `Authorization: Bearer <token>`. For an API that takes the actor token in the request body,
418
- such as SOAP, it returns only `X-State-Environment-Token`.
419
- - `fetch(path, init)` sends one request with those headers and returns the raw `Response`. A
420
- leading `/` means the root of `baseUrl`. It:
421
- - only reaches URLs under `baseUrl`, and throws for any other path
422
- - never follows redirects (`redirect: 'manual'`)
423
- - never sends your API key
424
- - never retries
425
- - rejects `x-state-*` headers from the caller
426
- - `actorToken()` returns the actor token itself, for APIs that take it in the request body, such
427
- as SOAP. Treat it as a secret.
428
-
429
- `formatActorHeader(actorHeader, token)` returns the `{ name, value }` for an API profile's actor
430
- header: `Bearer <token>` for `Authorization`, and the raw token for any other header. It returns
431
- `null` for a SOAP profile (`SOAP SessionHeader`), whose token goes in the envelope instead.
432
-
433
- **SOAP APIs:** Salesforce's `soap-partner` profile, when the release offers it (the default
434
- release does), takes the actor token in the envelope's `SessionHeader/sessionId`. XML-escape any
435
- value you put in the envelope:
207
+ ## Pagination
436
208
 
437
- ```ts
438
- const soap = await env.connect('salesforce', { apiId: 'soap-partner' });
439
- const envelope = `<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com">
440
- <env:Header><SessionHeader><sessionId>${soap.actorToken()}</sessionId></SessionHeader></env:Header>
441
- <env:Body><undelete><ids>001000000000000AAA</ids></undelete></env:Body>
442
- </env:Envelope>`;
443
- const response = await soap.fetch('/services/Soap/u/67.0', {
444
- method: 'POST',
445
- headers: { 'Content-Type': 'text/xml', SOAPAction: '""' },
446
- body: envelope,
447
- });
448
- ```
209
+ Every `list` returns one page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it
210
+ is `null`. [`openapi.json`](openapi.json) in this package gives the range and the default of `limit`.
449
211
 
450
- **Vendor clients:** the gateway needs `X-State-Environment-Token` on every request. A client such
451
- as jsforce only sends it if you configure custom headers. Prefer `Connection.fetch`. If you pass
452
- `headers()` to another client, those headers carry live tokens: only send them to `baseUrl`, and
453
- don't let the client follow redirects, which would forward the tokens to another host:
212
+ ## Recorded requests
454
213
 
455
- ```ts
456
- const salesforce = await env.connect('salesforce', { apiId: 'rest' });
457
- const response = await fetch(new URL('services/data/v67.0/limits', salesforce.baseUrl), {
458
- headers: salesforce.headers(),
459
- redirect: 'manual',
460
- });
461
- ```
214
+ `sm.requests.list({ environmentId })` pages the calls the gateway recorded for an environment,
215
+ newest first. `sm.requests.get(id)` adds headers and a preview of each body. `sm.requests.body(id,
216
+ 'response')` returns the full body as a `Uint8Array`. `sm.requests.stats({ environmentId, from,
217
+ to })` counts them. Recording is best effort.
462
218
 
463
219
  ## Supported runtimes
464
220
 
465
- - **Node.js 20.20 or later**, with 22 or 24 recommended: Node 20 is past its end of life, and
466
- earlier Node 20 releases fail when retrying a `POST`.
467
- - **Cloudflare Workers.**
221
+ - **Node.js**, at the versions that `engines` in `package.json` allows. ESM only.
222
+ - **Cloudflare Workers.** Pass `apiKey` explicitly.
468
223
  - **Not browsers.** Never ship an API key to a browser.
469
224
 
470
225
  TypeScript consumers need `lib: ["DOM"]` or `@types/node`, for the `fetch`, `Request` and
471
- `Response` types.
472
-
473
- ## Compatibility
474
-
475
- - While the version is `0.x`, a minor release may include breaking changes. Pin the minor
476
- version.
477
- - The API may add error codes and enum values, such as a new status or stop reason. Enums are
478
- open: handle values you don't recognise.
479
- - 0.4.0 adds suspend and resume and keeps every 0.3.0 call: `stop` still ends the environment.
480
- `Environment` gains `suspendedAt` and `sourceEnvironmentId`, so object literals typed as
481
- `Environment` need them. A snapshot's `consistency` is now `'environment'`, `'app_instance'`
482
- or `null` while it isn't ready (it was always `'per-app-instance'`). See
483
- [What's new in 0.4.0](#whats-new-in-040).
484
- - Upgrade from 0.3.0 to capture snapshots: the API now answers `consistency: null` while a
485
- capture runs, which 0.3.0 rejects as an invalid response even though the capture started.
486
- - 0.3.0 adds the snapshot APIs and keeps every 0.2.0 call. An input with neither `apps` nor
487
- `snapshotId` now fails with `Pass apps or snapshotId.` `Environment` and its apps gain
488
- `sourceEnvironmentSnapshotId` and `sourceAppInstanceSnapshotId` (`null` unless restored), so
489
- object literals typed as `Environment` need them.
490
- - 0.2.0 adds APIs and keeps every 0.1.x call, with these deliberate changes:
491
- - `connect()` without `actorId` now succeeds when an app has several actors, acting as the
492
- default actor (for Salesforce, the administrator, with full access).
493
- - An empty `actorId` (`''`) now throws `Unknown actor`, instead of acting as an app's only actor.
494
- - `create()` rejects an empty `apps` list or a repeated app key before sending a request, as a
495
- `StateMachinesError` with `status: null`.
496
- - `connect()`'s error messages now name the app: `Unknown actor <id> for <appKey>. …` and
497
- `Choose one API for <appKey>: …`.
498
-
499
- ## Known limitations
500
-
501
- - Vendor clients such as jsforce don't send `X-State-Environment-Token` unless you configure
502
- custom headers, so the gateway rejects them. Use `Connection.fetch`, or pass `headers()` as
503
- shown in [Connections](#connections).
504
- - There is no default API profile: pass `apiId` whenever an app has more than one.
505
- - Without a declared default connection, the default actor is the first one the release lists;
506
- releases don't declare a default actor yet.
507
- - `await using` isn't supported. Stop the environment in `finally`.
508
-
509
- ## What's new in 0.4.0
510
-
511
- - `sm.environments.suspend(id)` ends an environment's sandboxes and keeps its data;
512
- `sm.environments.resume(id)` runs it again on that data, and
513
- `sm.environments.waitUntilSuspended(id)` waits for the suspension (see
514
- [Suspending and resuming](#suspending-and-resuming)).
515
- - `RunningEnvironment` gains `suspend()`, `resume()`, `waitUntilSuspended()` and
516
- `waitUntilReady()`.
517
- - `stop` is unchanged: it ends the environment and discards its data, suspended or not.
518
- - New statuses: `suspending` and `suspended` for environments, `suspended` and `recovering` for
519
- apps. New fields: `suspendedAt` and `sourceEnvironmentId` on an environment, `forkedAt` on an
520
- app. `waitUntilReady()` throws `EnvironmentFailedError` on `suspending` and `suspended`.
521
- - Snapshots: `consistency` is `'environment'` (every app paused together), `'app_instance'` (each
522
- app captured on its own) or `null` until ready. One capture runs per environment at a time; a
523
- second gets `409 capture-in-progress`.
524
-
525
- ## What's new in 0.3.0
526
-
527
- - `sm.snapshots`: `capture`, `create`, `waitUntilReady`, `get`, `list`, `rename` and `delete`
528
- (see [Snapshots](#snapshots)).
529
- - `env.snapshot()` captures a running environment and waits until the snapshot is `ready`.
530
- - `start({ snapshotId })` and `create({ snapshotId })` start an environment from a snapshot.
531
- Environments carry `sourceEnvironmentSnapshotId`, and apps `sourceAppInstanceSnapshotId`.
532
- - `environments.list({ snapshotId })` lists the environments started from a snapshot.
533
- - `SnapshotFailedError` and `SnapshotTimeoutError`.
534
-
535
- ## What's new in 0.2.0
536
-
537
- - `sm.environments.start()` creates an environment and waits until it's running. It returns a
538
- `RunningEnvironment` with `connect`, `actors`, `stop` and `waitUntilStopped`.
539
- - String apps: `apps: ['salesforce']` means `[{ key: 'salesforce', appId: 'salesforce' }]`, in
540
- `start()` and `create()`.
541
- - `actors(appKey)` lists an app's actors in the release's order.
542
- - `actorId` is optional in `connect()`. It defaults to the environment's declared default
543
- connection's actor, or else the app's first actor.
544
- - When readiness fails, `start()` stops the environment before it rethrows.
545
- - Cancelling a call now takes effect immediately (fixes 0.1.1). A `signal` used to reach an
546
- in-flight request only when its attempt timeout (60 seconds by default) fired. An abort during
547
- a retry delay still takes effect when the delay ends.
226
+ `Response` types. While the version is `0.x`, a minor version may include breaking changes. Enums
227
+ are open: handle values you don't recognise.
228
+
229
+ ## Upgrade from 0.5
230
+
231
+ 0.6 follows the current API. 0.5 and older call routes that the API no longer serves, so they fail
232
+ against it.
233
+
234
+ | 0.5 | 0.6 |
235
+ | --- | --- |
236
+ | `STATE_API_KEY` | `STATEMACHINES_API_KEY` |
237
+ | Default host `https://state-api.usestatemachines.workers.dev` | `https://api.usestatemachines.com` |
238
+ | `sm.environments.start(input)` | `sm.environments.create(input)`, which waits until `running` |
239
+ | `env.stop()`, `sm.environments.stop(id)` | `env.delete()`, `sm.environments.delete(id)` |
240
+ | `env.suspend()`, `sm.environments.suspend(id)` | `env.pause()`, `sm.environments.pause(id)` |
241
+ | `env.resume()` then `env.waitUntilReady()` | `env.resume()`, which waits until `running` |
242
+ | `waitUntilReady`, `waitUntilStopped`, `waitUntilSuspended` | `sm.environments.wait(id, { status })` |
243
+ | Apps as `{ key, appId }` | Apps as `{ app, name }`, or the app name as a string |
244
+ | `env.connect(app, { apiId, actorId })` returns a `Connection` | `env.connect(app, { actorId })` returns `Credentials`; `fetch` picks the API from the path |
245
+ | `connection.baseUrl`, `connection.headers()` | `credentials.url`, `credentials.headers`, `credentials.token` |
246
+ | `X-State-Environment-Token` | `StateMachines-Environment-Token` |
247
+ | `env.snapshot(options)`, `sm.snapshots.capture(environmentId, options)` | `env.snapshots.create(options)`, `sm.snapshots.create({ environmentId, ...options })` |
248
+ | `sm.environments.start({ snapshotId })` | `sm.environments.create({ snapshotId })` |
249
+ | `sm.environments.listRequests`, `getRequest`, `getRequestPayload`, `getRequestStats` | `sm.requests.list({ environmentId })`, `get(id)`, `body(id, direction)`, `stats(query)` |
250
+ | `sm.credential.get()` | `sm.me()` |
251
+ | The catalog | `sm.apps.list()`, `sm.apps.versions(appId)` |
252
+ | `TimeoutError` | `WaitTimeoutError` |
253
+ | `error.retryAfter` | `error.retryAfterMs` |
254
+ | Error codes such as `environment-limit` and `capture-in-progress` | Codes such as `environment_limit_reached` and `snapshot_in_progress`; see [Errors](#errors) |
548
255
 
549
256
  ## License
550
257