alchemy 2.0.0-beta.28 → 2.0.0-beta.29

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 (91) hide show
  1. package/bin/exec.js +98 -6
  2. package/bin/exec.js.map +1 -1
  3. package/lib/AWS/IAM/SAMLProvider.d.ts +2 -1
  4. package/lib/AWS/IAM/SAMLProvider.d.ts.map +1 -1
  5. package/lib/AWS/IAM/SAMLProvider.js +30 -9
  6. package/lib/AWS/IAM/SAMLProvider.js.map +1 -1
  7. package/lib/Apply.d.ts.map +1 -1
  8. package/lib/Apply.js +55 -11
  9. package/lib/Apply.js.map +1 -1
  10. package/lib/Cli/commands/bootstrap.d.ts +1 -1
  11. package/lib/Cli/commands/bootstrap.d.ts.map +1 -1
  12. package/lib/Cli/commands/bootstrap.js +2 -1
  13. package/lib/Cli/commands/bootstrap.js.map +1 -1
  14. package/lib/Cloudflare/Access.d.ts +19 -0
  15. package/lib/Cloudflare/Access.d.ts.map +1 -0
  16. package/lib/Cloudflare/Access.js +64 -0
  17. package/lib/Cloudflare/Access.js.map +1 -0
  18. package/lib/Cloudflare/AiGateway/AiGateway.d.ts +244 -0
  19. package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -0
  20. package/lib/Cloudflare/AiGateway/AiGateway.js +269 -0
  21. package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -0
  22. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts +103 -0
  23. package/lib/Cloudflare/AiGateway/AiGatewayBinding.d.ts.map +1 -0
  24. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js +99 -0
  25. package/lib/Cloudflare/AiGateway/AiGatewayBinding.js.map +1 -0
  26. package/lib/Cloudflare/AiGateway/index.d.ts +3 -0
  27. package/lib/Cloudflare/AiGateway/index.d.ts.map +1 -0
  28. package/lib/Cloudflare/AiGateway/index.js +3 -0
  29. package/lib/Cloudflare/AiGateway/index.js.map +1 -0
  30. package/lib/Cloudflare/EdgeSession.d.ts +2 -1
  31. package/lib/Cloudflare/EdgeSession.d.ts.map +1 -1
  32. package/lib/Cloudflare/EdgeSession.js +8 -4
  33. package/lib/Cloudflare/EdgeSession.js.map +1 -1
  34. package/lib/Cloudflare/Providers.d.ts +2 -1
  35. package/lib/Cloudflare/Providers.d.ts.map +1 -1
  36. package/lib/Cloudflare/Providers.js +5 -1
  37. package/lib/Cloudflare/Providers.js.map +1 -1
  38. package/lib/Cloudflare/StateStore/Api.d.ts +10 -0
  39. package/lib/Cloudflare/StateStore/Api.d.ts.map +1 -1
  40. package/lib/Cloudflare/StateStore/Api.js +12 -1
  41. package/lib/Cloudflare/StateStore/Api.js.map +1 -1
  42. package/lib/Cloudflare/StateStore/State.d.ts +4 -3
  43. package/lib/Cloudflare/StateStore/State.d.ts.map +1 -1
  44. package/lib/Cloudflare/StateStore/State.js +103 -13
  45. package/lib/Cloudflare/StateStore/State.js.map +1 -1
  46. package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
  47. package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
  48. package/lib/Cloudflare/Workers/Worker.d.ts +2 -1
  49. package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
  50. package/lib/Cloudflare/Workers/Worker.js +40 -31
  51. package/lib/Cloudflare/Workers/Worker.js.map +1 -1
  52. package/lib/Cloudflare/index.d.ts +1 -0
  53. package/lib/Cloudflare/index.d.ts.map +1 -1
  54. package/lib/Cloudflare/index.js +1 -0
  55. package/lib/Cloudflare/index.js.map +1 -1
  56. package/lib/Plan.d.ts +8 -0
  57. package/lib/Plan.d.ts.map +1 -1
  58. package/lib/Plan.js +12 -5
  59. package/lib/Plan.js.map +1 -1
  60. package/lib/State/HttpStateApi.d.ts +19 -1
  61. package/lib/State/HttpStateApi.d.ts.map +1 -1
  62. package/lib/State/HttpStateApi.js +18 -1
  63. package/lib/State/HttpStateApi.js.map +1 -1
  64. package/lib/Util/cached-function.d.ts +38 -0
  65. package/lib/Util/cached-function.d.ts.map +1 -0
  66. package/lib/Util/cached-function.js +53 -0
  67. package/lib/Util/cached-function.js.map +1 -0
  68. package/lib/Util/scc.d.ts +12 -0
  69. package/lib/Util/scc.d.ts.map +1 -0
  70. package/lib/Util/scc.js +81 -0
  71. package/lib/Util/scc.js.map +1 -0
  72. package/lib/tsconfig.test.tsbuildinfo +1 -1
  73. package/package.json +2 -2
  74. package/src/AWS/IAM/SAMLProvider.ts +47 -18
  75. package/src/Apply.ts +72 -8
  76. package/src/Cli/commands/bootstrap.ts +2 -0
  77. package/src/Cloudflare/Access.ts +110 -0
  78. package/src/Cloudflare/AiGateway/AiGateway.ts +526 -0
  79. package/src/Cloudflare/AiGateway/AiGatewayBinding.ts +190 -0
  80. package/src/Cloudflare/AiGateway/index.ts +2 -0
  81. package/src/Cloudflare/EdgeSession.ts +15 -5
  82. package/src/Cloudflare/Providers.ts +7 -0
  83. package/src/Cloudflare/StateStore/Api.ts +18 -0
  84. package/src/Cloudflare/StateStore/State.ts +130 -12
  85. package/src/Cloudflare/Workers/InferEnv.ts +7 -5
  86. package/src/Cloudflare/Workers/Worker.ts +44 -33
  87. package/src/Cloudflare/index.ts +1 -0
  88. package/src/Plan.ts +21 -5
  89. package/src/State/HttpStateApi.ts +22 -1
  90. package/src/Util/cached-function.ts +75 -0
  91. package/src/Util/scc.ts +89 -0
