@distilled.cloud/fly-io 1.0.0-rc.11 → 1.0.0-rc.12

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sprites-mint.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sprites-mint.test.d.ts","sourceRoot":"","sources":["../src/sprites-mint.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,170 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import * as API from "@distilled.cloud/core/api";
3
+ import * as Cause from "effect/Cause";
4
+ import * as Deferred from "effect/Deferred";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Exit from "effect/Exit";
7
+ import * as Fiber from "effect/Fiber";
8
+ import * as Redacted from "effect/Redacted";
9
+ import * as HttpClient from "effect/unstable/http/HttpClient";
10
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
11
+ import { Credentials } from "./credentials.js";
12
+ import { SpritesProtocol } from "./protocol.js";
13
+ import { ListSpritesRequest } from "./services/sprites.js";
14
+ const mintFixture = (apiKey) => Effect.gen(function* () {
15
+ const protocol = yield* API.Protocol;
16
+ const attempts = yield* Effect.forEach([0, 1], () => Effect.gen(function* () {
17
+ return {
18
+ started: yield* Deferred.make(),
19
+ response: yield* Deferred.make(),
20
+ };
21
+ }));
22
+ const discoveries = [];
23
+ const mints = [];
24
+ let credentialReads = 0;
25
+ const client = HttpClient.make((request) => Effect.gen(function* () {
26
+ if (request.url === "https://fly.test/v1/tokens/current") {
27
+ discoveries.push(request);
28
+ return HttpClientResponse.fromWeb(request, Response.json({ tokens: [{ org_slug: "mint-test" }] }));
29
+ }
30
+ if (request.method !== "POST" ||
31
+ request.url !==
32
+ "https://api.sprites.dev/v1/organizations/mint-test/tokens") {
33
+ return yield* Effect.die("Unexpected test transport request");
34
+ }
35
+ const attempt = attempts[mints.length];
36
+ mints.push(request);
37
+ if (!attempt)
38
+ return yield* Effect.die("Unexpected mint attempt");
39
+ yield* Deferred.succeed(attempt.started, undefined);
40
+ const { status, body } = yield* Deferred.await(attempt.response);
41
+ return HttpClientResponse.fromWeb(request, Response.json(body, { status }));
42
+ }));
43
+ const encode = protocol
44
+ .encode({
45
+ input: {},
46
+ inputAst: ListSpritesRequest.ast,
47
+ config: { input: ListSpritesRequest },
48
+ })
49
+ .pipe(Effect.provideService(HttpClient.HttpClient, client), Effect.provideService(Credentials, Effect.sync(() => {
50
+ credentialReads++;
51
+ return {
52
+ apiKey: Redacted.make(apiKey),
53
+ apiBaseUrl: "https://fly.test",
54
+ };
55
+ })));
56
+ return {
57
+ encode,
58
+ discoveries,
59
+ mints,
60
+ credentialReads: () => credentialReads,
61
+ started: (attempt = 0) => Deferred.await(attempts[attempt].started),
62
+ respond: (body, status = 200, attempt = 0) => Deferred.succeed(attempts[attempt].response, { status, body }),
63
+ };
64
+ }).pipe(Effect.provide(SpritesProtocol));
65
+ describe("Sprites token mint coordination", () => {
66
+ test("shares concurrent minting and reuses the successful token without exposing it in request JSON", () => Effect.runPromise(Effect.gen(function* () {
67
+ const fixture = yield* mintFixture("mint-concurrent-fly-secret");
68
+ const owner = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
69
+ yield* fixture.started();
70
+ const waiters = yield* Effect.forEach(Array.from({ length: 20 }), () => fixture.encode.pipe(Effect.forkChild({ startImmediately: true })));
71
+ expect(fixture.credentialReads()).toBe(21);
72
+ expect(fixture.discoveries).toHaveLength(1);
73
+ expect(fixture.mints).toHaveLength(1);
74
+ yield* fixture.respond({ token: "mint-concurrent-sprites-secret" });
75
+ const requests = yield* Effect.forEach([owner, ...waiters], Fiber.join);
76
+ requests.push(yield* fixture.encode);
77
+ for (const request of requests) {
78
+ expect(request.url).toBe("https://api.sprites.dev/v1/sprites");
79
+ expect(request.headers.authorization).toBe("Bearer mint-concurrent-sprites-secret");
80
+ expect(JSON.stringify(request)).not.toContain("mint-concurrent-sprites-secret");
81
+ }
82
+ expect(fixture.mints[0].headers.authorization).toBe("FlyV1 mint-concurrent-fly-secret");
83
+ for (const request of [...fixture.discoveries, ...fixture.mints]) {
84
+ expect(JSON.stringify(request)).not.toContain("mint-concurrent-fly-secret");
85
+ }
86
+ expect(fixture.credentialReads()).toBe(22);
87
+ expect(fixture.discoveries).toHaveLength(1);
88
+ expect(fixture.mints).toHaveLength(1);
89
+ }).pipe(Effect.timeout("2 seconds"))));
90
+ test("shares a failed mint and allows a subsequent successful retry", () => Effect.runPromise(Effect.gen(function* () {
91
+ const fixture = yield* mintFixture("mint-retry-fly-secret");
92
+ const owner = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
93
+ yield* fixture.started();
94
+ const waiter = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
95
+ expect(fixture.credentialReads()).toBe(2);
96
+ expect(fixture.mints).toHaveLength(1);
97
+ yield* fixture.respond({ message: "synthetic mint failure" }, 500);
98
+ const exits = yield* Fiber.awaitAll([owner, waiter]);
99
+ for (const exit of exits) {
100
+ expect(Exit.isFailure(exit)).toBe(true);
101
+ if (Exit.isFailure(exit)) {
102
+ expect(Cause.pretty(exit.cause)).toContain("synthetic mint failure");
103
+ expect(Cause.pretty(exit.cause)).not.toContain("mint-retry-fly-secret");
104
+ }
105
+ }
106
+ expect(exits[0]).toEqual(exits[1]);
107
+ const retry = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
108
+ yield* fixture.started(1);
109
+ yield* fixture.respond({ token: "mint-retry-sprites-secret" }, 200, 1);
110
+ expect((yield* Fiber.join(retry)).headers.authorization).toBe("Bearer mint-retry-sprites-secret");
111
+ expect((yield* fixture.encode).headers.authorization).toBe("Bearer mint-retry-sprites-secret");
112
+ expect(fixture.discoveries).toHaveLength(2);
113
+ expect(fixture.mints).toHaveLength(2);
114
+ }).pipe(Effect.timeout("2 seconds"))));
115
+ test("isolates pending and successful tokens for separate credentials", () => Effect.runPromise(Effect.gen(function* () {
116
+ const first = yield* mintFixture("mint-isolation-first");
117
+ const second = yield* mintFixture("mint-isolation-second");
118
+ const firstFiber = yield* first.encode.pipe(Effect.forkChild({ startImmediately: true }));
119
+ const secondFiber = yield* second.encode.pipe(Effect.forkChild({ startImmediately: true }));
120
+ yield* first.started();
121
+ yield* second.started();
122
+ expect(first.mints[0].headers.authorization).toBe("FlyV1 mint-isolation-first");
123
+ expect(second.mints[0].headers.authorization).toBe("FlyV1 mint-isolation-second");
124
+ yield* second.respond({ token: "sprites-isolation-second" });
125
+ expect((yield* Fiber.join(secondFiber)).headers.authorization).toBe("Bearer sprites-isolation-second");
126
+ yield* first.respond({ token: "sprites-isolation-first" });
127
+ expect((yield* Fiber.join(firstFiber)).headers.authorization).toBe("Bearer sprites-isolation-first");
128
+ expect((yield* first.encode).headers.authorization).toBe("Bearer sprites-isolation-first");
129
+ expect((yield* second.encode).headers.authorization).toBe("Bearer sprites-isolation-second");
130
+ expect(first.mints).toHaveLength(1);
131
+ expect(second.mints).toHaveLength(1);
132
+ }).pipe(Effect.timeout("2 seconds"))));
133
+ test("interrupting a waiter does not cancel or evict the active mint", () => Effect.runPromise(Effect.gen(function* () {
134
+ const fixture = yield* mintFixture("mint-interrupted-waiter");
135
+ const owner = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
136
+ yield* fixture.started();
137
+ const waiter = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
138
+ expect(fixture.credentialReads()).toBe(2);
139
+ yield* Fiber.interrupt(waiter);
140
+ expect(Exit.hasInterrupts(yield* Fiber.await(waiter))).toBe(true);
141
+ const replacementWaiter = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
142
+ expect(fixture.credentialReads()).toBe(3);
143
+ expect(fixture.mints).toHaveLength(1);
144
+ yield* fixture.respond({ token: "sprites-after-waiter-interruption" });
145
+ for (const fiber of [owner, replacementWaiter]) {
146
+ expect((yield* Fiber.join(fiber)).headers.authorization).toBe("Bearer sprites-after-waiter-interruption");
147
+ }
148
+ expect(fixture.discoveries).toHaveLength(1);
149
+ }).pipe(Effect.timeout("2 seconds"))));
150
+ test("owner interruption releases all waiters and allows another mint", () => Effect.runPromise(Effect.gen(function* () {
151
+ const fixture = yield* mintFixture("mint-interrupted-owner");
152
+ const owner = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
153
+ yield* fixture.started();
154
+ const waiters = yield* Effect.forEach([0, 1, 2], () => fixture.encode.pipe(Effect.forkChild({ startImmediately: true })));
155
+ expect(fixture.credentialReads()).toBe(4);
156
+ expect(fixture.mints).toHaveLength(1);
157
+ yield* Fiber.interrupt(owner);
158
+ for (const exit of yield* Fiber.awaitAll([owner, ...waiters])) {
159
+ expect(Exit.hasInterrupts(exit)).toBe(true);
160
+ }
161
+ const retry = yield* fixture.encode.pipe(Effect.forkChild({ startImmediately: true }));
162
+ yield* fixture.started(1);
163
+ yield* fixture.respond({ token: "sprites-after-interruption" }, 200, 1);
164
+ expect((yield* Fiber.join(retry)).headers.authorization).toBe("Bearer sprites-after-interruption");
165
+ expect((yield* fixture.encode).headers.authorization).toBe("Bearer sprites-after-interruption");
166
+ expect(fixture.discoveries).toHaveLength(2);
167
+ expect(fixture.mints).toHaveLength(2);
168
+ }).pipe(Effect.timeout("2 seconds"))));
169
+ });
170
+ //# sourceMappingURL=sprites-mint.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sprites-mint.test.js","sourceRoot":"","sources":["../src/sprites-mint.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distilled.cloud/fly-io",
3
- "version": "1.0.0-rc.11",
3
+ "version": "1.0.0-rc.12",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/alchemy-run/distilled",
@@ -89,7 +89,7 @@
89
89
  }
