@distilled.cloud/fly-io 1.0.0-rc.10 → 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.
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +15 -11
- package/lib/protocol.js.map +1 -1
- package/lib/protocol.test.d.ts +2 -0
- package/lib/protocol.test.d.ts.map +1 -0
- package/lib/protocol.test.js +95 -0
- package/lib/protocol.test.js.map +1 -0
- package/lib/services/machines.d.ts +12 -2
- package/lib/services/machines.d.ts.map +1 -1
- package/lib/services/machines.js +28 -2
- package/lib/services/machines.js.map +1 -1
- package/lib/sprites-mint.test.d.ts +2 -0
- package/lib/sprites-mint.test.d.ts.map +1 -0
- package/lib/sprites-mint.test.js +170 -0
- package/lib/sprites-mint.test.js.map +1 -0
- package/package.json +2 -2
- package/src/protocol.test.ts +174 -0
- package/src/protocol.ts +31 -21
- package/src/services/machines.ts +41 -2
- package/src/sprites-mint.test.ts +270 -0
|
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,KAAK,GAAG,MAAM,2BAA2B,CAAC;AACjD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAE9D,OAAO,KAAK,kBAAkB,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CACrC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAClD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ;YACrC,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAqC;SACpE,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IACF,MAAM,WAAW,GAA0C,EAAE,CAAC;IAC9D,MAAM,KAAK,GAA0C,EAAE,CAAC;IACxD,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACzC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,IAAI,OAAO,CAAC,GAAG,KAAK,oCAAoC,EAAE,CAAC;YACzD,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1B,OAAO,kBAAkB,CAAC,OAAO,CAC/B,OAAO,EACP,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CACvD,CAAC;QACJ,CAAC;QACD,IACE,OAAO,CAAC,MAAM,KAAK,MAAM;YACzB,OAAO,CAAC,GAAG;gBACT,2DAA2D,EAC7D,CAAC;YACD,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACvC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAClE,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACpD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjE,OAAO,kBAAkB,CAAC,OAAO,CAC/B,OAAO,EACP,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAChC,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;IACF,MAAM,MAAM,GAAG,QAAQ;SACpB,MAAM,CAAC;QACN,KAAK,EAAE,EAAE;QACT,QAAQ,EAAE,kBAAkB,CAAC,GAAG;QAChC,MAAM,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE;KACtC,CAAC;SACD,IAAI,CACH,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,EACpD,MAAM,CAAC,cAAc,CACnB,WAAW,EACX,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACf,eAAe,EAAE,CAAC;QAClB,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;YAC7B,UAAU,EAAE,kBAAkB;SAC/B,CAAC;IACJ,CAAC,CAAC,CACH,CACF,CAAC;IACJ,OAAO;QACL,MAAM;QACN,WAAW;QACX,KAAK;QACL,eAAe,EAAE,GAAG,EAAE,CAAC,eAAe;QACtC,OAAO,EAAE,CAAC,OAAO,GAAG,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAE,CAAC,OAAO,CAAC;QACpE,OAAO,EAAE,CAAC,IAAa,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,GAAG,CAAC,EAAE,EAAE,CACpD,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAE,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;KAClE,CAAC;AACJ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;AAE3C,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,IAAI,CAAC,+FAA+F,EAAE,GAAG,EAAE,CACzG,MAAM,CAAC,UAAU,CACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,4BAA4B,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CACrE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,CAClE,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACrC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAC/D,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACxC,uCAAuC,CACxC,CAAC;YACF,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAC3C,gCAAgC,CACjC,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAClD,kCAAkC,CACnC,CAAC;QACF,KAAK,MAAM,OAAO,IAAI,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAC3C,4BAA4B,CAC7B,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CACrC,CAAC,CAAC;IAEL,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE,CACzE,MAAM,CAAC,UAAU,CACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACvC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,wBAAwB,EAAE,EAAE,GAAG,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QACrD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CACxC,wBAAwB,CACzB,CAAC;gBACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAC5C,uBAAuB,CACxB,CAAC;YACJ,CAAC;QACH,CAAC;QACD,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1B,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACvE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAC3D,kCAAkC,CACnC,CAAC;QACF,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACxD,kCAAkC,CACnC,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CACrC,CAAC,CAAC;IAEL,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE,CAC3E,MAAM,CAAC,UAAU,CACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CACzC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAC3C,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAChD,4BAA4B,CAC7B,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACjD,6BAA6B,CAC9B,CAAC;QACF,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;QAC7D,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACjE,iCAAiC,CAClC,CAAC;QACF,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAChE,gCAAgC,CACjC,CAAC;QACF,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACtD,gCAAgC,CACjC,CAAC;QACF,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACvD,iCAAiC,CAClC,CAAC;QACF,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CACrC,CAAC,CAAC;IAEL,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE,CAC1E,MAAM,CAAC,UAAU,CACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,yBAAyB,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACvC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,MAAM,iBAAiB,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAClD,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC,CAAC;QACvE,KAAK,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,iBAAiB,CAAC,EAAE,CAAC;YAC/C,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAC3D,0CAA0C,CAC3C,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CACrC,CAAC,CAAC;IAEL,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE,CAC3E,MAAM,CAAC,UAAU,CACf,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC,wBAAwB,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CACpD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,CAClE,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CACtC,MAAM,CAAC,SAAS,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAC7C,CAAC;QACF,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC1B,KAAK,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAC3D,mCAAmC,CACpC,CAAC;QACF,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CACxD,mCAAmC,CACpC,CAAC;QACF,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CACrC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@distilled.cloud/fly-io",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
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.
|
|
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
|
|
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<
|
|
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.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
)
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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,
|
package/src/services/machines.ts
CHANGED
|
@@ -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: [
|
|
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
|
}));
|