@alchemy.run/boat 0.0.0 → 2.0.0-beta.87

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 (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +66 -4
  3. package/lib/AiProxy.d.ts +89 -0
  4. package/lib/AiProxy.d.ts.map +1 -0
  5. package/lib/AiProxy.js +125 -0
  6. package/lib/AiProxy.js.map +1 -0
  7. package/lib/ApiKey.d.ts +105 -0
  8. package/lib/ApiKey.d.ts.map +1 -0
  9. package/lib/ApiKey.js +134 -0
  10. package/lib/ApiKey.js.map +1 -0
  11. package/lib/AuthProvider.d.ts +71 -0
  12. package/lib/AuthProvider.d.ts.map +1 -0
  13. package/lib/AuthProvider.js +322 -0
  14. package/lib/AuthProvider.js.map +1 -0
  15. package/lib/BoatBindings.d.ts +52 -0
  16. package/lib/BoatBindings.d.ts.map +1 -0
  17. package/lib/BoatBindings.js +104 -0
  18. package/lib/BoatBindings.js.map +1 -0
  19. package/lib/Bootstrap.d.ts +8 -0
  20. package/lib/Bootstrap.d.ts.map +1 -0
  21. package/lib/Bootstrap.js +28 -0
  22. package/lib/Bootstrap.js.map +1 -0
  23. package/lib/ConnectSandbox.d.ts +56 -0
  24. package/lib/ConnectSandbox.d.ts.map +1 -0
  25. package/lib/ConnectSandbox.js +3 -0
  26. package/lib/ConnectSandbox.js.map +1 -0
  27. package/lib/ConnectSandboxHttp.d.ts +21 -0
  28. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  29. package/lib/ConnectSandboxHttp.js +46 -0
  30. package/lib/ConnectSandboxHttp.js.map +1 -0
  31. package/lib/Contributions.d.ts +7 -0
  32. package/lib/Contributions.d.ts.map +1 -0
  33. package/lib/Contributions.js +6 -0
  34. package/lib/Contributions.js.map +1 -0
  35. package/lib/CreateSandbox.d.ts +134 -0
  36. package/lib/CreateSandbox.d.ts.map +1 -0
  37. package/lib/CreateSandbox.js +3 -0
  38. package/lib/CreateSandbox.js.map +1 -0
  39. package/lib/CreateSandboxHttp.d.ts +21 -0
  40. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  41. package/lib/CreateSandboxHttp.js +93 -0
  42. package/lib/CreateSandboxHttp.js.map +1 -0
  43. package/lib/CredentialProfile.d.ts +157 -0
  44. package/lib/CredentialProfile.d.ts.map +1 -0
  45. package/lib/CredentialProfile.js +245 -0
  46. package/lib/CredentialProfile.js.map +1 -0
  47. package/lib/Credentials.d.ts +15 -0
  48. package/lib/Credentials.d.ts.map +1 -0
  49. package/lib/Credentials.js +23 -0
  50. package/lib/Credentials.js.map +1 -0
  51. package/lib/Environment.d.ts +219 -0
  52. package/lib/Environment.d.ts.map +1 -0
  53. package/lib/Environment.js +273 -0
  54. package/lib/Environment.js.map +1 -0
  55. package/lib/Errors.d.ts +50 -0
  56. package/lib/Errors.d.ts.map +1 -0
  57. package/lib/Errors.js +17 -0
  58. package/lib/Errors.js.map +1 -0
  59. package/lib/Providers.d.ts +33 -0
  60. package/lib/Providers.d.ts.map +1 -0
  61. package/lib/Providers.js +47 -0
  62. package/lib/Providers.js.map +1 -0
  63. package/lib/Sandbox.d.ts +205 -0
  64. package/lib/Sandbox.d.ts.map +1 -0
  65. package/lib/Sandbox.js +368 -0
  66. package/lib/Sandbox.js.map +1 -0
  67. package/lib/SandboxClient.d.ts +159 -0
  68. package/lib/SandboxClient.d.ts.map +1 -0
  69. package/lib/SandboxClient.js +1 -0
  70. package/lib/SandboxClient.js.map +1 -0
  71. package/lib/SandboxEvents.d.ts +111 -0
  72. package/lib/SandboxEvents.d.ts.map +1 -0
  73. package/lib/SandboxEvents.js +60 -0
  74. package/lib/SandboxEvents.js.map +1 -0
  75. package/lib/SandboxEventsLive.d.ts +13 -0
  76. package/lib/SandboxEventsLive.d.ts.map +1 -0
  77. package/lib/SandboxEventsLive.js +63 -0
  78. package/lib/SandboxEventsLive.js.map +1 -0
  79. package/lib/Snapshot.d.ts +123 -0
  80. package/lib/Snapshot.d.ts.map +1 -0
  81. package/lib/Snapshot.js +173 -0
  82. package/lib/Snapshot.js.map +1 -0
  83. package/lib/Webhook.d.ts +56 -0
  84. package/lib/Webhook.d.ts.map +1 -0
  85. package/lib/Webhook.js +98 -0
  86. package/lib/Webhook.js.map +1 -0
  87. package/lib/index.d.ts +19 -0
  88. package/lib/index.d.ts.map +1 -0
  89. package/lib/index.js +19 -0
  90. package/lib/index.js.map +1 -0
  91. package/lib/internal/Declared.d.ts +34 -0
  92. package/lib/internal/Declared.d.ts.map +1 -0
  93. package/lib/internal/Declared.js +38 -0
  94. package/lib/internal/Declared.js.map +1 -0
  95. package/lib/internal/Org.d.ts +22 -0
  96. package/lib/internal/Org.d.ts.map +1 -0
  97. package/lib/internal/Org.js +23 -0
  98. package/lib/internal/Org.js.map +1 -0
  99. package/lib/internal/Ownership.d.ts +29 -0
  100. package/lib/internal/Ownership.d.ts.map +1 -0
  101. package/lib/internal/Ownership.js +50 -0
  102. package/lib/internal/Ownership.js.map +1 -0
  103. package/lib/internal/Program.d.ts +69 -0
  104. package/lib/internal/Program.d.ts.map +1 -0
  105. package/lib/internal/Program.js +178 -0
  106. package/lib/internal/Program.js.map +1 -0
  107. package/lib/internal/Ref.d.ts +14 -0
  108. package/lib/internal/Ref.d.ts.map +1 -0
  109. package/lib/internal/Ref.js +7 -0
  110. package/lib/internal/Ref.js.map +1 -0
  111. package/lib/internal/SandboxHandle.d.ts +17 -0
  112. package/lib/internal/SandboxHandle.d.ts.map +1 -0
  113. package/lib/internal/SandboxHandle.js +238 -0
  114. package/lib/internal/SandboxHandle.js.map +1 -0
  115. package/lib/internal/SavedSnapshot.d.ts +10 -0
  116. package/lib/internal/SavedSnapshot.d.ts.map +1 -0
  117. package/lib/internal/SavedSnapshot.js +22 -0
  118. package/lib/internal/SavedSnapshot.js.map +1 -0
  119. package/lib/internal/WebhookDelivery.d.ts +16 -0
  120. package/lib/internal/WebhookDelivery.d.ts.map +1 -0
  121. package/lib/internal/WebhookDelivery.js +109 -0
  122. package/lib/internal/WebhookDelivery.js.map +1 -0
  123. package/package.json +67 -3
  124. package/src/AiProxy.ts +171 -0
  125. package/src/ApiKey.ts +250 -0
  126. package/src/AuthProvider.ts +472 -0
  127. package/src/BoatBindings.ts +136 -0
  128. package/src/Bootstrap.ts +55 -0
  129. package/src/ConnectSandbox.ts +60 -0
  130. package/src/ConnectSandboxHttp.ts +59 -0
  131. package/src/Contributions.ts +9 -0
  132. package/src/CreateSandbox.ts +143 -0
  133. package/src/CreateSandboxHttp.ts +115 -0
  134. package/src/CredentialProfile.ts +350 -0
  135. package/src/Credentials.ts +33 -0
  136. package/src/Environment.ts +416 -0
  137. package/src/Errors.ts +34 -0
  138. package/src/Providers.ts +71 -0
  139. package/src/Sandbox.ts +647 -0
  140. package/src/SandboxClient.ts +200 -0
  141. package/src/SandboxEvents.ts +125 -0
  142. package/src/SandboxEventsLive.ts +88 -0
  143. package/src/Snapshot.ts +264 -0
  144. package/src/Webhook.ts +163 -0
  145. package/src/globals.d.ts +2 -0
  146. package/src/index.ts +18 -0
  147. package/src/internal/Declared.ts +87 -0
  148. package/src/internal/Org.ts +31 -0
  149. package/src/internal/Ownership.ts +75 -0
  150. package/src/internal/Program.ts +250 -0
  151. package/src/internal/Ref.ts +15 -0
  152. package/src/internal/SandboxHandle.ts +407 -0
  153. package/src/internal/SavedSnapshot.ts +34 -0
  154. package/src/internal/WebhookDelivery.ts +179 -0
@@ -0,0 +1,350 @@
1
+ import * as boat from "@distilled.cloud/boat-dev/boat";
2
+ import { Unowned } from "alchemy/AdoptPolicy";
3
+ import * as Provider from "alchemy/Provider";
4
+ import { Resource } from "alchemy/Resource";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Redacted from "effect/Redacted";
7
+ import { BoatUnexpectedResponse } from "./Errors.ts";
8
+ import { inOrg, isOrgAccount, sameOrg, stackOrg } from "./internal/Org.ts";
9
+ import { boatName, isGeneratedName } from "./internal/Ownership.ts";
10
+ import type { Providers } from "./Providers.ts";
11
+
12
+ type Secret = string | Redacted.Redacted<string>;
13
+
14
+ /** A secret for any HTTPS API, and the base URL it is sent to. */
15
+ export interface CredentialProfileApi {
16
+ /** The secret. A sandbox gets it as the variable the API is named after. */
17
+ key: Secret;
18
+ /**
19
+ * The API's base URL: HTTPS on the default port, with a public DNS name.
20
+ * A sandbox gets it as `<NAME>_BASE_URL`, and code there must send its
21
+ * requests to that variable: with the AI credentials proxy on it holds
22
+ * the proxy's URL, and only the proxy can swap the placeholder for `key`.
23
+ */
24
+ baseUrl: string;
25
+ }
26
+
27
+ export interface CredentialProfileProps {
28
+ /**
29
+ * Profile name, unique among its owner's profiles. If omitted, a unique
30
+ * name is generated from the stack, stage and logical ID. Renamed in
31
+ * place.
32
+ */
33
+ name?: string;
34
+ /** Anthropic API key. Sandboxes get it as `ANTHROPIC_API_KEY`. */
35
+ anthropic?: Secret;
36
+ /** OpenAI API key. Sandboxes get it as `OPENAI_API_KEY`. */
37
+ openai?: Secret;
38
+ /** OpenRouter API key. */
39
+ openrouter?: Secret;
40
+ /** LLM Gateway API key. */
41
+ llmgateway?: Secret;
42
+ /** DeepSeek API key. */
43
+ deepseek?: Secret;
44
+ /** Moonshot API key. */
45
+ moonshot?: Secret;
46
+ /** Mistral API key. */
47
+ mistral?: Secret;
48
+ /** xAI API key. */
49
+ xai?: Secret;
50
+ /** OpenCode API key. */
51
+ opencode?: Secret;
52
+ /**
53
+ * Secrets for any other HTTPS API, keyed by the environment variable a
54
+ * sandbox gets the secret as: `A-Z`, `0-9` and `_`, up to 64 characters.
55
+ * Provider names (`ANTHROPIC_*`, `OPENAI_*`, `AWS_*`, …) and names ending
56
+ * in `_BASE_URL` are reserved.
57
+ */
58
+ apis?: Record<string, CredentialProfileApi>;
59
+ }
60
+
61
+ export interface CredentialProfileAttributes {
62
+ /** Boat's profile id. */
63
+ credentialProfileId: string;
64
+ name: string;
65
+ /**
66
+ * The organization that owns the profile, as the stack's `org` named it;
67
+ * `undefined` for a personal profile.
68
+ */
69
+ org: string | undefined;
70
+ /** Whether it is its owner's default: what a sandbox gets when its environment names no profile. */
71
+ isDefault: boolean;
72
+ /** The credentials it holds, by Boat's ids (`anthropic-api-key`, `claude-subscription`, …). */
73
+ connected: string[];
74
+ /** The names of its custom APIs. */
75
+ customApis: string[];
76
+ }
77
+
78
+ export interface CredentialProfile extends Resource<
79
+ "Boat.CredentialProfile",
80
+ CredentialProfileProps,
81
+ CredentialProfileAttributes,
82
+ never,
83
+ Providers,
84
+ { name: string }
85
+ > {}
86
+
87
+ /**
88
+ * A Boat credential profile: a named set of API keys that Boat stores
89
+ * encrypted and hands to the sandboxes of an environment that names it.
90
+ *
91
+ * A profile holds provider API keys (Anthropic, OpenAI, …) and custom APIs:
92
+ * a secret for any other HTTPS API, with the base URL it is sent to. Boat
93
+ * never returns a secret once it is stored.
94
+ *
95
+ * The API keys and custom APIs of a declared profile are exactly the ones
96
+ * its props list: a deploy removes any other. Sign-ins (a Claude or ChatGPT
97
+ * subscription) and Bedrock are connected on Boat's dashboard and left
98
+ * alone.
99
+ *
100
+ * A profile is personal, or owned by an organization. With an `org` on the
101
+ * stack (`Boat.providers({ org })`, or the profile's) the organization owns
102
+ * it: every member's sandboxes can use it, and only owners change it.
103
+ * Without one it is yours. Changing the stack's `org` replaces it.
104
+ *
105
+ * ### Creating a profile
106
+ * **Example:** Provider API keys
107
+ * ```typescript
108
+ * export const MyProfile = Boat.CredentialProfile("MyProfile", {
109
+ * anthropic: Config.Redacted("ANTHROPIC_API_KEY"),
110
+ * openai: Config.Redacted("OPENAI_API_KEY"),
111
+ * });
112
+ * ```
113
+ *
114
+ * **Example:** A secret for any other API
115
+ * ```typescript
116
+ * export const Payments = Boat.CredentialProfile("Payments", {
117
+ * apis: {
118
+ * STRIPE_KEY: {
119
+ * key: Config.Redacted("STRIPE_KEY"),
120
+ * baseUrl: "https://api.stripe.com/v1",
121
+ * },
122
+ * },
123
+ * });
124
+ * ```
125
+ *
126
+ * A sandbox gets `STRIPE_KEY` and `STRIPE_KEY_BASE_URL`. Send requests to
127
+ * `$STRIPE_KEY_BASE_URL`, not to the API's own address, so the same code
128
+ * works when the AI credentials proxy replaces both.
129
+ *
130
+ * ### Giving sandboxes a profile
131
+ * **Example:** Name it on an environment
132
+ * ```typescript
133
+ * export const MyProfile = Boat.CredentialProfile("MyProfile", {
134
+ * anthropic: Config.Redacted("ANTHROPIC_API_KEY"),
135
+ * });
136
+ *
137
+ * export const MyEnv = Boat.Environment("MyEnv", { credentialProfile: MyProfile });
138
+ *
139
+ * export const Box = Boat.Sandbox("Box", { environment: MyEnv });
140
+ * ```
141
+ *
142
+ * **Example:** Placeholders instead of real keys
143
+ * ```typescript
144
+ * export const MyProfile = Boat.CredentialProfile("MyProfile", {
145
+ * anthropic: Config.Redacted("ANTHROPIC_API_KEY"),
146
+ * });
147
+ *
148
+ * // Billed: $1 a day, plus $1 per 1,000 proxied requests.
149
+ * export const Proxy = Boat.AiProxy("Proxy", { enabled: true });
150
+ *
151
+ * export const Tenants = Boat.Environment("Tenants", {
152
+ * safeForThirdParties: true,
153
+ * credentialProfile: MyProfile,
154
+ * proxyAiCredentials: Proxy,
155
+ * });
156
+ * ```
157
+ *
158
+ * ### Referencing a profile the stack doesn't manage
159
+ * **Example:** The default profile, with the sign-ins made on the dashboard
160
+ * ```typescript
161
+ * export const MyEnv = Boat.Environment("MyEnv", {
162
+ * credentialProfile: Boat.CredentialProfile.lookup({ name: "default" }),
163
+ * });
164
+ * ```
165
+ *
166
+ * @resource
167
+ * @product Credential Profile
168
+ */
169
+ export const CredentialProfile = Resource<CredentialProfile>("Boat.CredentialProfile");
170
+
171
+ /** Props that hold a provider API key, and the credential Boat stores each as. */
172
+ const PROVIDER_KEYS = {
173
+ anthropic: "anthropic-api-key",
174
+ openai: "openai-api-key",
175
+ openrouter: "openrouter-api-key",
176
+ llmgateway: "llmgateway-api-key",
177
+ deepseek: "deepseek-api-key",
178
+ moonshot: "moonshot-api-key",
179
+ mistral: "mistral-api-key",
180
+ xai: "xai-api-key",
181
+ opencode: "opencode-api-key",
182
+ } as const satisfies Partial<Record<keyof CredentialProfileProps, boat.CredentialId>>;
183
+
184
+ const providerKeys = Object.entries(PROVIDER_KEYS) as Array<
185
+ [keyof typeof PROVIDER_KEYS, (typeof PROVIDER_KEYS)[keyof typeof PROVIDER_KEYS]]
186
+ >;
187
+
188
+ const reveal = (value: Secret | undefined) =>
189
+ Redacted.isRedacted(value) ? Redacted.value(value) : value;
190
+
191
+ /** The profiles of the stack's owner: the organization, or you. */
192
+ const listProfiles = Effect.fn(function* (org: string | undefined) {
193
+ const { profiles } = yield* boat.listCredentialProfiles({});
194
+ return profiles.filter((profile) => isOrgAccount(profile.owner, org));
195
+ });
196
+
197
+ const findProfile = Effect.fn(function* (
198
+ org: string | undefined,
199
+ match: { credentialProfileId?: string; name: string },
200
+ ) {
201
+ const profiles = yield* listProfiles(org);
202
+ return (
203
+ profiles.find((profile) => profile.id === match.credentialProfileId) ??
204
+ profiles.find((profile) => profile.name === match.name)
205
+ );
206
+ });
207
+
208
+ const toAttributes = (
209
+ profile: boat.CredentialProfile,
210
+ org: string | undefined,
211
+ ): CredentialProfileAttributes => ({
212
+ credentialProfileId: profile.id,
213
+ name: profile.name,
214
+ org,
215
+ isDefault: profile.isDefault,
216
+ connected: [...profile.connected],
217
+ customApis: [...profile.customApis],
218
+ });
219
+
220
+ export const CredentialProfileProvider = () =>
221
+ Provider.effect(
222
+ CredentialProfile,
223
+ Effect.gen(function* () {
224
+ return {
225
+ stables: ["credentialProfileId", "org"],
226
+ // A profile belongs to one organization (or to you) for life.
227
+ diff: Effect.fn(function* ({ output }) {
228
+ if (output !== undefined && !sameOrg(output.org, yield* stackOrg)) {
229
+ return { action: "replace" } as const;
230
+ }
231
+ }),
232
+ read: Effect.fn(function* ({ id, olds, output }) {
233
+ const name = output?.name ?? (yield* boatName(id, olds?.name, 64));
234
+ const org = output ? output.org : yield* stackOrg;
235
+ const profile = yield* findProfile(org, {
236
+ credentialProfileId: output?.credentialProfileId,
237
+ name,
238
+ });
239
+ if (profile === undefined) return undefined;
240
+ const attrs = toAttributes(profile, org);
241
+ return output !== undefined || isGeneratedName(profile.name) ? attrs : Unowned(attrs);
242
+ }),
243
+ lookup: Effect.fn(function* ({ name }) {
244
+ const org = yield* stackOrg;
245
+ const profile = yield* findProfile(org, { name });
246
+ return profile === undefined ? undefined : toAttributes(profile, org);
247
+ }),
248
+ reconcile: Effect.fn(function* ({ id, news, olds, output, session }) {
249
+ const name = yield* boatName(id, news.name ?? output?.name, 64);
250
+ // A different org replaces (see diff), so this is the org that owns it.
251
+ const org = yield* stackOrg;
252
+
253
+ // Observe — by id once we have one (it survives renames), else by name.
254
+ let profile = yield* findProfile(org, {
255
+ credentialProfileId: output?.credentialProfileId,
256
+ name,
257
+ });
258
+
259
+ // Ensure — a concurrent create of the same name is a race, not a failure.
260
+ if (profile === undefined) {
261
+ yield* session.note(`Creating credential profile ${name}...`);
262
+ const created = yield* boat
263
+ // Without an `org`, the profile is yours.
264
+ .createCredentialProfile({ name, ...inOrg(org) })
265
+ .pipe(Effect.catchTag("Conflict", () => Effect.succeed(undefined)));
266
+ profile = created?.profile ?? (yield* findProfile(org, { name }));
267
+ if (profile === undefined) {
268
+ return yield* new BoatUnexpectedResponse({
269
+ message: `Boat credential profile ${name} vanished after create`,
270
+ });
271
+ }
272
+ }
273
+
274
+ // Sync the name.
275
+ if (profile.name !== name) {
276
+ profile = (yield* boat.updateCredentialProfile({ profile: profile.id, name })).profile;
277
+ }
278
+
279
+ // Sync provider keys. Boat never returns a key, so what it holds is
280
+ // observed (`connected`) and the value is compared with the one last
281
+ // deployed. An adopted profile has none, so every key is written.
282
+ for (const [prop, credential] of providerKeys) {
283
+ const value = reveal(news[prop]);
284
+ if (value === undefined) {
285
+ if (profile.connected.includes(credential)) {
286
+ profile =
287
+ (yield* boat
288
+ .deleteCredentialProfileKey({ profile: profile.id, credential })
289
+ .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined))))?.profile ??
290
+ profile;
291
+ }
292
+ } else if (
293
+ olds === undefined ||
294
+ !profile.connected.includes(credential) ||
295
+ reveal(olds[prop]) !== value
296
+ ) {
297
+ profile = (yield* boat.setCredentialProfileKey({
298
+ profile: profile.id,
299
+ credential,
300
+ value,
301
+ })).profile;
302
+ }
303
+ }
304
+
305
+ // Sync custom APIs, the same way.
306
+ const apis = news.apis ?? {};
307
+ for (const apiName of profile.customApis) {
308
+ if (apiName in apis) continue;
309
+ profile =
310
+ (yield* boat
311
+ .deleteCredentialProfileCustomApi({ profile: profile.id, name: apiName })
312
+ .pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined))))?.profile ??
313
+ profile;
314
+ }
315
+ for (const [apiName, api] of Object.entries(apis)) {
316
+ const old = olds?.apis?.[apiName];
317
+ if (
318
+ old !== undefined &&
319
+ profile.customApis.includes(apiName) &&
320
+ reveal(old.key) === reveal(api.key) &&
321
+ old.baseUrl === api.baseUrl
322
+ ) {
323
+ continue;
324
+ }
325
+ profile = (yield* boat.setCredentialProfileCustomApi({
326
+ profile: profile.id,
327
+ name: apiName,
328
+ value: reveal(api.key) ?? "",
329
+ baseUrl: api.baseUrl,
330
+ })).profile;
331
+ }
332
+
333
+ return toAttributes(profile, org);
334
+ }),
335
+ // An environment that still names the profile fails the delete with
336
+ // Boat's own `in_use`; the engine deletes dependents first.
337
+ delete: Effect.fn(function* ({ output }) {
338
+ yield* boat
339
+ .deleteCredentialProfile({ profile: output.credentialProfileId })
340
+ .pipe(Effect.catchTag("NotFound", () => Effect.void));
341
+ }),
342
+ list: Effect.fn(function* () {
343
+ const org = yield* stackOrg;
344
+ return (yield* listProfiles(org))
345
+ .filter((profile) => isGeneratedName(profile.name))
346
+ .map((profile) => toAttributes(profile, org));
347
+ }),
348
+ };
349
+ }),
350
+ );
@@ -0,0 +1,33 @@
1
+ import { Credentials } from "@distilled.cloud/boat-dev/Credentials";
2
+ import { ConfigError } from "@distilled.cloud/core/errors";
3
+ import { lazyCredentials } from "alchemy/Auth/Resolve";
4
+ import * as Layer from "effect/Layer";
5
+ import { BOAT_AUTH_PROVIDER_NAME, type BoatResolvedCredentials } from "./AuthProvider.ts";
6
+
7
+ export {
8
+ Credentials,
9
+ CredentialsFromEnv,
10
+ DEFAULT_API_BASE_URL,
11
+ fromApiKey,
12
+ } from "@distilled.cloud/boat-dev/Credentials";
13
+
14
+ /**
15
+ * Build Boat `Credentials` that resolve through the Alchemy AuthProvider:
16
+ * `BOAT_API_KEY` (+ `BOAT_ORG`) when set, otherwise the selected profile,
17
+ * whose `org` every call sends as `X-Boat-Org`; `options.org` overrides
18
+ * it. Resolution is
19
+ * deferred until the first API call, so building the provider layers never
20
+ * needs a profile.
21
+ */
22
+ export const fromAuthProvider = (options?: { readonly org?: string }) =>
23
+ Layer.effect(
24
+ Credentials,
25
+ lazyCredentials<BoatResolvedCredentials, ConfigError>()(BOAT_AUTH_PROVIDER_NAME, (creds) => {
26
+ const org = options?.org ?? creds.org;
27
+ return {
28
+ apiKey: creds.apiKey,
29
+ apiBaseUrl: creds.apiBaseUrl,
30
+ ...(org ? { org } : {}),
31
+ };
32
+ }),
33
+ );