90
90
  },
91
91
  "dependencies": {
92
- "@distilled.cloud/core": "1.0.0-rc.11"
92
+ "@distilled.cloud/core": "1.0.0-rc.12"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/bun": "1.3.14",
@@ -0,0 +1,174 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as HttpClient from "effect/unstable/http/HttpClient";
5
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
6
+ import { credentials } from "./credentials.ts";
7
+ import { BadRequest, UnknownFlyIoError } from "./errors.ts";
8
+ import type { FlyIoOpContext } from "./protocol.ts";
9
+ import * as Retry from "./retry.ts";
10
+ import {
11
+ MachineStartFromCreatedState,
12
+ MachineWaitTimeout,
13
+ startMachine,
14
+ stopMachine,
15
+ waitMachine,
16
+ } from "./services/machines.ts";
17
+
18
+ const recordedMessage =
19
+ "failed_precondition: unable to start machine from current state: 'created'";
20
+ const machine = { app_name: "decoder-test", machine_id: "machine-test" };
21
+
22
+ const decodeError = <A, E>(
23
+ operation: Effect.Effect<A, E, FlyIoOpContext>,
24
+ status: number,
25
+ body: string,
26
+ ) =>
27
+ Effect.runPromise(
28
+ operation.pipe(
29
+ Retry.none,
30
+ Effect.provide(
31
+ Layer.mergeAll(
32
+ Layer.succeed(
33
+ HttpClient.HttpClient,
34
+ HttpClient.make((request) =>
35
+ Effect.sync(() =>
36
+ HttpClientResponse.fromWeb(
37
+ request,
38
+ new Response(body, { status }),
39
+ ),
40
+ ),
41
+ ),
42
+ ),
43
+ credentials({
44
+ apiKey: "decoder-test",
45
+ apiBaseUrl: "https://fly.test",
46
+ }),
47
+ ),
48
+ ),
49
+ Effect.flip,
50
+ ),
51
+ );
52
+
53
+ // The suite recorded the message, but not the HTTP status or raw envelope.
54
+ const envelopes = [
55
+ { name: "error", body: JSON.stringify({ error: recordedMessage }) },
56
+ { name: "message", body: JSON.stringify({ message: recordedMessage }) },
57
+ {
58
+ name: "errors.detail",
59
+ body: JSON.stringify({ errors: { detail: recordedMessage } }),
60
+ },
61
+ { name: "plain text", body: recordedMessage },
62
+ ];
63
+
64
+ describe("Machines start precondition decoding", () => {
65
+ for (const status of [400, 412]) {
66
+ for (const { name, body } of envelopes) {
67
+ test(`decodes the exact recorded message from ${name} at ${status}`, async () => {
68
+ const error = await decodeError(startMachine(machine), status, body);
69
+ expect(error).toBeInstanceOf(MachineStartFromCreatedState);
70
+ expect(error).toMatchObject({ message: recordedMessage });
71
+ });
72
+ }
73
+
74
+ test(`does not specialize unrelated ${status} errors`, async () => {
75
+ for (const message of [
76
+ "invalid machine configuration",
77
+ "failed_precondition: unable to start machine from current state: 'destroyed'",
78
+ "failed_precondition: unable to stop machine from current state: 'created'",
79
+ ]) {
80
+ const error = await decodeError(
81
+ startMachine(machine),
82
+ status,
83
+ JSON.stringify({ error: message }),
84
+ );
85
+ expect(error).toBeInstanceOf(
86
+ status === 400 ? BadRequest : UnknownFlyIoError,
87
+ );
88
+ expect(error).toMatchObject({ message });
89
+ }
90
+ });
91
+ }
92
+
93
+ test("only specializes startMachine", async () => {
94
+ const error = await decodeError(
95
+ stopMachine(machine),
96
+ 412,
97
+ JSON.stringify({ error: recordedMessage }),
98
+ );
99
+ expect(error).toBeInstanceOf(UnknownFlyIoError);
100
+ expect(error).toMatchObject({ message: recordedMessage });
101
+ });
102
+ });
103
+
104
+ describe("Machines wait timeout decoding", () => {
105
+ const recordedWaitMessage =
106
+ "deadline_exceeded: machine failed to reach desired state, started, currently stopped";
107
+
108
+ // Statuses are synthetic; the live failure only captured the message.
109
+ for (const status of [408, 412]) {
110
+ test(`decodes the recorded waiter failure at ${status}`, async () => {
111
+ const error = await decodeError(
112
+ waitMachine(machine),
113
+ status,
114
+ JSON.stringify({ error: recordedWaitMessage }),
115
+ );
116
+ expect(error).toBeInstanceOf(MachineWaitTimeout);
117
+ expect(error).toMatchObject({ message: recordedWaitMessage });
118
+ });
119
+ }
120
+
121
+ test("decodes the recorded waiter failure from plain text", async () => {
122
+ const error = await decodeError(
123
+ waitMachine(machine),
124
+ 412,
125
+ recordedWaitMessage,
126
+ );
127
+ expect(error).toBeInstanceOf(MachineWaitTimeout);
128
+ expect(error).toMatchObject({ message: recordedWaitMessage });
129
+ });
130
+
131
+ test("matches other desired and current machine states", async () => {
132
+ const message =
133
+ "deadline_exceeded: machine failed to reach desired state, stopped, currently stopping";
134
+ const error = await decodeError(
135
+ waitMachine(machine),
136
+ 412,
137
+ JSON.stringify({ error: message }),
138
+ );
139
+ expect(error).toBeInstanceOf(MachineWaitTimeout);
140
+ expect(error).toMatchObject({ message });
141
+ });
142
+
143
+ for (const status of [400, 412]) {
144
+ test(`does not specialize unrelated waiter errors at ${status}`, async () => {
145
+ for (const message of [
146
+ "deadline_exceeded: request timed out",
147
+ "failed_precondition: machine failed to reach desired state, started, currently stopped",
148
+ "machine failed to reach desired state, started, currently stopped",
149
+ `other failure: ${recordedWaitMessage}`,
150
+ `${recordedWaitMessage}; invalid configuration`,
151
+ ]) {
152
+ const error = await decodeError(
153
+ waitMachine(machine),
154
+ status,
155
+ JSON.stringify({ error: message }),
156
+ );
157
+ expect(error).toBeInstanceOf(
158
+ status === 400 ? BadRequest : UnknownFlyIoError,
159
+ );
160
+ expect(error).toMatchObject({ message });
161
+ }
162
+ });
163
+ }
164
+
165
+ test("only specializes waitMachine", async () => {
166
+ const error = await decodeError(
167
+ startMachine(machine),
168
+ 412,
169
+ JSON.stringify({ error: recordedWaitMessage }),
170
+ );
171
+ expect(error).toBeInstanceOf(UnknownFlyIoError);
172
+ expect(error).toMatchObject({ message: recordedWaitMessage });
173
+ });
174
+ });
package/src/protocol.ts CHANGED
@@ -19,7 +19,9 @@
19
19
  * `Authorization: Bearer` on sprite ops. There is no `SPRITES_TOKEN`.
20
20
  */
21
21
  import * as Context from "effect/Context";
22
+ import * as Deferred from "effect/Deferred";
22
23
  import * as Effect from "effect/Effect";
24
+ import * as Exit from "effect/Exit";
23
25
  import * as Layer from "effect/Layer";
24
26
  import * as Option from "effect/Option";
25
27
  import * as Redacted from "effect/Redacted";
@@ -432,34 +434,42 @@ const mintOnce = (fly: Config) =>
432
434
  }),
