@alchemy.run/fly 2.0.0-beta.84 → 2.0.0-beta.86

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 (307) hide show
  1. package/lib/App.d.ts +27 -5
  2. package/lib/App.d.ts.map +1 -1
  3. package/lib/App.js +29 -8
  4. package/lib/App.js.map +1 -1
  5. package/lib/AuthProvider.d.ts +1 -1
  6. package/lib/AuthProvider.d.ts.map +1 -1
  7. package/lib/AuthProvider.js.map +1 -1
  8. package/lib/BindService.d.ts +7 -7
  9. package/lib/BindService.d.ts.map +1 -1
  10. package/lib/BindService.js +21 -28
  11. package/lib/BindService.js.map +1 -1
  12. package/lib/Bootstrap.d.ts.map +1 -1
  13. package/lib/Bootstrap.js +2 -1
  14. package/lib/Bootstrap.js.map +1 -1
  15. package/lib/Bucket.d.ts +38 -4
  16. package/lib/Bucket.d.ts.map +1 -1
  17. package/lib/Bucket.js +190 -37
  18. package/lib/Bucket.js.map +1 -1
  19. package/lib/BucketBinding.d.ts.map +1 -1
  20. package/lib/BucketBinding.js +1 -1
  21. package/lib/BucketBinding.js.map +1 -1
  22. package/lib/Catalog.d.ts.map +1 -1
  23. package/lib/Catalog.js.map +1 -1
  24. package/lib/Certificate.d.ts +25 -1
  25. package/lib/Certificate.d.ts.map +1 -1
  26. package/lib/Certificate.js +22 -4
  27. package/lib/Certificate.js.map +1 -1
  28. package/lib/Checkpoint.d.ts.map +1 -1
  29. package/lib/Checkpoint.js.map +1 -1
  30. package/lib/CheckpointHttp.d.ts.map +1 -1
  31. package/lib/CheckpointHttp.js.map +1 -1
  32. package/lib/ConnectPostgres.d.ts +1 -1
  33. package/lib/ConnectPostgres.d.ts.map +1 -1
  34. package/lib/ConnectPostgres.js.map +1 -1
  35. package/lib/ConnectPostgresHttp.d.ts.map +1 -1
  36. package/lib/ConnectPostgresHttp.js +1 -1
  37. package/lib/ConnectPostgresHttp.js.map +1 -1
  38. package/lib/Contributions.d.ts +1 -1
  39. package/lib/Contributions.d.ts.map +1 -1
  40. package/lib/Contributions.js.map +1 -1
  41. package/lib/Credentials.d.ts +5 -4
  42. package/lib/Credentials.d.ts.map +1 -1
  43. package/lib/Credentials.js +18 -18
  44. package/lib/Credentials.js.map +1 -1
  45. package/lib/Decrypt.d.ts.map +1 -1
  46. package/lib/Decrypt.js.map +1 -1
  47. package/lib/DecryptHttp.d.ts.map +1 -1
  48. package/lib/DecryptHttp.js.map +1 -1
  49. package/lib/DeleteObject.d.ts.map +1 -1
  50. package/lib/DeleteObject.js.map +1 -1
  51. package/lib/DeleteObjectHttp.d.ts.map +1 -1
  52. package/lib/DeleteObjectHttp.js.map +1 -1
  53. package/lib/Deployment.d.ts.map +1 -1
  54. package/lib/Deployment.js.map +1 -1
  55. package/lib/DeploymentImages.d.ts.map +1 -1
  56. package/lib/DeploymentImages.js +1 -1
  57. package/lib/DeploymentImages.js.map +1 -1
  58. package/lib/DeploymentState.d.ts.map +1 -1
  59. package/lib/DeploymentState.js.map +1 -1
  60. package/lib/Encrypt.d.ts.map +1 -1
  61. package/lib/Encrypt.js.map +1 -1
  62. package/lib/EncryptHttp.d.ts.map +1 -1
  63. package/lib/EncryptHttp.js.map +1 -1
  64. package/lib/Environment.d.ts.map +1 -1
  65. package/lib/Environment.js.map +1 -1
  66. package/lib/Errors.d.ts +1 -1
  67. package/lib/Errors.d.ts.map +1 -1
  68. package/lib/Errors.js.map +1 -1
  69. package/lib/Exec.d.ts.map +1 -1
  70. package/lib/Exec.js.map +1 -1
  71. package/lib/ExecHttp.d.ts.map +1 -1
  72. package/lib/ExecHttp.js.map +1 -1
  73. package/lib/GetObject.d.ts.map +1 -1
  74. package/lib/GetObject.js.map +1 -1
  75. package/lib/GetObjectHttp.d.ts.map +1 -1
  76. package/lib/GetObjectHttp.js.map +1 -1
  77. package/lib/GetSecret.d.ts.map +1 -1
  78. package/lib/GetSecret.js.map +1 -1
  79. package/lib/GetSecretHttp.d.ts.map +1 -1
  80. package/lib/GetSecretHttp.js.map +1 -1
  81. package/lib/HeadObject.d.ts.map +1 -1
  82. package/lib/HeadObject.js.map +1 -1
  83. package/lib/HeadObjectHttp.d.ts.map +1 -1
  84. package/lib/HeadObjectHttp.js.map +1 -1
  85. package/lib/IpAssignment.d.ts +24 -1
  86. package/lib/IpAssignment.d.ts.map +1 -1
  87. package/lib/IpAssignment.js +21 -4
  88. package/lib/IpAssignment.js.map +1 -1
  89. package/lib/ListObjectsV2.d.ts.map +1 -1
  90. package/lib/ListObjectsV2.js.map +1 -1
  91. package/lib/ListObjectsV2Http.d.ts.map +1 -1
  92. package/lib/ListObjectsV2Http.js.map +1 -1
  93. package/lib/ListSecrets.d.ts.map +1 -1
  94. package/lib/ListSecrets.js.map +1 -1
  95. package/lib/ListSecretsHttp.d.ts.map +1 -1
  96. package/lib/ListSecretsHttp.js.map +1 -1
  97. package/lib/Machine.d.ts +27 -2
  98. package/lib/Machine.d.ts.map +1 -1
  99. package/lib/Machine.js +18 -3
  100. package/lib/Machine.js.map +1 -1
  101. package/lib/MachineContainers.d.ts.map +1 -1
  102. package/lib/MachineContainers.js.map +1 -1
  103. package/lib/Metadata.d.ts.map +1 -1
  104. package/lib/Metadata.js.map +1 -1
  105. package/lib/MountVolume.d.ts +7 -2
  106. package/lib/MountVolume.d.ts.map +1 -1
  107. package/lib/MountVolume.js.map +1 -1
  108. package/lib/Postgres.d.ts +34 -2
  109. package/lib/Postgres.d.ts.map +1 -1
  110. package/lib/Postgres.js +26 -6
  111. package/lib/Postgres.js.map +1 -1
  112. package/lib/PostgresMigrations.d.ts +7 -22
  113. package/lib/PostgresMigrations.d.ts.map +1 -1
  114. package/lib/PostgresMigrations.js +37 -89
  115. package/lib/PostgresMigrations.js.map +1 -1
  116. package/lib/Providers.d.ts +1 -1
  117. package/lib/Providers.d.ts.map +1 -1
  118. package/lib/Providers.js.map +1 -1
  119. package/lib/PutObject.d.ts.map +1 -1
  120. package/lib/PutObject.js.map +1 -1
  121. package/lib/PutObjectHttp.d.ts.map +1 -1
  122. package/lib/PutObjectHttp.js.map +1 -1
  123. package/lib/ReadRedis.d.ts.map +1 -1
  124. package/lib/ReadRedis.js.map +1 -1
  125. package/lib/ReadRedisHttp.d.ts.map +1 -1
  126. package/lib/ReadRedisHttp.js.map +1 -1
  127. package/lib/ReadWriteRedis.d.ts.map +1 -1
  128. package/lib/ReadWriteRedis.js.map +1 -1
  129. package/lib/ReadWriteRedisHttp.d.ts.map +1 -1
  130. package/lib/ReadWriteRedisHttp.js.map +1 -1
  131. package/lib/Redis.d.ts +23 -3
  132. package/lib/Redis.d.ts.map +1 -1
  133. package/lib/Redis.js +37 -6
  134. package/lib/Redis.js.map +1 -1
  135. package/lib/RedisBinding.d.ts.map +1 -1
  136. package/lib/RedisBinding.js +1 -1
  137. package/lib/RedisBinding.js.map +1 -1
  138. package/lib/RedisHttp.d.ts +1 -1
  139. package/lib/RedisHttp.d.ts.map +1 -1
  140. package/lib/RedisHttp.js +4 -1
  141. package/lib/RedisHttp.js.map +1 -1
  142. package/lib/Region.d.ts.map +1 -1
  143. package/lib/Region.js.map +1 -1
  144. package/lib/Secret.d.ts +23 -2
  145. package/lib/Secret.d.ts.map +1 -1
  146. package/lib/Secret.js +17 -2
  147. package/lib/Secret.js.map +1 -1
  148. package/lib/SecretHttp.d.ts.map +1 -1
  149. package/lib/SecretHttp.js.map +1 -1
  150. package/lib/SecretKey.d.ts +23 -2
  151. package/lib/SecretKey.d.ts.map +1 -1
  152. package/lib/SecretKey.js +17 -2
  153. package/lib/SecretKey.js.map +1 -1
  154. package/lib/SecretKeyHttp.d.ts.map +1 -1
  155. package/lib/SecretKeyHttp.js.map +1 -1
  156. package/lib/Service.d.ts +104 -34
  157. package/lib/Service.d.ts.map +1 -1
  158. package/lib/Service.js +301 -54
  159. package/lib/Service.js.map +1 -1
  160. package/lib/Sign.d.ts.map +1 -1
  161. package/lib/Sign.js.map +1 -1
  162. package/lib/SignHttp.d.ts.map +1 -1
  163. package/lib/SignHttp.js.map +1 -1
  164. package/lib/Sprite.d.ts +24 -5
  165. package/lib/Sprite.d.ts.map +1 -1
  166. package/lib/Sprite.js +22 -5
  167. package/lib/Sprite.js.map +1 -1
  168. package/lib/SpriteHttp.d.ts.map +1 -1
  169. package/lib/SpriteHttp.js.map +1 -1
  170. package/lib/Verify.d.ts.map +1 -1
  171. package/lib/Verify.js.map +1 -1
  172. package/lib/VerifyHttp.d.ts.map +1 -1
  173. package/lib/VerifyHttp.js.map +1 -1
  174. package/lib/Volume.d.ts +4 -4
  175. package/lib/Volume.d.ts.map +1 -1
  176. package/lib/Volume.js +21 -5
  177. package/lib/Volume.js.map +1 -1
  178. package/lib/VolumeSnapshot.d.ts +32 -1
  179. package/lib/VolumeSnapshot.d.ts.map +1 -1
  180. package/lib/VolumeSnapshot.js +24 -1
  181. package/lib/VolumeSnapshot.js.map +1 -1
  182. package/lib/Website/AssetDeployment.d.ts.map +1 -1
  183. package/lib/Website/AssetDeployment.js +9 -9
  184. package/lib/Website/AssetDeployment.js.map +1 -1
  185. package/lib/Website/Astro.d.ts +8 -6
  186. package/lib/Website/Astro.d.ts.map +1 -1
  187. package/lib/Website/Astro.js +12 -13
  188. package/lib/Website/Astro.js.map +1 -1
  189. package/lib/Website/Foldkit.d.ts.map +1 -1
  190. package/lib/Website/Foldkit.js.map +1 -1
  191. package/lib/Website/FrameworkSite.d.ts +39 -2
  192. package/lib/Website/FrameworkSite.d.ts.map +1 -1
  193. package/lib/Website/FrameworkSite.js +46 -3
  194. package/lib/Website/FrameworkSite.js.map +1 -1
  195. package/lib/Website/Nextjs.d.ts.map +1 -1
  196. package/lib/Website/Nextjs.js.map +1 -1
  197. package/lib/Website/Nuxt.d.ts.map +1 -1
  198. package/lib/Website/Nuxt.js.map +1 -1
  199. package/lib/Website/Octane.d.ts.map +1 -1
  200. package/lib/Website/Octane.js.map +1 -1
  201. package/lib/Website/ReactRouter.d.ts.map +1 -1
  202. package/lib/Website/ReactRouter.js.map +1 -1
  203. package/lib/Website/SolidStart.d.ts.map +1 -1
  204. package/lib/Website/SolidStart.js.map +1 -1
  205. package/lib/Website/StaticSite.d.ts +10 -3
  206. package/lib/Website/StaticSite.d.ts.map +1 -1
  207. package/lib/Website/StaticSite.js +16 -4
  208. package/lib/Website/StaticSite.js.map +1 -1
  209. package/lib/Website/SvelteKit.d.ts.map +1 -1
  210. package/lib/Website/SvelteKit.js.map +1 -1
  211. package/lib/Website/TanStackStart.d.ts.map +1 -1
  212. package/lib/Website/TanStackStart.js.map +1 -1
  213. package/lib/Website/Vinext.d.ts.map +1 -1
  214. package/lib/Website/Vinext.js.map +1 -1
  215. package/lib/Website/Vite.d.ts +9 -0
  216. package/lib/Website/Vite.d.ts.map +1 -1
  217. package/lib/Website/Vite.js +9 -0
  218. package/lib/Website/Vite.js.map +1 -1
  219. package/lib/Website/Vocs.d.ts.map +1 -1
  220. package/lib/Website/Vocs.js.map +1 -1
  221. package/lib/Website/Waku.d.ts.map +1 -1
  222. package/lib/Website/Waku.js.map +1 -1
  223. package/lib/Website/index.d.ts.map +1 -1
  224. package/lib/Website/index.js.map +1 -1
  225. package/lib/WriteCertificates.d.ts.map +1 -1
  226. package/lib/WriteCertificates.js.map +1 -1
  227. package/lib/WriteCertificatesHttp.d.ts.map +1 -1
  228. package/lib/WriteCertificatesHttp.js +2 -2
  229. package/lib/WriteCertificatesHttp.js.map +1 -1
  230. package/lib/WriteRedis.d.ts.map +1 -1
  231. package/lib/WriteRedis.js.map +1 -1
  232. package/lib/WriteRedisHttp.d.ts.map +1 -1
  233. package/lib/WriteRedisHttp.js.map +1 -1
  234. package/lib/WriteSecret.d.ts.map +1 -1
  235. package/lib/WriteSecret.js.map +1 -1
  236. package/lib/WriteSecretHttp.d.ts.map +1 -1
  237. package/lib/WriteSecretHttp.js.map +1 -1
  238. package/lib/bluegreen.d.ts +5 -4
  239. package/lib/bluegreen.d.ts.map +1 -1
  240. package/lib/bluegreen.js +7 -5
  241. package/lib/bluegreen.js.map +1 -1
  242. package/lib/hosted.d.ts +19 -4
  243. package/lib/hosted.d.ts.map +1 -1
  244. package/lib/hosted.js +101 -11
  245. package/lib/hosted.js.map +1 -1
  246. package/lib/index.d.ts.map +1 -1
  247. package/lib/index.js.map +1 -1
  248. package/lib/internal/Oidc.d.ts +15 -0
  249. package/lib/internal/Oidc.d.ts.map +1 -0
  250. package/lib/internal/Oidc.js +73 -0
  251. package/lib/internal/Oidc.js.map +1 -0
  252. package/lib/internal/RemoteBuilder.d.ts.map +1 -1
  253. package/lib/internal/RemoteBuilder.js +21 -8
  254. package/lib/internal/RemoteBuilder.js.map +1 -1
  255. package/lib/leases.d.ts +6 -2
  256. package/lib/leases.d.ts.map +1 -1
  257. package/lib/leases.js +25 -5
  258. package/lib/leases.js.map +1 -1
  259. package/lib/ports.d.ts.map +1 -1
  260. package/lib/ports.js.map +1 -1
  261. package/lib/replicas.d.ts +39 -27
  262. package/lib/replicas.d.ts.map +1 -1
  263. package/lib/replicas.js +63 -8
  264. package/lib/replicas.js.map +1 -1
  265. package/lib/rpc.d.ts +14 -12
  266. package/lib/rpc.d.ts.map +1 -1
  267. package/lib/rpc.js +38 -40
  268. package/lib/rpc.js.map +1 -1
  269. package/package.json +15 -15
  270. package/src/App.ts +41 -10
  271. package/src/BindService.ts +28 -36
  272. package/src/Bootstrap.ts +2 -0
  273. package/src/Bucket.ts +278 -45
  274. package/src/BucketBinding.ts +1 -1
  275. package/src/Certificate.ts +36 -5
  276. package/src/ConnectPostgres.ts +1 -1
  277. package/src/ConnectPostgresHttp.ts +1 -1
  278. package/src/Credentials.ts +18 -41
  279. package/src/DeploymentImages.ts +1 -1
  280. package/src/Errors.ts +1 -1
  281. package/src/IpAssignment.ts +35 -5
  282. package/src/Machine.ts +39 -4
  283. package/src/MountVolume.ts +5 -2
  284. package/src/Postgres.ts +50 -8
  285. package/src/PostgresMigrations.ts +39 -99
  286. package/src/Redis.ts +54 -9
  287. package/src/RedisBinding.ts +1 -1
  288. package/src/RedisHttp.ts +4 -1
  289. package/src/Secret.ts +32 -4
  290. package/src/SecretKey.ts +32 -4
  291. package/src/Service.ts +441 -88
  292. package/src/Sprite.ts +45 -17
  293. package/src/Volume.ts +30 -6
  294. package/src/VolumeSnapshot.ts +40 -2
  295. package/src/Website/AssetDeployment.ts +13 -15
  296. package/src/Website/Astro.ts +20 -15
  297. package/src/Website/FrameworkSite.ts +92 -6
  298. package/src/Website/StaticSite.ts +27 -7
  299. package/src/Website/Vite.ts +9 -0
  300. package/src/WriteCertificatesHttp.ts +2 -2
  301. package/src/bluegreen.ts +11 -5
  302. package/src/hosted.ts +121 -17
  303. package/src/internal/Oidc.ts +99 -0
  304. package/src/internal/RemoteBuilder.ts +37 -19
  305. package/src/leases.ts +35 -7
  306. package/src/replicas.ts +70 -9
  307. package/src/rpc.ts +37 -40
