@usestatemachines/sdk 0.5.0 → 0.6.1

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 +177 -521
  2. package/agents/SKILL.md +38 -47
  3. package/agents/index.js +7 -7
  4. package/agents/llms.txt +50 -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
@@ -1,330 +1,214 @@
1
1
  import { SDK_METADATA } from '../lib/config.js';
2
2
  import { HTTPClient } from '../lib/http.js';
3
3
  import { StateMachines as Generated } from '../sdk/sdk.js';
4
- import { expandSource } from './apps.js';
5
- import { Connection } from './connection.js';
6
- import { CallerAbortedError, EnvironmentFailedError, SnapshotFailedError, SnapshotTimeoutError, StateMachinesError, TimeoutError, toStateMachinesError, } from './errors.js';
7
- import { RunningEnvironment } from './running-environment.js';
4
+ import { bindCredentials } from './credentials.js';
5
+ import { CallerAbortedError, StateMachinesError, toStateMachinesError, } from './errors.js';
8
6
  import { linkSignal } from './signal.js';
9
- import { backoff, defaultTimeoutMs, disposed, poll, pollUntil, ready, sleep, snapshotReady, suspended, } from './wait.js';
10
- const stopAfterFailureMs = 30000;
11
- const busyBudgetMs = 60000;
7
+ import { environmentAt, poll, snapshotAt, } from './wait.js';
12
8
  export class StateMachines {
13
9
  #sdk;
14
10
  #fetch;
15
11
  #workspaceId;
16
12
  #callSignals = new Map();
13
+ #apis = new Map();
17
14
  #lastCall = 0;
18
- #credentialWorkspace;
19
15
  constructor(options = {}) {
20
16
  const apiKey = options.apiKey ?? environmentApiKey();
21
17
  if (!apiKey) {
22
- throw new TypeError('Pass apiKey or set STATE_API_KEY.');
18
+ throw new TypeError('Pass apiKey or set STATEMACHINES_API_KEY.');
23
19
  }
24
20
  // Checked here so a malformed key is never echoed by a header validation error.
25
21
  if (!/^[\x21-\x7e]+$/.test(apiKey)) {
26
22
  throw new TypeError('apiKey must be printable ASCII with no spaces.');
27
23
  }
28
- if (options.fetch && options.httpClient) {
29
- throw new TypeError('Pass fetch or httpClient, not both.');
24
+ const attemptTimeoutMs = options.attemptTimeoutMs ?? 60000;
25
+ if (!(attemptTimeoutMs > 0 && Number.isFinite(attemptTimeoutMs))) {
26
+ throw new RangeError('attemptTimeoutMs must be a positive, finite number.');
30
27
  }
31
28
  const fetcher = options.fetch;
32
29
  // The arrow keeps `this` unbound; runtimes such as workerd reject fetch called on another object.
33
- const timeoutMs = options.timeoutMs ?? 60000;
34
- if (!(timeoutMs > 0 && Number.isFinite(timeoutMs))) {
35
- throw new RangeError('timeoutMs must be a positive, finite number.');
36
- }
37
- const httpClient = options.httpClient?.clone() ??
38
- new HTTPClient(fetcher ? { fetcher: (input) => fetcher(input) } : {});
39
- addAttemptTimeout(httpClient, timeoutMs, this.#callSignals);
30
+ const httpClient = new HTTPClient(fetcher ? { fetcher: (input) => fetcher(input) } : {});
31
+ addAttemptTimeout(httpClient, attemptTimeoutMs, this.#callSignals);
40
32
  this.#fetch = fetcher;
41
33
  this.#workspaceId = options.workspaceId;
42
34
  this.#sdk = new Generated({
43
35
  apiKey,
44
36
  userAgent: `statemachines-sdk-ts/${SDK_METADATA.sdkVersion}`,
45
37
  httpClient,
46
- ...(options.serverURL ? { serverURL: options.serverURL } : {}),
38
+ ...(options.baseUrl ? { serverURL: options.baseUrl } : {}),
47
39
  });
48
40
  }
49
- credential = {
50
- get: (options) => this.#unwrap(options, (call) => this.#sdk.credential.get(call)),
51
- };
52
- catalog = {
53
- get: (options) => this.#unwrap(options, (call) => this.#sdk.catalog.get(call)),
54
- release: (releaseId, options) => this.#unwrap(options, (call) => this.#sdk.catalog.release({ releaseId }, call)),
41
+ /** The caller: its principal, organization, workspace, permissions and limits. */
42
+ me() {
43
+ return this.#call(undefined, (call) => this.#sdk.me(call));
44
+ }
45
+ apps = {
46
+ list: (query = {}) => this.#call(undefined, (call) => this.#sdk.apps.list(query, call)),
47
+ /** Published versions, including retired ones. */
48
+ versions: (appId, query = {}) => this.#call(undefined, (call) => this.#sdk.apps.versions({ ...query, appId }, call)),
55
49
  };
56
50
  workspaces = {
57
- list: (query = {}, options) => this.#unwrap(options, (call) => this.#sdk.workspaces.list(query, call)),
58
- get: (workspaceId, options) => this.#unwrap(options, (call) => this.#sdk.workspaces.get({ workspaceId }, call)),
59
- events: (workspaceId, query = {}, options) => this.#call(options, (call) => this.#sdk.workspaces.events({ ...query, workspaceId }, call)),
51
+ list: (query = {}) => this.#call(undefined, (call) => this.#sdk.workspaces.list(query, call)),
52
+ get: (workspaceId) => this.#call(undefined, (call) => this.#sdk.workspaces.get({ workspaceId }, call)),
60
53
  };
61
54
  environments = {
62
- list: (query = {}, options) => this.#call(options, async (call) => {
63
- const { workspaceId, ...filters } = query;
64
- const resolved = await this.#workspace(workspaceId, call.signal);
65
- return this.#sdk.environments.list({ ...filters, workspaceId: resolved }, call);
66
- }),
67
- get: (environmentId, options) => this.#unwrap(options, (call) => this.#sdk.environments.get({ environmentId }, call)),
68
- /**
69
- * Creates an environment and waits until it's running. If the wait fails, stops the
70
- * environment (unless the caller passed its own `idempotencyKey`), then rethrows the
71
- * error with `environmentId`, `stopped` and `stopError`.
72
- */
73
- start: async (input, options = {}) => {
74
- const { readyTimeoutMs, ...createOptions } = options;
75
- const { idempotencyKey, signal } = options;
76
- const timeoutMs = readyTimeoutMs ?? defaultTimeoutMs;
77
- if (!(timeoutMs > 0)) {
78
- throw new RangeError('readyTimeoutMs must be more than 0.');
79
- }
80
- const created = await this.environments.create(input, createOptions);
81
- try {
82
- const environment = await this.environments.waitUntilReady(created.id, {
83
- timeoutMs,
84
- ...(signal ? { signal } : {}),
85
- });
86
- return new RunningEnvironment(this, environment);
87
- }
88
- catch (error) {
89
- // The API replays a create by key, returning the same environment to another
90
- // start() that may still be using it, so only the SDK's own key allows a stop.
91
- const outcome = idempotencyKey === undefined
92
- ? await this.#stopAfterFailure(created.id)
93
- : { environmentId: created.id, stopped: false };
94
- const isCallerReason = signal?.aborted && Object.is(error, signal.reason);
95
- if (isCallerReason) {
96
- if (outcome.stopped) {
97
- throw error;
98
- }
99
- throw Object.assign(new StateMachinesError(new CallerAbortedError(error)), outcome);
100
- }
101
- if (isStartError(error) && Object.isExtensible(error)) {
102
- Object.assign(error, outcome);
103
- }
104
- throw error;
105
- }
106
- },
107
- /**
108
- * A string app is shorthand for `{ key: <the string>, appId: <the string> }`. Every
109
- * failure carries the `idempotencyKey`; replaying the create with it is safe.
110
- */
55
+ /** Creates an environment and, unless `wait` is false, waits until it is `running`. */
111
56
  create: async (input, options = {}) => {
57
+ const { signal } = options;
112
58
  const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
113
- try {
114
- const body = expandSource(input);
115
- return await this.#call(options, async (call) => {
116
- const workspaceId = await this.#workspace(options.workspaceId, call.signal);
117
- const { data } = await this.#sdk.environments.create({ workspaceId, idempotencyKey, body }, call);
118
- const [environment] = data;
119
- if (!environment) {
120
- throw new Error('Invalid response from the State API: the create returned no environment.');
121
- }
122
- return environment;
59
+ const created = await this.#call(signal, (call) => this.#sdk.environments.create({ idempotencyKey, body: this.#inWorkspace(input) }, call));
60
+ return options.wait === false
61
+ ? this.#bind(created)
62
+ : this.environments.wait(created.id, {
63
+ status: 'running',
64
+ ...waitOptions(options),
123
65
  });
124
- }
125
- catch (error) {
126
- // Always a new instance: concurrent creates can share one failed workspace lookup.
127
- throw new StateMachinesError(error, { idempotencyKey });
128
- }
129
66
  },
130
- /** Ends the environment and discards its data. Safe to repeat. */
131
- stop: (environmentId, options) => this.#command('stop', environmentId, options),
132
- /**
133
- * Suspends a running environment: its sandboxes end and its data stays for `resume`.
134
- * Its lifetime is paused meanwhile. Use `stop` to end it.
135
- */
136
- suspend: (environmentId, options) => this.#command('suspend', environmentId, options),
137
- /** Resumes a suspended or failed environment using its retained data. */
138
- resume: (environmentId, options) => this.#command('resume', environmentId, options),
139
- events: (environmentId, query = {}, options) => this.#call(options, (call) => this.#sdk.environments.events({ ...query, environmentId }, call)),
140
- listRequests: (environmentId, query = {}, options) => this.#call(options, (call) => this.#sdk.environments.listRequests({ ...query, environmentId }, call)),
141
- getRequestStats: (environmentId, query, options) => this.#unwrap(options, (call) => this.#sdk.environments.getRequestStats({ ...query, environmentId }, call)),
142
- getRequest: (environmentId, requestId, options) => this.#unwrap(options, (call) => this.#sdk.environments.getRequest({ environmentId, requestId }, call)),
143
- getRequestPayload: (environmentId, requestId, direction, options) => this.#call(options, (call) => this.#sdk.environments.getRequestPayload({ environmentId, requestId, direction }, call)),
144
- /** The app's users, in the release's order. */
145
- actors: async (environmentId, appKey, options) => (await this.environments.getConnection(environmentId, appKey, options))
146
- .actors,
147
- getConnection: (environmentId, appKey, options) => this.#unwrap(options, (call) => this.#sdk.environments.getConnection({ environmentId, appKey }, call)),
148
- reveal: (environmentId, appKey, actorId, options) => this.#reveal(environmentId, appKey, { actorId }, options),
149
- /** Resolves when the environment is running. Never suspends or stops it. */
150
- waitUntilReady: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), ready, options),
151
- /** Resolves when the environment is suspended and can resume. */
152
- waitUntilSuspended: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), suspended, options),
153
- /** Resolves when the environment ended and every sandbox is confirmed disposed. */
154
- waitUntilStopped: (environmentId, options) => poll((signal) => this.#poll(environmentId, signal), disposed, options),
67
+ /** Newest first. Leaves deleted environments out unless `status` asks for them. */
68
+ list: async (query = {}) => {
69
+ const page = await this.#call(undefined, (call) => this.#sdk.environments.list(this.#inWorkspace(query), call));
70
+ return { ...page, data: page.data.map((each) => this.#bind(each)) };
71
+ },
72
+ get: async (environmentId) => this.#bind(await this.#call(undefined, (call) => this.#sdk.environments.get({ environmentId }, call))),
73
+ /** `labels` replaces the whole map. */
74
+ update: async (environmentId, body) => this.#bind(await this.#call(undefined, (call) => this.#sdk.environments.update({ environmentId, body }, call))),
75
+ /** Takes effect when the API answers: the gateway refuses calls from then on. */
76
+ pause: async (environmentId, options = {}) => this.#bind(await this.#call(options.signal, (call) => this.#sdk.environments.pause({ environmentId }, call))),
77
+ /** Resumes a paused or failed environment and, unless `wait` is false, waits until it is `running`. */
78
+ resume: async (environmentId, options = {}) => {
79
+ const resumed = await this.#call(options.signal, (call) => this.#sdk.environments.resume({ environmentId }, call));
80
+ return options.wait === false
81
+ ? this.#bind(resumed)
82
+ : this.environments.wait(environmentId, {
83
+ status: 'running',
84
+ ...waitOptions(options),
85
+ });
86
+ },
87
+ /** Deletes the environment and its data. Safe to repeat. */
88
+ delete: async (environmentId, options = {}) => this.#bind(await this.#call(options.signal, (call) => this.#sdk.environments.delete({ environmentId }, call))),
155
89
  /**
156
- * actorId defaults to the actor of the environment's declared default connection, or
157
- * else the app's first actor (for Salesforce, the administrator). Pick another from
158
- * actors() to act with that user's permissions.
90
+ * Resolves when the environment has `status`. Throws once it cannot get there without
91
+ * another call: `EnvironmentFailedError` if it failed, else `UnexpectedStatusError`.
159
92
  */
160
- connect: async (environmentId, appKey, options = {}) => {
161
- const details = await this.environments.getConnection(environmentId, appKey, options);
162
- if (details.status !== 'ready' || !details.baseUrl) {
163
- throw new TypeError(`App ${appKey} is not ready to connect (${details.status}).`);
164
- }
165
- // The API resolves { actorId } to that actor's first connection, not the declared one.
166
- const body = options.actorId === undefined && details.defaultConnection
167
- ? { connectionKey: details.defaultConnection }
168
- : { actorId: pickActor(appKey, details.actors, options.actorId).id };
169
- const api = pickApi(appKey, details.apis, options.apiId);
170
- const secret = await this.#reveal(environmentId, appKey, body, options);
171
- return new Connection(details.baseUrl, api, secret, this.#fetch);
172
- },
93
+ wait: async (environmentId, options) => this.#bind(await poll(environmentId, (signal) => this.#read(signal, (call) => this.#sdk.environments.get({ environmentId }, call)), environmentAt(options.status), options)),
94
+ /** Credentials for one app of the environment. */
95
+ connect: (environmentId, app, options = {}) => this.#connect(environmentId, null, app, options),
173
96
  };
174
97
  snapshots = {
175
- /** Returns the API's answer at once: it may still be `capturing`. */
176
- create: (environmentId, input = {}, options = {}) => this.#createSnapshot(environmentId, input, options, 0),
177
- /**
178
- * Creates a snapshot and waits until it's `ready`. Needs `snapshots:create` and
179
- * `snapshots:read`. A failed or timed-out snapshot is never deleted: the error
180
- * carries `snapshotId` and `idempotencyKey`.
181
- */
182
- capture: async (environmentId, input = {}, options = {}) => {
183
- const { readyTimeoutMs, ...createOptions } = options;
184
- const { signal } = options;
98
+ /** Creates a snapshot of a running environment and, unless `wait` is false, waits until it is `ready`. */
99
+ create: async (input, options = {}) => {
185
100
  const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
186
- const timeoutMs = readyTimeoutMs ?? defaultTimeoutMs;
187
- if (!(timeoutMs > 0)) {
188
- throw new RangeError('readyTimeoutMs must be more than 0.');
189
- }
190
- const created = await this.#createSnapshot(environmentId, input, { ...createOptions, idempotencyKey }, busyBudgetMs);
191
- try {
192
- signal?.throwIfAborted();
193
- if (snapshotReady(created)) {
194
- return created;
195
- }
196
- return await this.snapshots.waitUntilReady(created.id, {
197
- timeoutMs,
198
- ...(signal ? { signal } : {}),
101
+ const created = await this.#call(options.signal, (call) => this.#sdk.snapshots.create({ idempotencyKey, body: input }, call));
102
+ return options.wait === false
103
+ ? created
104
+ : this.snapshots.wait(created.id, {
105
+ status: 'ready',
106
+ ...waitOptions(options),
199
107
  });
200
- }
201
- catch (error) {
202
- const snapshotId = created.id;
203
- if (signal?.aborted && Object.is(error, signal.reason)) {
204
- throw Object.assign(new StateMachinesError(new CallerAbortedError(error), {
205
- idempotencyKey,
206
- }), { snapshotId });
207
- }
208
- if (error instanceof StateMachinesError ||
209
- error instanceof SnapshotFailedError ||
210
- error instanceof SnapshotTimeoutError) {
211
- Object.assign(error, { snapshotId, idempotencyKey });
212
- }
213
- throw error;
214
- }
215
108
  },
216
- /** Resolves when the snapshot is `ready`. Never deletes it. */
217
- waitUntilReady: (snapshotId, options) => pollUntil((signal) => this.#unwrap({ signal }, (call) => this.#sdk.snapshots.get({ snapshotId }, { ...call, retries: { strategy: 'none' } })), snapshotReady, options, (timeoutMs, last) => new SnapshotTimeoutError(timeoutMs, last, snapshotId)),
218
- /** Deleted snapshots too, with `deletedAt` set. */
219
- get: (snapshotId, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.get({ snapshotId }, call)),
220
- /** Newest first. Deleted snapshots are left out. */
221
- list: (query = {}, options) => this.#call(options, async (call) => {
222
- const { workspaceId, ...filters } = query;
223
- const resolved = await this.#workspace(workspaceId, call.signal);
224
- return this.#sdk.snapshots.list({ ...filters, workspaceId: resolved }, call);
225
- }),
109
+ /** Newest first. Leaves deleted snapshots out unless `status` asks for them. */
110
+ list: (query = {}) => this.#call(undefined, (call) => this.#sdk.snapshots.list(this.#inWorkspace(query), call)),
111
+ get: (snapshotId) => this.#call(undefined, (call) => this.#sdk.snapshots.get({ snapshotId }, call)),
112
+ /** `null` clears the name. */
113
+ update: (snapshotId, body) => this.#call(undefined, (call) => this.#sdk.snapshots.update({ snapshotId, body }, call)),
114
+ /** A `saving` snapshot cannot be deleted. Safe to repeat. */
115
+ delete: (snapshotId) => this.#call(undefined, (call) => this.#sdk.snapshots.delete({ snapshotId }, call)),
226
116
  /**
227
- * `null` clears the name. A stale `expectedVersion`, or a deleted snapshot, is a 409
228
- * `version-conflict`; so is a retry of a rename whose first answer was lost.
117
+ * Resolves when the snapshot has `status`. Throws once it cannot get there without
118
+ * another call: `SnapshotFailedError` if it failed, else `UnexpectedStatusError`.
229
119
  */
230
- rename: (snapshotId, body, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.update({ snapshotId, body }, call)),
231
- /** Marks the snapshot deleted and returns it; safe to repeat. A capturing snapshot is a 409 `not-ready`. */
232
- delete: (snapshotId, options) => this.#unwrap(options, (call) => this.#sdk.snapshots.delete({ snapshotId }, call)),
120
+ wait: (snapshotId, options) => poll(snapshotId, (signal) => this.#read(signal, (call) => this.#sdk.snapshots.get({ snapshotId }, call)), snapshotAt(options.status), options),
233
121
  };
234
- async #command(verb, environmentId, options = {}) {
235
- const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
236
- try {
237
- return await this.#unwrap(options, (call) => this.#sdk.environments[verb]({ environmentId, idempotencyKey }, call));
238
- }
239
- catch (error) {
240
- throw new StateMachinesError(error, { idempotencyKey });
241
- }
242
- }
243
- async #createSnapshot(environmentId, input, options, busyMs) {
244
- const idempotencyKey = options.idempotencyKey ?? crypto.randomUUID();
245
- try {
246
- return await this.#call(options, async (call) => {
247
- const workspaceId = await this.#workspace(options.workspaceId, call.signal);
248
- const request = {
249
- workspaceId,
250
- idempotencyKey,
251
- body: { ...input, environmentId },
252
- };
253
- const deadline = Date.now() + busyMs;
254
- for (let attempt = 0;; attempt += 1) {
255
- try {
256
- return (await this.#sdk.snapshots.capture(request, call)).data;
257
- }
258
- catch (error) {
259
- const busy = toStateMachinesError(error);
260
- const waitMs = Math.max(busy.retryAfter ?? 0, backoff(attempt));
261
- if (busy.code !== 'snapshot-limit' ||
262
- Date.now() + waitMs >= deadline) {
263
- throw error;
264
- }
265
- await sleep(waitMs, call.signal);
266
- }
267
- }
268
- });
269
- }
270
- catch (error) {
271
- throw new StateMachinesError(error, { idempotencyKey });
272
- }
273
- }
274
- #reveal(environmentId, appKey, body, options) {
275
- return this.#unwrap(options, (call) => this.#sdk.environments.reveal({ environmentId, appKey, body }, call));
276
- }
277
- async #stopAfterFailure(environmentId) {
278
- const budget = linkSignal(undefined, stopAfterFailureMs);
279
- try {
280
- // abortable() ends the wait at the budget even during a retry delay.
281
- await abortable(this.environments.stop(environmentId, { signal: budget.signal }), budget.signal);
282
- return { environmentId, stopped: true };
283
- }
284
- catch (stopError) {
285
- return { environmentId, stopped: false, stopError };
286
- }
287
- finally {
288
- budget.dispose();
289
- }
122
+ requests = {
123
+ /** Calls the gateway recorded for one environment, newest first. Recording is best effort. */
124
+ list: (query) => this.#call(undefined, (call) => this.#sdk.requests.list(query, call)),
125
+ get: (requestId) => this.#call(undefined, (call) => this.#sdk.requests.get({ requestId }, call)),
126
+ stats: (query) => this.#call(undefined, (call) => this.#sdk.requests.stats(query, call)),
127
+ /** The recorded body, in full. */
128
+ body: (requestId, direction) =>
129
+ // Read inside the call: settling it aborts the signal the body streams under.
130
+ this.#call(undefined, async (call) => {
131
+ const stream = await this.#sdk.requests.body({ requestId, direction }, call);
132
+ return new Uint8Array(await new Response(stream).arrayBuffer());
133
+ }),
134
+ };
135
+ auditEvents = {
136
+ /** Newest first. An API key that names no filter gets its own workspace. */
137
+ list: (query = {}) => this.#call(undefined, (call) => this.#sdk.auditEvents.list(query.environmentId ? query : this.#inWorkspace(query), call)),
138
+ };
139
+ #inWorkspace(input) {
140
+ return input.workspaceId === undefined && this.#workspaceId !== undefined
141
+ ? { ...input, workspaceId: this.#workspaceId }
142
+ : input;
290
143
  }
