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

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/http/HttpClient";
10
+ import * as HttpClientResponse from "effect/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.13",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/alchemy-run/distilled",
@@ -89,15 +89,15 @@
89
89
  }
90
90
  },
91
91
  "dependencies": {
92
- "@distilled.cloud/core": "1.0.0-rc.11"
92
+ "@distilled.cloud/core": "1.0.0-rc.13"
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/bun": "1.3.14",
96
96
  "@types/node": "26.2.0",
97
- "@effect/platform-bun": ">=4.0.0-rc.115 || >=4.0.0"
97
+ "@effect/platform-bun": "^4.0.0"
98
98
  },
99
99
  "peerDependencies": {
100
- "effect": ">=4.0.0-rc.115 || >=4.0.0"
100
+ "effect": "^4.0.0"
101
101
  },
102
102
  "scripts": {
103
103
  "convert": "bun scripts/convert.ts",
@@ -0,0 +1,234 @@
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/http/HttpClient";
5
+ import * as HttpClientResponse from "effect/http/HttpClientResponse";
6
+ import * as ResponseValidation from "@distilled.cloud/core/response-validation";
7
+ import { credentials } from "./credentials.ts";
8
+ import { BadRequest, FlyIoParseError, UnknownFlyIoError } from "./errors.ts";
9
+ import type { FlyIoOpContext } from "./protocol.ts";
10
+ import * as Retry from "./retry.ts";
11
+ import { agreedToProviderTos } from "./services/addons.ts";
12
+ import {
13
+ MachineStartFromCreatedState,
14
+ MachineWaitTimeout,
15
+ startMachine,
16
+ stopMachine,
17
+ waitMachine,
18
+ } from "./services/machines.ts";
19
+
20
+ const recordedMessage =
21
+ "failed_precondition: unable to start machine from current state: 'created'";
22
+ const machine = { app_name: "decoder-test", machine_id: "machine-test" };
23
+
24
+ const respondWith = (status: number, body: string) =>
25
+ Layer.mergeAll(
26
+ Layer.succeed(
27
+ HttpClient.HttpClient,
28
+ HttpClient.make((request) =>
29
+ Effect.sync(() =>
30
+ HttpClientResponse.fromWeb(request, new Response(body, { status })),
31
+ ),
32
+ ),
33
+ ),
34
+ credentials({
35
+ apiKey: "decoder-test",
36
+ apiBaseUrl: "https://fly.test",
37
+ }),
38
+ );
39
+
40
+ const decodeError = <A, E>(
41
+ operation: Effect.Effect<A, E, FlyIoOpContext>,
42
+ status: number,
43
+ body: string,
44
+ ) =>
45
+ Effect.runPromise(
46
+ operation.pipe(
47
+ Retry.none,
48
+ Effect.provide(respondWith(status, body)),
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
+ });
175
+
176
+ describe("GraphQL response validation", () => {
177
+ const tos = { slug: "decoder-test", providerName: "tigris" };
178
+ // `agreedToProviderTos` is `boolean | null`; a string is a mismatch.
179
+ const mismatched = JSON.stringify({
180
+ data: { organization: { agreedToProviderTos: "yes" } },
181
+ });
182
+
183
+ test("lenient mode returns the payload as read", async () => {
184
+ const result = await Effect.runPromise(
185
+ agreedToProviderTos(tos).pipe(
186
+ Retry.none,
187
+ Effect.provide(respondWith(200, mismatched)),
188
+ ),
189
+ );
190
+ expect(result as unknown).toBe("yes");
191
+ });
192
+
193
+ test("strict mode fails a mismatched payload with FlyIoParseError", async () => {
194
+ const error = await Effect.runPromise(
195
+ agreedToProviderTos(tos).pipe(
196
+ Retry.none,
197
+ Effect.provide(respondWith(200, mismatched)),
198
+ Effect.provide(ResponseValidation.strict),
199
+ Effect.flip,
200
+ ),
201
+ );
202
+ expect(error).toBeInstanceOf(FlyIoParseError);
203
+ });
204
+
205
+ test("strict mode passes a matching payload", async () => {
206
+ const result = await Effect.runPromise(
207
+ agreedToProviderTos(tos).pipe(
208
+ Retry.none,
209
+ Effect.provide(
210
+ respondWith(
211
+ 200,
212
+ JSON.stringify({
213
+ data: { organization: { agreedToProviderTos: true } },
214
+ }),
215
+ ),
216
+ ),
217
+ Effect.provide(ResponseValidation.strict),
218
+ ),
219
+ );
220
+ expect(result).toBe(true);
221
+ });
222
+
223
+ test("a non-JSON body: lenient returns the text, strict fails", async () => {
224
+ const call = agreedToProviderTos(tos).pipe(
225
+ Retry.none,
226
+ Effect.provide(respondWith(200, "not json")),
227
+ );
228
+ expect((await Effect.runPromise(call)) as unknown).toBe("not json");
229
+ const error = await Effect.runPromise(
230
+ call.pipe(Effect.provide(ResponseValidation.strict), Effect.flip),
231
+ );
232
+ expect(error).toBeInstanceOf(FlyIoParseError);
233
+ });
234
+ });