package/src/Bucket.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { fromCredentials, type Credentials } from "@distilled.cloud/aws/Credentials";
2
+ import * as AwsEndpoint from "@distilled.cloud/aws/Endpoint";
3
+ import type { RegionName } from "@distilled.cloud/aws/Region";
4
+ import * as S3 from "@distilled.cloud/aws/s3";
1
5
  import type { AddOnsResponseEdgesItemNode } from "@distilled.cloud/fly-io/addons";
2
6
  import * as addons from "@distilled.cloud/fly-io/addons";
3
7
  import * as machines from "@distilled.cloud/fly-io/machines";
@@ -9,6 +13,9 @@ import { createInternalTags } from "alchemy/Tags";
9
13
  import * as Data from "effect/Data";
10
14
  import * as Duration from "effect/Duration";
11
15
  import * as Effect from "effect/Effect";
16
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
17
+ import type * as HttpClient from "effect/http/HttpClient";
18
+ import * as Layer from "effect/Layer";
12
19
  import * as Redacted from "effect/Redacted";
13
20
  import * as Result from "effect/Result";
14
21
  import * as Schedule from "effect/Schedule";
@@ -133,9 +140,19 @@ export type Bucket = Resource<
133
140
  bucketName: Redacted.Redacted<string> | undefined;