291
- /** One read for the poller, which owns retries, timing and the caller's signal. */
292
- #poll(environmentId, signal) {
293
- return this.#unwrap({ signal }, (call) => this.#sdk.environments.get({ environmentId }, { ...call, retries: { strategy: 'none' } }));
144
+ async #connect(environmentId, known, app, options) {
145
+ const body = options.actorId === undefined ? {} : { actorId: options.actorId };
146
+ const [credentials, apis] = await Promise.all([
147
+ this.#call(undefined, (call) => this.#sdk.environments.connect({ environmentId, appName: app, body }, call)),
148
+ (async () => {
149
+ const environment = known ?? (await this.environments.get(environmentId));
150
+ const entry = environment.apps.find(({ name }) => name === app);
151
+ return entry ? this.#versionApis(entry.appId, entry.versionId) : [];
152
+ })(),
153
+ ]);
154
+ return bindCredentials(credentials, apis, this.#fetch);
294
155
  }
295
- /** Resolves the workspace once per client; a failed lookup is retried by the next call. */
296
- #workspace(explicit, signal) {
297
- const workspaceId = explicit ?? this.#workspaceId;
298
- if (workspaceId) {
299
- return Promise.resolve(workspaceId);
156
+ /** A version's APIs never change, so each is read once per client. */
157
+ #versionApis(appId, versionId) {
158
+ let apis = this.#apis.get(versionId);
159
+ if (!apis) {
160
+ apis = this.#findVersion(appId, versionId);
161
+ apis.catch(() => this.#apis.delete(versionId));
162
+ this.#apis.set(versionId, apis);
300
163
  }
301
- this.#credentialWorkspace ?? (this.#credentialWorkspace = this.#lookupWorkspace());
302
- // The lookup is shared, so one caller's abort only stops that caller waiting.
303
- return abortable(this.#credentialWorkspace, signal);
164
+ return apis;
304
165
  }
305
- #lookupWorkspace() {
306
- const lookup = this.credential.get().then((credential) => {
307
- if (!credential.workspaceId) {
308
- throw new TypeError('This credential has no workspace. Pass workspaceId, or use a workspace API key.');
309
- }
310
- return credential.workspaceId;
311
- });
312
- lookup.catch(() => {
313
- if (this.#credentialWorkspace === lookup) {
314
- this.#credentialWorkspace = undefined;
166
+ async #findVersion(appId, versionId) {
167
+ let cursor;
168
+ do {
169
+ const page = await this.apps.versions(appId, cursor ? { cursor } : {});
170
+ const version = page.data.find(({ id }) => id === versionId);
171
+ if (version) {
172
+ return version.apis;
315
173
  }
174
+ cursor = page.nextCursor ?? undefined;
175
+ } while (cursor);
176
+ throw new StateMachinesError({
177
+ message: `Version ${versionId} of ${appId} was not found.`,
178
+ status: null,
179
+ code: 'not_found',
180
+ requestId: null,
181
+ fields: null,
182
+ retryAfterMs: null,
316
183
  });
317
- return lookup;
318
184
  }
319
- #unwrap(options, run) {
320
- return this.#call(options, async (call) => (await run(call)).data);
185
+ #bind(data) {
186
+ const { id } = data;
187
+ const helpers = {
188
+ connect: (app, options = {}) => this.#connect(id, data, app, options),
189
+ pause: (options) => this.environments.pause(id, options),
190
+ resume: (options) => this.environments.resume(id, options),
191
+ delete: (options) => this.environments.delete(id, options),
192
+ refresh: () => this.environments.get(id),
193
+ snapshots: {
194
+ create: (input = {}, options) => this.snapshots.create({ ...input, environmentId: id }, options),
195
+ },
196
+ };
197
+ return Object.defineProperties({ ...data }, Object.fromEntries(Object.entries(helpers).map(([name, value]) => [
198
+ name,
199
+ { value, enumerable: false },
200
+ ])));
201
+ }
202
+ /** One read for the poller, which owns retries, timing and the caller's signal. */
203
+ #read(signal, run) {
204
+ return this.#call(signal, (call) => run({ ...call, retries: { strategy: 'none' } }));
321
205
  }
322
206
  /**
323
207
  * Runs one API call under its own signal. Closing it when the call settles
324
208
  * releases every attempt's timeout timer and listener.
325
209
  */
326
- async #call(options, run) {
327
- const scope = linkSignal(options?.signal);
210
+ async #call(signal, run) {
211
+ const scope = linkSignal(signal);
328
212
  const id = String(++this.#lastCall);
329
213
  this.#callSignals.set(id, scope.signal);
330
214
  try {
@@ -335,9 +219,7 @@ export class StateMachines {
335
219
  }
336
220
  catch (error) {
337
221
  // A caller abort can surface as a raw TimeoutError while a body is read; it is never retryable.
338
- throw toStateMachinesError(options?.signal?.aborted
339
- ? new CallerAbortedError(options.signal.reason)
340
- : error);
222
+ throw toStateMachinesError(signal?.aborted ? new CallerAbortedError(signal.reason) : error);
341
223
  }
342
224
  finally {
343
225
  this.#callSignals.delete(id);
@@ -360,14 +242,13 @@ function addAttemptTimeout(httpClient, timeoutMs, calls) {
360
242
  httpClient.addHook('beforeRequest', (request) => {
361
243
  const headers = new Headers(request.headers);
362
244
  const call = calls.get(headers.get(CALL_HEADER) ?? '');
363
- // The request's own signal still counts: a caller's hook may have set it.
364
245
  const sources = call ? [call, request.signal] : [request.signal];
365
246
  const aborted = sources.find(({ aborted }) => aborted);
366
247
  headers.delete(CALL_HEADER);
367
248
  if (aborted) {
368
249
  throw new CallerAbortedError(aborted.reason);
369
250
  }
370
- const attempt = new Request(request, {
251
+ const attempt = new globalThis.Request(request, {
371
252
  headers,
372
253
  signal: linkSignal(sources, timeoutMs).signal,
373
254
  });
@@ -382,56 +263,15 @@ function addAttemptTimeout(httpClient, timeoutMs, calls) {
382
263
  }
383
264
  });
384
265
  }
385
- function isStartError(error) {
386
- return (error instanceof StateMachinesError ||
387
- error instanceof EnvironmentFailedError ||
388
- error instanceof TimeoutError);
389
- }
390
- function abortable(promise, signal) {
391
- if (!signal) {
392
- return promise;
393
- }
394
- return new Promise((resolve, reject) => {
395
- const onAbort = () => reject(signal.reason);
396
- if (signal.aborted) {
397
- onAbort();
398
- return;
399
- }
400
- signal.addEventListener('abort', onAbort, { once: true });
401
- promise
402
- .then(resolve, reject)
403
- .finally(() => signal.removeEventListener('abort', onAbort));
404
- });
405
- }
406
- function pickActor(appKey, actors, actorId) {
407
- const [first] = actors;
408
- if (!first) {
409
- throw new TypeError(`App ${appKey} has no actors.`);
410
- }
411
- if (actorId === undefined) {
412
- return first;
413
- }
414
- const actor = actors.find(({ id }) => id === actorId);
415
- if (!actor) {
416
- throw new TypeError(`Unknown actor ${actorId} for ${appKey}. Choose one of: ${ids(actors)}.`);
417
- }
418
- return actor;
419
- }
420
- function pickApi(appKey, apis, apiId) {
421
- const api = apiId
422
- ? apis.find(({ id }) => id === apiId)
423
- : apis.length === 1
424
- ? apis[0]
425
- : undefined;
426
- if (!api) {
427
- throw new TypeError(`Choose one API for ${appKey}: ${ids(apis) || 'none available'}.`);
428
- }
429
- return api;
430
- }
431
- function ids(choices) {
432
- return choices.map(({ id }) => id).join(', ');
266
+ function waitOptions(options) {
267
+ return {
268
+ ...(options.timeoutMs === undefined
269
+ ? {}
270
+ : { timeoutMs: options.timeoutMs }),
271
+ ...(options.signal ? { signal: options.signal } : {}),
272
+ };
433
273
  }
434
274
  function environmentApiKey() {
435
275
  const runtime = globalThis;
436
- return runtime.process?.env?.STATE_API_KEY;
276
+ return runtime.process?.env?.STATEMACHINES_API_KEY;
437
277
  }
@@ -0,0 +1,13 @@
1
+ import type { Api } from '../models/api.js';
2
+ import type { Credentials as CredentialsData } from '../models/credentials.js';
3
+ /** The API's `Credentials`, plus `fetch` to call the app with them. */
4
+ export type Credentials = CredentialsData & {
5
+ /**
6
+ * Sends one request to the app: `path` is joined to `url`, `headers` are added, and
7
+ * `token` goes in the header of the API whose path matches (`Authorization: Bearer`
8
+ * for Salesforce REST and Bulk 2.0). Never retries, never follows redirects, never
9
+ * sends the State Machines API key. The SOAP session header is the caller's job.
10
+ */
11
+ fetch(path: string, init?: RequestInit): Promise<Response>;
12
+ };
13
+ export declare function bindCredentials(data: CredentialsData, apis: readonly Api[], fetcher: typeof fetch | undefined): Credentials;