@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.
@@ -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" | "scheduled" | "queued" | "suppressed" | "bounced" | "complained" | "delivery_delayed";
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" | "scheduled" | "queued" | "suppressed" | "bounced" | "complained" | "delivery_delayed";
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" | "scheduled" | "queued" | "suppressed" | "bounced" | "complained" | "delivery_delayed";
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" | "scheduled" | "queued" | "suppressed" | "bounced" | "complained" | "delivery_delayed";
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";
@@ -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.147.1",
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.147.1",
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
+ }