@automate.ax/integration-contracts 0.104.0 → 0.107.0
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/dist/close/events.d.ts +13 -13
- package/dist/close/schemas.d.ts +9 -9
- package/dist/cloudflare/api.d.ts +93 -0
- package/dist/cloudflare/api.js +250 -0
- package/dist/cloudflare/events.d.ts +1340 -0
- package/dist/cloudflare/events.js +178 -0
- package/dist/cloudflare/index.d.ts +3 -0
- package/dist/cloudflare/index.js +3 -0
- package/dist/cloudflare/schemas.d.ts +608 -0
- package/dist/cloudflare/schemas.js +340 -0
- package/dist/linear/api.d.ts +1 -1
- package/dist/linear/api.js +9 -6
- package/dist/linear/index.d.ts +3479 -782
- package/dist/linear/index.js +249 -13
- package/dist/linear/schemas.d.ts +2721 -28
- package/dist/linear/schemas.js +515 -2
- package/dist/notion/index.js +2 -2
- package/dist/notion/schemas.d.ts +2140 -58
- package/dist/notion/schemas.js +41 -2
- package/dist/outlook/schemas.d.ts +2 -2
- package/dist/resend/action-schemas.d.ts +9 -9
- package/dist/resend/index.d.ts +36 -36
- package/dist/resend/schemas.d.ts +40 -40
- package/dist/slack/event-schemas.d.ts +583 -583
- package/dist/slack/index.d.ts +303 -303
- package/dist/slack/schemas.d.ts +1 -1
- package/dist/teams/schemas.d.ts +5 -5
- package/dist/triggers.d.ts +2 -1
- package/dist/whatsapp/index.d.ts +2 -2
- package/dist/whatsapp/schemas.d.ts +5 -5
- package/package.json +8 -2
- package/src/cloudflare/api.ts +340 -0
- package/src/cloudflare/events.ts +212 -0
- package/src/cloudflare/index.ts +3 -0
- package/src/cloudflare/schemas.ts +359 -0
- package/src/linear/api.ts +10 -6
- package/src/linear/index.ts +249 -26
- package/src/linear/schemas.ts +688 -2
- package/src/notion/index.ts +2 -2
- package/src/notion/schemas.ts +58 -4
- package/src/triggers.ts +2 -0
package/dist/slack/schemas.d.ts
CHANGED
|
@@ -110,8 +110,8 @@ export declare const SLACK_REACTION_ITEM_SCHEMA: z.ZodDiscriminatedUnion<[z.ZodO
|
|
|
110
110
|
conversationId: z.ZodString;
|
|
111
111
|
conversationType: z.ZodOptional<z.ZodEnum<{
|
|
112
112
|
group: "group";
|
|
113
|
-
channel: "channel";
|
|
114
113
|
im: "im";
|
|
114
|
+
channel: "channel";
|
|
115
115
|
mpim: "mpim";
|
|
116
116
|
}>>;
|
|
117
117
|
messageTimestamp: z.ZodString;
|
package/dist/teams/schemas.d.ts
CHANGED
|
@@ -56,11 +56,11 @@ export declare const TEAMS_CHANNEL_SCHEMA: z.ZodObject<{
|
|
|
56
56
|
channelId: z.ZodString;
|
|
57
57
|
isArchived: z.ZodPrefault<z.ZodBoolean>;
|
|
58
58
|
membershipType: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
59
|
+
standard: "standard";
|
|
59
60
|
private: "private";
|
|
60
61
|
shared: "shared";
|
|
61
62
|
unknownFutureValue: "unknownFutureValue";
|
|
62
|
-
|
|
63
|
-
}>>>, z.ZodTransform<"private" | "shared" | "unknownFutureValue" | "standard" | undefined, "private" | "shared" | "unknownFutureValue" | "standard" | null | undefined>>;
|
|
63
|
+
}>>>, z.ZodTransform<"standard" | "private" | "shared" | "unknownFutureValue" | undefined, "standard" | "private" | "shared" | "unknownFutureValue" | null | undefined>>;
|
|
64
64
|
webUrl: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | undefined, string | null | undefined>>;
|
|
65
65
|
}, z.core.$strip>;
|
|
66
66
|
export declare const GRAPH_CHANNEL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
@@ -71,10 +71,10 @@ export declare const GRAPH_CHANNEL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
71
71
|
id: z.ZodString;
|
|
72
72
|
isArchived: z.ZodOptional<z.ZodBoolean>;
|
|
73
73
|
membershipType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
74
|
+
standard: "standard";
|
|
74
75
|
private: "private";
|
|
75
76
|
shared: "shared";
|
|
76
77
|
unknownFutureValue: "unknownFutureValue";
|
|
77
|
-
standard: "standard";
|
|
78
78
|
}>>>;
|
|
79
79
|
webUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
80
80
|
}, z.core.$loose>, z.ZodTransform<{
|
|
@@ -84,7 +84,7 @@ export declare const GRAPH_CHANNEL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
84
84
|
email: string | undefined;
|
|
85
85
|
channelId: string;
|
|
86
86
|
isArchived: boolean;
|
|
87
|
-
membershipType: "
|
|
87
|
+
membershipType: "standard" | "private" | "shared" | "unknownFutureValue" | undefined;
|
|
88
88
|
webUrl: string | undefined;
|
|
89
89
|
}, {
|
|
90
90
|
[x: string]: unknown;
|
|
@@ -94,7 +94,7 @@ export declare const GRAPH_CHANNEL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
94
94
|
description?: string | null | undefined;
|
|
95
95
|
email?: string | null | undefined;
|
|
96
96
|
isArchived?: boolean | undefined;
|
|
97
|
-
membershipType?: "
|
|
97
|
+
membershipType?: "standard" | "private" | "shared" | "unknownFutureValue" | null | undefined;
|
|
98
98
|
webUrl?: string | null | undefined;
|
|
99
99
|
}>>;
|
|
100
100
|
export declare const TEAMS_MEMBER_SCHEMA: z.ZodObject<{
|
package/dist/triggers.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { asanaTriggerContracts } from "./asana/index.js";
|
|
|
4
4
|
import type { brevoTriggerContracts } from "./brevo/index.js";
|
|
5
5
|
import type { convexTriggerContracts } from "./convex/index.js";
|
|
6
6
|
import type { closeTriggerContracts } from "./close/index.js";
|
|
7
|
+
import type { cloudflareTriggerContracts } from "./cloudflare/index.js";
|
|
7
8
|
import type { githubTriggerContracts } from "./github/index.js";
|
|
8
9
|
import type { gmailTriggerContracts } from "./gmail/index.js";
|
|
9
10
|
import type { googleCalendarTriggerContracts } from "./google-calendar/index.js";
|
|
@@ -21,7 +22,7 @@ import type { vercelTriggerContracts } from "./vercel/index.js";
|
|
|
21
22
|
import type { webflowTriggerContracts } from "./webflow/index.js";
|
|
22
23
|
import type { whatsappTriggerContracts } from "./whatsapp/index.js";
|
|
23
24
|
import type { z } from "zod";
|
|
24
|
-
export type TriggerContractMap = typeof airtableTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof convexTriggerContracts & typeof closeTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof linearTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
|
|
25
|
+
export type TriggerContractMap = typeof airtableTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof convexTriggerContracts & typeof closeTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof linearTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
|
|
25
26
|
export type IntegrationTriggerType = keyof TriggerContractMap;
|
|
26
27
|
/** Canonical authoring configuration for one integration trigger type. */
|
|
27
28
|
export type TriggerConfig<TType extends IntegrationTriggerType> = z.input<TriggerContractMap[TType]["configSchema"]> extends Record<string, never> ? object : z.input<TriggerContractMap[TType]["configSchema"]>;
|
package/dist/whatsapp/index.d.ts
CHANGED
|
@@ -324,8 +324,8 @@ export declare const whatsappTriggerContracts: {
|
|
|
324
324
|
type: z.ZodEnum<{
|
|
325
325
|
unknown: "unknown";
|
|
326
326
|
text: "text";
|
|
327
|
-
document: "document";
|
|
328
327
|
audio: "audio";
|
|
328
|
+
document: "document";
|
|
329
329
|
image: "image";
|
|
330
330
|
video: "video";
|
|
331
331
|
location: "location";
|
|
@@ -419,8 +419,8 @@ export declare const whatsappTriggerContracts: {
|
|
|
419
419
|
type: z.ZodEnum<{
|
|
420
420
|
unknown: "unknown";
|
|
421
421
|
text: "text";
|
|
422
|
-
document: "document";
|
|
423
422
|
audio: "audio";
|
|
423
|
+
document: "document";
|
|
424
424
|
image: "image";
|
|
425
425
|
video: "video";
|
|
426
426
|
location: "location";
|
|
@@ -90,8 +90,8 @@ export declare const WHATSAPP_WEBHOOK_SCHEMA: z.ZodObject<{
|
|
|
90
90
|
type: z.ZodEnum<{
|
|
91
91
|
unknown: "unknown";
|
|
92
92
|
text: "text";
|
|
93
|
-
document: "document";
|
|
94
93
|
audio: "audio";
|
|
94
|
+
document: "document";
|
|
95
95
|
image: "image";
|
|
96
96
|
video: "video";
|
|
97
97
|
location: "location";
|
|
@@ -183,8 +183,8 @@ export declare const WHATSAPP_MESSAGE_RECEIVED_EVENT_SCHEMA: z.ZodObject<{
|
|
|
183
183
|
type: z.ZodEnum<{
|
|
184
184
|
unknown: "unknown";
|
|
185
185
|
text: "text";
|
|
186
|
-
document: "document";
|
|
187
186
|
audio: "audio";
|
|
187
|
+
document: "document";
|
|
188
188
|
image: "image";
|
|
189
189
|
video: "video";
|
|
190
190
|
location: "location";
|
|
@@ -278,8 +278,8 @@ export declare const WHATSAPP_MESSAGE_RECEIVED_EVENT_SCHEMA: z.ZodObject<{
|
|
|
278
278
|
type: z.ZodEnum<{
|
|
279
279
|
unknown: "unknown";
|
|
280
280
|
text: "text";
|
|
281
|
-
document: "document";
|
|
282
281
|
audio: "audio";
|
|
282
|
+
document: "document";
|
|
283
283
|
image: "image";
|
|
284
284
|
video: "video";
|
|
285
285
|
location: "location";
|
|
@@ -768,7 +768,7 @@ export declare function normalizeWhatsAppWebhook(input: unknown): {
|
|
|
768
768
|
message: {
|
|
769
769
|
id: string;
|
|
770
770
|
timestamp: string;
|
|
771
|
-
type: "unknown" | "text" | "
|
|
771
|
+
type: "unknown" | "text" | "audio" | "document" | "image" | "video" | "location" | "system" | "order" | "reaction" | "button" | "contacts" | "interactive" | "sticker" | "unsupported";
|
|
772
772
|
content?: z.core.util.JSONType | undefined;
|
|
773
773
|
context?: {
|
|
774
774
|
messageId: string;
|
|
@@ -781,7 +781,7 @@ export declare function normalizeWhatsAppWebhook(input: unknown): {
|
|
|
781
781
|
from: string;
|
|
782
782
|
id: string;
|
|
783
783
|
timestamp: string;
|
|
784
|
-
type: "unknown" | "text" | "
|
|
784
|
+
type: "unknown" | "text" | "audio" | "document" | "image" | "video" | "location" | "system" | "order" | "reaction" | "button" | "contacts" | "interactive" | "sticker" | "unsupported";
|
|
785
785
|
audio?: {
|
|
786
786
|
id: string;
|
|
787
787
|
mime_type?: string | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automate.ax/integration-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.107.0",
|
|
4
4
|
"description": "Shared integration payload contracts and provider primitives for Automate.ax.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"./automate": "./src/automate/index.ts",
|
|
23
23
|
"./asana": "./src/asana/index.ts",
|
|
24
24
|
"./brevo": "./src/brevo/index.ts",
|
|
25
|
+
"./cloudflare": "./src/cloudflare/index.ts",
|
|
25
26
|
"./convex": "./src/convex/index.ts",
|
|
26
27
|
"./close": "./src/close/index.ts",
|
|
27
28
|
"./gmail": "./src/gmail/index.ts",
|
|
@@ -49,7 +50,7 @@
|
|
|
49
50
|
}
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"@automate.ax/codec": "0.
|
|
53
|
+
"@automate.ax/codec": "0.107.0",
|
|
53
54
|
"@cfworker/json-schema": "^4.1.1",
|
|
54
55
|
"@googleapis/calendar": "^15.0.0",
|
|
55
56
|
"@googleapis/forms": "^6.0.1",
|
|
@@ -120,6 +121,11 @@
|
|
|
120
121
|
"types": "./dist/brevo/index.d.ts",
|
|
121
122
|
"default": "./dist/brevo/index.js"
|
|
122
123
|
},
|
|
124
|
+
"./cloudflare": {
|
|
125
|
+
"bun": "./src/cloudflare/index.ts",
|
|
126
|
+
"types": "./dist/cloudflare/index.d.ts",
|
|
127
|
+
"default": "./dist/cloudflare/index.js"
|
|
128
|
+
},
|
|
123
129
|
"./convex": {
|
|
124
130
|
"bun": "./src/convex/index.ts",
|
|
125
131
|
"types": "./dist/convex/index.d.ts",
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import type { Encodable } from "@automate.ax/codec"
|
|
2
|
+
import * as z from "zod"
|
|
3
|
+
|
|
4
|
+
const CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4/"
|
|
5
|
+
const CLOUDFLARE_API_ORIGIN = new URL(CLOUDFLARE_API_BASE_URL).origin
|
|
6
|
+
const CLOUDFLARE_API_TOKEN_SECRET_SCHEMA = z.object({
|
|
7
|
+
apiKey: z.string().min(1),
|
|
8
|
+
})
|
|
9
|
+
const CLOUDFLARE_OAUTH_SECRET_SCHEMA = z.object({
|
|
10
|
+
accessToken: z.string().min(1),
|
|
11
|
+
})
|
|
12
|
+
const CLOUDFLARE_RESPONSE_INFO_SCHEMA = z.looseObject({
|
|
13
|
+
code: z.number().optional(),
|
|
14
|
+
documentation_url: z.string().optional(),
|
|
15
|
+
message: z.string(),
|
|
16
|
+
source: z.object({ pointer: z.string().optional() }).optional(),
|
|
17
|
+
})
|
|
18
|
+
const CLOUDFLARE_ENVELOPE_SCHEMA = z.looseObject({
|
|
19
|
+
errors: CLOUDFLARE_RESPONSE_INFO_SCHEMA.array().default([]),
|
|
20
|
+
messages: CLOUDFLARE_RESPONSE_INFO_SCHEMA.array().default([]),
|
|
21
|
+
result: z.unknown().optional(),
|
|
22
|
+
result_info: z
|
|
23
|
+
.looseObject({
|
|
24
|
+
count: z.number().int().nonnegative().optional(),
|
|
25
|
+
page: z.number().int().positive().optional(),
|
|
26
|
+
per_page: z.number().int().positive().optional(),
|
|
27
|
+
total_count: z.number().int().nonnegative().optional(),
|
|
28
|
+
total_pages: z.number().int().nonnegative().optional(),
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
success: z.boolean(),
|
|
32
|
+
})
|
|
33
|
+
const CLOUDFLARE_RESULT_INFO_SCHEMA = z.looseObject({
|
|
34
|
+
count: z.number().int().nonnegative().optional(),
|
|
35
|
+
page: z.number().int().positive().optional(),
|
|
36
|
+
perPage: z.number().int().positive().optional(),
|
|
37
|
+
totalCount: z.number().int().nonnegative().optional(),
|
|
38
|
+
totalPages: z.number().int().nonnegative().optional(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
/** Scalar query value supported by the Cloudflare client. */
|
|
42
|
+
type CloudflareQueryValue = boolean | number | string | undefined
|
|
43
|
+
|
|
44
|
+
/** Resolved Cloudflare account accepted by the shared API client. */
|
|
45
|
+
export interface CloudflareResolvedAccount {
|
|
46
|
+
connectionMethodId: string
|
|
47
|
+
secret: Record<string, unknown>
|
|
48
|
+
serviceId: "cloudflare"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Options for one authenticated Cloudflare REST request. */
|
|
52
|
+
export interface CloudflareRequestOptions<TSchema extends z.ZodType> {
|
|
53
|
+
/** Public camelCase JSON body. */
|
|
54
|
+
body?: Encodable
|
|
55
|
+
|
|
56
|
+
/** HTTP verb. Defaults to `GET`. */
|
|
57
|
+
method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT"
|
|
58
|
+
|
|
59
|
+
/** Public camelCase query parameters. */
|
|
60
|
+
query?: Record<string, CloudflareQueryValue>
|
|
61
|
+
|
|
62
|
+
/** Schema for the normalized provider result. */
|
|
63
|
+
responseSchema: TSchema
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One normalized Cloudflare result page. */
|
|
67
|
+
export interface CloudflarePage<T> {
|
|
68
|
+
result: T
|
|
69
|
+
resultInfo?: {
|
|
70
|
+
count?: number
|
|
71
|
+
page?: number
|
|
72
|
+
perPage?: number
|
|
73
|
+
totalCount?: number
|
|
74
|
+
totalPages?: number
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Structured Cloudflare REST error. */
|
|
79
|
+
export class CloudflareApiError extends Error {
|
|
80
|
+
/** Provider error records. */
|
|
81
|
+
readonly errors: z.output<typeof CLOUDFLARE_RESPONSE_INFO_SCHEMA>[]
|
|
82
|
+
|
|
83
|
+
/** Parsed Cloudflare rate-limit header, when present. */
|
|
84
|
+
readonly rateLimit?: string
|
|
85
|
+
|
|
86
|
+
/** Parsed Cloudflare rate-limit policy header, when present. */
|
|
87
|
+
readonly rateLimitPolicy?: string
|
|
88
|
+
|
|
89
|
+
/** Retry delay in seconds, when Cloudflare returned one. */
|
|
90
|
+
readonly retryAfter?: number
|
|
91
|
+
|
|
92
|
+
/** HTTP status returned by Cloudflare. */
|
|
93
|
+
readonly status: number
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a structured error from one Cloudflare response.
|
|
97
|
+
*
|
|
98
|
+
* @param options - Response status, provider errors, and limit metadata.
|
|
99
|
+
* @param options.errors - Provider error records.
|
|
100
|
+
* @param options.rateLimit - Current rate-limit header.
|
|
101
|
+
* @param options.rateLimitPolicy - Current rate-limit policy header.
|
|
102
|
+
* @param options.retryAfter - Retry delay in seconds.
|
|
103
|
+
* @param options.status - HTTP response status.
|
|
104
|
+
*/
|
|
105
|
+
constructor(options: {
|
|
106
|
+
errors?: z.output<typeof CLOUDFLARE_RESPONSE_INFO_SCHEMA>[]
|
|
107
|
+
rateLimit?: string
|
|
108
|
+
rateLimitPolicy?: string
|
|
109
|
+
retryAfter?: number
|
|
110
|
+
status: number
|
|
111
|
+
}) {
|
|
112
|
+
const errors = options.errors ?? []
|
|
113
|
+
super(
|
|
114
|
+
errors[0]?.message
|
|
115
|
+
? `Cloudflare API error (${options.status}): ${errors[0].message}`
|
|
116
|
+
: `Cloudflare API request failed with status ${options.status}.`,
|
|
117
|
+
)
|
|
118
|
+
this.name = "CloudflareApiError"
|
|
119
|
+
this.errors = errors
|
|
120
|
+
this.rateLimit = options.rateLimit
|
|
121
|
+
this.rateLimitPolicy = options.rateLimitPolicy
|
|
122
|
+
this.retryAfter = options.retryAfter
|
|
123
|
+
this.status = options.status
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates an authenticated Cloudflare REST client.
|
|
129
|
+
*
|
|
130
|
+
* @param account - Resolved OAuth or API-token account.
|
|
131
|
+
* @throws When the connection method or secret shape is unsupported.
|
|
132
|
+
*/
|
|
133
|
+
export function getCloudflareApi(account: CloudflareResolvedAccount) {
|
|
134
|
+
const accessToken =
|
|
135
|
+
account.connectionMethodId === "oauth"
|
|
136
|
+
? CLOUDFLARE_OAUTH_SECRET_SCHEMA.parse(account.secret).accessToken
|
|
137
|
+
: account.connectionMethodId === "api-token"
|
|
138
|
+
? CLOUDFLARE_API_TOKEN_SECRET_SCHEMA.parse(account.secret).apiKey
|
|
139
|
+
: undefined
|
|
140
|
+
if (!accessToken) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Unsupported Cloudflare connection method: ${account.connectionMethodId}`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Runs one request and returns its normalized result.
|
|
148
|
+
*
|
|
149
|
+
* @param path - Cloudflare API path relative to the v4 root.
|
|
150
|
+
* @param options - Method, parameters, and response schema.
|
|
151
|
+
*/
|
|
152
|
+
async function request<TSchema extends z.ZodType>(
|
|
153
|
+
path: string,
|
|
154
|
+
options: CloudflareRequestOptions<TSchema>,
|
|
155
|
+
): Promise<z.output<TSchema>> {
|
|
156
|
+
return (await requestPage(path, options)).result
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Runs one request and preserves Cloudflare page metadata.
|
|
161
|
+
*
|
|
162
|
+
* @param path - Cloudflare API path relative to the v4 root.
|
|
163
|
+
* @param options - Method, parameters, and response schema.
|
|
164
|
+
*/
|
|
165
|
+
async function requestPage<TSchema extends z.ZodType>(
|
|
166
|
+
path: string,
|
|
167
|
+
options: CloudflareRequestOptions<TSchema>,
|
|
168
|
+
): Promise<CloudflarePage<z.output<TSchema>>> {
|
|
169
|
+
const url = new URL(path.replace(/^\//, ""), CLOUDFLARE_API_BASE_URL)
|
|
170
|
+
if (url.origin !== CLOUDFLARE_API_ORIGIN) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
"Cloudflare API paths must use the Cloudflare API origin.",
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
176
|
+
if (value !== undefined)
|
|
177
|
+
url.searchParams.set(toSnakeCase(name), String(value))
|
|
178
|
+
}
|
|
179
|
+
const response = await fetch(url, {
|
|
180
|
+
body:
|
|
181
|
+
options.body === undefined
|
|
182
|
+
? undefined
|
|
183
|
+
: JSON.stringify(toCloudflare(options.body)),
|
|
184
|
+
headers: {
|
|
185
|
+
Accept: "application/json",
|
|
186
|
+
Authorization: `Bearer ${accessToken}`,
|
|
187
|
+
...(options.body === undefined
|
|
188
|
+
? {}
|
|
189
|
+
: { "Content-Type": "application/json" }),
|
|
190
|
+
},
|
|
191
|
+
method: options.method ?? "GET",
|
|
192
|
+
})
|
|
193
|
+
const parsed = CLOUDFLARE_ENVELOPE_SCHEMA.safeParse(
|
|
194
|
+
parseJson(await response.text()),
|
|
195
|
+
)
|
|
196
|
+
if (!response.ok || !parsed.success || !parsed.data.success) {
|
|
197
|
+
throw new CloudflareApiError({
|
|
198
|
+
errors: parsed.success ? parsed.data.errors : undefined,
|
|
199
|
+
rateLimit: response.headers.get("Ratelimit") ?? undefined,
|
|
200
|
+
rateLimitPolicy: response.headers.get("Ratelimit-Policy") ?? undefined,
|
|
201
|
+
retryAfter: parseFiniteNumber(response.headers.get("Retry-After")),
|
|
202
|
+
status: response.status,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
result: options.responseSchema.parse(fromCloudflare(parsed.data.result)),
|
|
207
|
+
...(parsed.data.result_info && {
|
|
208
|
+
resultInfo: CLOUDFLARE_RESULT_INFO_SCHEMA.parse(
|
|
209
|
+
fromCloudflare(parsed.data.result_info),
|
|
210
|
+
),
|
|
211
|
+
}),
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { request, requestPage }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Converts public camelCase JSON to Cloudflare wire keys.
|
|
220
|
+
*
|
|
221
|
+
* @param value - Codec-safe public value.
|
|
222
|
+
*/
|
|
223
|
+
export function toCloudflare(value: Encodable): Encodable {
|
|
224
|
+
return convertToCloudflare(value, false)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Converts one value while optionally preserving object keys.
|
|
229
|
+
*
|
|
230
|
+
* @param value - Public value to convert.
|
|
231
|
+
* @param preserveKeys - Whether this object is a provider-defined map.
|
|
232
|
+
*/
|
|
233
|
+
function convertToCloudflare(
|
|
234
|
+
value: Encodable,
|
|
235
|
+
preserveKeys: boolean,
|
|
236
|
+
): Encodable {
|
|
237
|
+
if (value instanceof Date) return value.toISOString()
|
|
238
|
+
if (Array.isArray(value))
|
|
239
|
+
return value.map((item) => convertToCloudflare(item, preserveKeys))
|
|
240
|
+
if (!isPlainObject(value)) return value
|
|
241
|
+
return Object.fromEntries(
|
|
242
|
+
Object.entries(value).map(([key, item]) => [
|
|
243
|
+
preserveKeys ? key : toSnakeCase(key),
|
|
244
|
+
convertToCloudflare(item, key === "header" || key === "headers"),
|
|
245
|
+
]),
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Converts Cloudflare wire JSON to public camelCase keys.
|
|
251
|
+
*
|
|
252
|
+
* @param value - Untrusted provider JSON value.
|
|
253
|
+
*/
|
|
254
|
+
export function fromCloudflare(value: unknown): Encodable {
|
|
255
|
+
return convertFromCloudflare(value, false)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Converts one provider value while optionally preserving object keys.
|
|
260
|
+
*
|
|
261
|
+
* @param value - Provider value to convert.
|
|
262
|
+
* @param preserveKeys - Whether this object is a provider-defined map.
|
|
263
|
+
*/
|
|
264
|
+
function convertFromCloudflare(
|
|
265
|
+
value: unknown,
|
|
266
|
+
preserveKeys: boolean,
|
|
267
|
+
): Encodable {
|
|
268
|
+
if (Array.isArray(value))
|
|
269
|
+
return value.map((item) => convertFromCloudflare(item, preserveKeys))
|
|
270
|
+
if (!isPlainObject(value)) return CLOUDFLARE_VALUE_SCHEMA.parse(value)
|
|
271
|
+
return Object.fromEntries(
|
|
272
|
+
Object.entries(value).map(([key, item]) => [
|
|
273
|
+
preserveKeys ? key : toCamelCase(key),
|
|
274
|
+
convertFromCloudflare(item, key === "header" || key === "headers"),
|
|
275
|
+
]),
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const CLOUDFLARE_VALUE_SCHEMA = z.union([
|
|
280
|
+
z.boolean(),
|
|
281
|
+
z.number(),
|
|
282
|
+
z.string(),
|
|
283
|
+
z.null(),
|
|
284
|
+
z.undefined(),
|
|
285
|
+
])
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Returns whether a value is a plain JSON object.
|
|
289
|
+
*
|
|
290
|
+
* @param value - Candidate value.
|
|
291
|
+
*/
|
|
292
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
293
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parses JSON without obscuring the eventual structured API error.
|
|
298
|
+
*
|
|
299
|
+
* @param value - Raw response text.
|
|
300
|
+
*/
|
|
301
|
+
function parseJson(value: string): unknown {
|
|
302
|
+
try {
|
|
303
|
+
return JSON.parse(value)
|
|
304
|
+
} catch {
|
|
305
|
+
return undefined
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Parses a finite numeric response header.
|
|
311
|
+
*
|
|
312
|
+
* @param value - Header value.
|
|
313
|
+
*/
|
|
314
|
+
function parseFiniteNumber(value: string | null) {
|
|
315
|
+
if (value === null) return undefined
|
|
316
|
+
const number = Number(value)
|
|
317
|
+
return Number.isFinite(number) ? number : undefined
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Converts one Cloudflare snake_case key to camelCase.
|
|
322
|
+
*
|
|
323
|
+
* @param value - Provider key.
|
|
324
|
+
*/
|
|
325
|
+
function toCamelCase(value: string) {
|
|
326
|
+
return value.replace(/_([a-z0-9])/g, (_match, character: string) =>
|
|
327
|
+
character.toUpperCase(),
|
|
328
|
+
)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Converts one public camelCase key to snake_case.
|
|
333
|
+
*
|
|
334
|
+
* @param value - Public key.
|
|
335
|
+
*/
|
|
336
|
+
function toSnakeCase(value: string) {
|
|
337
|
+
return value
|
|
338
|
+
.replace(/([a-zA-Z])([0-9])/g, "$1_$2")
|
|
339
|
+
.replace(/[A-Z]/g, (character) => `_${character.toLowerCase()}`)
|
|
340
|
+
}
|