@automate.ax/integration-contracts 0.147.1 → 0.148.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 +4 -4
- package/dist/close/schemas.d.ts +6 -6
- package/dist/google-drive/index.d.ts +3 -3
- package/dist/kit/api.d.ts +78 -0
- package/dist/kit/api.js +386 -0
- package/dist/kit/events.d.ts +437 -0
- package/dist/kit/events.js +285 -0
- package/dist/kit/index.d.ts +6 -0
- package/dist/kit/index.js +5 -0
- package/dist/kit/openapi-operations.generated.d.ts +5 -0
- package/dist/kit/openapi-operations.generated.js +5 -0
- package/dist/kit/openapi-types.generated.d.ts +9485 -0
- package/dist/kit/openapi-types.generated.js +1 -0
- package/dist/kit/operation-manifest.d.ts +657 -0
- package/dist/kit/operation-manifest.js +98 -0
- package/dist/kit/schemas.d.ts +42 -0
- package/dist/kit/schemas.js +43 -0
- package/dist/kit/types.d.ts +41 -0
- package/dist/kit/types.js +1 -0
- package/dist/notion/schemas.d.ts +108 -108
- package/dist/resend/action-schemas.d.ts +14 -14
- package/dist/triggers.d.ts +2 -1
- package/package.json +9 -2
- package/src/kit/api.ts +523 -0
- package/src/kit/events.ts +352 -0
- package/src/kit/index.ts +6 -0
- package/src/kit/openapi-operations.generated.ts +7 -0
- package/src/kit/openapi-types.generated.ts +9710 -0
- package/src/kit/operation-manifest.ts +771 -0
- package/src/kit/schemas.ts +93 -0
- package/src/kit/types.ts +87 -0
- package/src/triggers.ts +2 -0
|
@@ -6,11 +6,11 @@ export declare const RESEND_EMAIL_STATUS_SCHEMA: z.ZodEnum<{
|
|
|
6
6
|
delivered: "delivered";
|
|
7
7
|
clicked: "clicked";
|
|
8
8
|
opened: "opened";
|
|
9
|
+
bounced: "bounced";
|
|
10
|
+
complained: "complained";
|
|
9
11
|
scheduled: "scheduled";
|
|
10
12
|
queued: "queued";
|
|
11
13
|
suppressed: "suppressed";
|
|
12
|
-
bounced: "bounced";
|
|
13
|
-
complained: "complained";
|
|
14
14
|
delivery_delayed: "delivery_delayed";
|
|
15
15
|
}>;
|
|
16
16
|
export declare const RESEND_TAG_SCHEMA: z.ZodObject<{
|
|
@@ -81,11 +81,11 @@ export declare const RESEND_EMAIL_SUMMARY_WIRE_SCHEMA: z.ZodObject<{
|
|
|
81
81
|
delivered: "delivered";
|
|
82
82
|
clicked: "clicked";
|
|
83
83
|
opened: "opened";
|
|
84
|
+
bounced: "bounced";
|
|
85
|
+
complained: "complained";
|
|
84
86
|
scheduled: "scheduled";
|
|
85
87
|
queued: "queued";
|
|
86
88
|
suppressed: "suppressed";
|
|
87
|
-
bounced: "bounced";
|
|
88
|
-
complained: "complained";
|
|
89
89
|
delivery_delayed: "delivery_delayed";
|
|
90
90
|
}>;
|
|
91
91
|
message_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -108,11 +108,11 @@ export declare const RESEND_EMAIL_SUMMARY_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
108
108
|
delivered: "delivered";
|
|
109
109
|
clicked: "clicked";
|
|
110
110
|
opened: "opened";
|
|
111
|
+
bounced: "bounced";
|
|
112
|
+
complained: "complained";
|
|
111
113
|
scheduled: "scheduled";
|
|
112
114
|
queued: "queued";
|
|
113
115
|
suppressed: "suppressed";
|
|
114
|
-
bounced: "bounced";
|
|
115
|
-
complained: "complained";
|
|
116
116
|
delivery_delayed: "delivery_delayed";
|
|
117
117
|
}>;
|
|
118
118
|
message_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -123,7 +123,7 @@ export declare const RESEND_EMAIL_SUMMARY_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
123
123
|
topic_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
124
124
|
}, z.core.$strip>, z.ZodTransform<{
|
|
125
125
|
createdAt: string;
|
|
126
|
-
lastEvent: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "
|
|
126
|
+
lastEvent: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "bounced" | "complained" | "scheduled" | "queued" | "suppressed" | "delivery_delayed";
|
|
127
127
|
messageId: string | null | undefined;
|
|
128
128
|
replyTo: string[] | null | undefined;
|
|
129
129
|
scheduledAt: string | null | undefined;
|
|
@@ -138,7 +138,7 @@ export declare const RESEND_EMAIL_SUMMARY_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
138
138
|
created_at: string;
|
|
139
139
|
from: string;
|
|
140
140
|
id: string;
|
|
141
|
-
last_event: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "
|
|
141
|
+
last_event: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "bounced" | "complained" | "scheduled" | "queued" | "suppressed" | "delivery_delayed";
|
|
142
142
|
subject: string;
|
|
143
143
|
to: string[];
|
|
144
144
|
bcc?: string[] | null | undefined;
|
|
@@ -159,11 +159,11 @@ export declare const RESEND_EMAIL_WIRE_SCHEMA: z.ZodObject<{
|
|
|
159
159
|
delivered: "delivered";
|
|
160
160
|
clicked: "clicked";
|
|
161
161
|
opened: "opened";
|
|
162
|
+
bounced: "bounced";
|
|
163
|
+
complained: "complained";
|
|
162
164
|
scheduled: "scheduled";
|
|
163
165
|
queued: "queued";
|
|
164
166
|
suppressed: "suppressed";
|
|
165
|
-
bounced: "bounced";
|
|
166
|
-
complained: "complained";
|
|
167
167
|
delivery_delayed: "delivery_delayed";
|
|
168
168
|
}>;
|
|
169
169
|
message_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -193,11 +193,11 @@ export declare const RESEND_EMAIL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
193
193
|
delivered: "delivered";
|
|
194
194
|
clicked: "clicked";
|
|
195
195
|
opened: "opened";
|
|
196
|
+
bounced: "bounced";
|
|
197
|
+
complained: "complained";
|
|
196
198
|
scheduled: "scheduled";
|
|
197
199
|
queued: "queued";
|
|
198
200
|
suppressed: "suppressed";
|
|
199
|
-
bounced: "bounced";
|
|
200
|
-
complained: "complained";
|
|
201
201
|
delivery_delayed: "delivery_delayed";
|
|
202
202
|
}>;
|
|
203
203
|
message_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
@@ -217,7 +217,7 @@ export declare const RESEND_EMAIL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
217
217
|
text: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
218
218
|
}, z.core.$strip>, z.ZodTransform<{
|
|
219
219
|
createdAt: string;
|
|
220
|
-
lastEvent: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "
|
|
220
|
+
lastEvent: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "bounced" | "complained" | "scheduled" | "queued" | "suppressed" | "delivery_delayed";
|
|
221
221
|
messageId: string | null | undefined;
|
|
222
222
|
replyTo: string[] | null | undefined;
|
|
223
223
|
scheduledAt: string | null | undefined;
|
|
@@ -239,7 +239,7 @@ export declare const RESEND_EMAIL_SCHEMA: z.ZodPipe<z.ZodObject<{
|
|
|
239
239
|
created_at: string;
|
|
240
240
|
from: string;
|
|
241
241
|
id: string;
|
|
242
|
-
last_event: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "
|
|
242
|
+
last_event: "failed" | "canceled" | "sent" | "delivered" | "clicked" | "opened" | "bounced" | "complained" | "scheduled" | "queued" | "suppressed" | "delivery_delayed";
|
|
243
243
|
subject: string;
|
|
244
244
|
to: string[];
|
|
245
245
|
object: "email";
|
package/dist/triggers.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { googleAdsTriggerContracts } from "./google-ads/index.js";
|
|
|
22
22
|
import type { googleSheetsTriggerContracts } from "./google-sheets/index.js";
|
|
23
23
|
import type { hubspotTriggerContracts } from "./hubspot/index.js";
|
|
24
24
|
import type { highLevelTriggerContracts } from "./highlevel/index.js";
|
|
25
|
+
import type { kitTriggerContracts } from "./kit/index.js";
|
|
25
26
|
import type { linearTriggerContracts } from "./linear/index.js";
|
|
26
27
|
import type { millionVerifierTriggerContracts } from "./millionverifier/index.js";
|
|
27
28
|
import type { metaAdsTriggerContracts } from "./meta-ads/index.js";
|
|
@@ -38,7 +39,7 @@ import type { vercelTriggerContracts } from "./vercel/index.js";
|
|
|
38
39
|
import type { webflowTriggerContracts } from "./webflow/index.js";
|
|
39
40
|
import type { whatsappTriggerContracts } from "./whatsapp/index.js";
|
|
40
41
|
import type { z } from "zod";
|
|
41
|
-
export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof calcomTriggerContracts & typeof convexTriggerContracts & typeof discordTriggerContracts & typeof closeTriggerContracts & typeof clickUpTriggerContracts & typeof calendlyTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleDriveTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleAdsTriggerContracts & typeof googleSheetsTriggerContracts & typeof hubspotTriggerContracts & typeof highLevelTriggerContracts & typeof linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof tallyTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
|
|
42
|
+
export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof calcomTriggerContracts & typeof convexTriggerContracts & typeof discordTriggerContracts & typeof closeTriggerContracts & typeof clickUpTriggerContracts & typeof calendlyTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleDriveTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleAdsTriggerContracts & typeof googleSheetsTriggerContracts & typeof hubspotTriggerContracts & typeof highLevelTriggerContracts & typeof kitTriggerContracts & typeof linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof tallyTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
|
|
42
43
|
export type IntegrationTriggerType = keyof TriggerContractMap;
|
|
43
44
|
/** Canonical authoring configuration for one integration trigger type. */
|
|
44
45
|
export type TriggerConfig<TType extends IntegrationTriggerType> = z.input<TriggerContractMap[TType]["configSchema"]> extends Record<string, never> ? object : z.input<TriggerContractMap[TType]["configSchema"]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automate.ax/integration-contracts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.148.0",
|
|
4
4
|
"description": "Shared integration payload contracts and provider primitives for Automate.ax.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"./calendly": "./src/calendly/index.ts",
|
|
29
29
|
"./brevo": "./src/brevo/index.ts",
|
|
30
30
|
"./calcom": "./src/calcom/index.ts",
|
|
31
|
+
"./kit": "./src/kit/index.ts",
|
|
31
32
|
"./cloudflare": "./src/cloudflare/index.ts",
|
|
32
33
|
"./meta-ads": "./src/meta-ads/index.ts",
|
|
33
34
|
"./convex": "./src/convex/index.ts",
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
72
|
"@anthropic-ai/sdk": "0.123.0",
|
|
72
|
-
"@automate.ax/codec": "0.
|
|
73
|
+
"@automate.ax/codec": "0.148.0",
|
|
73
74
|
"@cfworker/json-schema": "^4.1.1",
|
|
74
75
|
"@googleapis/calendar": "^16.0.0",
|
|
75
76
|
"@googleapis/forms": "^6.0.1",
|
|
@@ -106,6 +107,7 @@
|
|
|
106
107
|
"generate:github": "bun scripts/generate-github-webhook-schemas.ts",
|
|
107
108
|
"generate:calcom": "bun scripts/generate-calcom-openapi.ts",
|
|
108
109
|
"generate:highlevel": "bun scripts/generate-highlevel-openapi.ts",
|
|
110
|
+
"generate:kit": "bun scripts/generate-kit-openapi.ts",
|
|
109
111
|
"generate:tally": "bun scripts/generate-tally-openapi.ts",
|
|
110
112
|
"typecheck": "resource-broker run --pool automate-ax-validation --limit 2 --weight 1 -- tsc --noEmit",
|
|
111
113
|
"lint": "oxlint --type-aware --threads=2 && bun --bun eslint . --cache --cache-strategy content",
|
|
@@ -175,6 +177,11 @@
|
|
|
175
177
|
"types": "./dist/calcom/index.d.ts",
|
|
176
178
|
"default": "./dist/calcom/index.js"
|
|
177
179
|
},
|
|
180
|
+
"./kit": {
|
|
181
|
+
"bun": "./src/kit/index.ts",
|
|
182
|
+
"types": "./dist/kit/index.d.ts",
|
|
183
|
+
"default": "./dist/kit/index.js"
|
|
184
|
+
},
|
|
178
185
|
"./cloudflare": {
|
|
179
186
|
"bun": "./src/cloudflare/index.ts",
|
|
180
187
|
"types": "./dist/cloudflare/index.d.ts",
|
package/src/kit/api.ts
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import { encodableSchema, type Encodable } from "@automate.ax/codec"
|
|
2
|
+
import * as z from "zod"
|
|
3
|
+
import type { KitOperationKey } from "./operation-manifest"
|
|
4
|
+
import {
|
|
5
|
+
KIT_WIRE_DEFINITIONS,
|
|
6
|
+
kitOperation,
|
|
7
|
+
kitOperationOutputSchema,
|
|
8
|
+
type KitRuntimeParameter,
|
|
9
|
+
} from "./schemas"
|
|
10
|
+
import type { KitOperationInput, KitOperationOutput } from "./types"
|
|
11
|
+
|
|
12
|
+
const KIT_API_BASE_URL = "https://api.kit.com/v4/"
|
|
13
|
+
const KIT_API_ORIGIN = new URL(KIT_API_BASE_URL).origin
|
|
14
|
+
const KIT_API_KEY_SECRET_SCHEMA = z.object({
|
|
15
|
+
apiKey: z.string().trim().min(1),
|
|
16
|
+
})
|
|
17
|
+
const KIT_OAUTH_SECRET_SCHEMA = z.object({
|
|
18
|
+
accessToken: z.string().trim().min(1),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
/** Resolved account accepted by the shared Kit API client. */
|
|
22
|
+
export interface KitResolvedAccount {
|
|
23
|
+
connectionMethodId: string
|
|
24
|
+
secret: Record<string, unknown>
|
|
25
|
+
serviceId: "kit"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for an internal provider-native Kit request. */
|
|
29
|
+
export interface KitRequestOptions {
|
|
30
|
+
body?: Encodable
|
|
31
|
+
method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT"
|
|
32
|
+
query?: Record<
|
|
33
|
+
string,
|
|
34
|
+
boolean | number | string | (boolean | number | string)[] | null | undefined
|
|
35
|
+
>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Structured error returned by a rejected Kit request. */
|
|
39
|
+
export class KitApiError extends Error {
|
|
40
|
+
readonly body?: Encodable
|
|
41
|
+
readonly retryAfter?: string
|
|
42
|
+
readonly status: number
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates a structured provider error.
|
|
46
|
+
*
|
|
47
|
+
* @param options Error properties.
|
|
48
|
+
* @param options.body Parsed provider response body.
|
|
49
|
+
* @param options.retryAfter Provider retry guidance.
|
|
50
|
+
* @param options.status HTTP status code.
|
|
51
|
+
*/
|
|
52
|
+
constructor(options: {
|
|
53
|
+
body?: Encodable
|
|
54
|
+
retryAfter?: string
|
|
55
|
+
status: number
|
|
56
|
+
}) {
|
|
57
|
+
super(
|
|
58
|
+
getErrorMessage(options.body) ??
|
|
59
|
+
`Kit API request failed with status ${options.status}.`,
|
|
60
|
+
)
|
|
61
|
+
this.name = "KitApiError"
|
|
62
|
+
this.body = options.body
|
|
63
|
+
this.retryAfter = options.retryAfter
|
|
64
|
+
this.status = options.status
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates an authenticated Kit V4 API client.
|
|
70
|
+
*
|
|
71
|
+
* @param account Resolved Kit account.
|
|
72
|
+
*/
|
|
73
|
+
export function getKitApi(account: KitResolvedAccount) {
|
|
74
|
+
const authorization = getAuthorization(account)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
/**
|
|
78
|
+
* Executes and validates one named public Kit operation.
|
|
79
|
+
*
|
|
80
|
+
* @param key Public operation name.
|
|
81
|
+
* @param input Flattened operation input.
|
|
82
|
+
*/
|
|
83
|
+
async operation<TKey extends KitOperationKey>(
|
|
84
|
+
key: TKey,
|
|
85
|
+
input: KitOperationInput<TKey>,
|
|
86
|
+
): Promise<KitOperationOutput<TKey>> {
|
|
87
|
+
const definition = kitOperation(key)
|
|
88
|
+
const inputRecord = input as Record<string, Encodable | undefined>
|
|
89
|
+
// Build the path before request assembly so every interpolation is encoded.
|
|
90
|
+
const path = definition.path
|
|
91
|
+
.replace(/^\/v4\//, "")
|
|
92
|
+
.replace(/\{([^}]+)\}/g, (_match, providerName: string) => {
|
|
93
|
+
const parameter = definition.pathParameters.find(
|
|
94
|
+
(candidate) => candidate.providerName === providerName,
|
|
95
|
+
)
|
|
96
|
+
if (!parameter) {
|
|
97
|
+
throw new TypeError(`Unknown Kit path parameter: ${providerName}`)
|
|
98
|
+
}
|
|
99
|
+
return encodePathSegment(inputRecord[parameter.name], parameter.name)
|
|
100
|
+
})
|
|
101
|
+
// Preserve undefined query fields for the shared serializer to omit.
|
|
102
|
+
const query = Object.fromEntries(
|
|
103
|
+
definition.queryParameters.map((parameter) => [
|
|
104
|
+
parameter.providerName,
|
|
105
|
+
parseQueryValue(inputRecord[parameter.name]),
|
|
106
|
+
]),
|
|
107
|
+
)
|
|
108
|
+
const body = Object.fromEntries(
|
|
109
|
+
definition.bodyParameters.flatMap((name) =>
|
|
110
|
+
inputRecord[name] === undefined ? [] : [[name, inputRecord[name]]],
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
const parsed = await sendKitRequest(authorization, path, {
|
|
114
|
+
...(definition.bodyParameters.length > 0 && {
|
|
115
|
+
body: kitPublicToWire(body, definition.bodyWireSchema ?? {}),
|
|
116
|
+
}),
|
|
117
|
+
method: definition.method,
|
|
118
|
+
query,
|
|
119
|
+
})
|
|
120
|
+
// Normalize provider fields before validating the public return type.
|
|
121
|
+
const output =
|
|
122
|
+
definition.responseMode === "void"
|
|
123
|
+
? {}
|
|
124
|
+
: kitWireToPublic(parsed, definition.outputWireSchema)
|
|
125
|
+
return kitOperationOutputSchema(key).parse(output)
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Sends an internal provider-native request used for managed webhooks.
|
|
130
|
+
*
|
|
131
|
+
* This is not exposed as an automation action.
|
|
132
|
+
*
|
|
133
|
+
* @param path Provider-relative Kit V4 path.
|
|
134
|
+
* @param options Request options.
|
|
135
|
+
*/
|
|
136
|
+
async requestJson(path: string, options: KitRequestOptions = {}) {
|
|
137
|
+
return await sendKitRequest(authorization, path, options)
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Encodes every interpolation as one Kit URL path segment.
|
|
144
|
+
*
|
|
145
|
+
* @param strings Template string segments.
|
|
146
|
+
* @param values Interpolated path values.
|
|
147
|
+
*/
|
|
148
|
+
export function kitPath(
|
|
149
|
+
strings: TemplateStringsArray,
|
|
150
|
+
...values: (number | string)[]
|
|
151
|
+
) {
|
|
152
|
+
return strings.reduce(
|
|
153
|
+
(path, part, index) =>
|
|
154
|
+
`${path}${index === 0 ? "" : encodeURIComponent(String(values[index - 1]))}${part}`,
|
|
155
|
+
"",
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Converts public camelCase fields to documented Kit wire fields.
|
|
161
|
+
*
|
|
162
|
+
* @param value Public value.
|
|
163
|
+
* @param schema Provider wire schema.
|
|
164
|
+
*/
|
|
165
|
+
export function kitPublicToWire(
|
|
166
|
+
value: unknown,
|
|
167
|
+
schema: Record<string, unknown>,
|
|
168
|
+
): Encodable {
|
|
169
|
+
return encodableSchema.parse(transformKitValue(value, schema, "toWire"))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Converts documented Kit wire fields to public camelCase fields.
|
|
174
|
+
*
|
|
175
|
+
* @param value Provider value.
|
|
176
|
+
* @param schema Provider wire schema.
|
|
177
|
+
*/
|
|
178
|
+
export function kitWireToPublic(
|
|
179
|
+
value: unknown,
|
|
180
|
+
schema: Record<string, unknown>,
|
|
181
|
+
): Encodable {
|
|
182
|
+
return encodableSchema.parse(transformKitValue(value, schema, "toPublic"))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Returns the authorization header for a resolved Kit account.
|
|
187
|
+
*
|
|
188
|
+
* @param account Resolved Kit account.
|
|
189
|
+
* @throws When the connection method is unsupported.
|
|
190
|
+
*/
|
|
191
|
+
function getAuthorization(account: KitResolvedAccount): Record<string, string> {
|
|
192
|
+
if (account.connectionMethodId === "oauth") {
|
|
193
|
+
return {
|
|
194
|
+
Authorization: `Bearer ${KIT_OAUTH_SECRET_SCHEMA.parse(account.secret).accessToken}`,
|
|
195
|
+
} satisfies Record<string, string>
|
|
196
|
+
}
|
|
197
|
+
if (account.connectionMethodId === "api-key") {
|
|
198
|
+
return {
|
|
199
|
+
"X-Kit-Api-Key": KIT_API_KEY_SECRET_SCHEMA.parse(account.secret).apiKey,
|
|
200
|
+
} satisfies Record<string, string>
|
|
201
|
+
}
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Unsupported Kit connection method: ${account.connectionMethodId}`,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Sends one guarded request to the Kit V4 API.
|
|
209
|
+
*
|
|
210
|
+
* @param authorization Authentication headers.
|
|
211
|
+
* @param path Provider-relative API path.
|
|
212
|
+
* @param options Request options.
|
|
213
|
+
*/
|
|
214
|
+
async function sendKitRequest(
|
|
215
|
+
authorization: Record<string, string>,
|
|
216
|
+
path: string,
|
|
217
|
+
options: KitRequestOptions,
|
|
218
|
+
) {
|
|
219
|
+
const normalizedPath = path.replace(/^\/+/, "")
|
|
220
|
+
const url = new URL(normalizedPath, KIT_API_BASE_URL)
|
|
221
|
+
if (
|
|
222
|
+
!normalizedPath ||
|
|
223
|
+
normalizedPath.includes("://") ||
|
|
224
|
+
normalizedPath.includes("\\") ||
|
|
225
|
+
normalizedPath.includes("?") ||
|
|
226
|
+
normalizedPath.includes("#") ||
|
|
227
|
+
url.origin !== KIT_API_ORIGIN ||
|
|
228
|
+
!url.pathname.startsWith(new URL(KIT_API_BASE_URL).pathname)
|
|
229
|
+
) {
|
|
230
|
+
throw new TypeError("Kit API paths must remain provider-relative.")
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
234
|
+
appendQuery(
|
|
235
|
+
url,
|
|
236
|
+
{ explode: true, name, providerName: name, style: "form" },
|
|
237
|
+
value,
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
const response = await fetch(url, {
|
|
241
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
242
|
+
headers: {
|
|
243
|
+
Accept: "application/json",
|
|
244
|
+
...authorization,
|
|
245
|
+
...(options.body === undefined
|
|
246
|
+
? {}
|
|
247
|
+
: { "Content-Type": "application/json" }),
|
|
248
|
+
},
|
|
249
|
+
method: options.method ?? "GET",
|
|
250
|
+
redirect: "error",
|
|
251
|
+
})
|
|
252
|
+
const parsed = await parseResponse(response)
|
|
253
|
+
if (!response.ok) {
|
|
254
|
+
const body = encodableSchema.safeParse(parsed)
|
|
255
|
+
throw new KitApiError({
|
|
256
|
+
...(body.success && { body: body.data }),
|
|
257
|
+
retryAfter: response.headers.get("Retry-After") ?? undefined,
|
|
258
|
+
status: response.status,
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
return parsed
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Appends one documented query parameter.
|
|
266
|
+
*
|
|
267
|
+
* @param url Mutable request URL.
|
|
268
|
+
* @param parameter Query parameter metadata.
|
|
269
|
+
* @param value Public parameter value.
|
|
270
|
+
*/
|
|
271
|
+
function appendQuery(url: URL, parameter: KitRuntimeParameter, value: unknown) {
|
|
272
|
+
if (value == null) return
|
|
273
|
+
if (Array.isArray(value)) {
|
|
274
|
+
if (parameter.style === "form" && parameter.explode) {
|
|
275
|
+
for (const item of value) {
|
|
276
|
+
url.searchParams.append(
|
|
277
|
+
parameter.providerName,
|
|
278
|
+
stringifyQueryValue(item),
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
url.searchParams.set(
|
|
283
|
+
parameter.providerName,
|
|
284
|
+
value.map(stringifyQueryValue).join(","),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
url.searchParams.set(parameter.providerName, stringifyQueryValue(value))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Converts a query value to its wire representation.
|
|
294
|
+
*
|
|
295
|
+
* @param value Public query value.
|
|
296
|
+
* @throws When the value is not scalar.
|
|
297
|
+
*/
|
|
298
|
+
function stringifyQueryValue(value: unknown) {
|
|
299
|
+
if (
|
|
300
|
+
typeof value !== "boolean" &&
|
|
301
|
+
typeof value !== "number" &&
|
|
302
|
+
typeof value !== "string"
|
|
303
|
+
) {
|
|
304
|
+
throw new TypeError("Kit query parameters must be scalar.")
|
|
305
|
+
}
|
|
306
|
+
return String(value)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Validates a flattened action query value.
|
|
311
|
+
*
|
|
312
|
+
* @param value Public action field.
|
|
313
|
+
* @throws When the value is not a scalar or scalar array.
|
|
314
|
+
*/
|
|
315
|
+
function parseQueryValue(
|
|
316
|
+
value: unknown,
|
|
317
|
+
):
|
|
318
|
+
| boolean
|
|
319
|
+
| number
|
|
320
|
+
| string
|
|
321
|
+
| (boolean | number | string)[]
|
|
322
|
+
| null
|
|
323
|
+
| undefined {
|
|
324
|
+
if (
|
|
325
|
+
value === null ||
|
|
326
|
+
value === undefined ||
|
|
327
|
+
typeof value === "boolean" ||
|
|
328
|
+
typeof value === "number" ||
|
|
329
|
+
typeof value === "string"
|
|
330
|
+
) {
|
|
331
|
+
return value
|
|
332
|
+
}
|
|
333
|
+
if (
|
|
334
|
+
Array.isArray(value) &&
|
|
335
|
+
value.every(
|
|
336
|
+
(item) =>
|
|
337
|
+
typeof item === "boolean" ||
|
|
338
|
+
typeof item === "number" ||
|
|
339
|
+
typeof item === "string",
|
|
340
|
+
)
|
|
341
|
+
) {
|
|
342
|
+
return value
|
|
343
|
+
}
|
|
344
|
+
throw new TypeError("Kit query parameters must be scalar or scalar arrays.")
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Encodes one required path parameter.
|
|
349
|
+
*
|
|
350
|
+
* @param value Public path value.
|
|
351
|
+
* @param name Public parameter name.
|
|
352
|
+
* @throws When the path value is missing or invalid.
|
|
353
|
+
*/
|
|
354
|
+
function encodePathSegment(value: unknown, name: string) {
|
|
355
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
356
|
+
throw new TypeError(`Kit path parameter ${name} is required.`)
|
|
357
|
+
}
|
|
358
|
+
return encodeURIComponent(String(value))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Recursively maps Kit fields between public and wire names.
|
|
363
|
+
*
|
|
364
|
+
* @param value Value to transform.
|
|
365
|
+
* @param schema Provider wire schema.
|
|
366
|
+
* @param direction Mapping direction.
|
|
367
|
+
*/
|
|
368
|
+
function transformKitValue(
|
|
369
|
+
value: unknown,
|
|
370
|
+
schema: Record<string, unknown>,
|
|
371
|
+
direction: "toPublic" | "toWire",
|
|
372
|
+
): unknown {
|
|
373
|
+
const resolved = resolveWireSchema(schema)
|
|
374
|
+
if (Array.isArray(value)) {
|
|
375
|
+
const itemSchema = isRecord(resolved.items) ? resolved.items : {}
|
|
376
|
+
return value.map((item) => transformKitValue(item, itemSchema, direction))
|
|
377
|
+
}
|
|
378
|
+
if (!isRecord(value)) return value
|
|
379
|
+
|
|
380
|
+
const properties = collectWireProperties(resolved)
|
|
381
|
+
return Object.fromEntries(
|
|
382
|
+
Object.entries(value).map(([name, item]) => {
|
|
383
|
+
const providerName =
|
|
384
|
+
direction === "toPublic"
|
|
385
|
+
? name
|
|
386
|
+
: (Object.keys(properties).find(
|
|
387
|
+
(candidate) => camelCase(candidate) === name,
|
|
388
|
+
) ?? name)
|
|
389
|
+
const publicName =
|
|
390
|
+
direction === "toPublic" && providerName in properties
|
|
391
|
+
? camelCase(providerName)
|
|
392
|
+
: name
|
|
393
|
+
const childSchemas = propertySchemas(resolved, providerName)
|
|
394
|
+
// Combine alternate provider shapes before recursively mapping nested keys.
|
|
395
|
+
const childSchema =
|
|
396
|
+
childSchemas.length === 0
|
|
397
|
+
? {}
|
|
398
|
+
: childSchemas.length === 1
|
|
399
|
+
? (childSchemas[0] ?? {})
|
|
400
|
+
: { anyOf: childSchemas }
|
|
401
|
+
return [
|
|
402
|
+
direction === "toPublic" ? publicName : providerName,
|
|
403
|
+
transformKitValue(item, childSchema, direction),
|
|
404
|
+
]
|
|
405
|
+
}),
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Collects schema properties across composition branches.
|
|
411
|
+
*
|
|
412
|
+
* @param schema Provider wire schema.
|
|
413
|
+
*/
|
|
414
|
+
function collectWireProperties(
|
|
415
|
+
schema: Record<string, unknown>,
|
|
416
|
+
): Record<string, unknown> {
|
|
417
|
+
const resolved = resolveWireSchema(schema)
|
|
418
|
+
const properties: Record<string, unknown> = {}
|
|
419
|
+
return Object.assign(
|
|
420
|
+
properties,
|
|
421
|
+
isRecord(resolved.properties) ? resolved.properties : {},
|
|
422
|
+
...schemaBranches(resolved).map(collectWireProperties),
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Finds every schema declared for one provider field.
|
|
428
|
+
*
|
|
429
|
+
* @param schema Provider wire schema.
|
|
430
|
+
* @param providerName Provider field name.
|
|
431
|
+
*/
|
|
432
|
+
function propertySchemas(
|
|
433
|
+
schema: Record<string, unknown>,
|
|
434
|
+
providerName: string,
|
|
435
|
+
): Record<string, unknown>[] {
|
|
436
|
+
const resolved = resolveWireSchema(schema)
|
|
437
|
+
// Inspect the direct declaration before recursively visiting composed branches.
|
|
438
|
+
const direct =
|
|
439
|
+
isRecord(resolved.properties) && isRecord(resolved.properties[providerName])
|
|
440
|
+
? [resolved.properties[providerName]]
|
|
441
|
+
: []
|
|
442
|
+
return [
|
|
443
|
+
...direct,
|
|
444
|
+
...schemaBranches(resolved).flatMap((branch) =>
|
|
445
|
+
propertySchemas(branch, providerName),
|
|
446
|
+
),
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Returns all composed schema branches.
|
|
452
|
+
*
|
|
453
|
+
* @param schema Provider wire schema.
|
|
454
|
+
*/
|
|
455
|
+
function schemaBranches(schema: Record<string, unknown>) {
|
|
456
|
+
return ["allOf", "anyOf", "oneOf"].flatMap((keyword) =>
|
|
457
|
+
Array.isArray(schema[keyword]) ? schema[keyword].filter(isRecord) : [],
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Resolves a top-level provider component reference.
|
|
463
|
+
*
|
|
464
|
+
* @param schema Provider wire schema.
|
|
465
|
+
*/
|
|
466
|
+
function resolveWireSchema(schema: Record<string, unknown>) {
|
|
467
|
+
if (typeof schema.$ref !== "string") return schema
|
|
468
|
+
const name = /^#\/components\/schemas\/(.+)$/.exec(schema.$ref)?.[1]
|
|
469
|
+
return name ? (KIT_WIRE_DEFINITIONS[name] ?? schema) : schema
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Parses a provider response without assuming JSON on errors.
|
|
474
|
+
*
|
|
475
|
+
* @param response Provider response.
|
|
476
|
+
*/
|
|
477
|
+
async function parseResponse(response: Response) {
|
|
478
|
+
const text = await response.text()
|
|
479
|
+
if (!text) return {}
|
|
480
|
+
try {
|
|
481
|
+
return JSON.parse(text) as unknown
|
|
482
|
+
} catch {
|
|
483
|
+
return { response: text }
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Extracts Kit's documented error messages.
|
|
489
|
+
*
|
|
490
|
+
* @param value Parsed provider response.
|
|
491
|
+
*/
|
|
492
|
+
function getErrorMessage(value: Encodable | undefined) {
|
|
493
|
+
if (!isRecord(value)) return undefined
|
|
494
|
+
if (Array.isArray(value.errors)) {
|
|
495
|
+
const messages = value.errors.filter(
|
|
496
|
+
(item): item is string => typeof item === "string" && item.length > 0,
|
|
497
|
+
)
|
|
498
|
+
if (messages.length > 0) return messages.join("; ")
|
|
499
|
+
}
|
|
500
|
+
return typeof value.message === "string" && value.message
|
|
501
|
+
? value.message
|
|
502
|
+
: undefined
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Converts one provider snake_case field name to camelCase.
|
|
507
|
+
*
|
|
508
|
+
* @param value Provider field name.
|
|
509
|
+
*/
|
|
510
|
+
function camelCase(value: string) {
|
|
511
|
+
return value.replace(/_([a-z\d])/g, (_match, letter: string) =>
|
|
512
|
+
letter.toUpperCase(),
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Narrows an unknown value to a record.
|
|
518
|
+
*
|
|
519
|
+
* @param value Candidate object.
|
|
520
|
+
*/
|
|
521
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
522
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
523
|
+
}
|