@@ -0,0 +1,526 @@
1
+ import * as aiGateway from "@distilled.cloud/cloudflare/ai-gateway";
2
+ import * as Effect from "effect/Effect";
3
+ import { deepEqual, isResolved } from "../../Diff.ts";
4
+ import { createPhysicalName } from "../../PhysicalName.ts";
5
+ import * as Provider from "../../Provider.ts";
6
+ import { Resource } from "../../Resource.ts";
7
+ import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
8
+ import type { Providers } from "../Providers.ts";
9
+ import { AiGatewayBinding } from "./AiGatewayBinding.ts";
10
+
11
+ export type AiGatewayRateLimitingTechnique = "fixed" | "sliding";
12
+
13
+ export type AiGatewayLogManagementStrategy = "STOP_INSERTING" | "DELETE_OLDEST";
14
+
15
+ export type AiGatewayDlp =
16
+ | {
17
+ /**
18
+ * Action to take when a DLP profile matches.
19
+ */
20
+ action: "BLOCK" | "FLAG";
21
+ /**
22
+ * Whether DLP is enabled.
23
+ */
24
+ enabled: boolean;
25
+ /**
26
+ * DLP profile identifiers to apply.
27
+ */
28
+ profiles: string[];
29
+ }
30
+ | {
31
+ /**
32
+ * Whether DLP is enabled.
33
+ */
34
+ enabled: boolean;
35
+ /**
36
+ * DLP policies to apply.
37
+ */
38
+ policies: {
39
+ /**
40
+ * DLP policy identifier.
41
+ */
42
+ id: string;
43
+ /**
44
+ * Action to take when the policy matches.
45
+ */
46
+ action: "FLAG" | "BLOCK";
47
+ /**
48
+ * Request or response phases checked by the policy.
49
+ */
50
+ check: ("REQUEST" | "RESPONSE")[];
51
+ /**
52
+ * Whether the policy is enabled.
53
+ */
54
+ enabled: boolean;
55
+ /**
56
+ * DLP profile identifiers to apply.
57
+ */
58
+ profiles: string[];
59
+ }[];
60
+ };
61
+
62
+ export type AiGatewayOtel = {
63
+ /**
64
+ * Authorization header value for the OpenTelemetry endpoint.
65
+ */
66
+ authorization: string;
67
+ /**
68
+ * Additional headers sent to the OpenTelemetry endpoint.
69
+ */
70
+ headers: Record<string, unknown>;
71
+ /**
72
+ * OpenTelemetry endpoint URL.
73
+ */
74
+ url: string;
75
+ };
76
+
77
+ export type AiGatewayStripe = {
78
+ /**
79
+ * Authorization header value for Stripe usage events.
80
+ */
81
+ authorization: string;
82
+ /**
83
+ * Stripe usage event payload definitions.
84
+ */
85
+ usageEvents: {
86
+ /**
87
+ * Usage event payload.
88
+ */
89
+ payload: string;
90
+ }[];
91
+ };
92
+
93
+ export type AiGatewayProps = {
94
+ /**
95
+ * Gateway identifier. If omitted, a unique ID will be generated.
96
+ *
97
+ * Must be 1-64 characters and match Cloudflare's AI Gateway ID pattern:
98
+ * lowercase letters, numbers, underscores, and hyphens.
99
+ *
100
+ * @default ${app}-${stage}-${id}
101
+ */
102
+ id?: string;
103
+ /**
104
+ * Whether cached responses are invalidated when a request changes.
105
+ *
106
+ * @default false
107
+ */
108
+ cacheInvalidateOnUpdate?: boolean;
109
+ /**
110
+ * Cache time-to-live in seconds. Set to `null` to disable caching.
111
+ *
112
+ * @default null
113
+ */
114
+ cacheTtl?: number | null;
115
+ /**
116
+ * Whether AI Gateway stores request logs.
117
+ *
118
+ * @default true
119
+ */
120
+ collectLogs?: boolean;
121
+ /**
122
+ * Rate limiting interval in seconds. Set to `null` to disable rate limiting.
123
+ *
124
+ * @default null
125
+ */
126
+ rateLimitingInterval?: number | null;
127
+ /**
128
+ * Maximum requests allowed during the rate limiting interval. Set to `null`
129
+ * to disable rate limiting.
130
+ *
131
+ * @default null
132
+ */
133
+ rateLimitingLimit?: number | null;
134
+ /**
135
+ * Rate limiting algorithm.
136
+ *
137
+ * @default "fixed"
138
+ */
139
+ rateLimitingTechnique?: AiGatewayRateLimitingTechnique;
140
+ /**
141
+ * Whether gateway authentication is enabled.
142
+ */
143
+ authentication?: boolean;
144
+ /**
145
+ * DLP configuration. The installed distilled Cloudflare client applies this
146
+ * through the update API after gateway creation.
147
+ */
148
+ dlp?: AiGatewayDlp;
149
+ /**
150
+ * Whether this gateway is the account default.
151
+ */
152
+ isDefault?: boolean;
153
+ /**
154
+ * Maximum number of log entries to retain.
155
+ */
156
+ logManagement?: number | null;
157
+ /**
158
+ * Strategy used when retained logs reach `logManagement`.
159
+ */
160
+ logManagementStrategy?: AiGatewayLogManagementStrategy | null;
161
+ /**
162
+ * Whether Logpush is enabled for this gateway.
163
+ */
164
+ logpush?: boolean;
165
+ /**
166
+ * Public key used for Logpush encryption.
167
+ */
168
+ logpushPublicKey?: string | null;
169
+ /**
170
+ * OpenTelemetry export configuration.
171
+ */
172
+ otel?: AiGatewayOtel[] | null;
173
+ /**
174
+ * Store identifier used by the gateway.
175
+ */
176
+ storeId?: string | null;
177
+ /**
178
+ * Stripe usage export configuration.
179
+ */
180
+ stripe?: AiGatewayStripe | null;
181
+ /**
182
+ * Whether Zero Data Retention is enabled.
183
+ */
184
+ zdr?: boolean;
185
+ };
186
+
187
+ export type AiGateway = Resource<
188
+ "Cloudflare.AiGateway",
189
+ AiGatewayProps,
190
+ {
191
+ gatewayId: string;
192
+ accountId: string;
193
+ accountTag: string | undefined;
194
+ internalId: string | undefined;
195
+ cacheInvalidateOnUpdate: boolean;
196
+ cacheTtl: number | null;
197
+ collectLogs: boolean;
198
+ createdAt: string;
199
+ modifiedAt: string;
200
+ rateLimitingInterval: number | null;
201
+ rateLimitingLimit: number | null;
202
+ rateLimitingTechnique: AiGatewayRateLimitingTechnique;
203
+ authentication: boolean | undefined;
204
+ dlp: AiGatewayDlp | null | undefined;
205
+ isDefault: boolean | undefined;
206
+ logManagement: number | null | undefined;
207
+ logManagementStrategy: AiGatewayLogManagementStrategy | null | undefined;
208
+ logpush: boolean | undefined;
209
+ logpushPublicKey: string | null | undefined;
210
+ otel: AiGatewayOtel[] | null | undefined;
211
+ storeId: string | null | undefined;
212
+ stripe: AiGatewayStripe | null | undefined;
213
+ zdr: boolean | undefined;
214
+ },
215
+ never,
216
+ Providers
217
+ >;
218
+
219
+ // Cloudflare's AI Gateway API uses 0 to mean "disabled" for cache TTL and
220
+ // rate limiting fields. Normalize back to null so user-facing semantics
221
+ // match what was passed in.
222
+ const nullIfZero = (value: number | null | undefined): number | null =>
223
+ value == null || value === 0 ? null : value;
224
+
225
+ export const isAiGateway = (value: unknown): value is AiGateway =>
226
+ typeof value === "object" &&
227
+ value !== null &&
228
+ "Type" in value &&
229
+ (value as AiGateway).Type === "Cloudflare.AiGateway";
230
+
231
+ /**
232
+ * A Cloudflare AI Gateway for observability, caching, rate limiting, and
233
+ * governance across AI provider requests.
234
+ *
235
+ * AI Gateway gives your application a stable gateway ID and account-scoped
236
+ * endpoint that can route model requests through Cloudflare.
237
+ *
238
+ * @section Creating a Gateway
239
+ * @example Basic gateway
240
+ * ```typescript
241
+ * const gateway = yield* Cloudflare.AiGateway("Gateway");
242
+ * ```
243
+ *
244
+ * @example Gateway with caching and rate limiting
245
+ * ```typescript
246
+ * const gateway = yield* Cloudflare.AiGateway("Gateway", {
247
+ * id: "my-gateway",
248
+ * cacheTtl: 300,
249
+ * cacheInvalidateOnUpdate: true,
250
+ * rateLimitingInterval: 60,
251
+ * rateLimitingLimit: 100,
252
+ * rateLimitingTechnique: "sliding",
253
+ * });
254
+ * ```
255
+ *
256
+ * @section Logging
257
+ * @example Gateway with log retention
258
+ * ```typescript
259
+ * const gateway = yield* Cloudflare.AiGateway("Gateway", {
260
+ * collectLogs: true,
261
+ * logManagement: 10000,
262
+ * logManagementStrategy: "STOP_INSERTING",
263
+ * });
264
+ * ```
265
+ */
266
+ export const AiGateway = Resource<AiGateway>("Cloudflare.AiGateway")({
267
+ /**
268
+ * Bind this gateway to the surrounding Worker, returning an Effect-native
269
+ * client for the runtime AI Gateway binding.
270
+ */
271
+ bind: AiGatewayBinding.bind,
272
+ });
273
+
274
+ export const AiGatewayProvider = () =>
275
+ Provider.effect(
276
+ AiGateway,
277
+ Effect.gen(function* () {
278
+ const { accountId } = yield* CloudflareEnvironment;
279
+ const createAiGateway = yield* aiGateway.createAiGateway;
280
+ const getAiGateway = yield* aiGateway.getAiGateway;
281
+ const updateAiGateway = yield* aiGateway.updateAiGateway;
282
+ const deleteAiGateway = yield* aiGateway.deleteAiGateway;
283
+
284
+ const createGatewayId = (id: string, gatewayId: string | undefined) =>
285
+ Effect.gen(function* () {
286
+ if (gatewayId) return gatewayId;
287
+ return yield* createPhysicalName({
288
+ id,
289
+ maxLength: 64,
290
+ lowercase: true,
291
+ });
292
+ });
293
+
294
+ const desired = (id: string, props: AiGatewayProps | undefined) =>
295
+ Effect.gen(function* () {
296
+ return {
297
+ gatewayId: yield* createGatewayId(id, props?.id),
298
+ cacheInvalidateOnUpdate: props?.cacheInvalidateOnUpdate ?? false,
299
+ cacheTtl: props?.cacheTtl ?? null,
300
+ collectLogs: props?.collectLogs ?? true,
301
+ rateLimitingInterval: props?.rateLimitingInterval ?? null,
302
+ rateLimitingLimit: props?.rateLimitingLimit ?? null,
303
+ rateLimitingTechnique: props?.rateLimitingTechnique ?? "fixed",
304
+ authentication: props?.authentication,
305
+ dlp: props?.dlp,
306
+ isDefault: props?.isDefault,
307
+ logManagement: props?.logManagement,
308
+ logManagementStrategy: props?.logManagementStrategy,
309
+ logpush: props?.logpush,
310
+ logpushPublicKey: props?.logpushPublicKey,
311
+ otel: props?.otel,
312
+ storeId: props?.storeId,
313
+ stripe: props?.stripe,
314
+ zdr: props?.zdr,
315
+ };
316
+ });
317
+
318
+ const mapGateway = (
319
+ gateway:
320
+ | aiGateway.GetAiGatewayResponse
321
+ | aiGateway.CreateAiGatewayResponse
322
+ | aiGateway.UpdateAiGatewayResponse,
323
+ accountId: string,
324
+ ): AiGateway["Attributes"] => ({
325
+ gatewayId: gateway.id,
326
+ accountId,
327
+ accountTag: gateway.accountTag ?? undefined,
328
+ internalId: gateway.internalId ?? undefined,
329
+ cacheInvalidateOnUpdate: gateway.cacheInvalidateOnUpdate,
330
+ cacheTtl: nullIfZero(gateway.cacheTtl),
331
+ collectLogs: gateway.collectLogs,
332
+ createdAt: gateway.createdAt,
333
+ modifiedAt: gateway.modifiedAt,
334
+ rateLimitingInterval: nullIfZero(gateway.rateLimitingInterval),
335
+ rateLimitingLimit: nullIfZero(gateway.rateLimitingLimit),
336
+ rateLimitingTechnique: gateway.rateLimitingTechnique ?? "fixed",
337
+ authentication: gateway.authentication ?? undefined,
338
+ dlp: gateway.dlp,
339
+ isDefault: gateway.isDefault ?? undefined,
340
+ logManagement: gateway.logManagement,
341
+ logManagementStrategy: gateway.logManagementStrategy,
342
+ logpush: gateway.logpush ?? undefined,
343
+ logpushPublicKey: gateway.logpushPublicKey,
344
+ otel: gateway.otel,
345
+ storeId: gateway.storeId,
346
+ stripe: gateway.stripe,
347
+ zdr: gateway.zdr ?? undefined,
348
+ });
349
+
350
+ const mutable = (gateway: AiGateway["Attributes"]) => ({
351
+ cacheInvalidateOnUpdate: gateway.cacheInvalidateOnUpdate,
352
+ cacheTtl: gateway.cacheTtl,
353
+ collectLogs: gateway.collectLogs,
354
+ rateLimitingInterval: gateway.rateLimitingInterval,
355
+ rateLimitingLimit: gateway.rateLimitingLimit,
356
+ rateLimitingTechnique: gateway.rateLimitingTechnique,
357
+ authentication: gateway.authentication,
358
+ dlp: gateway.dlp ?? undefined,
359
+ isDefault: gateway.isDefault,
360
+ logManagement: gateway.logManagement,
361
+ logManagementStrategy: gateway.logManagementStrategy,
362
+ logpush: gateway.logpush,
363
+ logpushPublicKey: gateway.logpushPublicKey,
364
+ otel: gateway.otel,
365
+ storeId: gateway.storeId,
366
+ stripe: gateway.stripe,
367
+ zdr: gateway.zdr,
368
+ });
369
+
370
+ const createRequest = Effect.fn(function* (
371
+ id: string,
372
+ props: AiGatewayProps | undefined,
373
+ ) {
374
+ const next = yield* desired(id, props);
375
+ return {
376
+ accountId,
377
+ id: next.gatewayId,
378
+ cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
379
+ cacheTtl: next.cacheTtl,
380
+ collectLogs: next.collectLogs,
381
+ rateLimitingInterval: next.rateLimitingInterval,
382
+ rateLimitingLimit: next.rateLimitingLimit,
383
+ rateLimitingTechnique: next.rateLimitingTechnique,
384
+ authentication: next.authentication,
385
+ isDefault: next.isDefault,
386
+ logManagement: next.logManagement,
387
+ logManagementStrategy: next.logManagementStrategy,
388
+ logpush: next.logpush,
389
+ logpushPublicKey: next.logpushPublicKey,
390
+ zdr: next.zdr,
391
+ } satisfies aiGateway.CreateAiGatewayRequest;
392
+ });
393
+
394
+ const updateRequest = Effect.fn(function* (
395
+ id: string,
396
+ props: AiGatewayProps | undefined,
397
+ accountId: string,
398
+ ) {
399
+ const next = yield* desired(id, props);
400
+ return {
401
+ accountId,
402
+ id: next.gatewayId,
403
+ cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
404
+ cacheTtl: next.cacheTtl,
405
+ collectLogs: next.collectLogs,
406
+ rateLimitingInterval: next.rateLimitingInterval,
407
+ rateLimitingLimit: next.rateLimitingLimit,
408
+ rateLimitingTechnique: next.rateLimitingTechnique,
409
+ authentication: next.authentication,
410
+ dlp: next.dlp,
411
+ isDefault: next.isDefault,
412
+ logManagement: next.logManagement,
413
+ logManagementStrategy: next.logManagementStrategy,
414
+ logpush: next.logpush,
415
+ logpushPublicKey: next.logpushPublicKey,
416
+ otel: next.otel,
417
+ storeId: next.storeId,
418
+ stripe: next.stripe,
419
+ zdr: next.zdr,
420
+ } satisfies aiGateway.UpdateAiGatewayRequest;
421
+ });
422
+
423
+ return {
424
+ stables: ["gatewayId", "accountId"],
425
+ diff: Effect.fn(function* ({ id, olds = {}, news = {}, output }) {
426
+ if (!isResolved(news)) return undefined;
427
+ const next = yield* desired(id, news);
428
+ const oldGatewayId =
429
+ output?.gatewayId ?? (yield* createGatewayId(id, olds.id));
430
+ if (
431
+ (output?.accountId ?? accountId) !== accountId ||
432
+ oldGatewayId !== next.gatewayId
433
+ ) {
434
+ return { action: "replace" } as const;
435
+ }
436
+
437
+ const oldMutable = output
438
+ ? mutable(output)
439
+ : yield* desired(id, olds).pipe(
440
+ Effect.map((old) => ({
441
+ cacheInvalidateOnUpdate: old.cacheInvalidateOnUpdate,
442
+ cacheTtl: old.cacheTtl,
443
+ collectLogs: old.collectLogs,
444
+ rateLimitingInterval: old.rateLimitingInterval,
445
+ rateLimitingLimit: old.rateLimitingLimit,
446
+ rateLimitingTechnique: old.rateLimitingTechnique,
447
+ authentication: old.authentication,
448
+ dlp: old.dlp,
449
+ isDefault: old.isDefault,
450
+ logManagement: old.logManagement,
451
+ logManagementStrategy: old.logManagementStrategy,
452
+ logpush: old.logpush,
453
+ logpushPublicKey: old.logpushPublicKey,
454
+ otel: old.otel,
455
+ storeId: old.storeId,
456
+ stripe: old.stripe,
457
+ zdr: old.zdr,
458
+ })),
459
+ );
460
+ const nextMutable = {
461
+ cacheInvalidateOnUpdate: next.cacheInvalidateOnUpdate,
462
+ cacheTtl: next.cacheTtl,
463
+ collectLogs: next.collectLogs,
464
+ rateLimitingInterval: next.rateLimitingInterval,
465
+ rateLimitingLimit: next.rateLimitingLimit,
466
+ rateLimitingTechnique: next.rateLimitingTechnique,
467
+ authentication: next.authentication,
468
+ dlp: next.dlp,
469
+ isDefault: next.isDefault,
470
+ logManagement: next.logManagement,
471
+ logManagementStrategy: next.logManagementStrategy,
472
+ logpush: next.logpush,
473
+ logpushPublicKey: next.logpushPublicKey,
474
+ otel: next.otel,
475
+ storeId: next.storeId,
476
+ stripe: next.stripe,
477
+ zdr: next.zdr,
478
+ };
479
+ if (!deepEqual(oldMutable, nextMutable)) {
480
+ return { action: "update" } as const;
481
+ }
482
+ }),
483
+ create: Effect.fn(function* ({ id, news = {} }) {
484
+ const request = yield* createRequest(id, news);
485
+ const gateway = yield* createAiGateway(request).pipe(
486
+ Effect.catchTag("GatewayAlreadyExists", () =>
487
+ getAiGateway({
488
+ accountId,
489
+ id: request.id,
490
+ }),
491
+ ),
492
+ );
493
+ const update = yield* updateRequest(id, news, accountId);
494
+ return yield* updateAiGateway(update).pipe(
495
+ Effect.map((gateway) => mapGateway(gateway, accountId)),
496
+ Effect.catchTag("GatewayNotFound", () =>
497
+ Effect.succeed(mapGateway(gateway, accountId)),
498
+ ),
499
+ );
500
+ }),
501
+ update: Effect.fn(function* ({ id, news = {}, output }) {
502
+ const request = yield* updateRequest(id, news, output.accountId);
503
+ const gateway = yield* updateAiGateway(request);
504
+ return mapGateway(gateway, output.accountId);
505
+ }),
506
+ delete: Effect.fn(function* ({ output }) {
507
+ yield* deleteAiGateway({
508
+ accountId: output.accountId,
509
+ id: output.gatewayId,
510
+ }).pipe(Effect.catchTag("GatewayNotFound", () => Effect.void));
511
+ }),
512
+ read: Effect.fn(function* ({ id, olds, output }) {
513
+ const gatewayId =
514
+ output?.gatewayId ?? (yield* createGatewayId(id, olds?.id));
515
+ const acct = output?.accountId ?? accountId;
516
+ return yield* getAiGateway({
517
+ accountId: acct,
518
+ id: gatewayId,
519
+ }).pipe(
520
+ Effect.map((gateway) => mapGateway(gateway, acct)),
521
+ Effect.catchTag("GatewayNotFound", () => Effect.succeed(undefined)),
522
+ );
523
+ }),
524
+ };
525
+ }),
526
+ );
@@ -0,0 +1,190 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+
3
+ import * as Data from "effect/Data";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Option from "effect/Option";
7
+ import * as Binding from "../../Binding.ts";
8
+ import type { ResourceLike } from "../../Resource.ts";
9
+ import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
10
+ import type { AiGateway as AiGatewayResource } from "./AiGateway.ts";
11
+
12
+ /**
13
+ * Error raised by AI Gateway runtime operations.
14
+ */
15
+ export class AiGatewayError extends Data.TaggedError("AiGatewayError")<{
16
+ /**
17
+ * Human-readable runtime error message.
18
+ */
19
+ message: string;
20
+ /**
21
+ * Original error thrown by the Cloudflare runtime binding.
22
+ */
23
+ cause: unknown;
24
+ }> {}
25
+
26
+ /**
27
+ * Effect-native client for a Cloudflare AI Gateway Worker binding.
28
+ *
29
+ * Wraps the runtime {@link AiGateway} binding so each operation returns an
30
+ * Effect tagged with {@link AiGatewayError}. Use
31
+ * `Cloudflare.AiGatewayBinding.bind(gateway)` inside a Worker's init phase.
32
+ */
33
+ export interface AiGatewayClient {
34
+ /**
35
+ * Effect resolving to the raw Workers AI binding.
36
+ */
37
+ raw: Effect.Effect<Ai, never, WorkerEnvironment>;
38
+ /**
39
+ * Effect resolving to the raw AI Gateway runtime binding.
40
+ */
41
+ gateway: Effect.Effect<AiGateway, never, WorkerEnvironment>;
42
+ /**
43
+ * Update metadata on an existing AI Gateway log entry.
44
+ */
45
+ patchLog(
46
+ logId: string,
47
+ data: Parameters<AiGateway["patchLog"]>[1],
48
+ ): Effect.Effect<void, AiGatewayError, WorkerEnvironment>;
49
+ /**
50
+ * Read an AI Gateway log entry by ID.
51
+ */
52
+ getLog(
53
+ logId: string,
54
+ ): Effect.Effect<AiGatewayLog, AiGatewayError, WorkerEnvironment>;
55
+ /**
56
+ * Build a provider URL routed through this gateway.
57
+ */
58
+ getUrl(
59
+ provider?: Parameters<AiGateway["getUrl"]>[0],
60
+ ): Effect.Effect<string, AiGatewayError, WorkerEnvironment>;
61
+ /**
62
+ * Run an AI Gateway request through the Cloudflare runtime binding.
63
+ */
64
+ run(
65
+ data: Parameters<AiGateway["run"]>[0],
66
+ options?: Parameters<AiGateway["run"]>[1],
67
+ ): Effect.Effect<Response, AiGatewayError, WorkerEnvironment>;
68
+ }
69
+
70
+ /**
71
+ * Binding service that turns an {@link AiGatewayResource} resource into a typed
72
+ * {@link AiGatewayClient} for Worker runtime code.
73
+ *
74
+ * @section Calling AI Gateway
75
+ * Bind the gateway during the Worker's init phase, then use `run` or `getUrl`
76
+ * from request handlers.
77
+ *
78
+ * @example Run through a gateway
79
+ * ```typescript
80
+ * const aiGateway = yield* Cloudflare.AiGatewayBinding.bind(gateway);
81
+ *
82
+ * return {
83
+ * fetch: Effect.gen(function* () {
84
+ * return yield* aiGateway.run({
85
+ * provider: "workers-ai",
86
+ * endpoint: "@cf/meta/llama-3.1-8b-instruct",
87
+ * headers: { "content-type": "application/json" },
88
+ * query: { prompt: "Write a concise status update" },
89
+ * });
90
+ * }),
91
+ * };
92
+ * ```
93
+ *
94
+ * Provide {@link AiGatewayBindingLive} in the worker's runtime layer to
95
+ * resolve the underlying Cloudflare AI binding at request time.
96
+ */
97
+ export class AiGatewayBinding extends Binding.Service<
98
+ AiGatewayBinding,
99
+ (gateway: AiGatewayResource) => Effect.Effect<AiGatewayClient>
100
+ >()("Cloudflare.AiGateway.Binding") {}
101
+
102
+ /**
103
+ * Runtime layer for {@link AiGatewayBinding}.
104
+ */
105
+ export const AiGatewayBindingLive = Layer.effect(
106
+ AiGatewayBinding,
107
+ Effect.gen(function* () {
108
+ const Policy = yield* AiGatewayBindingPolicy;
109
+
110
+ return Effect.fn(function* (gateway: AiGatewayResource) {
111
+ yield* Policy(gateway);
112
+ // Capture the gatewayId accessor (which requires WorkerEnvironment) but
113
+ // don't resolve it here — that happens lazily at runtime when each
114
+ // method is invoked. Resolving eagerly would require WorkerEnvironment
115
+ // at deploy time, where it's intentionally not provided.
116
+ const gatewayIdAccessor = yield* gateway.gatewayId;
117
+ const ai = yield* Effect.serviceOption(WorkerEnvironment).pipe(
118
+ Effect.map(Option.getOrUndefined),
119
+ Effect.map((env) => env?.[gateway.LogicalId]! as Ai),
120
+ Effect.cached,
121
+ );
122
+ const runtimeGateway = yield* Effect.zip(ai, gatewayIdAccessor).pipe(
123
+ Effect.map(([ai, gatewayId]) => ai.gateway(gatewayId)),
124
+ Effect.cached,
125
+ );
126
+
127
+ const use = <T>(
128
+ fn: (gateway: AiGateway) => Promise<T>,
129
+ ): Effect.Effect<T, AiGatewayError, WorkerEnvironment> =>
130
+ runtimeGateway.pipe(
131
+ Effect.flatMap((gateway) => tryPromise(() => fn(gateway))),
132
+ );
133
+
134
+ return {
135
+ raw: ai,
136
+ gateway: runtimeGateway,
137
+ patchLog: (logId, data) =>
138
+ use((gateway) => gateway.patchLog(logId, data)),
139
+ getLog: (logId) => use((gateway) => gateway.getLog(logId)),
140
+ getUrl: (provider) => use((gateway) => gateway.getUrl(provider)),
141
+ run: (data, options) => use((gateway) => gateway.run(data, options)),
142
+ } satisfies AiGatewayClient;
143
+ });
144
+ }),
145
+ );
146
+
147
+ /**
148
+ * Deploy-time policy service that attaches an AI binding to Workers.
149
+ */
150
+ export class AiGatewayBindingPolicy extends Binding.Policy<
151
+ AiGatewayBindingPolicy,
152
+ (gateway: AiGatewayResource) => Effect.Effect<void>
153
+ >()("Cloudflare.AiGateway.Binding") {}
154
+
155
+ /**
156
+ * Live deploy-time policy layer for {@link AiGatewayBindingPolicy}.
157
+ */
158
+ export const AiGatewayBindingPolicyLive = AiGatewayBindingPolicy.layer.succeed(
159
+ Effect.fn(function* (host: ResourceLike, gateway: AiGatewayResource) {
160
+ if (isWorker(host)) {
161
+ yield* host.bind(gateway.LogicalId, {
162
+ bindings: [
163
+ {
164
+ type: "ai",
165
+ name: gateway.LogicalId,
166
+ },
167
+ ],
168
+ });
169
+ } else {
170
+ return yield* Effect.die(
171
+ new Error(`AiGatewayBinding does not support runtime '${host.Type}'`),
172
+ );
173
+ }
174
+ }),
175
+ );
176
+
177
+ const tryPromise = <T>(
178
+ fn: () => Promise<T>,
179
+ ): Effect.Effect<T, AiGatewayError> =>
180
+ Effect.tryPromise({
181
+ try: fn,
182
+ catch: (error) =>
183
+ new AiGatewayError({
184
+ message:
185
+ error instanceof Error
186
+ ? error.message
187
+ : "Unknown AI Gateway runtime error",
188
+ cause: error,
189
+ }),
190
+ });