134
141
  },
135
142
  never,
136
- Providers
143
+ Providers,
144
+ BucketLookup
137
145
  >;
138
146
 
147
+ /**
148
+ * Props for {@link Bucket.lookup}: find a Tigris bucket this stack
149
+ * doesn't manage by its name.
150
+ */
151
+ export interface BucketLookup {
152
+ /** Tigris bucket / add-on name. */
153
+ name: string;
154
+ }
155
+
139
156
  /**
140
157
  * A Fly.Bucket is Tigris S3-compatible object storage, billed through
141
158
  * Fly. It is an org-scoped GraphQL add-on (`AddOnType` `tigris`).
@@ -245,13 +262,25 @@ export type Bucket = Resource<
245
262
  * });
246
263
  * ```
247
264
  *
265
+ * ### Looking up an existing bucket
266
+ * `Bucket.lookup` references a bucket Alchemy doesn't manage (created with
267
+ * `fly storage create` or by another stack). It is read during plan/deploy,
268
+ * never created, updated or deleted. Tigris returns a bucket's access keys
269
+ * only when it is created, so a looked-up bucket's credential attributes
270
+ * are `undefined` unless Fly still reports them.
271
+ *
272
+ * **Example:** Reference an existing bucket
273
+ * ```typescript
274
+ * const assets = Fly.Bucket.lookup({ name: "my-assets" });
275
+ * ```
276
+ *
248
277
  * @resource
249
278
  * @product Bucket
250
279
  */
