@automate.ax/integration-contracts 0.143.9 → 0.144.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.
@@ -0,0 +1,55 @@
1
+ import type { Encodable } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ /** Resolved Calendly OAuth account accepted by the shared API client. */
4
+ export interface CalendlyResolvedAccount {
5
+ connectionMethodId: string;
6
+ secret: Record<string, unknown>;
7
+ serviceId: "calendly";
8
+ }
9
+ /** Options for one authenticated Calendly REST request. */
10
+ export interface CalendlyRequestOptions<TSchema extends z.ZodType> {
11
+ body?: Encodable;
12
+ method?: "DELETE" | "GET" | "PATCH" | "POST";
13
+ query?: Record<string, boolean | number | string | undefined>;
14
+ responseSchema: TSchema;
15
+ }
16
+ /** Structured Calendly REST error. */
17
+ export declare class CalendlyApiError extends Error {
18
+ readonly body?: Encodable;
19
+ readonly rateLimitReset?: number;
20
+ readonly status: number;
21
+ /**
22
+ * Creates a structured Calendly API error.
23
+ *
24
+ * @param options - Provider response details.
25
+ * @param options.body - Parsed provider error body.
26
+ * @param options.rateLimitReset - Provider retry delay, in seconds.
27
+ * @param options.status - HTTP response status.
28
+ */
29
+ constructor(options: {
30
+ body?: Encodable;
31
+ rateLimitReset?: number;
32
+ status: number;
33
+ });
34
+ }
35
+ /**
36
+ * Creates an authenticated Calendly REST client.
37
+ *
38
+ * @param account - Resolved Calendly OAuth account.
39
+ * @throws When the account method or secret is invalid.
40
+ */
41
+ export declare function getCalendlyApi(account: CalendlyResolvedAccount): {
42
+ request: <TSchema extends z.ZodType>(path: string, options: CalendlyRequestOptions<TSchema>) => Promise<z.output<TSchema>>;
43
+ };
44
+ /**
45
+ * Converts public camelCase JSON to Calendly wire keys.
46
+ *
47
+ * @param value - Public action input.
48
+ */
49
+ export declare function toCalendly(value: Encodable): Encodable;
50
+ /**
51
+ * Converts Calendly wire JSON to public camelCase keys.
52
+ *
53
+ * @param value - Parsed Calendly response value.
54
+ */
55
+ export declare function fromCalendly(value: unknown): unknown;
@@ -0,0 +1,176 @@
1
+ import { encodableSchema } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ const CALENDLY_API_BASE_URL = "https://api.calendly.com/";
4
+ const CALENDLY_API_ORIGIN = new URL(CALENDLY_API_BASE_URL).origin;
5
+ const CALENDLY_OAUTH_SECRET_SCHEMA = z.object({
6
+ accessToken: z.string().min(1),
7
+ });
8
+ /** Structured Calendly REST error. */
9
+ export class CalendlyApiError extends Error {
10
+ body;
11
+ rateLimitReset;
12
+ status;
13
+ /**
14
+ * Creates a structured Calendly API error.
15
+ *
16
+ * @param options - Provider response details.
17
+ * @param options.body - Parsed provider error body.
18
+ * @param options.rateLimitReset - Provider retry delay, in seconds.
19
+ * @param options.status - HTTP response status.
20
+ */
21
+ constructor(options) {
22
+ super(getErrorMessage(options.body) ??
23
+ `Calendly API request failed with status ${options.status}.`);
24
+ this.name = "CalendlyApiError";
25
+ this.body = options.body;
26
+ this.rateLimitReset = options.rateLimitReset;
27
+ this.status = options.status;
28
+ }
29
+ }
30
+ /**
31
+ * Creates an authenticated Calendly REST client.
32
+ *
33
+ * @param account - Resolved Calendly OAuth account.
34
+ * @throws When the account method or secret is invalid.
35
+ */
36
+ export function getCalendlyApi(account) {
37
+ if (account.connectionMethodId !== "oauth") {
38
+ throw new Error(`Unsupported Calendly connection method: ${account.connectionMethodId}`);
39
+ }
40
+ const { accessToken } = CALENDLY_OAUTH_SECRET_SCHEMA.parse(account.secret);
41
+ /**
42
+ * Calls one Calendly endpoint and validates the response.
43
+ *
44
+ * @param path - Calendly API path.
45
+ * @param options - Request options and response schema.
46
+ */
47
+ async function request(path, options) {
48
+ const url = new URL(path.replace(/^\//, ""), CALENDLY_API_BASE_URL);
49
+ if (url.origin !== CALENDLY_API_ORIGIN) {
50
+ throw new Error("Calendly API paths must use the Calendly API origin.");
51
+ }
52
+ for (const [name, value] of Object.entries(options.query ?? {})) {
53
+ if (value !== undefined)
54
+ url.searchParams.set(toSnakeCase(name), String(value));
55
+ }
56
+ const response = await fetch(url, {
57
+ body: options.body === undefined
58
+ ? undefined
59
+ : JSON.stringify(toCalendly(options.body)),
60
+ headers: {
61
+ Accept: "application/json",
62
+ Authorization: `Bearer ${accessToken}`,
63
+ ...(options.body === undefined
64
+ ? {}
65
+ : { "Content-Type": "application/json" }),
66
+ },
67
+ method: options.method ?? "GET",
68
+ });
69
+ const text = await response.text();
70
+ const normalized = text ? fromCalendly(parseJson(text)) : undefined;
71
+ if (!response.ok) {
72
+ const body = encodableSchema.safeParse(normalized);
73
+ throw new CalendlyApiError({
74
+ ...(body.success && { body: body.data }),
75
+ rateLimitReset: parseFiniteNumber(response.headers.get("X-RateLimit-Reset")),
76
+ status: response.status,
77
+ });
78
+ }
79
+ return options.responseSchema.parse(normalized);
80
+ }
81
+ return { request };
82
+ }
83
+ /**
84
+ * Converts public camelCase JSON to Calendly wire keys.
85
+ *
86
+ * @param value - Public action input.
87
+ */
88
+ export function toCalendly(value) {
89
+ if (value instanceof Date)
90
+ return value.toISOString();
91
+ if (Array.isArray(value))
92
+ return value.map(toCalendly);
93
+ if (!isPlainObject(value))
94
+ return value;
95
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
96
+ toSnakeCase(key),
97
+ toCalendly(item),
98
+ ]));
99
+ }
100
+ /**
101
+ * Converts Calendly wire JSON to public camelCase keys.
102
+ *
103
+ * @param value - Parsed Calendly response value.
104
+ */
105
+ export function fromCalendly(value) {
106
+ if (Array.isArray(value))
107
+ return value.map(fromCalendly);
108
+ if (!isPlainObject(value))
109
+ return value;
110
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
111
+ toCamelCase(key),
112
+ fromCalendly(item),
113
+ ]));
114
+ }
115
+ /**
116
+ * Whether a value can be traversed as a plain JSON object.
117
+ *
118
+ * @param value - Candidate JSON value.
119
+ */
120
+ function isPlainObject(value) {
121
+ return typeof value === "object" && value !== null && !Array.isArray(value);
122
+ }
123
+ /**
124
+ * Parses a provider body without masking its HTTP failure.
125
+ *
126
+ * @param value - Provider response body.
127
+ */
128
+ function parseJson(value) {
129
+ try {
130
+ return JSON.parse(value);
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ }
136
+ /**
137
+ * Parses a finite numeric response header.
138
+ *
139
+ * @param value - Response header value.
140
+ */
141
+ function parseFiniteNumber(value) {
142
+ if (value === null)
143
+ return undefined;
144
+ const number = Number(value);
145
+ return Number.isFinite(number) ? number : undefined;
146
+ }
147
+ /**
148
+ * Extracts Calendly's human-readable error message.
149
+ *
150
+ * @param body - Parsed provider error body.
151
+ */
152
+ function getErrorMessage(body) {
153
+ if (!isPlainObject(body))
154
+ return undefined;
155
+ if (typeof body.message === "string")
156
+ return body.message;
157
+ if (typeof body.title === "string")
158
+ return body.title;
159
+ return undefined;
160
+ }
161
+ /**
162
+ * Converts one public field name to Calendly's wire convention.
163
+ *
164
+ * @param value - Public field name.
165
+ */
166
+ function toSnakeCase(value) {
167
+ return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
168
+ }
169
+ /**
170
+ * Converts one Calendly wire field name to the public convention.
171
+ *
172
+ * @param value - Calendly wire field name.
173
+ */
174
+ function toCamelCase(value) {
175
+ return value.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
176
+ }