@usestatemachines/sdk 0.5.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 (259) hide show
  1. package/README.md +176 -522
  2. package/agents/SKILL.md +38 -47
  3. package/agents/index.js +7 -7
  4. package/agents/llms.txt +49 -46
  5. package/agents/prompt.md +11 -11
  6. package/esm/extensions/client.d.ts +104 -127
  7. package/esm/extensions/client.js +162 -322
  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/{environments-get-connection.js → apps-list.js} +11 -16
  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} +7 -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} +6 -5
  38. package/esm/funcs/environments-resume.d.ts +2 -1
  39. package/esm/funcs/environments-resume.js +2 -1
  40. package/esm/funcs/environments-update.d.ts +12 -0
  41. package/esm/funcs/{environments-events.js → environments-update.js} +9 -13
  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-request-payload.js → requests-body.js} +5 -9
  46. package/esm/funcs/{environments-get-request-stats.d.ts → requests-get.d.ts} +2 -1
  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/{environments-list-requests.js → requests-list.js} +9 -14
  50. package/esm/funcs/requests-stats.d.ts +12 -0
  51. package/esm/funcs/{environments-get-request-stats.js → requests-stats.js} +10 -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/{request-payload-preview.d.ts → body-preview.d.ts} +4 -4
  77. package/esm/models/{request-payload-preview.js → body-preview.js} +5 -4
  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 -20
  95. package/esm/models/index.js +17 -20
  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-environment-request-payload.js → get-request-body.js} +3 -4
  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 -16
  121. package/esm/models/operations/index.js +13 -16
  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 +0 -12
  139. package/esm/models/operations/resume-environment.js +0 -9
  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/{environment-request-stats.d.ts → request-stats.d.ts} +3 -3
  151. package/esm/models/{environment-request-stats.js → request-stats.js} +3 -3
  152. package/esm/models/request.d.ts +30 -0
  153. package/esm/models/{environment-request.js → request.js} +11 -11
  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 -13
  167. package/esm/sdk/environments.js +18 -38
  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 -8
  171. package/esm/sdk/sdk.js +16 -11
  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/openapi.json +1697 -2507
  177. package/package.json +15 -13
  178. package/esm/extensions/apps.d.ts +0 -18
  179. package/esm/extensions/apps.js +0 -39
  180. package/esm/extensions/connection.d.ts +0 -42
  181. package/esm/extensions/connection.js +0 -133
  182. package/esm/extensions/running-environment.d.ts +0 -46
  183. package/esm/extensions/running-environment.js +0 -66
  184. package/esm/funcs/create-environment-definition.d.ts +0 -11
  185. package/esm/funcs/create-environment-definition.js +0 -94
  186. package/esm/funcs/credential-get.js +0 -76
  187. package/esm/funcs/environments-get-request-payload.d.ts +0 -11
  188. package/esm/funcs/environments-get-request.d.ts +0 -11
  189. package/esm/funcs/environments-get-request.js +0 -96
  190. package/esm/funcs/environments-list-requests.d.ts +0 -11
  191. package/esm/funcs/environments-reveal.d.ts +0 -11
  192. package/esm/funcs/environments-suspend.d.ts +0 -11
  193. package/esm/funcs/snapshots-capture.d.ts +0 -11
  194. package/esm/models/app-instance-snapshot.d.ts +0 -15
  195. package/esm/models/app-instance-snapshot.js +0 -19
  196. package/esm/models/catalog.d.ts +0 -12
  197. package/esm/models/catalog.js +0 -15
  198. package/esm/models/connection-secret.d.ts +0 -30
  199. package/esm/models/connection-secret.js +0 -31
  200. package/esm/models/connection.d.ts +0 -40
  201. package/esm/models/connection.js +0 -44
  202. package/esm/models/create-environment-input.d.ts +0 -39
  203. package/esm/models/create-environment-input.js +0 -25
  204. package/esm/models/credential-kind.d.ts +0 -9
  205. package/esm/models/credential-kind.js +0 -10
  206. package/esm/models/credential.d.ts +0 -48
  207. package/esm/models/credential.js +0 -44
  208. package/esm/models/environment-app.d.ts +0 -45
  209. package/esm/models/environment-app.js +0 -47
  210. package/esm/models/environment-lifetime.d.ts +0 -10
  211. package/esm/models/environment-lifetime.js +0 -14
  212. package/esm/models/environment-request-detail.d.ts +0 -40
  213. package/esm/models/environment-request-detail.js +0 -39
  214. package/esm/models/environment-request.d.ts +0 -31
  215. package/esm/models/environment-snapshot.d.ts +0 -40
  216. package/esm/models/environment-snapshot.js +0 -42
  217. package/esm/models/error-response.d.ts +0 -57
  218. package/esm/models/error-response.js +0 -59
  219. package/esm/models/event-page.d.ts +0 -11
  220. package/esm/models/event-page.js +0 -16
  221. package/esm/models/operations/capture-snapshot.d.ts +0 -41
  222. package/esm/models/operations/capture-snapshot.js +0 -36
  223. package/esm/models/operations/create-environment-definition.d.ts +0 -153
  224. package/esm/models/operations/create-environment-definition.js +0 -93
  225. package/esm/models/operations/create-environments.d.ts +0 -27
  226. package/esm/models/operations/create-environments.js +0 -27
  227. package/esm/models/operations/get-catalog.d.ts +0 -13
  228. package/esm/models/operations/get-catalog.js +0 -13
  229. package/esm/models/operations/get-connection.d.ts +0 -25
  230. package/esm/models/operations/get-connection.js +0 -21
  231. package/esm/models/operations/get-credential.d.ts +0 -13
  232. package/esm/models/operations/get-credential.js +0 -13
  233. package/esm/models/operations/get-environment-request-payload.d.ts +0 -23
  234. package/esm/models/operations/get-environment-request-stats.d.ts +0 -70
  235. package/esm/models/operations/get-environment-request-stats.js +0 -55
  236. package/esm/models/operations/get-environment-request.d.ts +0 -25
  237. package/esm/models/operations/get-environment-request.js +0 -21
  238. package/esm/models/operations/get-release.d.ts +0 -23
  239. package/esm/models/operations/get-release.js +0 -20
  240. package/esm/models/operations/list-environment-events.d.ts +0 -15
  241. package/esm/models/operations/list-environment-events.js +0 -13
  242. package/esm/models/operations/list-environment-requests.d.ts +0 -75
  243. package/esm/models/operations/list-environment-requests.js +0 -59
  244. package/esm/models/operations/list-workspace-events.d.ts +0 -15
  245. package/esm/models/operations/list-workspace-events.js +0 -13
  246. package/esm/models/operations/reveal-connection.d.ts +0 -59
  247. package/esm/models/operations/reveal-connection.js +0 -55
  248. package/esm/models/operations/stop-environment.d.ts +0 -25
  249. package/esm/models/operations/stop-environment.js +0 -26
  250. package/esm/models/operations/suspend-environment.d.ts +0 -25
  251. package/esm/models/operations/suspend-environment.js +0 -26
  252. package/esm/models/release-actor.d.ts +0 -11
  253. package/esm/models/release-actor.js +0 -15
  254. package/esm/models/release.d.ts +0 -37
  255. package/esm/models/release.js +0 -39
  256. package/esm/sdk/catalog.d.ts +0 -6
  257. package/esm/sdk/catalog.js +0 -15
  258. package/esm/sdk/credential.d.ts +0 -5
  259. package/esm/sdk/credential.js +0 -11
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,587 +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.
54
-
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.
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.
59
32
 
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:
59
+ ## Apps
92
60
 
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
- ```
100
-
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()` starts it again; it answers `409 not-suspended` unless the environment is
107
- `suspended` or `failed`, 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()` interrupts ongoing work, including suspension and snapshot capture.
111
-
112
- Lifecycle commands require orchestration availability. `stop`, `suspend` and `resume` each
113
- send a fresh idempotency key and reuse it across automatic retries. You can pass your own
114
- `{ idempotencyKey }`. Failures carry that key so you can retry the same command after a timeout;
115
- a timeout does not establish whether the command was accepted.
116
-
117
- ## Full control
118
-
119
- Use `create`, `waitUntilReady` and `connect` directly when you want to run each step yourself, or
120
- keep an environment up after a failed boot so you can inspect it. You don't need this path for
121
- app keys, labels or `lifetimeMinutes`: `start()` takes the same input.
122
- **Nothing is stopped automatically on this path**: stop the environment yourself, even when a step
123
- 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:
124
63
 
125
64
  ```ts