251
280
  export const Bucket = Resource<Bucket>("Fly.Bucket");
252
281
 
253
282
  export class BucketNotCreated extends Data.TaggedError("Fly.BucketNotCreated")<{
254
- name: string;
283
+ resourceName: string;
255
284
  }> {}
256
285
 
257
286
  export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
@@ -259,18 +288,37 @@ export class BucketOrgMissing extends Data.TaggedError("Fly.BucketOrgMissing")<{
259
288
  }> {}
260
289
 
261
290
  export class BucketProvisionFailed extends Data.TaggedError("Fly.BucketProvisionFailed")<{
262
- name: string;
291
+ resourceName: string;
263
292
  status: string | undefined;
264
293
  errorMessage: string | undefined;
265
294
  }> {}
266
295
 
267
296
  class BucketPending extends Data.TaggedError("Fly.BucketPending")<{
268
- name: string;
297
+ resourceName: string;
269
298
  status: string;
270
- }> {}
299
+ }> {
300
+ override get message() {
301
+ return `Tigris bucket ${this.resourceName} is still ${this.status}`;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Fly lists an add-on whose Tigris bucket is gone, and Fly's delete
307
+ * fails on the missing bucket.
308
+ */
309
+ export class BucketRecordStale extends Data.TaggedError("Fly.BucketRecordStale")<{
310
+ resourceName: string;
311
+ reason: string;
312
+ }> {
313
+ override get message() {
314
+ return `Fly keeps a stale add-on record for Tigris bucket ${this.resourceName}: ${this.reason}`;
315
+ }
316
+ }
271
317
 
272
318
  const TIGRIS = "tigris" as const;
273
319
 
320
+ const TIGRIS_ENDPOINT = "https://fly.storage.tigris.dev";
321
+
274
322
  const backoff = Schedule.min([
275
323
  Schedule.exponential(Duration.millis(500), 1.5),
276
324
  Schedule.spaced(Duration.seconds(5)),
@@ -465,20 +513,22 @@ const waitUntilReady = (name: string, addOnId: string) =>
465
513
  addOn,
466
514
  ): Effect.Effect<AddOnsResponseEdgesItemNode, BucketPending | BucketProvisionFailed> => {
467
515
  if (addOn === undefined) {
468
- return Effect.fail(new BucketPending({ name, status: "missing" }));
516
+ return Effect.fail(new BucketPending({ resourceName: name, status: "missing" }));
469
517
  }
470
518
  const status = addOn.status ?? undefined;
471
519
  if (failedStatus(status)) {
472
520
  return Effect.fail(
473
521
  new BucketProvisionFailed({
474
- name,
522
+ resourceName: name,
475
523
  status,
476
524
  errorMessage: addOn.errorMessage ?? undefined,
477
525
  }),
478
526
  );
479
527
  }
480
528
  if (pendingStatus(status)) {
481
- return Effect.fail(new BucketPending({ name, status: status ?? "creating" }));
529
+ return Effect.fail(
530
+ new BucketPending({ resourceName: name, status: status ?? "creating" }),
531
+ );
482
532
  }
483
533
  return Effect.succeed(addOn);
484
534
  },
@@ -491,37 +541,218 @@ const waitUntilReady = (name: string, addOnId: string) =>
491
541
  Effect.catchTag("Fly.BucketPending", () => findById(addOnId)),
492
542
  );
493
543
 
494
- const waitUntilGone = (addOnId: string, name: string) =>
495
- Effect.gen(function* () {
496
- // Best-effort: Tigris often answers "The specified bucket does not
497
- // exist" (untyped UnknownFlyIoError) for a delete that already
498
- // succeeded. Never fail the wait on that — only on the add-on still
499
- // showing up in list.
500
- yield* Effect.result(
501
- addons.deleteAddOn({
502
- input: addOnId.length > 0 ? { addOnId } : { name, provider: TIGRIS },
544
+ interface TigrisKeys {
545
+ accessKeyId: Redacted.Redacted<string>;
546
+ secretAccessKey: Redacted.Redacted<string>;
547
+ endpoint: string;
548
+ }
549
+
550
+ /** The bucket's own S3 keys. Fly returns them only from createAddOn. */
551
+ const tigrisKeysOf = (
552
+ attrs: Pick<Bucket["Attributes"], "accessKeyId" | "secretAccessKey" | "endpoint">,
553
+ ): TigrisKeys | undefined =>
554
+ attrs.accessKeyId !== undefined && attrs.secretAccessKey !== undefined
555
+ ? {
556
+ accessKeyId: attrs.accessKeyId,
557
+ secretAccessKey: attrs.secretAccessKey,
558
+ endpoint: asString(attrs.endpoint) ?? TIGRIS_ENDPOINT,
559
+ }
560
+ : undefined;
561
+
562
+ const withTigrisKeys = <A, E>(
563
+ keys: TigrisKeys,
564
+ operation: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>,
565
+ ) =>
566
+ operation.pipe(
567
+ Effect.provide(
568
+ Layer.mergeAll(
569
+ fromCredentials(
570
+ { accessKeyId: keys.accessKeyId, secretAccessKey: keys.secretAccessKey },
571
+ "auto" as RegionName,
572
+ ),
573
+ AwsEndpoint.of(keys.endpoint),
574
+ FetchHttpClient.layer,
575
+ ),
576
+ ),
577
+ );
578
+
579
+ /**
580
+ * Signed HeadBucket. Tigris answers 404 for a name it has no bucket
581
+ * under and 403 for a bucket the keys don't own. After a delete it can
582
+ * keep answering 200 to the deleted bucket's own keys for minutes.
583
+ */
584
+ const tigrisHasBucket = (keys: TigrisKeys, bucketName: string) =>
585
+ withTigrisKeys(keys, S3.headBucket({ Bucket: bucketName })).pipe(
586
+ Effect.as(true),
587
+ Effect.catchTag(["NotFound", "NoSuchBucket"], () => Effect.succeed(false)),
588
+ );
589
+
590
+ const requestDelete = (addOnId: string, name: string) =>
591
+ addons
592
+ .deleteAddOn({
593
+ input: addOnId.length > 0 ? { addOnId } : { name, provider: TIGRIS },
594
+ })
595
+ .pipe(
596
+ Effect.as("deleted" as const),
597
+ Effect.catchTag("TigrisBucketNotFound", () => Effect.succeed("missing" as const)),
598
+ );
599
+
600
+ interface DeleteTarget {
601
+ addOnId: string;
602
+ /** Fly add-on name. */
603
+ name: string;
604
+ /** Tigris bucket name (the add-on name unless Fly said otherwise). */
605
+ bucketName: string;
606
+ keys: TigrisKeys | undefined;
607
+ }
608
+
609
+ const deleteTargetOf = (attrs: Bucket["Attributes"]): DeleteTarget => ({
610
+ addOnId: attrs.addOnId,
611
+ name: attrs.name,
612
+ bucketName: asString(attrs.bucketName) ?? attrs.name,
613
+ keys: tigrisKeysOf(attrs),
614
+ });
615
+
616
+ /**
617
+ * Delete through Fly (which also empties the bucket), then confirm.
618
+ * With the bucket's keys, Tigris decides (its missing-bucket answer to
619
+ * the delete, else HeadBucket): Fly's list lags it and can keep a stale
620
+ * record. Without keys, the add-on must leave Fly's list.
621
+ * Fails with BucketPending once the confirm runs out; `missing` reports
622
+ * whether Fly's last delete said Tigris has no such bucket while the
623
+ * record stayed listed (only without keys).
624
+ */
625
+ const deleteVerified = (target: DeleteTarget) =>
626
+ Effect.suspend(() => {
627
+ const state = { missing: false };
628
+ return Effect.gen(function* () {
629
+ // Other delete failures (a bucket still attached to an App, a Fly
630
+ // 5xx) are transient; the confirm below decides.
631
+ const requested = yield* Effect.result(requestDelete(target.addOnId, target.name));
632
+ state.missing = Result.isSuccess(requested) && requested.success === "missing";
633
+ if (target.keys !== undefined) {
634
+ // Fly forwards Tigris's own "bucket does not exist" for the delete:
635
+ // the bucket is gone. HeadBucket can keep answering 200 to the
636
+ // deleted bucket's keys for minutes after that.
637
+ if (state.missing) return;
638
+ if (!(yield* tigrisHasBucket(target.keys, target.bucketName))) return;
639
+ return yield* new BucketPending({ resourceName: target.name, status: "deleting" });
640
+ }
641
+ const still =
642
+ (target.addOnId.length > 0 ? yield* findById(target.addOnId) : undefined) ??
643
+ (target.name.length > 0 ? yield* findByName(target.name) : undefined);
644
+ if (still !== undefined) {
645
+ return yield* new BucketPending({
646
+ resourceName: target.name,
647
+ status: still.status ?? "deleting",
648
+ });
649
+ }
650
+ }).pipe(
651
+ Effect.retry({
652
+ while: (e) => e._tag === "Fly.BucketPending",
653
+ schedule: Schedule.spaced("2 seconds"),
654
+ times: 20,
503
655
  }),
656
+ Effect.as({ missing: false }),
657
+ Effect.catchTag("Fly.BucketPending", (pending) =>
658
+ state.missing && target.keys === undefined
659
+ ? Effect.succeed({ missing: true })
660
+ : Effect.fail(pending),
661
+ ),
504
662
  );
505
- const still =
506
- (yield* findById(addOnId)) ?? (name.length > 0 ? yield* findByName(name) : undefined);
507
- if (still !== undefined) {
508
- return yield* new BucketPending({
509
- name,
510
- status: still.status ?? "deleting",
511
- });
512
- }
513
- }).pipe(
514
- Effect.retry({
515
- while: (e) => e._tag === "Fly.BucketPending",
516
- schedule: Schedule.spaced("2 seconds"),
517
- times: 20,
663
+ });
664
+
665
+ const randomBase32 = (length: number) =>
666
+ Effect.sync(() => {
667
+ const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
668
+ return Array.from(crypto.getRandomValues(new Uint8Array(length)), (b) => alphabet[b % 32]).join(
669
+ "",
670
+ );
671
+ });
672
+
673
+ /**
674
+ * A throwaway bucket, only for its keys. Its name matches the physical
675
+ * name shape, so a leaked one is listed and nuked like any other.
676
+ */
677
+ const helperBucket = Effect.fn(function* (orgSlug: string) {
678
+ const org = yield* resolveOrganization(orgSlug);
679
+ if (org === undefined) {
680
+ return yield* new BucketOrgMissing({ orgSlug });
681
+ }
682
+ yield* ensureTos(orgSlug, org.id);
683
+ const name = `alchemy-fix-${yield* randomBase32(16)}`;
684
+ const created = yield* addons.createAddOn({
685
+ input: {
686
+ type: TIGRIS,
687
+ name,
688
+ organizationId: org.id,
689
+ options: desiredOptions({}),
690
+ },
691
+ });
692
+ return toAttrs(created.addOn, undefined, { name, orgSlug });
693
+ });
694
+
695
+ /**
696
+ * Fly keeps an add-on record whose Tigris bucket is gone, and its
697
+ * delete fails on the missing bucket. Recreate the name in Tigris with a
698
+ * throwaway bucket's keys so Fly's delete has a bucket to remove.
699
+ * CreateBucket fails on a name Tigris still holds, so this never touches
700
+ * a live bucket. A name deleted in the last several minutes stays
701
+ * blocked (`BucketInaccessible`); the delete fails and a later one
702
+ * repairs it.
703
+ */
704
+ const repairStaleRecord = Effect.fn(function* (target: DeleteTarget, orgSlug: string) {
705
+ yield* Effect.logWarning(
706
+ `Fly still lists Tigris bucket ${target.name} after Tigris reported it gone; repairing the stale record`,
707
+ );
708
+ yield* Effect.scoped(
709
+ Effect.gen(function* () {
710
+ const helper = yield* Effect.acquireRelease(helperBucket(orgSlug), (attrs) =>
711
+ deleteVerified(deleteTargetOf(attrs)).pipe(Effect.ignore),
712
+ );
713
+ const keys = tigrisKeysOf(helper);
714
+ if (keys === undefined) {
715
+ return yield* new BucketRecordStale({
716
+ resourceName: target.name,
717
+ reason: "Fly returned no keys for the temporary repair bucket",
718
+ });
719
+ }
720
+ const recreated = yield* Effect.result(
721
+ withTigrisKeys(keys, S3.createBucket({ Bucket: target.bucketName })),
722
+ );
723
+ if (Result.isFailure(recreated)) {
724
+ return yield* new BucketRecordStale({
725
+ resourceName: target.name,
726
+ reason: `Tigris would not recreate the name (${recreated.failure._tag}); a recently deleted name stays blocked for several minutes, so retry the delete later`,
727
+ });
728
+ }
729
+ const result = yield* deleteVerified({ ...target, keys: undefined });
730
+ if (result.missing) {
731
+ return yield* new BucketRecordStale({
732
+ resourceName: target.name,
733
+ reason: "Fly's delete still reported a missing bucket after the name was recreated",
734
+ });
735
+ }
518
736
  }),
519
737
  );
738
+ });
739
+
740
+ /**
741
+ * Delete and confirm. A stale Fly record is repaired only when there are
742
+ * no keys to ask Tigris with (nuke, adopted buckets): with keys, Tigris's
743
+ * 404 already proves the bucket is gone.
744
+ */
745
+ const waitUntilGone = Effect.fn(function* (attrs: Bucket["Attributes"]) {
746
+ const target = deleteTargetOf(attrs);
747
+ const result = yield* deleteVerified(target);
748
+ if (!result.missing) return;
749
+ yield* repairStaleRecord(target, attrs.orgSlug ?? (yield* resolveOrgSlug()));
750
+ });
520
751
 
521
752
  const resolveOrganization = (orgSlug: string) =>
522
753
  addons
523
754
  .organization({ slug: orgSlug })
524
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
755
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
525
756
 
526
757
  const ensureTos = (orgSlug: string, organizationId: string) =>
527
758
  Effect.gen(function* () {
@@ -580,6 +811,15 @@ export const BucketProvider = () =>
580
811
  return undefined;
581
812
  }),
582
813
 
814
+ lookup: Effect.fn(function* ({ name }) {
815
+ const found = yield* findByName(name);
816
+ if (found === undefined) return undefined;
817
+ const detail = yield* addons
818
+ .addOn({ id: found.id, name: found.name ?? name, provider: TIGRIS })
819
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
820
+ return toAttrs(detail ?? found, undefined, { name });
821
+ }),
822
+
583
823
  read: Effect.fn(function* ({ id, olds, output }) {
584
824
  const name = yield* resolveBucketName(id, olds?.name, output?.name);
585
825
  const found =
@@ -650,13 +890,13 @@ export const BucketProvider = () =>
650
890
  }
651
891
 
652
892
  if (current === undefined || current.id.length === 0) {
653
- return yield* new BucketNotCreated({ name });
893
+ return yield* new BucketNotCreated({ resourceName: name });
654
894
  }
655
895
 
656
896
  const status = current.status ?? undefined;
657
897
  if (failedStatus(status)) {
658
898
  return yield* new BucketProvisionFailed({
659
- name,
899
+ resourceName: name,
660
900
  status,
661
901
  errorMessage: current.errorMessage ?? undefined,
662
902
  });
@@ -714,15 +954,8 @@ export const BucketProvider = () =>
714
954
  }),
715
955
 
716
956
  delete: Effect.fn(function* ({ output }) {
717
- const addOnId = output.addOnId;
718
- const name = output.name;
719
- if (addOnId.length === 0 && name.length === 0) return;
720
- // Always wait for the add-on to leave the list. A failed
721
- // deleteAddOn plus a single list miss is not gone — Tigris
722
- // list lags, and a bucket still attached to an App is a
723
- // transient error. waitUntilGone retries delete and only
724
- // succeeds once list (id and name) is empty.
725
- yield* waitUntilGone(addOnId, name);
957
+ if (output.addOnId.length === 0 && output.name.length === 0) return;
958
+ yield* waitUntilGone(output);
726
959
  }),
727
960
  });
728
961
 
@@ -749,7 +982,7 @@ const requireBucketSecrets = Effect.fn(function* (appName: string, values: Recor
749
982
  ];
750
983
  if (required.some((key) => !values[key]?.trim())) {
751
984
  return yield* new TigrisCredentialsMissing({
752
- name: values.BUCKET_NAME || appName,
985
+ resourceName: values.BUCKET_NAME || appName,
753
986
  });
754
987
  }
755
988
  });
@@ -820,7 +1053,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
820
1053
  : undefined;
821
1054
  if (listed === undefined) {
822
1055
  return yield* new BucketPending({
823
- name: input.name || input.id || "",
1056
+ resourceName: input.name || input.id || "",
824
1057
  status: "missing",
825
1058
  });
826
1059
  }
@@ -830,7 +1063,7 @@ const findAttachedBucket = (input: { id?: string; name: string }) =>
830
1063
  name: listed.name ?? input.name,
831
1064
  provider: TIGRIS,
832
1065
  })
833
- .pipe(Effect.catchTag("FlyIoParseError", () => Effect.succeed(undefined)));
1066
+ .pipe(Effect.catchTag("FlyIoParseError", () => Effect.undefined));
834
1067
  return detail ?? listed;
835
1068
  });
836
1069
 
@@ -106,7 +106,7 @@ export const makeTigrisS3Binding = <I extends { Bucket?: string }, A, E>(options
106
106
  resolvedEndpoint === undefined
107
107
  ) {
108
108
  return yield* new TigrisCredentialsMissing({
109
- name: resolvedName ?? bucket.LogicalId,
109
+ resourceName: resolvedName ?? bucket.LogicalId,
110
110
  });
111
111
  }
112
112
  const scope = {
@@ -97,9 +97,21 @@ export type Certificate = Resource<
97
97
  source: CertificateSource;
98
98
  },
99
99
  never,
100
- Providers
100
+ Providers,
101
+ CertificateLookup
101
102
  >;
102
103
 
104
+ /**
105
+ * Props for {@link Certificate.lookup}: find a certificate this stack
106
+ * doesn't manage by its App and hostname.
107
+ */
108
+ export interface CertificateLookup {
109
+ /** Fly App name the certificate is attached to. */
110
+ appName: string;
111
+ /** Hostname the certificate covers. */
112
+ hostname: string;
113
+ }
114
+
103
115
  /**
104
116
  * A Fly.Certificate covers a hostname on an {@link App}. Default
105
117
  * `kind` is `"acme"` (Let's Encrypt). `"custom"` uploads a PEM.
@@ -176,6 +188,20 @@ export type Certificate = Resource<
176
188
  * });
177
189
  * ```
178
190
  *
191
+ * ### Looking up an existing certificate
192
+ * `Certificate.lookup` references a certificate Alchemy doesn't manage
193
+ * (added with `fly certs add` or by another stack). It is read during
194
+ * plan/deploy, never created, updated or deleted.
195
+ *
196
+ * **Example:** Read the DNS records Fly expects
197
+ * ```typescript
198
+ * const cert = Fly.Certificate.lookup({
199
+ * appName: "my-site",
200
+ * hostname: "www.example.com",
201
+ * });
202
+ * const target = cert.dnsRequirements;
203
+ * ```
204
+ *
179
205
  * @resource
180
206
  * @product Certificate
181
207
  */
@@ -314,7 +340,7 @@ const toAttrsFromSummary = (
314
340
  const getByHostname = (appName: string, hostname: string) =>
315
341
  machines
316
342
  .getAppCertificate({ app_name: appName, hostname })
317
- .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
343
+ .pipe(Effect.catchTag("NotFound", () => Effect.undefined));
318
344
 
319
345
  const listAppCertificates = (appName: string) =>
320
346
  Effect.gen(function* () {
@@ -372,7 +398,7 @@ const createAcme = (appName: string, hostname: string) =>
372
398
  app_name: appName,
373
399
  hostname,
374
400
  })
375
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
401
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
376
402
 
377
403
  const createCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
378
404
  machines
@@ -382,7 +408,7 @@ const createCustom = (appName: string, hostname: string, fullchain: string, priv
382
408
  fullchain,
383
409
  private_key: privateKey,
384
410
  })
385
- .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
411
+ .pipe(Effect.catchTag("Conflict", () => Effect.undefined));
386
412
 
387
413
  const replaceCustom = (appName: string, hostname: string, fullchain: string, privateKey: string) =>
388
414
  Effect.gen(function* () {
@@ -423,6 +449,11 @@ export const CertificateProvider = () =>
423
449
  return undefined;
424
450
  }),
425
451
 
452
+ lookup: Effect.fn(function* ({ appName, hostname }) {
453
+ const found = yield* getByHostname(appName, hostname);
454
+ return found === undefined ? undefined : toAttrs(appName, found, hostname);
455
+ }),
456
+
426
457
  read: Effect.fn(function* ({ olds, output }) {
427
458
  const appName = output?.appName ?? appNameOf(olds?.app);
428
459
  const hostname = output?.hostname ?? olds?.hostname;
@@ -524,7 +555,7 @@ export const CertificateProvider = () =>
524
555
  app_name: appName,
525
556
  hostname,
526
557
  })
527
- .pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.succeed(undefined)));
558
+ .pipe(Effect.catchTag(["NotFound", "BadRequest"], () => Effect.undefined));
528
559
  if (checked !== undefined) {
529
560
  current = checked;
530
561
  }
