alchemy 2.0.0-beta.47 → 2.0.0-beta.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/exec.js +28 -5
- package/bin/exec.js.map +1 -1
- package/lib/AWS/Lambda/BucketEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/BucketEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/EventBridgeEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/QueueEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/StreamEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TableEventSource.d.ts.map +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts +1 -1
- package/lib/AWS/Lambda/TopicEventSource.d.ts.map +1 -1
- package/lib/AdoptPolicy.js +1 -1
- package/lib/AdoptPolicy.js.map +1 -1
- package/lib/Binding.d.ts +5 -1
- package/lib/Binding.d.ts.map +1 -1
- package/lib/Binding.js.map +1 -1
- package/lib/Cli/commands/cloudflare.d.ts +4 -1
- package/lib/Cli/commands/cloudflare.d.ts.map +1 -1
- package/lib/Cli/commands/cloudflare.js +274 -1
- package/lib/Cli/commands/cloudflare.js.map +1 -1
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts +1 -1
- package/lib/Cloudflare/AiGateway/AiGateway.d.ts.map +1 -1
- package/lib/Cloudflare/AiGateway/AiGateway.js +2 -1
- package/lib/Cloudflare/AiGateway/AiGateway.js.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts +40 -2
- package/lib/Cloudflare/ApiToken/AccountApiToken.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/AccountApiToken.js +49 -6
- package/lib/Cloudflare/ApiToken/AccountApiToken.js.map +1 -1
- package/lib/Cloudflare/ApiToken/Common.d.ts +36 -4
- package/lib/Cloudflare/ApiToken/Common.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/Common.js +8 -0
- package/lib/Cloudflare/ApiToken/Common.js.map +1 -1
- package/lib/Cloudflare/ApiToken/UserApiToken.d.ts +38 -2
- package/lib/Cloudflare/ApiToken/UserApiToken.d.ts.map +1 -1
- package/lib/Cloudflare/ApiToken/UserApiToken.js +47 -6
- package/lib/Cloudflare/ApiToken/UserApiToken.js.map +1 -1
- package/lib/Cloudflare/Browser/Browser.d.ts +121 -0
- package/lib/Cloudflare/Browser/Browser.d.ts.map +1 -0
- package/lib/Cloudflare/{BrowserRendering/BrowserRendering.js → Browser/Browser.js} +19 -26
- package/lib/Cloudflare/Browser/Browser.js.map +1 -0
- package/lib/Cloudflare/Browser/BrowserBinding.d.ts +55 -0
- package/lib/Cloudflare/Browser/BrowserBinding.d.ts.map +1 -0
- package/lib/Cloudflare/{BrowserRendering/BrowserRenderingBinding.js → Browser/BrowserBinding.js} +11 -11
- package/lib/Cloudflare/Browser/BrowserBinding.js.map +1 -0
- package/lib/Cloudflare/Browser/index.d.ts +3 -0
- package/lib/Cloudflare/Browser/index.d.ts.map +1 -0
- package/lib/Cloudflare/Browser/index.js +3 -0
- package/lib/Cloudflare/Browser/index.js.map +1 -0
- package/lib/Cloudflare/Container/ContainerBinding.d.ts +2 -1
- package/lib/Cloudflare/Container/ContainerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Database.d.ts.map +1 -1
- package/lib/Cloudflare/D1/D1Database.js +2 -1
- package/lib/Cloudflare/D1/D1Database.js.map +1 -1
- package/lib/Cloudflare/Dns/DnsBinding.d.ts +52 -0
- package/lib/Cloudflare/Dns/DnsBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Dns/DnsBinding.js +59 -0
- package/lib/Cloudflare/Dns/DnsBinding.js.map +1 -0
- package/lib/Cloudflare/Dns/DnsRead.d.ts +80 -0
- package/lib/Cloudflare/Dns/DnsRead.d.ts.map +1 -0
- package/lib/Cloudflare/Dns/DnsRead.js +70 -0
- package/lib/Cloudflare/Dns/DnsRead.js.map +1 -0
- package/lib/Cloudflare/Dns/DnsReadWrite.d.ts +79 -0
- package/lib/Cloudflare/Dns/DnsReadWrite.d.ts.map +1 -0
- package/lib/Cloudflare/Dns/DnsReadWrite.js +72 -0
- package/lib/Cloudflare/Dns/DnsReadWrite.js.map +1 -0
- package/lib/Cloudflare/Dns/DnsWrite.d.ts +113 -0
- package/lib/Cloudflare/Dns/DnsWrite.d.ts.map +1 -0
- package/lib/Cloudflare/Dns/DnsWrite.js +100 -0
- package/lib/Cloudflare/Dns/DnsWrite.js.map +1 -0
- package/lib/Cloudflare/Dns/index.d.ts +5 -0
- package/lib/Cloudflare/Dns/index.d.ts.map +1 -0
- package/lib/Cloudflare/Dns/index.js +5 -0
- package/lib/Cloudflare/Dns/index.js.map +1 -0
- package/lib/Cloudflare/Email/EmailRouting.d.ts +1 -1
- package/lib/Cloudflare/Email/EmailRouting.d.ts.map +1 -1
- package/lib/Cloudflare/Email/EmailRouting.js +1 -1
- package/lib/Cloudflare/Email/EmailRouting.js.map +1 -1
- package/lib/Cloudflare/Email/EmailRule.d.ts +1 -1
- package/lib/Cloudflare/Email/EmailRule.d.ts.map +1 -1
- package/lib/Cloudflare/Email/EmailRule.js +1 -1
- package/lib/Cloudflare/Email/EmailRule.js.map +1 -1
- package/lib/Cloudflare/Hyperdrive/Hyperdrive.d.ts +1 -1
- package/lib/Cloudflare/Images/Images.d.ts +32 -23
- package/lib/Cloudflare/Images/Images.d.ts.map +1 -1
- package/lib/Cloudflare/Images/Images.js +10 -13
- package/lib/Cloudflare/Images/Images.js.map +1 -1
- package/lib/Cloudflare/KV/KVNamespace.d.ts +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +14 -3
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/Queue/Queue.d.ts +1 -1
- package/lib/Cloudflare/R2/R2Bucket.d.ts +2 -2
- package/lib/Cloudflare/R2/R2Bucket.d.ts.map +1 -1
- package/lib/Cloudflare/R2/R2Bucket.js +13 -7
- package/lib/Cloudflare/R2/R2Bucket.js.map +1 -1
- package/lib/Cloudflare/RateLimit/RateLimit.d.ts +132 -0
- package/lib/Cloudflare/RateLimit/RateLimit.d.ts.map +1 -0
- package/lib/Cloudflare/RateLimit/RateLimit.js +80 -0
- package/lib/Cloudflare/RateLimit/RateLimit.js.map +1 -0
- package/lib/Cloudflare/RateLimit/RateLimitBinding.d.ts +30 -0
- package/lib/Cloudflare/RateLimit/RateLimitBinding.d.ts.map +1 -0
- package/lib/Cloudflare/RateLimit/RateLimitBinding.js +50 -0
- package/lib/Cloudflare/RateLimit/RateLimitBinding.js.map +1 -0
- package/lib/Cloudflare/RateLimit/index.d.ts +3 -0
- package/lib/Cloudflare/RateLimit/index.d.ts.map +1 -0
- package/lib/Cloudflare/RateLimit/index.js +3 -0
- package/lib/Cloudflare/RateLimit/index.js.map +1 -0
- package/lib/Cloudflare/SecretsStore/Secret.d.ts +3 -2
- package/lib/Cloudflare/SecretsStore/Secret.d.ts.map +1 -1
- package/lib/Cloudflare/SecretsStore/Secret.js +7 -4
- package/lib/Cloudflare/SecretsStore/Secret.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +1 -1
- package/lib/Cloudflare/Tunnel/Tunnel.d.ts +27 -0
- package/lib/Cloudflare/Tunnel/Tunnel.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/Tunnel.js +27 -0
- package/lib/Cloudflare/Tunnel/Tunnel.js.map +1 -1
- package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts +48 -0
- package/lib/Cloudflare/Tunnel/TunnelBinding.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelBinding.js +56 -0
- package/lib/Cloudflare/Tunnel/TunnelBinding.js.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelRead.d.ts +83 -0
- package/lib/Cloudflare/Tunnel/TunnelRead.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelRead.js +79 -0
- package/lib/Cloudflare/Tunnel/TunnelRead.js.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelReadWrite.d.ts +70 -0
- package/lib/Cloudflare/Tunnel/TunnelReadWrite.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelReadWrite.js +63 -0
- package/lib/Cloudflare/Tunnel/TunnelReadWrite.js.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelWrite.d.ts +92 -0
- package/lib/Cloudflare/Tunnel/TunnelWrite.d.ts.map +1 -0
- package/lib/Cloudflare/Tunnel/TunnelWrite.js +88 -0
- package/lib/Cloudflare/Tunnel/TunnelWrite.js.map +1 -0
- package/lib/Cloudflare/Tunnel/index.d.ts +4 -0
- package/lib/Cloudflare/Tunnel/index.d.ts.map +1 -1
- package/lib/Cloudflare/Tunnel/index.js +4 -0
- package/lib/Cloudflare/Tunnel/index.js.map +1 -1
- package/lib/Cloudflare/Vectorize/VectorizeIndex.d.ts +1 -1
- package/lib/Cloudflare/Vectorize/VectorizeIndex.js +1 -0
- package/lib/Cloudflare/Vectorize/VectorizeIndex.js.map +1 -1
- package/lib/Cloudflare/VpcService/VpcService.d.ts +1 -1
- package/lib/Cloudflare/VpcService/VpcService.d.ts.map +1 -1
- package/lib/Cloudflare/VpcService/VpcService.js.map +1 -1
- package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts +119 -11
- package/lib/Cloudflare/Workers/DynamicWorkerLoader.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DynamicWorkerLoader.js +88 -9
- package/lib/Cloudflare/Workers/DynamicWorkerLoader.js.map +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts +1 -1
- package/lib/Cloudflare/Workers/InferEnv.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +2 -0
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +18 -2
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +7 -5
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.js +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.js.map +1 -1
- package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts +1 -1
- package/lib/Cloudflare/Zaraz/ZarazConfig.d.ts.map +1 -1
- package/lib/Cloudflare/Zaraz/ZarazConfig.js +1 -1
- package/lib/Cloudflare/Zaraz/ZarazConfig.js.map +1 -1
- package/lib/Cloudflare/Zone/Zone.d.ts +170 -0
- package/lib/Cloudflare/Zone/Zone.d.ts.map +1 -0
- package/lib/Cloudflare/Zone/Zone.js +180 -0
- package/lib/Cloudflare/Zone/Zone.js.map +1 -0
- package/lib/Cloudflare/Zone/index.d.ts +3 -0
- package/lib/Cloudflare/Zone/index.d.ts.map +1 -0
- package/lib/Cloudflare/Zone/index.js +3 -0
- package/lib/Cloudflare/Zone/index.js.map +1 -0
- package/lib/Cloudflare/Zone/lookup.d.ts +33 -0
- package/lib/Cloudflare/Zone/lookup.d.ts.map +1 -0
- package/lib/Cloudflare/{Zone.js → Zone/lookup.js} +2 -2
- package/lib/Cloudflare/Zone/lookup.js.map +1 -0
- package/lib/Cloudflare/index.d.ts +4 -1
- package/lib/Cloudflare/index.d.ts.map +1 -1
- package/lib/Cloudflare/index.js +4 -1
- package/lib/Cloudflare/index.js.map +1 -1
- package/lib/Output.d.ts +13 -1
- package/lib/Output.d.ts.map +1 -1
- package/lib/Output.js +43 -11
- package/lib/Output.js.map +1 -1
- package/lib/Plan.d.ts.map +1 -1
- package/lib/Plan.js +8 -0
- package/lib/Plan.js.map +1 -1
- package/lib/Platform.d.ts +5 -5
- package/lib/Platform.d.ts.map +1 -1
- package/lib/Platform.js +5 -2
- package/lib/Platform.js.map +1 -1
- package/lib/RemovalPolicy.js +2 -2
- package/lib/RemovalPolicy.js.map +1 -1
- package/lib/Resource.d.ts +15 -2
- package/lib/Resource.d.ts.map +1 -1
- package/lib/Resource.js +3 -2
- package/lib/Resource.js.map +1 -1
- package/lib/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/AdoptPolicy.ts +1 -1
- package/src/Binding.ts +5 -1
- package/src/Cli/commands/cloudflare.ts +394 -1
- package/src/Cloudflare/AiGateway/AiGateway.ts +2 -1
- package/src/Cloudflare/ApiToken/AccountApiToken.ts +60 -7
- package/src/Cloudflare/ApiToken/Common.ts +45 -6
- package/src/Cloudflare/ApiToken/UserApiToken.ts +58 -7
- package/src/Cloudflare/Browser/Browser.ts +140 -0
- package/src/Cloudflare/Browser/BrowserBinding.ts +128 -0
- package/src/Cloudflare/Browser/index.ts +11 -0
- package/src/Cloudflare/D1/D1Database.ts +6 -2
- package/src/Cloudflare/Dns/DnsBinding.ts +123 -0
- package/src/Cloudflare/Dns/DnsRead.ts +129 -0
- package/src/Cloudflare/Dns/DnsReadWrite.ts +99 -0
- package/src/Cloudflare/Dns/DnsWrite.ts +209 -0
- package/src/Cloudflare/Dns/index.ts +4 -0
- package/src/Cloudflare/Email/EmailRouting.ts +1 -1
- package/src/Cloudflare/Email/EmailRule.ts +6 -6
- package/src/Cloudflare/Images/Images.ts +40 -27
- package/src/Cloudflare/Providers.ts +22 -3
- package/src/Cloudflare/R2/R2Bucket.ts +17 -9
- package/src/Cloudflare/RateLimit/RateLimit.ts +160 -0
- package/src/Cloudflare/RateLimit/RateLimitBinding.ts +90 -0
- package/src/Cloudflare/RateLimit/index.ts +2 -0
- package/src/Cloudflare/SecretsStore/Secret.ts +12 -6
- package/src/Cloudflare/Tunnel/Tunnel.ts +27 -0
- package/src/Cloudflare/Tunnel/TunnelBinding.ts +114 -0
- package/src/Cloudflare/Tunnel/TunnelRead.ts +168 -0
- package/src/Cloudflare/Tunnel/TunnelReadWrite.ts +88 -0
- package/src/Cloudflare/Tunnel/TunnelWrite.ts +194 -0
- package/src/Cloudflare/Tunnel/index.ts +4 -0
- package/src/Cloudflare/Vectorize/VectorizeIndex.ts +2 -1
- package/src/Cloudflare/VpcService/VpcService.ts +4 -2
- package/src/Cloudflare/Workers/DynamicWorkerLoader.ts +139 -13
- package/src/Cloudflare/Workers/InferEnv.ts +19 -15
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +13 -2
- package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +16 -2
- package/src/Cloudflare/Workers/WorkerBinding.ts +6 -2
- package/src/Cloudflare/Zaraz/ZarazConfig.ts +1 -1
- package/src/Cloudflare/Zone/Zone.ts +349 -0
- package/src/Cloudflare/Zone/index.ts +2 -0
- package/src/Cloudflare/{Zone.ts → Zone/lookup.ts} +16 -3
- package/src/Cloudflare/index.ts +4 -1
- package/src/Output.ts +67 -11
- package/src/Plan.ts +9 -0
- package/src/Platform.ts +19 -15
- package/src/RemovalPolicy.ts +2 -2
- package/src/Resource.ts +19 -2
- package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts +0 -116
- package/lib/Cloudflare/BrowserRendering/BrowserRendering.d.ts.map +0 -1
- package/lib/Cloudflare/BrowserRendering/BrowserRendering.js.map +0 -1
- package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts +0 -55
- package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.d.ts.map +0 -1
- package/lib/Cloudflare/BrowserRendering/BrowserRenderingBinding.js.map +0 -1
- package/lib/Cloudflare/BrowserRendering/index.d.ts +0 -3
- package/lib/Cloudflare/BrowserRendering/index.d.ts.map +0 -1
- package/lib/Cloudflare/BrowserRendering/index.js +0 -3
- package/lib/Cloudflare/BrowserRendering/index.js.map +0 -1
- package/lib/Cloudflare/Zone.d.ts +0 -14
- package/lib/Cloudflare/Zone.d.ts.map +0 -1
- package/lib/Cloudflare/Zone.js.map +0 -1
- package/src/Cloudflare/BrowserRendering/BrowserRendering.ts +0 -132
- package/src/Cloudflare/BrowserRendering/BrowserRenderingBinding.ts +0 -137
- package/src/Cloudflare/BrowserRendering/index.ts +0 -11
|
@@ -27,10 +27,20 @@ export type ApiTokenPermissionGroupRef =
|
|
|
27
27
|
| PermissionGroupName
|
|
28
28
|
| { id: string; meta?: { key?: string; value?: string } };
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Value of a resource entry in an {@link ApiTokenPolicy}. Usually `"*"`, but
|
|
32
|
+
* account-owned tokens must nest zone resources under the account resource —
|
|
33
|
+
* e.g. `{ "com.cloudflare.api.account.<id>": { "com.cloudflare.api.account.zone.*": "*" } }`
|
|
34
|
+
* — so a nested object is also allowed.
|
|
35
|
+
*/
|
|
36
|
+
export type ApiTokenResourceScope =
|
|
37
|
+
| string
|
|
38
|
+
| { [K in ApiTokenResourceKey]?: string };
|
|
39
|
+
|
|
30
40
|
export interface ApiTokenPolicy {
|
|
31
41
|
effect: "allow" | "deny";
|
|
32
42
|
permissionGroups: ApiTokenPermissionGroupRef[];
|
|
33
|
-
resources: { [K in ApiTokenResourceKey]?:
|
|
43
|
+
resources: { [K in ApiTokenResourceKey]?: ApiTokenResourceScope };
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
export interface ApiTokenCondition {
|
|
@@ -52,10 +62,11 @@ export type ApiTokenProps = {
|
|
|
52
62
|
*/
|
|
53
63
|
accountId?: string;
|
|
54
64
|
/**
|
|
55
|
-
* Access policies attached to the token.
|
|
56
|
-
*
|
|
65
|
+
* Access policies attached to the token. Cloudflare requires at least one
|
|
66
|
+
* policy on a token; if you omit `policies` here, the policies must instead
|
|
67
|
+
* be contributed by bindings (see {@link ApiTokenBinding}).
|
|
57
68
|
*/
|
|
58
|
-
policies
|
|
69
|
+
policies?: ApiTokenPolicy[];
|
|
59
70
|
/** ISO 8601 expiration timestamp. */
|
|
60
71
|
expiresOn?: string;
|
|
61
72
|
/** ISO 8601 "not before" timestamp. */
|
|
@@ -64,6 +75,34 @@ export type ApiTokenProps = {
|
|
|
64
75
|
condition?: ApiTokenCondition;
|
|
65
76
|
};
|
|
66
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Binding contract for {@link AccountApiToken} / {@link UserApiToken}.
|
|
80
|
+
*
|
|
81
|
+
* A binding contributes additional access policies to the token. This lets a
|
|
82
|
+
* downstream resource (e.g. a runtime capability that needs to call a specific
|
|
83
|
+
* Cloudflare API) create a token and attach exactly the policies it requires,
|
|
84
|
+
* without the token's owner having to enumerate them up front.
|
|
85
|
+
*
|
|
86
|
+
* Binding-contributed policies are merged with any `policies` passed directly
|
|
87
|
+
* as props; the union must contain at least one policy.
|
|
88
|
+
*/
|
|
89
|
+
export type ApiTokenBinding = {
|
|
90
|
+
/** Access policies to attach to the token. */
|
|
91
|
+
policies?: ApiTokenPolicy[];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Collect the policies a token should be created with: those passed directly
|
|
96
|
+
* as props, plus those contributed by bindings.
|
|
97
|
+
*/
|
|
98
|
+
export const collectPolicies = (
|
|
99
|
+
props: ApiTokenPolicy[] | undefined,
|
|
100
|
+
bindings: { data: ApiTokenBinding }[],
|
|
101
|
+
): ApiTokenPolicy[] => [
|
|
102
|
+
...(props ?? []),
|
|
103
|
+
...bindings.flatMap((binding) => binding.data.policies ?? []),
|
|
104
|
+
];
|
|
105
|
+
|
|
67
106
|
export type ResolvedPolicy = {
|
|
68
107
|
effect: "allow" | "deny";
|
|
69
108
|
permissionGroups: { id: string; meta?: { key?: string; value?: string } }[];
|
|
@@ -87,8 +126,8 @@ export const resolvePermissionGroup = (ref: ApiTokenPermissionGroupRef) => {
|
|
|
87
126
|
|
|
88
127
|
const resolveResources = (
|
|
89
128
|
resources: ApiTokenPolicy["resources"],
|
|
90
|
-
): Record<string,
|
|
91
|
-
const out: Record<string,
|
|
129
|
+
): Record<string, ApiTokenResourceScope> => {
|
|
130
|
+
const out: Record<string, ApiTokenResourceScope> = {};
|
|
92
131
|
for (const [key, value] of Object.entries(resources)) {
|
|
93
132
|
if (value === undefined) continue;
|
|
94
133
|
out[key] = value;
|
|
@@ -8,9 +8,11 @@ import { Resource } from "../../Resource.ts";
|
|
|
8
8
|
import type { Providers } from "../Providers.ts";
|
|
9
9
|
import {
|
|
10
10
|
buildConditionPayload,
|
|
11
|
+
collectPolicies,
|
|
11
12
|
conditionFingerprint,
|
|
12
13
|
policyFingerprint,
|
|
13
14
|
resolvePolicies,
|
|
15
|
+
type ApiTokenBinding,
|
|
14
16
|
type ApiTokenProps,
|
|
15
17
|
} from "./Common.ts";
|
|
16
18
|
|
|
@@ -27,7 +29,7 @@ export type UserApiToken = Resource<
|
|
|
27
29
|
*/
|
|
28
30
|
value: Redacted.Redacted<string>;
|
|
29
31
|
},
|
|
30
|
-
|
|
32
|
+
ApiTokenBinding,
|
|
31
33
|
Providers
|
|
32
34
|
>;
|
|
33
35
|
|
|
@@ -60,6 +62,42 @@ export type UserApiToken = Resource<
|
|
|
60
62
|
* ],
|
|
61
63
|
* });
|
|
62
64
|
* ```
|
|
65
|
+
*
|
|
66
|
+
* @section Attaching Policies via Bindings
|
|
67
|
+
* @example Let a downstream capability contribute its own policies
|
|
68
|
+
* A token can be created with no `policies` of its own; the policies are
|
|
69
|
+
* supplied through its binding contract (see {@link ApiTokenBinding}).
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const token = yield* Cloudflare.UserApiToken("scoped-token");
|
|
72
|
+
*
|
|
73
|
+
* yield* token.bind("MyCapability", {
|
|
74
|
+
* policies: [
|
|
75
|
+
* {
|
|
76
|
+
* effect: "allow",
|
|
77
|
+
* permissionGroups: ["Workers Scripts Read"],
|
|
78
|
+
* resources: { [`com.cloudflare.api.account.${accountId}`]: "*" },
|
|
79
|
+
* },
|
|
80
|
+
* ],
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @section Exposing a Token to a Worker
|
|
85
|
+
* @example Read the token value at runtime
|
|
86
|
+
* Bind the token's value output in the Worker's Init phase to get a runtime
|
|
87
|
+
* accessor. Binding it injects a `secret_text` Worker binding; the returned
|
|
88
|
+
* accessor reads it back (as `Redacted`) at runtime.
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // init
|
|
91
|
+
* const value = yield* token.value; // Accessor<Redacted<string>>
|
|
92
|
+
*
|
|
93
|
+
* return {
|
|
94
|
+
* fetch: Effect.gen(function* () {
|
|
95
|
+
* const apiToken = yield* value; // Redacted<string>
|
|
96
|
+
* // ... call the Cloudflare API with `apiToken`
|
|
97
|
+
* return HttpServerResponse.text("ok");
|
|
98
|
+
* }),
|
|
99
|
+
* };
|
|
100
|
+
* ```
|
|
63
101
|
*/
|
|
64
102
|
export const UserApiToken = Resource<UserApiToken>("Cloudflare.UserApiToken");
|
|
65
103
|
|
|
@@ -83,26 +121,32 @@ export const UserApiTokenProvider = () =>
|
|
|
83
121
|
tokenData: {
|
|
84
122
|
id?: string | null;
|
|
85
123
|
name?: string | null;
|
|
86
|
-
|
|
124
|
+
// Distilled widened generated string enums to open unions (`string & {}`).
|
|
125
|
+
status?: string | null;
|
|
87
126
|
},
|
|
88
127
|
value: Redacted.Redacted<string>,
|
|
89
128
|
): UserApiTokenAttributes => ({
|
|
90
129
|
tokenId: tokenData.id ?? "",
|
|
91
130
|
name: tokenData.name ?? "",
|
|
92
|
-
status: tokenData.status ?? "active"
|
|
131
|
+
status: (tokenData.status ?? "active") as
|
|
132
|
+
| "active"
|
|
133
|
+
| "disabled"
|
|
134
|
+
| "expired",
|
|
93
135
|
value,
|
|
94
136
|
});
|
|
95
137
|
|
|
96
138
|
return {
|
|
97
139
|
stables: ["tokenId"],
|
|
98
|
-
diff: Effect.fn(function* ({ id, olds, news, output }) {
|
|
140
|
+
diff: Effect.fn(function* ({ id, olds, news = {}, output }) {
|
|
99
141
|
if (!isResolved(news)) return undefined;
|
|
100
142
|
const oldName = output?.name ?? (yield* resolveName(id, olds?.name));
|
|
101
143
|
const newName = yield* resolveName(id, news.name);
|
|
102
144
|
const oldPolicyFp = policyFingerprint(
|
|
103
145
|
resolvePolicies(olds?.policies ?? []),
|
|
104
146
|
);
|
|
105
|
-
const newPolicyFp = policyFingerprint(
|
|
147
|
+
const newPolicyFp = policyFingerprint(
|
|
148
|
+
resolvePolicies(news.policies ?? []),
|
|
149
|
+
);
|
|
106
150
|
const oldCondFp = conditionFingerprint(olds?.condition);
|
|
107
151
|
const newCondFp = conditionFingerprint(news.condition);
|
|
108
152
|
if (
|
|
@@ -115,9 +159,16 @@ export const UserApiTokenProvider = () =>
|
|
|
115
159
|
return { action: "update" } as const;
|
|
116
160
|
}
|
|
117
161
|
}),
|
|
118
|
-
reconcile: Effect.fn(function* ({ id, news, output }) {
|
|
162
|
+
reconcile: Effect.fn(function* ({ id, news = {}, output, bindings }) {
|
|
119
163
|
const name = yield* resolveName(id, news.name);
|
|
120
|
-
const
|
|
164
|
+
const collected = collectPolicies(news.policies, bindings);
|
|
165
|
+
if (collected.length === 0) {
|
|
166
|
+
return yield* Effect.die(
|
|
167
|
+
`Cloudflare requires at least one policy on token "${name}". ` +
|
|
168
|
+
"Pass `policies` or attach them via a binding.",
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const policies = resolvePolicies(collected);
|
|
121
172
|
|
|
122
173
|
// Observe — fetch current state if we know the token id;
|
|
123
174
|
// Cloudflare reports a deleted token as `TokenNotFound`, which
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type * as Effect from "effect/Effect";
|
|
2
|
+
import { SingleShotGen } from "effect/Utils";
|
|
3
|
+
import { BrowserBinding, type BrowserClient } from "./BrowserBinding.ts";
|
|
4
|
+
|
|
5
|
+
type BrowserTypeId = typeof BrowserTypeId;
|
|
6
|
+
const BrowserTypeId = "Cloudflare.Browser" as const;
|
|
7
|
+
|
|
8
|
+
export type BrowserProps = {
|
|
9
|
+
/**
|
|
10
|
+
* Binding name used when `Browser` is bound from inside a Worker init phase
|
|
11
|
+
* (`yield* Cloudflare.Browser(...)`). When passed through
|
|
12
|
+
* `Worker({ env: { ... } })`, the object key remains the binding name.
|
|
13
|
+
*
|
|
14
|
+
* @default "BROWSER"
|
|
15
|
+
*/
|
|
16
|
+
name?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The Effect yielded when a `Browser` marker is used inside a Worker init
|
|
21
|
+
* phase: it attaches the `browser` binding to the surrounding Worker and
|
|
22
|
+
* resolves to the runtime {@link BrowserClient}.
|
|
23
|
+
*/
|
|
24
|
+
type BindEffect = Effect.Effect<BrowserClient, never, BrowserBinding>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Marker for a Cloudflare Browser Rendering binding.
|
|
28
|
+
*
|
|
29
|
+
* It is a plain data structure (so it can be declared directly on a Worker's
|
|
30
|
+
* `env`) that is **also** yieldable inside an Effect-native Worker. Yielding it
|
|
31
|
+
* (`yield* Cloudflare.Browser(...)`) attaches the binding to the surrounding
|
|
32
|
+
* Worker and returns the runtime {@link BrowserClient} — no separate
|
|
33
|
+
* `.bind(...)` step required.
|
|
34
|
+
*
|
|
35
|
+
* The divergence is achieved via `[Symbol.iterator]`: the object is not an
|
|
36
|
+
* `Effect` (so `InferEnv` resolves it to the native `Fetcher` in the `env`
|
|
37
|
+
* position), but it is iterable as one when `yield*`-ed.
|
|
38
|
+
*/
|
|
39
|
+
export interface Browser {
|
|
40
|
+
kind: BrowserTypeId;
|
|
41
|
+
name: string;
|
|
42
|
+
asEffect(): BindEffect;
|
|
43
|
+
[Symbol.iterator](): SingleShotGen<BindEffect, BrowserClient>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const isBrowser = (value: unknown): value is Browser =>
|
|
47
|
+
typeof value === "object" && (value as Browser)?.kind === BrowserTypeId;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A Cloudflare Browser Rendering binding for launching headless browser
|
|
51
|
+
* sessions from Workers via `@cloudflare/puppeteer`.
|
|
52
|
+
*
|
|
53
|
+
* @binding
|
|
54
|
+
*
|
|
55
|
+
* @section Effect-style Worker (recommended)
|
|
56
|
+
* @example Render a page title with managed browser cleanup
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import puppeteer from "@cloudflare/puppeteer";
|
|
59
|
+
* import * as Effect from "effect/Effect";
|
|
60
|
+
*
|
|
61
|
+
* Cloudflare.Worker(
|
|
62
|
+
* "BrowserWorker",
|
|
63
|
+
* { main: import.meta.filename },
|
|
64
|
+
* Effect.gen(function* () {
|
|
65
|
+
* // Attaches the binding to this Worker AND returns the runtime client.
|
|
66
|
+
* const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
|
|
67
|
+
*
|
|
68
|
+
* return {
|
|
69
|
+
* fetch: browser.withBrowser(puppeteer, (browser) =>
|
|
70
|
+
* Effect.gen(function* () {
|
|
71
|
+
* const page = yield* Effect.tryPromise(() => browser.newPage());
|
|
72
|
+
* yield* Effect.tryPromise(() => page.goto("https://example.com"));
|
|
73
|
+
* const title = yield* Effect.tryPromise(() => page.title());
|
|
74
|
+
* return Response.json({ title });
|
|
75
|
+
* }),
|
|
76
|
+
* ),
|
|
77
|
+
* };
|
|
78
|
+
* }).pipe(Effect.provide(Cloudflare.BrowserBindingLive)),
|
|
79
|
+
* );
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @section Worker binding metadata
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* export const Worker = Cloudflare.Worker("Worker", {
|
|
86
|
+
* main: "./src/worker.ts",
|
|
87
|
+
* env: {
|
|
88
|
+
* BROWSER: Cloudflare.Browser(),
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
|
|
93
|
+
* // { BROWSER: Fetcher }
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @example Async-style worker with the raw runtime binding
|
|
97
|
+
* ```typescript
|
|
98
|
+
* import puppeteer from "@cloudflare/puppeteer";
|
|
99
|
+
* import type { WorkerEnv } from "../alchemy.run.ts";
|
|
100
|
+
*
|
|
101
|
+
* export default {
|
|
102
|
+
* async fetch(request: Request, env: WorkerEnv) {
|
|
103
|
+
* const browser = await puppeteer.launch(env.BROWSER);
|
|
104
|
+
* const page = await browser.newPage();
|
|
105
|
+
* await page.goto("https://example.com");
|
|
106
|
+
* const screenshot = await page.screenshot();
|
|
107
|
+
* await browser.close();
|
|
108
|
+
*
|
|
109
|
+
* return new Response(screenshot, {
|
|
110
|
+
* headers: { "content-type": "image/png" },
|
|
111
|
+
* });
|
|
112
|
+
* },
|
|
113
|
+
* };
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @see https://developers.cloudflare.com/browser-rendering/workers-binding-api/
|
|
117
|
+
*/
|
|
118
|
+
export const Browser: {
|
|
119
|
+
(props?: BrowserProps): Browser;
|
|
120
|
+
/**
|
|
121
|
+
* Bind an existing `Browser` marker to the surrounding Worker, returning the
|
|
122
|
+
* runtime client. Equivalent to `yield* browser` — prefer yielding the marker
|
|
123
|
+
* directly.
|
|
124
|
+
*/
|
|
125
|
+
bind: typeof BrowserBinding.bind;
|
|
126
|
+
} = Object.assign(
|
|
127
|
+
(props?: BrowserProps): Browser => {
|
|
128
|
+
const self: Browser = {
|
|
129
|
+
kind: BrowserTypeId,
|
|
130
|
+
name: props?.name ?? "BROWSER",
|
|
131
|
+
asEffect: () => BrowserBinding.bind(self),
|
|
132
|
+
[Symbol.iterator]: () => new SingleShotGen(BrowserBinding.bind(self)),
|
|
133
|
+
};
|
|
134
|
+
return self;
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
bind: (...args: Parameters<typeof BrowserBinding.bind>) =>
|
|
138
|
+
BrowserBinding.bind(...args),
|
|
139
|
+
},
|
|
140
|
+
);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type * as cf from "@cloudflare/workers-types";
|
|
2
|
+
import * as Data from "effect/Data";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as Binding from "../../Binding.ts";
|
|
6
|
+
import type { ResourceLike } from "../../Resource.ts";
|
|
7
|
+
import { isWorker, WorkerEnvironment } from "../Workers/Worker.ts";
|
|
8
|
+
import type { Browser as BrowserLike } from "./Browser.ts";
|
|
9
|
+
|
|
10
|
+
export class BrowserError extends Data.TaggedError("BrowserError")<{
|
|
11
|
+
message: string;
|
|
12
|
+
cause: unknown;
|
|
13
|
+
}> {}
|
|
14
|
+
|
|
15
|
+
export interface BrowserHandle {
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BrowserPuppeteer<Browser extends BrowserHandle> {
|
|
20
|
+
launch(binding: cf.Fetcher): Promise<Browser>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Effect-native client for a Cloudflare Browser Rendering binding.
|
|
25
|
+
*
|
|
26
|
+
* Browser Rendering's Workers binding is consumed by `@cloudflare/puppeteer`.
|
|
27
|
+
* Alchemy keeps Puppeteer as a caller-provided dependency while wrapping
|
|
28
|
+
* launch and cleanup in Effects.
|
|
29
|
+
*/
|
|
30
|
+
export interface BrowserClient {
|
|
31
|
+
/**
|
|
32
|
+
* Effect resolving to the raw Cloudflare Browser Rendering runtime binding.
|
|
33
|
+
*/
|
|
34
|
+
raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>;
|
|
35
|
+
/**
|
|
36
|
+
* Launch a Browser Rendering session through `@cloudflare/puppeteer`.
|
|
37
|
+
*/
|
|
38
|
+
launch<Browser extends BrowserHandle>(
|
|
39
|
+
puppeteer: BrowserPuppeteer<Browser>,
|
|
40
|
+
): Effect.Effect<Browser, BrowserError, WorkerEnvironment>;
|
|
41
|
+
/**
|
|
42
|
+
* Launch a browser, run an Effect with it, and close it afterward.
|
|
43
|
+
*/
|
|
44
|
+
withBrowser<Browser extends BrowserHandle, A, E, R>(
|
|
45
|
+
puppeteer: BrowserPuppeteer<Browser>,
|
|
46
|
+
use: (browser: Browser) => Effect.Effect<A, E, R>,
|
|
47
|
+
): Effect.Effect<A, BrowserError | E, WorkerEnvironment | R>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class BrowserBinding extends Binding.Service<
|
|
51
|
+
BrowserBinding,
|
|
52
|
+
(browser: BrowserLike) => Effect.Effect<BrowserClient>
|
|
53
|
+
>()("Cloudflare.Browser.Binding") {}
|
|
54
|
+
|
|
55
|
+
export const BrowserBindingLive = Layer.effect(
|
|
56
|
+
BrowserBinding,
|
|
57
|
+
Effect.gen(function* () {
|
|
58
|
+
const Policy = yield* BrowserBindingPolicy;
|
|
59
|
+
|
|
60
|
+
return Effect.fn(function* (browser: BrowserLike) {
|
|
61
|
+
yield* Policy(browser);
|
|
62
|
+
// Cloudflare exposes Browser Rendering as a service-style binding for
|
|
63
|
+
// @cloudflare/puppeteer; workers-types has no narrower interface.
|
|
64
|
+
const raw = WorkerEnvironment.useSync(
|
|
65
|
+
(env) => (env as Record<string, cf.Fetcher>)[browser.name]!,
|
|
66
|
+
);
|
|
67
|
+
return makeBrowserClient(raw);
|
|
68
|
+
});
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export class BrowserBindingPolicy extends Binding.Policy<
|
|
73
|
+
BrowserBindingPolicy,
|
|
74
|
+
(browser: BrowserLike) => Effect.Effect<void>
|
|
75
|
+
>()("Cloudflare.Browser.Binding") {}
|
|
76
|
+
|
|
77
|
+
export const BrowserBindingPolicyLive = BrowserBindingPolicy.layer.succeed(
|
|
78
|
+
Effect.fn(function* (host: ResourceLike, browser: BrowserLike) {
|
|
79
|
+
if (isWorker(host)) {
|
|
80
|
+
yield* host.bind(browser.name, {
|
|
81
|
+
bindings: [
|
|
82
|
+
{
|
|
83
|
+
type: "browser",
|
|
84
|
+
name: browser.name,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
return yield* Effect.die(
|
|
90
|
+
new Error(`BrowserBinding does not support runtime '${host.Type}'`),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const tryPromise = <T>(fn: () => Promise<T>): Effect.Effect<T, BrowserError> =>
|
|
97
|
+
Effect.tryPromise({
|
|
98
|
+
try: fn,
|
|
99
|
+
catch: (error) =>
|
|
100
|
+
new BrowserError({
|
|
101
|
+
message:
|
|
102
|
+
error instanceof Error
|
|
103
|
+
? error.message
|
|
104
|
+
: "Unknown Browser Rendering error",
|
|
105
|
+
cause: error,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** @internal */
|
|
110
|
+
export const makeBrowserClient = (
|
|
111
|
+
raw: Effect.Effect<cf.Fetcher, never, WorkerEnvironment>,
|
|
112
|
+
): BrowserClient => {
|
|
113
|
+
const launch = <Browser extends BrowserHandle>(
|
|
114
|
+
puppeteer: BrowserPuppeteer<Browser>,
|
|
115
|
+
) =>
|
|
116
|
+
raw.pipe(
|
|
117
|
+
Effect.flatMap((binding) => tryPromise(() => puppeteer.launch(binding))),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
raw,
|
|
122
|
+
launch,
|
|
123
|
+
withBrowser: (puppeteer, use) =>
|
|
124
|
+
Effect.acquireUseRelease(launch(puppeteer), use, (browser) =>
|
|
125
|
+
tryPromise(() => browser.close()),
|
|
126
|
+
),
|
|
127
|
+
} satisfies BrowserClient;
|
|
128
|
+
};
|
|
@@ -332,7 +332,10 @@ export const DatabaseProvider = () =>
|
|
|
332
332
|
databaseId: db.uuid ?? output.databaseId,
|
|
333
333
|
databaseName: db.name ?? output.databaseName,
|
|
334
334
|
jurisdiction: output.jurisdiction,
|
|
335
|
-
|
|
335
|
+
// Distilled widened generated string enums to open unions.
|
|
336
|
+
readReplication: (db.readReplication ?? undefined) as
|
|
337
|
+
| { mode: "auto" | "disabled" }
|
|
338
|
+
| undefined,
|
|
336
339
|
accountId: output.accountId,
|
|
337
340
|
migrationsDir: output.migrationsDir,
|
|
338
341
|
migrationsTable: output.migrationsTable,
|
|
@@ -375,7 +378,8 @@ export const DatabaseProvider = () =>
|
|
|
375
378
|
| {
|
|
376
379
|
uuid?: string | null;
|
|
377
380
|
name?: string | null;
|
|
378
|
-
|
|
381
|
+
// Distilled widened generated string enums to open unions.
|
|
382
|
+
readReplication?: { mode: string } | null;
|
|
379
383
|
}
|
|
380
384
|
| undefined;
|
|
381
385
|
if (output?.databaseId) {
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Credentials,
|
|
3
|
+
fromApiToken,
|
|
4
|
+
} from "@distilled.cloud/cloudflare/Credentials";
|
|
5
|
+
import * as Effect from "effect/Effect";
|
|
6
|
+
import * as Layer from "effect/Layer";
|
|
7
|
+
import * as Redacted from "effect/Redacted";
|
|
8
|
+
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
|
|
9
|
+
import { type HttpClient } from "effect/unstable/http/HttpClient";
|
|
10
|
+
import type * as Binding from "../../Binding.ts";
|
|
11
|
+
import * as Output from "../../Output.ts";
|
|
12
|
+
import { RuntimeContext } from "../../RuntimeContext.ts";
|
|
13
|
+
import { AccountApiToken } from "../ApiToken/AccountApiToken.ts";
|
|
14
|
+
import type { ApiTokenPermissionGroupRef } from "../ApiToken/Common.ts";
|
|
15
|
+
import type { Zone } from "../Zone/Zone.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Runtime accessor for a DNS binding's token, obtained by binding the
|
|
19
|
+
* {@link AccountApiToken}'s `value` output in the Worker's Init phase. Reads the
|
|
20
|
+
* value back from the Worker's environment at runtime. DNS record operations
|
|
21
|
+
* are zone-scoped (the `zoneId` is passed per call), so the account id is not
|
|
22
|
+
* needed at runtime.
|
|
23
|
+
*/
|
|
24
|
+
export interface DnsToken {
|
|
25
|
+
/** The token's plaintext value (injected as a `secret_text` binding). */
|
|
26
|
+
value: Effect.Effect<Redacted.Redacted<string>>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Bind an {@link AccountApiToken}'s `value` output into the Worker (as a
|
|
31
|
+
* `secret_text` binding) and return the {@link DnsToken} accessor.
|
|
32
|
+
*/
|
|
33
|
+
export const bindDnsToken = (token: AccountApiToken) =>
|
|
34
|
+
Effect.gen(function* () {
|
|
35
|
+
const value = yield* token.value;
|
|
36
|
+
return { value } satisfies DnsToken;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolve credentials from a bound token and provide them (plus the
|
|
41
|
+
* fetch-based HTTP client) to a raw SDK operation.
|
|
42
|
+
*/
|
|
43
|
+
export const authorizeDns =
|
|
44
|
+
(token: DnsToken) =>
|
|
45
|
+
<A, E>(
|
|
46
|
+
eff: Effect.Effect<A, E, Credentials | HttpClient>,
|
|
47
|
+
): Effect.Effect<A, E, RuntimeContext> =>
|
|
48
|
+
token.value.pipe(
|
|
49
|
+
Effect.flatMap((value) =>
|
|
50
|
+
eff.pipe(
|
|
51
|
+
Effect.provide(
|
|
52
|
+
fromApiToken({ apiToken: Redacted.value(value) }).pipe(
|
|
53
|
+
Layer.provideMerge(FetchHttpClient.layer),
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
),
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Shared runtime body for a DNS binding: create a token scoped to the requested
|
|
62
|
+
* zone, attach the (narrow) policy, bind the token's value and the zone's id
|
|
63
|
+
* into the Worker, then build the client. Pass the result to
|
|
64
|
+
* `Layer.effect(<Binding>, ...)`.
|
|
65
|
+
*
|
|
66
|
+
* The zone's `zoneId` is bound into the Worker at init time, so the resulting
|
|
67
|
+
* client closes over it and callers never pass it per request — the
|
|
68
|
+
* provisioned token only grants access to that one zone anyway.
|
|
69
|
+
*/
|
|
70
|
+
export const makeDnsClient = <C>(
|
|
71
|
+
Policy: Binding.Policy<
|
|
72
|
+
any,
|
|
73
|
+
any,
|
|
74
|
+
(token: AccountApiToken, zone: Zone) => Effect.Effect<void>
|
|
75
|
+
>,
|
|
76
|
+
tokenId: string,
|
|
77
|
+
makeClient: (token: DnsToken, zoneId: Effect.Effect<string>) => C,
|
|
78
|
+
) =>
|
|
79
|
+
Effect.gen(function* () {
|
|
80
|
+
const Token = yield* AccountApiToken;
|
|
81
|
+
const attach = yield* Policy;
|
|
82
|
+
|
|
83
|
+
return Effect.fn(function* (zone: Zone) {
|
|
84
|
+
const token = yield* Token(tokenId);
|
|
85
|
+
yield* attach(token, zone);
|
|
86
|
+
const zoneId = yield* zone.zoneId;
|
|
87
|
+
return makeClient(yield* bindDnsToken(token), zoneId);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build the deploy-time policy layer for a DNS binding: attach an allow policy
|
|
93
|
+
* with the given permission groups, scoped to the requested zone
|
|
94
|
+
* (`com.cloudflare.api.account.zone.<zoneId>`).
|
|
95
|
+
*/
|
|
96
|
+
export const makeDnsPolicyLive = <Self, Id extends string>(
|
|
97
|
+
Policy: Binding.Policy<
|
|
98
|
+
Self,
|
|
99
|
+
Id,
|
|
100
|
+
(token: AccountApiToken, zone: Zone) => Effect.Effect<void>
|
|
101
|
+
>,
|
|
102
|
+
sid: string,
|
|
103
|
+
permissionGroups: ApiTokenPermissionGroupRef[],
|
|
104
|
+
) =>
|
|
105
|
+
Policy.layer.succeed((_host, token, zone) =>
|
|
106
|
+
token.bind`${sid}(${zone})`({
|
|
107
|
+
policies: [
|
|
108
|
+
{
|
|
109
|
+
effect: "allow",
|
|
110
|
+
permissionGroups,
|
|
111
|
+
resources: zone.zoneId.pipe(
|
|
112
|
+
Output.flatMap(
|
|
113
|
+
(zoneId) =>
|
|
114
|
+
Output.interpolate`com.cloudflare.api.account.zone.${zoneId}`,
|
|
115
|
+
),
|
|
116
|
+
Output.map((zoneId) => ({
|
|
117
|
+
[zoneId]: "*",
|
|
118
|
+
})),
|
|
119
|
+
),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
123
|
+
);
|