433
435
  );
434
436
  }
435
- // Cache the raw string — Effect.cached must not hold a Redacted (the
436
- // registry entry is not restored from a memoized Exit).
437
+ // Cache the raw string, not a Redacted whose registry entry may be erased.
437
438
  return token;
438
439
  });
439
440
 
440
441
  const mintedTokens = new Map<string, string>();
441
- const mintInFlight = new Map<string, ReturnType<typeof mintOnce>>();
442
+ const mintInFlight = new Map<
443
+ string,
444
+ Deferred.Deferred<string, Effect.Error<ReturnType<typeof mintOnce>>>
445
+ >();
442
446
 
443
447
  /** Mint (and process-wide cache) a Sprites bearer from `FLY_API_TOKEN`. */
444
448
  const mintSpritesToken = (fly: Config) =>
445
- Effect.gen(function* () {
446
- const key = Redacted.value(fly.apiKey);
447
- const hit = mintedTokens.get(key);
448
- if (hit !== undefined) return hit;
449
- const pending = mintInFlight.get(key);
450
- if (pending !== undefined) return yield* pending;
451
- const run = mintOnce(fly).pipe(
452
- Effect.tap((token) =>
453
- Effect.sync(() => {
454
- mintedTokens.set(key, token);
455
- mintInFlight.delete(key);
456
- }),
457
- ),
458
- Effect.tapError(() => Effect.sync(() => mintInFlight.delete(key))),
459
- );
460
- mintInFlight.set(key, run);
461
- return yield* run;
462
- });
449
+ Effect.uninterruptibleMask((restore) =>
450
+ Effect.suspend(() => {
451
+ const key = Redacted.value(fly.apiKey);
452
+ const hit = mintedTokens.get(key);
453
+ if (hit !== undefined) return Effect.succeed(hit);
454
+ const pending = mintInFlight.get(key);
455
+ if (pending !== undefined) return restore(Deferred.await(pending));
456
+ const result = Deferred.makeUnsafe<
457
+ string,
458
+ Effect.Error<ReturnType<typeof mintOnce>>
459
+ >();
460
+ mintInFlight.set(key, result);
461
+ return restore(mintOnce(fly)).pipe(
462
+ Effect.onExit((exit) =>
463
+ Effect.gen(function* () {
464
+ if (Exit.isSuccess(exit)) mintedTokens.set(key, exit.value);
465
+ // Release all waiters, including on defects and owner interruption.
466
+ mintInFlight.delete(key);
467
+ yield* Deferred.done(result, exit);
468
+ }),
469
+ ),
470
+ );
471
+ }),
472
+ );
463
473
 