126
- const environment = await sm.environments.create({
127
- name: 'nightly',
128
- // key is your own name for the app; later calls take it.
129
- apps: [{ key: 'crm', appId: 'salesforce' }],
130
- lifetimeMinutes: 30,
131
- 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
+ ],
132
77
  });
78
+ ```
133
79
 
134
- try {
135
- 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.
136
85
 
137
- const crm = await sm.environments.connect(environment.id, 'crm', { apiId: 'rest' });
138
- const res = await crm.fetch('/services/data/v67.0/limits');
139
- console.log(res.status);
140
- } finally {
141
- await sm.environments.stop(environment.id);
142
- // In CI: fail the run when cleanup can't be confirmed.
143
- await sm.environments.waitUntilStopped(environment.id);
144
- }
145
- ```
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.
146
88
 
147
- 1. `create(input)` returns one `Environment` as soon as the API accepts it, before the apps boot.
148
- 2. `waitUntilReady(id)` polls until it is `running`. It throws `EnvironmentFailedError` if the
149
- environment becomes `failed`, `suspending`, `suspended`, `stopping` or `stopped`, and
150
- `TimeoutError` after `timeoutMs` (default 10 minutes). It **never stops the environment**,
151
- even when it throws.
152
- 3. `connect(id, appKey, { apiId, actorId })` returns a `Connection` to one app (see
153
- [Connections](#connections)).
154
- 4. `stop(id)` returns as soon as the API accepts it, and a second call also resolves.
155
- `waitUntilStopped(id)` resolves only when the environment is `stopped` **and**
156
- every app's `disposal` is `complete`. If any disposal is `failed` or `unknown`, it throws
157
- `DisposalIncompleteError` with the environment.
158
-
159
- `lifetimeMinutes` counts from when the environment starts, so boot time uses part of it; time spent
160
- suspended doesn't count. When it runs out, the environment stops with `stopReason: 'expired'`. The
161
- default and maximum are in `(await sm.credential.get()).limits.environmentLifetime`.
162
-
163
- Both waits accept `{ timeoutMs, signal }`. Aborting the `signal` rejects the wait with
164
- `signal.reason`. They poll at growing intervals of about 1 to 5 seconds, with random jitter, and
165
- keep polling through transient failures.
166
-
167
- `start()` and `create()` take the same input, including `{ key, appId }` objects for a custom key
168
- or two instances of one app.
89
+ ## Credentials
169
90
 
170
- ## Snapshots
91
+ `env.connect(app, { actorId? })` returns the API's `Credentials`: `url`, `headers`, `token`,
92
+ `expiresAt` and `login`, plus `fetch(path, init?)`.
171
93
 
172
- A snapshot saves the data of a running environment's apps. Seed one environment, snapshot it, and
173
- 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.
174
107
 
175
- ```ts
176
- 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.
177
110
 
178
- try {
179
- const salesforce = await env.connect('salesforce', { apiId: 'rest' });
180
- await salesforce.fetch('/services/data/v67.0/sobjects/Account', {
181
- method: 'POST',
182
- headers: { 'Content-Type': 'application/json' },
183
- body: JSON.stringify({ Name: 'Acme' }),
184
- });
185
-
186
- // Waits until the snapshot is ready.
187
- const snapshot = await env.snapshot({ name: 'seeded accounts' });
188
- console.log(snapshot.id, snapshot.sizeBytes);
189
- } finally {
190
- await env.stop();
191
- }
192
- ```
111
+ ## Pausing and resuming
193
112
 
194
- ```ts
195
- // Later, or in every CI job: start from the seeded state.
196
- const restored = await sm.environments.start({ snapshotId: 'snp_…' });
197
- ```
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.
198
117
 
199
- Pass `snapshotId` instead of `apps` to `start()` or `create()`. Each app keeps the key it had when
200
- captured; `snapshotAppInstanceKeys` restores only some of them. The new environment's
201
- `sourceEnvironmentSnapshotId` and each app's `sourceAppInstanceSnapshotId` name the source.
202
-
203
- - Every app is paused while the snapshot is cut, so a multi-app snapshot records one instant
204
- across apps (`consistency: 'environment'`). A single app, or a capture whose pause was refused,
205
- is `'app_instance'`. It can only be taken while the environment is `running` and each app is
206
- `ready`.
207
- - `readyTimeoutMs` bounds the wait for `ready` (10 minutes by default), as in `start()`. While the
208
- API is busy (429 `snapshot-limit`), the capture is retried with the same idempotency key for up
209
- to 60 seconds.
210
- - The SDK never deletes a snapshot for you, and every capture error thrown after the snapshot was
211
- created carries `snapshotId`. A wait past `readyTimeoutMs` throws `SnapshotTimeoutError`, and an
212
- abort after the create leaves the capture running on the server: `sm.snapshots.waitUntilReady(err.snapshotId)`
213
- waits again. A failed capture throws `SnapshotFailedError` and stays failed: capture again.
214
- - Capturing needs `snapshots:create` and `snapshots:read`, restoring needs `snapshots:read`,
215
- renaming needs `snapshots:create`, and deleting needs `snapshots:delete`. Keys created by members don't hold `snapshots:delete`, so
216
- `delete` returns 403 for them.
217
-
218
- `sm.snapshots` has the individual steps:
219
-
220
- - `create(environmentId, input)` returns the API's answer at once, possibly still `capturing`, and
221
- doesn't retry `snapshot-limit`. It needs only `snapshots:create`. `waitUntilReady(id)` waits for
222
- `ready`. `capture(environmentId, input, options)` does both; `env.snapshot()` calls it.
223
- - `list({ sourceEnvironmentId, cursor, limit })` returns one page, newest first, without deleted
224
- snapshots. `get(id)` also returns deleted ones, with `deletedAt` set.
225
- - `rename(id, { name, expectedVersion })` needs the snapshot's current `version`. A stale one, or a
226
- deleted snapshot, is 409 `version-conflict`, and so is a retried rename whose first answer was
227
- lost: `get(id)` shows the current name and version. `name: null` clears the name.
228
- - `delete(id)` marks the snapshot deleted and returns it. A snapshot that is still `capturing`
229
- can't be deleted (409 `not-ready`).
230
-
231
- ## Authentication
232
-
233
- `new StateMachines()` reads the API key from `STATE_API_KEY`, but only where a global `process`
234
- exists. Without a key, the constructor throws a `TypeError`.
235
-
236
- In Cloudflare Workers, pass `apiKey` explicitly:
118
+ ## Snapshots
237
119
 
238
120
  ```ts
239
- import { StateMachines } from '@usestatemachines/sdk';
240
-
241
- export default {
242
- async fetch(_request: Request, env: { STATE_API_KEY: string }) {
243
- const sm = new StateMachines({ apiKey: env.STATE_API_KEY });
244
- // Bound the call: retries could otherwise hold the request for minutes.
245
- const { data } = await sm.environments.list(
246
- { status: 'live' },
247
- { signal: AbortSignal.timeout(20_000) },
248
- );
249
-
250
- return Response.json(data.map(({ id, status }) => ({ id, status })));
251
- },
252
- };
121
+ const seeded = await env.snapshots.create({ name: 'seeded' });
122
+ const copy = await sm.environments.create({ snapshotId: seeded.id });
253
123
  ```
254
124
 
255
- Calls that need a workspace (`environments.list`, `environments.create`, `snapshots.list`,
256
- `snapshots.create` and `snapshots.capture`) use, in order:
257
-
258
- 1. `workspaceId` passed to that call
259
- 2. `workspaceId` passed to the constructor
260
- 3. the workspace of the API key, read once from `sm.credential.get()` and cached
261
-
262
- A user session that has no workspace can't supply one, so those calls throw and ask for
263
- `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.
264
129
 
265
- Other constructor options: `serverURL`, `fetch` (a custom fetch implementation), `httpClient`
266
- (copied, never changed; not combined with `fetch`) and `timeoutMs` (see below).
130
+ ## Waiting
267
131
 
268
- ## Retries and timeouts
269
-
270
- Every API call retries on its own, with growing, jittered delays of up to about 5 seconds between
271
- attempts:
272
-
273
- | Response | Retried | Why |
132
+ | Method | Waits for | Throws |
274
133
  | --- | --- | --- |
275
- | `502`, `503`, `504` | Yes | The API is saturated (`503`), or Cloudflare can't reach it for a moment (`502`, `504`). |
276
- | No response: connection error or attempt timeout | Yes | The request may never have arrived. |
277
- | `429` (`environment-limit`) | No | A quota on live environments. Suspend or stop one, then create again. |
278
- | `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. |
279
- | Any other status: other `4xx`, and other `5xx` | No | Not retried by the SDK. |
280
-
281
- - A `Retry-After` header is honoured, up to 5 seconds.
282
- - Retries have a 10-minute budget, checked after each failed attempt. It is not a deadline: the
283
- last delay and attempt can end later. Pass a `signal` to bound a call: an abort during a
284
- retry delay takes effect when the delay ends, at most about 5 seconds later.
285
- - `timeoutMs` (constructor option, default 60 seconds) limits **each attempt**. A timed-out
286
- attempt is retried with a fresh timeout.
287
- - A `signal` passed to a call aborts the whole call, including retries. The call rejects with a
288
- `StateMachinesError` whose `status` is `null`. The waits, and `start()` while it waits, reject
289
- with `signal.reason` instead (see [Full control](#full-control)).
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 |
290
138
 
291
- ```ts
292
- const environment = await sm.environments.get('env_123', {
293
- signal: AbortSignal.timeout(30_000),
294
- });
295
- ```
296
-
297
- ## 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.
298
142
 
299
- Every error thrown by `environments.create` carries `err.idempotencyKey`. Replaying the create
300
- with that key and the **same input** returns the environment the first call created, instead of
301
- creating a duplicate. If the first call never reached the API, the replay creates it. Replaying
302
- with a different input fails with `idempotency-conflict`. Keys are kept for a day.
143
+ ## YAML
303
144
 
304
145
  ```ts
305
- import { StateMachines, StateMachinesError } from '@usestatemachines/sdk';
306
-
307
- const sm = new StateMachines();
308
- const input = { name: 'nightly', apps: [{ key: 'crm', appId: 'salesforce' }] };
309
-
310
- async function createOnce() {
311
- try {
312
- return await sm.environments.create(input);
313
- } catch (error) {
314
- // Safe after any failure: the replay can't create a second environment.
315
- if (error instanceof StateMachinesError && error.idempotencyKey) {
316
- return sm.environments.create(input, { idempotencyKey: error.idempotencyKey });
317
- }
318
-
319
- throw error;
320
- }
321
- }
146
+ import { parseEnvironmentYaml } from '@usestatemachines/sdk';
322
147
 
323
- const environment = await createOnce();
148
+ const input = parseEnvironmentYaml(await readFile('environment.yaml', 'utf8'));
149
+ const env = await sm.environments.create(input);
324
150
  ```
325
151
 
326
- `start()` makes the same guarantee for its create step: call
327
- `sm.environments.start(input, { idempotencyKey: err.idempotencyKey })` with the same input. A key
328
- only recovers a failed create. Don't reuse the key of a `start()` whose wait failed: the replay
329
- returns that same environment, which `start()` stopped (or, with your own key, left running for
330
- you to stop). And don't replay an error whose `cause`
331
- 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.
332
155
 
333
156
  ## Errors
334
157
 
335
- 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.
336
161
 
337
- | Field | Type | Meaning |
162
+ | Class | Thrown when | Carries |
338
163
  | --- | --- | --- |
339
- | `status` | `number \| null` | HTTP status; `null` when there was no parsed response. |
340
- | `code` | `ErrorCode \| null` | API error code, such as `environment-limit`. |
341
- | `requestId` | `string \| null` | Include it when you contact support. |
342
- | `fields` | `Record<string, string[]> \| null` | Per-field validation messages. |
343
- | `retryAfter` | `number \| null` | Milliseconds the API asked you to wait. |
344
- | `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. |
345
- | `idempotencyKey` | `string \| undefined` | Set on `environments.create` failures, including the create step of `start()`, and on `snapshots.create` and `snapshots.capture` failures. |
346
-
347
- `status` is `null` when no HTTP response was received, when a response wasn't valid JSON, and when
348
- the SDK rejected the input before sending (its `cause` is then a `TypeError`).
349
- Error messages never include response bodies. The waits throw their own classes:
350
- `EnvironmentFailedError` and `DisposalIncompleteError` (both with `environment`) and
351
- `TimeoutError` (with `timeoutMs` and the last `environment` seen, or `null`). Check errors with
352
- `instanceof`, not `name`: a platform abort is also named `TimeoutError`.
353
-
354
- An SDK error that `start()` rethrows after a failed wait also carries `environmentId`, `stopped`
355
- and, when the stop failed, `stopError`. The stop gets 30 seconds and doesn't wait for disposal.
356
- Invalid `apps` fail before any request, as a `StateMachinesError` with `status: null`, the
357
- `idempotencyKey`, and one of these messages: `Pass at least one app.`,
358
- `App key "<key>" is used twice.` or `"<s>" can't be used as an app key; pass { key, appId } instead.`
359
- Starting from a snapshot fails the same way with `Pass apps or snapshotId.`,
360
- `Pass apps or snapshotId, not both.` or `Pass snapshotAppInstanceKeys with snapshotId.`
361
-
362
- Snapshot waits throw `SnapshotFailedError`, with the `snapshot` (its `errorCode` is
363
- `capture-failed`, or `capture-interrupted` when the API restarted mid-capture), and
364
- `SnapshotTimeoutError`, with `timeoutMs` and the last `snapshot` seen, or `null`. Both carry
365
- `snapshotId`. From `capture`, they and any `StateMachinesError` thrown after the snapshot was
366
- 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` |
367
169
 
368
170
  | Code | Status | Meaning |
369
171
  | --- | --- | --- |
370
- | `capture-in-progress` | 409 | This environment is already capturing a snapshot; one runs at a time. Wait for it (`waitUntilReady`), then capture again. |
371
- | `snapshot-limit` | 429 | Older APIs only: too many captures were running. `capture` retries it for up to 60 seconds; `create` doesn't. |
372
- | `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`. |
373
- | `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`. |
374
- | `release-unavailable` | 422 | Restore: no release on this deployment matches the snapshot's apps. |
375
-
376
- ```ts
377
- import { ErrorCode, StateMachinesError } from '@usestatemachines/sdk';
378
-
379
- try {
380
- await sm.environments.create(input);
381
- } catch (error) {
382
- if (error instanceof StateMachinesError && error.code === ErrorCode.EnvironmentLimit) {
383
- console.error('Too many live environments; suspend or stop one first.');
384
- } else {
385
- throw error;
386
- }
387
- }
388
- ```
389
-
390
- ## Pagination
391
-
392
- `environments.list`, `environments.events`, `workspaces.events` and `snapshots.list` return one
393
- page, `{ data, nextCursor }`. Pass `nextCursor` back as `cursor` until it is `null`:
394
-
395
- ```ts
396
- let cursor: string | undefined;
397
-
398
- do {
399
- const page = await sm.environments.list({ status: 'live', cursor });
400
-
401
- for (const environment of page.data) {
402
- console.log(environment.id, environment.status);
403
- }
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:
404
190
 
405
- cursor = page.nextCursor ?? undefined;
406
- } while (cursor);
407
- ```
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. |
408
199
 
409
- ## Connections
410
-
411
- `connect()` needs the app to be ready, which `start()` guarantees. It takes `{ apiId, actorId }`
412
- and throws a `TypeError` before revealing any token when a choice is missing or wrong:
413
-
414
- - `apiId` is required when the app has several API profiles, as Salesforce does:
415
- `Choose one API for <appKey>: <ids>.` There is no default API.
416
- - `actorId` defaults to the actor of the environment's declared default connection, or else the
417
- app's first actor (for Salesforce, the administrator). One that isn't among the app's actors
418
- throws `Unknown actor <id> for <appKey>. Choose one of: <ids>.`
419
-
420
- It returns a `Connection` with `baseUrl`, `environmentId`, `appKey`, `actorId`, `api` and
421
- `expiresAt`, and these ways to call the app:
422
-
423
- - `headers()` returns `X-State-Environment-Token` plus the app's actor header, such as
424
- `Authorization: Bearer <token>`. For an API that takes the actor token in the request body,
425
- such as SOAP, it returns only `X-State-Environment-Token`.
426
- - `fetch(path, init)` sends one request with those headers and returns the raw `Response`. A
427
- leading `/` means the root of `baseUrl`. It:
428
- - only reaches URLs under `baseUrl`, and throws for any other path
429
- - never follows redirects (`redirect: 'manual'`)
430
- - never sends your API key
431
- - never retries
432
- - rejects `x-state-*` headers from the caller
433
- - `actorToken()` returns the actor token itself, for APIs that take it in the request body, such
434
- as SOAP. Treat it as a secret.
435
-
436
- `formatActorHeader(actorHeader, token)` returns the `{ name, value }` for an API profile's actor
437
- header: `Bearer <token>` for `Authorization`, and the raw token for any other header. It returns
438
- `null` for a SOAP profile (`SOAP SessionHeader`), whose token goes in the envelope instead.
439
-
440
- **SOAP APIs:** Salesforce's `soap-partner` profile, when the release offers it (the default
441
- release does), takes the actor token in the envelope's `SessionHeader/sessionId`. XML-escape any
442
- value you put in the envelope:
200
+ ## Retries and timeouts
443
201
 
444
- ```ts
445
- const soap = await env.connect('salesforce', { apiId: 'soap-partner' });
446
- const envelope = `<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com">
447
- <env:Header><SessionHeader><sessionId>${soap.actorToken()}</sessionId></SessionHeader></env:Header>
448
- <env:Body><undelete><ids>001000000000000AAA</ids></undelete></env:Body>
449
- </env:Envelope>`;
450
- const response = await soap.fetch('/services/Soap/u/67.0', {
451
- method: 'POST',
452
- headers: { 'Content-Type': 'text/xml', SOAPAction: '""' },
453
- body: envelope,
454
- });
455
- ```
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`.
456
206
 
457
- **Vendor clients:** the gateway needs `X-State-Environment-Token` on every request. A client such
458
- as jsforce only sends it if you configure custom headers. Prefer `Connection.fetch`. If you pass
459
- `headers()` to another client, those headers carry live tokens: only send them to `baseUrl`, and
460
- don't let the client follow redirects, which would forward the tokens to another host:
207
+ ## Pagination
461
208
 
462
- ```ts
463
- const salesforce = await env.connect('salesforce', { apiId: 'rest' });
464
- const response = await fetch(new URL('services/data/v67.0/limits', salesforce.baseUrl), {
465
- headers: salesforce.headers(),
466
- redirect: 'manual',
467
- });
468
- ```
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`.
469
211
 
470
212
  ## Recorded requests
471
213
 
472
- Request history is best effort. Missing records do not mean that no requests ran.
473
- Stored records remain readable after an environment stops.
474
-
475
- ```ts
476
- const stats = await sm.environments.getRequestStats(env.id, {
477
- from: new Date(Date.now() - 60 * 60_000),
478
- to: new Date(),
479
- });
480
- console.log(stats.count, stats.p95Ms);
481
- const page = await sm.environments.listRequests(env.id, { appKey: 'salesforce', limit: 20 });
482
- const first = page.data[0];
483
- if (first) {
484
- const detail = await sm.environments.getRequest(env.id, first.id);
485
- console.log(detail.status, detail.responseBody.preview);
486
- const body = await sm.environments.getRequestPayload(env.id, first.id, 'response');
487
- const bytes = await new Response(body).arrayBuffer();
488
- }
489
- ```
490
-
491
- Payload coverage reports credential redaction and omissions. The detail preview
492
- is bounded; the payload method returns the full stored body as a byte stream.
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.
493
218
 
494
219
  ## Supported runtimes
495
220
 
496
- - **Node.js 20.20 or later**, with 22 or 24 recommended: Node 20 is past its end of life, and
497
- earlier Node 20 releases fail when retrying a `POST`.
498
- - **Cloudflare Workers.**
221
+ - **Node.js**, at the versions that `engines` in `package.json` allows. ESM only.
222
+ - **Cloudflare Workers.** Pass `apiKey` explicitly.
499
223
  - **Not browsers.** Never ship an API key to a browser.
500
224
 
501
225
  TypeScript consumers need `lib: ["DOM"]` or `@types/node`, for the `fetch`, `Request` and
502
- `Response` types.
503
-
504
- ## Compatibility
505
-
506
- - While the version is `0.x`, a minor release may include breaking changes. Pin the minor
507
- version.
508
- - The API may add error codes and enum values, such as a new status or stop reason. Enums are
509
- open: handle values you don't recognise.
510
- - 0.5.0 removes database history and fork metadata. See [What's new in 0.5.0](#whats-new-in-050).
511
- - 0.4.0 adds suspend and resume and keeps every 0.3.0 call: `stop` still ends the environment.
512
- `Environment` gains `suspendedAt` and `sourceEnvironmentId`, so object literals typed as
513
- `Environment` need them. A snapshot's `consistency` is now `'environment'`, `'app_instance'`
514
- or `null` while it isn't ready (it was always `'per-app-instance'`). See
515
- [What's new in 0.4.0](#whats-new-in-040).
516
- - Upgrade from 0.3.0 to capture snapshots: the API now answers `consistency: null` while a
517
- capture runs, which 0.3.0 rejects as an invalid response even though the capture started.
518
- - 0.3.0 adds the snapshot APIs and keeps every 0.2.0 call. An input with neither `apps` nor
519
- `snapshotId` now fails with `Pass apps or snapshotId.` `Environment` and its apps gain
520
- `sourceEnvironmentSnapshotId` and `sourceAppInstanceSnapshotId` (`null` unless restored), so
521
- object literals typed as `Environment` need them.
522
- - 0.2.0 adds APIs and keeps every 0.1.x call, with these deliberate changes:
523
- - `connect()` without `actorId` now succeeds when an app has several actors, acting as the
524
- default actor (for Salesforce, the administrator, with full access).
525
- - An empty `actorId` (`''`) now throws `Unknown actor`, instead of acting as an app's only actor.
526
- - `create()` rejects an empty `apps` list or a repeated app key before sending a request, as a
527
- `StateMachinesError` with `status: null`.
528
- - `connect()`'s error messages now name the app: `Unknown actor <id> for <appKey>. …` and
529
- `Choose one API for <appKey>: …`.
530
-
531
- ## Known limitations
532
-
533
- - Vendor clients such as jsforce don't send `X-State-Environment-Token` unless you configure
534
- custom headers, so the gateway rejects them. Use `Connection.fetch`, or pass `headers()` as
535
- shown in [Connections](#connections).
536
- - There is no default API profile: pass `apiId` whenever an app has more than one.
537
- - Without a declared default connection, the default actor is the first one the release lists;
538
- releases don't declare a default actor yet.
539
- - `await using` isn't supported. Stop the environment in `finally`.
540
-
541
- ## What's new in 0.5.0
542
-
543
- ### Added
544
-
545
- - `sm.environments.listRequests`, `getRequest`, `getRequestPayload` and `getRequestStats`
546
- read best-effort gateway request history, including after the environment stops.
547
- - A failed environment retains its application data and can be resumed or stopped.
548
- Stop takes priority over startup, recovery and snapshot capture.
549
-
550
- ### Removed
551
-
552
- - The generated `listEnvironmentHistory`, `getEnvironmentPosition` and
553
- `getEnvironmentExchange` operations and their history/position/exchange models.
554
- Use the request-history methods above for recorded replica traffic.
555
- - `EnvironmentApp.forkedAt`. Database object history and point-in-time forks are no longer
556
- part of the API.
557
-
558
- `waitUntilStopped()` now requires `stopped` and completed disposal. A retained `failed`
559
- environment is not stopped. Lifecycle commands require Temporal availability; existing
560
- replica traffic does not.
561
-
562
- ## What's new in 0.4.0
563
-
564
- - `sm.environments.suspend(id)` ends an environment's sandboxes and keeps its data;
565
- `sm.environments.resume(id)` runs it again on that data, and
566
- `sm.environments.waitUntilSuspended(id)` waits for the suspension (see
567
- [Suspending and resuming](#suspending-and-resuming)).
568
- - `RunningEnvironment` gains `suspend()`, `resume()`, `waitUntilSuspended()` and
569
- `waitUntilReady()`.
570
- - `stop` is unchanged: it ends the environment and discards its data, suspended or not.
571
- - New statuses: `suspending` and `suspended` for environments, `suspended` and `recovering` for
572
- apps. New fields: `suspendedAt` and `sourceEnvironmentId` on an environment, `forkedAt` on an
573
- app. `waitUntilReady()` throws `EnvironmentFailedError` on `suspending` and `suspended`.
574
- - Snapshots: `consistency` is `'environment'` (every app paused together), `'app_instance'` (each
575
- app captured on its own) or `null` until ready. One capture runs per environment at a time; a
576
- second gets `409 capture-in-progress`.
577
-
578
- ## What's new in 0.3.0
579
-
580
- - `sm.snapshots`: `capture`, `create`, `waitUntilReady`, `get`, `list`, `rename` and `delete`
581
- (see [Snapshots](#snapshots)).
582
- - `env.snapshot()` captures a running environment and waits until the snapshot is `ready`.
583
- - `start({ snapshotId })` and `create({ snapshotId })` start an environment from a snapshot.
584
- Environments carry `sourceEnvironmentSnapshotId`, and apps `sourceAppInstanceSnapshotId`.
585
- - `environments.list({ snapshotId })` lists the environments started from a snapshot.
586
- - `SnapshotFailedError` and `SnapshotTimeoutError`.
587
-
588
- ## What's new in 0.2.0
589
-
590
- - `sm.environments.start()` creates an environment and waits until it's running. It returns a
591
- `RunningEnvironment` with `connect`, `actors`, `stop` and `waitUntilStopped`.
592
- - String apps: `apps: ['salesforce']` means `[{ key: 'salesforce', appId: 'salesforce' }]`, in
593
- `start()` and `create()`.
594
- - `actors(appKey)` lists an app's actors in the release's order.
595
- - `actorId` is optional in `connect()`. It defaults to the environment's declared default
596
- connection's actor, or else the app's first actor.
597
- - When readiness fails, `start()` stops the environment before it rethrows.
598
- - Cancelling a call now takes effect immediately (fixes 0.1.1). A `signal` used to reach an
599
- in-flight request only when its attempt timeout (60 seconds by default) fired. An abort during
600
- 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) |
601
255
 
602
256
  ## License
603
257