@@ -46,7 +46,7 @@ export const connectEnvKeys = (postgres: Pick<Postgres, "LogicalId">) => {
46
46
  };
47
47
 
48
48
  export class PostgresUrlMissing extends Data.TaggedError("Fly.PostgresUrlMissing")<{
49
- name: string;
49
+ resourceName: string;
50
50
  }> {}
51
51
 
52
52
  export interface ConnectPostgresClient {
@@ -26,7 +26,7 @@ const asRedactedUrl = (
26
26
  const url = typeof plain === "string" ? plain : "";
27
27
  return url.length > 0
28
28
  ? Effect.succeed(Redacted.make(url))
29
- : Effect.fail(new PostgresUrlMissing({ name }));
29
+ : Effect.fail(new PostgresUrlMissing({ resourceName: name }));
30
30
  };
31
31
 
32
32
  /**
@@ -1,20 +1,12 @@
1
1
  import { ConfigError } from "@distilled.cloud/core/errors";
2
2
  import { Credentials, CredentialsFromEnv } from "@distilled.cloud/fly-io";
3
- import {
4
- deferUntilFirstUse,
5
- orDieCredentialsUnavailable,
6
- resolveProviderConfig,
7
- } from "alchemy/Auth/Resolve";
3
+ import { lazyCredentials, orDieCredentialsUnavailable } from "alchemy/Auth/Resolve";
8
4
  import * as Output from "alchemy/Output";
9
5
  import * as Effect from "effect/Effect";
10
6
  import * as Layer from "effect/Layer";
11
7
  import * as Option from "effect/Option";
12
8
  import * as Redacted from "effect/Redacted";
13
- import {
14
- FLY_AUTH_PROVIDER_NAME,
15
- type FlyAuthConfig,
16
- type FlyResolvedCredentials,
17
- } from "./AuthProvider.ts";
9
+ import { FLY_AUTH_PROVIDER_NAME, type FlyResolvedCredentials } from "./AuthProvider.ts";
18
10
 
19
11
  export {
20
12
  Credentials,
@@ -32,11 +24,17 @@ export {
32
24
  */
33
25
  export const bindFlyApiToken = (): Effect.Effect<void, never, Credentials> =>
34
26
  Effect.gen(function* () {
27
+ // Bound as Redacted so a Service that owns its App stores the token
28
+ // as an App secret, not plain Machine env. The runtime reads it back
29
+ // with `CredentialsFromEnv`.
35
30
  const token = globalThis.__ALCHEMY_RUNTIME__
36
- ? ""
31
+ ? Redacted.make("")
37
32
  : yield* Credentials.pipe(
38
33
  Effect.flatMap((resolve) => resolve),
39
- Effect.map((cfg) => Redacted.value(cfg.apiKey)),
34
+ Effect.map((cfg) => cfg.apiKey),
35
+ // Binding initialization cannot fail; the engine reports this
36
+ // defect as `CredentialsRequired`.
37
+ orDieCredentialsUnavailable("Fly"),
40
38
  );
41
39
  yield* Output.named(Output.asOutput(token), "FLY_API_TOKEN").asEffect().pipe(Effect.asVoid);
42
40
  });
@@ -49,9 +47,10 @@ export const bindFlyApiToken = (): Effect.Effect<void, never, Credentials> =>
49
47
  * `Credentials`, and the binding must use them — a laptop deploy has no
50
48
  * `FLY_API_TOKEN` in its env once the token lives in the Alchemy profile.
51
49
  * Inside a deployed Machine there is no profile; `bindFlyApiToken`
52
- * has already `RuntimeContext.set` the org token, which Platform copies
53
- * into Machine env as `FLY_API_TOKEN`. So: reuse the ambient
54
- * `Credentials` when present, otherwise read the env.
50
+ * has already `RuntimeContext.set` the org token, which reaches the
51
+ * Machine as `FLY_API_TOKEN` (an App secret, or Machine env in a shared
52
+ * App). So: reuse the ambient `Credentials` when present, otherwise read
53
+ * it with {@link CredentialsFromEnv}.
55
54
  */
56
55
  export const CredentialsFromAmbientOrEnv: Layer.Layer<Credentials> = Layer.effect(
57
56
  Credentials,
@@ -73,30 +72,8 @@ export const CredentialsFromAmbientOrEnv: Layer.Layer<Credentials> = Layer.effec
73
72
  export const fromAuthProvider = () =>
74
73
  Layer.effect(
75
74
  Credentials,
76
- Effect.gen(function* () {
77
- // Defer profile lookup and credential resolution until first use, so
78
- // building the provider layers never requires a configured profile.
79
- const resolve = yield* resolveProviderConfig<FlyAuthConfig, FlyResolvedCredentials>(
80
- FLY_AUTH_PROVIDER_NAME,
81
- ).pipe(
82
- Effect.flatMap(({ profileName, resolve }) =>
83
- resolve.pipe(
84
- Effect.map((creds) => ({
85
- apiKey: creds.apiKey,
86
- apiBaseUrl: creds.apiBaseUrl,
87
- })),
88
- Effect.mapError((e) =>
89
- ConfigError.make({
90
- message: `Failed to resolve Fly credentials from ${profileName === undefined ? "the CI environment" : `profile '${profileName}'`}: ${(e as { message?: string }).message ?? String(e)}`,
91
- }),
92
- ),
93
- ),
94
- ),
95
- deferUntilFirstUse,
96
- );
97
- return yield* resolve.pipe(
98
- orDieCredentialsUnavailable(FLY_AUTH_PROVIDER_NAME),
99
- Effect.cached,
100
- );
101
- }),
75
+ lazyCredentials<FlyResolvedCredentials, ConfigError>()(FLY_AUTH_PROVIDER_NAME, (creds) => ({
76
+ apiKey: creds.apiKey,
77
+ apiBaseUrl: creds.apiBaseUrl,
78
+ })),
102
79
  );
@@ -35,7 +35,7 @@ export const encodeImageSet = (pins: readonly ContainerImagePin[]): string | und
35
35
 
36
36
  export const decodeImageSet = (raw: string | undefined): ContainerImagePin[] | undefined => {
37
37
  if (raw === undefined) return undefined;
38
- const decoded = Schema.decodeUnknownOption(Schema.fromJsonString(ImageSet))(raw);
38
+ const decoded = Schema.decodeOption(Schema.fromJsonString(ImageSet))(raw);
39
39
  if (!Option.isSome(decoded)) return undefined;
40
40
  const pins = decoded.value;
41
41
  return valid(pins) ? canonical(pins) : undefined;
package/src/Errors.ts CHANGED
@@ -10,5 +10,5 @@ import * as Data from "effect/Data";
10
10
  * operation fails with this instead of signing an anonymous request.
11
11
  */
12
12
  export class TigrisCredentialsMissing extends Data.TaggedError("Fly.TigrisCredentialsMissing")<{
13
- name: string;
13
+ resourceName: string;
14
14
  }> {}