464
474
  const encodeSpritesRequest = ({
465
475
  input,
@@ -19,6 +19,37 @@ import * as Retry from "../retry.ts";
19
19
 
20
20
  export type { FlyIoOpError, FlyIoOpContext };
21
21
 
22
+ export class MachineStartFromCreatedState
23
+ extends /*@__PURE__*/ T.applyErrorMatchers(
24
+ /*@__PURE__*/ S.TaggedError<MachineStartFromCreatedState>()(
25
+ "MachineStartFromCreatedState",
26
+ {
27
+ message: S.String,
28
+ },
29
+ ),
30
+ [
31
+ {
32
+ message:
33
+ "failed_precondition: unable to start machine from current state: 'created'",
34
+ },
35
+ ],
36
+ ) {}
37
+
38
+ export class MachineWaitTimeout
39
+ extends /*@__PURE__*/ T.applyErrorMatchers(
40
+ /*@__PURE__*/ S.TaggedError<MachineWaitTimeout>()("MachineWaitTimeout", {
41
+ message: S.String,
42
+ }),
43
+ [
44
+ {
45
+ message: {
46
+ matches:
47
+ "^deadline_exceeded: machine failed to reach desired state, [a-z_]+, currently [a-z_]+$",
48
+ },
49
+ },
50
+ ],
51
+ ) {}
52
+
22
53
  export interface AuthenticateTokenRequest {
23
54
  header?: string;
24
55
  }
@@ -7214,6 +7245,7 @@ export type StartMachineError =
7214
7245
  | Forbidden
7215
7246
  | NotFound
7216
7247
  | Conflict
7248
+ | MachineStartFromCreatedState
7217
7249
  | FlyIoOpError;
7218
7250
  /** Start Machine Start a specific Machine within an app. */
7219
7251
  export const startMachine: API.OperationMethod<
@@ -7224,7 +7256,13 @@ export const startMachine: API.OperationMethod<
7224
7256
  > = /*@__PURE__*/ API.make(() => ({
7225
7257
  input: StartMachineRequest,
7226
7258
  output: StartMachineResponse,
7227
- errors: [BadRequest, Forbidden, NotFound, Conflict],
7259
+ errors: [
7260
+ BadRequest,
7261
+ Forbidden,
7262
+ NotFound,
7263
+ Conflict,
7264
+ MachineStartFromCreatedState,
7265
+ ],
7228
7266
  protocol: FlyIoProtocol,
7229
7267
  retry: Retry.Retry,
7230
7268
  }));
@@ -7422,6 +7460,7 @@ export type WaitMachineError =
7422
7460
  | Forbidden
7423
7461
  | NotFound
7424
7462
  | GatewayTimeout
7463
+ | MachineWaitTimeout
7425
7464
  | FlyIoOpError;
7426
7465
  /** Wait for State Wait for a Machine to reach a specific state. Specify the desired state with the state parameter. See the [Machine states table](https://fly.io/docs/machines/working-with-machines/#machine-states) for a list of possible states. The default for this parameter is `started`. This request will block for up to 60 seconds. Set a shorter timeout with the timeout parameter. */
7427
7466
  export const waitMachine: API.OperationMethod<
@@ -7432,7 +7471,7 @@ export const waitMachine: API.OperationMethod<
7432
7471
  > = /*@__PURE__*/ API.make(() => ({
7433
7472
  input: WaitMachineRequest,
7434
7473
  output: WaitMachineResponse,
7435
- errors: [BadRequest, Forbidden, NotFound, GatewayTimeout],
7474
+ errors: [BadRequest, Forbidden, NotFound, GatewayTimeout, MachineWaitTimeout],
7436
7475
  protocol: FlyIoProtocol,
7437
7476
  retry: Retry.Retry,
7438
7477
  }));