@automate.ax/integration-contracts 0.143.8 → 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.
@@ -109,8 +109,8 @@ export declare const SLACK_MESSAGE_SCHEMA: z.ZodObject<{
109
109
  export declare const SLACK_REACTION_ITEM_SCHEMA: z.ZodDiscriminatedUnion<[z.ZodObject<{
110
110
  conversationId: z.ZodString;
111
111
  conversationType: z.ZodOptional<z.ZodEnum<{
112
- channel: "channel";
113
112
  group: "group";
113
+ channel: "channel";
114
114
  im: "im";
115
115
  mpim: "mpim";
116
116
  }>>;
@@ -560,8 +560,8 @@ export declare const TEAMS_CHANGE_EVENT_SCHEMAS: {
560
560
  changeType: z.ZodLiteral<"updated">;
561
561
  data: z.ZodObject<{
562
562
  chatType: z.ZodEnum<{
563
- meeting: "meeting";
564
563
  group: "group";
564
+ meeting: "meeting";
565
565
  unknownFutureValue: "unknownFutureValue";
566
566
  oneOnOne: "oneOnOne";
567
567
  }>;
@@ -580,8 +580,8 @@ export declare const TEAMS_CHANGE_EVENT_SCHEMAS: {
580
580
  changeType: z.ZodLiteral<"updated">;
581
581
  data: z.ZodObject<{
582
582
  chatType: z.ZodEnum<{
583
- meeting: "meeting";
584
583
  group: "group";
584
+ meeting: "meeting";
585
585
  unknownFutureValue: "unknownFutureValue";
586
586
  oneOnOne: "oneOnOne";
587
587
  }>;
@@ -1172,8 +1172,8 @@ export declare const TEAMS_CHANGE_EVENT_SCHEMA: z.ZodUnion<readonly [z.ZodDiscri
1172
1172
  changeType: z.ZodLiteral<"updated">;
1173
1173
  data: z.ZodObject<{
1174
1174
  chatType: z.ZodEnum<{
1175
- meeting: "meeting";
1176
1175
  group: "group";
1176
+ meeting: "meeting";
1177
1177
  unknownFutureValue: "unknownFutureValue";
1178
1178
  oneOnOne: "oneOnOne";
1179
1179
  }>;
@@ -2151,8 +2151,8 @@ export declare const teamsTriggerContracts: {
2151
2151
  changeType: z.ZodLiteral<"updated">;
2152
2152
  data: z.ZodObject<{
2153
2153
  chatType: z.ZodEnum<{
2154
- meeting: "meeting";
2155
2154
  group: "group";
2155
+ meeting: "meeting";
2156
2156
  unknownFutureValue: "unknownFutureValue";
2157
2157
  oneOnOne: "oneOnOne";
2158
2158
  }>;
@@ -2176,8 +2176,8 @@ export declare const teamsTriggerContracts: {
2176
2176
  changeType: z.ZodLiteral<"updated">;
2177
2177
  data: z.ZodObject<{
2178
2178
  chatType: z.ZodEnum<{
2179
- meeting: "meeting";
2180
2179
  group: "group";
2180
+ meeting: "meeting";
2181
2181
  unknownFutureValue: "unknownFutureValue";
2182
2182
  oneOnOne: "oneOnOne";
2183
2183
  }>;
@@ -134,8 +134,8 @@ export declare const GRAPH_MEMBER_SCHEMA: z.ZodPipe<z.ZodObject<{
134
134
  }>>;
135
135
  export declare const TEAMS_CHAT_SCHEMA: z.ZodObject<{
136
136
  chatType: z.ZodEnum<{
137
- meeting: "meeting";
138
137
  group: "group";
138
+ meeting: "meeting";
139
139
  unknownFutureValue: "unknownFutureValue";
140
140
  oneOnOne: "oneOnOne";
141
141
  }>;
@@ -148,8 +148,8 @@ export declare const TEAMS_CHAT_SCHEMA: z.ZodObject<{
148
148
  }, z.core.$strip>;
149
149
  export declare const GRAPH_CHAT_SCHEMA: z.ZodPipe<z.ZodObject<{
150
150
  chatType: z.ZodEnum<{
151
- meeting: "meeting";
152
151
  group: "group";
152
+ meeting: "meeting";
153
153
  unknownFutureValue: "unknownFutureValue";
154
154
  oneOnOne: "oneOnOne";
155
155
  }>;
@@ -160,7 +160,7 @@ export declare const GRAPH_CHAT_SCHEMA: z.ZodPipe<z.ZodObject<{
160
160
  topic: z.ZodOptional<z.ZodNullable<z.ZodString>>;
161
161
  webUrl: z.ZodOptional<z.ZodNullable<z.ZodString>>;
162
162
  }, z.core.$loose>, z.ZodTransform<{
163
- chatType: "meeting" | "group" | "unknownFutureValue" | "oneOnOne";
163
+ chatType: "group" | "meeting" | "unknownFutureValue" | "oneOnOne";
164
164
  createdAt: Date | undefined;
165
165
  chatId: string;
166
166
  updatedAt: Date | undefined;
@@ -169,7 +169,7 @@ export declare const GRAPH_CHAT_SCHEMA: z.ZodPipe<z.ZodObject<{
169
169
  webUrl: string | undefined;
170
170
  }, {
171
171
  [x: string]: unknown;
172
- chatType: "meeting" | "group" | "unknownFutureValue" | "oneOnOne";
172
+ chatType: "group" | "meeting" | "unknownFutureValue" | "oneOnOne";
173
173
  id: string;
174
174
  createdDateTime?: string | null | undefined;
175
175
  lastUpdatedDateTime?: string | null | undefined;
@@ -32,8 +32,8 @@ export declare const TRELLO_BOARD_MEMBERSHIP_SCHEMA: z.ZodObject<{
32
32
  id: z.ZodString;
33
33
  memberId: z.ZodString;
34
34
  memberType: z.ZodEnum<{
35
- normal: "normal";
36
35
  admin: "admin";
36
+ normal: "normal";
37
37
  observer: "observer";
38
38
  }>;
39
39
  unconfirmed: z.ZodBoolean;
@@ -230,8 +230,8 @@ export declare const TRELLO_PROVIDER_BOARD_MEMBERSHIP_SCHEMA: z.ZodObject<{
230
230
  id: z.ZodString;
231
231
  idMember: z.ZodString;
232
232
  memberType: z.ZodEnum<{
233
- normal: "normal";
234
233
  admin: "admin";
234
+ normal: "normal";
235
235
  observer: "observer";
236
236
  }>;
237
237
  unconfirmed: z.ZodBoolean;
@@ -10,6 +10,7 @@ import type { discordTriggerContracts } from "./discord/index.js";
10
10
  import type { closeTriggerContracts } from "./close/index.js";
11
11
  import type { cloudflareTriggerContracts } from "./cloudflare/index.js";
12
12
  import type { clickUpTriggerContracts } from "./clickup/index.js";
13
+ import type { calendlyTriggerContracts } from "./calendly/index.js";
13
14
  import type { githubTriggerContracts } from "./github/index.js";
14
15
  import type { gmailTriggerContracts } from "./gmail/index.js";
15
16
  import type { googleCalendarTriggerContracts } from "./google-calendar/index.js";
@@ -34,7 +35,7 @@ import type { vercelTriggerContracts } from "./vercel/index.js";
34
35
  import type { webflowTriggerContracts } from "./webflow/index.js";
35
36
  import type { whatsappTriggerContracts } from "./whatsapp/index.js";
36
37
  import type { z } from "zod";
37
- export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & typeof convexTriggerContracts & typeof discordTriggerContracts & typeof closeTriggerContracts & typeof clickUpTriggerContracts & typeof cloudflareTriggerContracts & typeof githubTriggerContracts & typeof gmailTriggerContracts & typeof googleCalendarTriggerContracts & typeof googleDriveTriggerContracts & typeof googleFormsTriggerContracts & typeof googleMeetTriggerContracts & typeof googleAdsTriggerContracts & typeof googleSheetsTriggerContracts & typeof hubspotTriggerContracts & typeof linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
38
+ export type TriggerContractMap = typeof airtableTriggerContracts & typeof anthropicTriggerContracts & typeof apifyTriggerContracts & typeof axiomTriggerContracts & typeof automateTriggerContracts & typeof asanaTriggerContracts & typeof brevoTriggerContracts & 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 linearTriggerContracts & typeof millionVerifierTriggerContracts & typeof metaAdsTriggerContracts & typeof notionTriggerContracts & typeof outlookTriggerContracts & typeof redditTriggerContracts & typeof resendTriggerContracts & typeof slackTriggerContracts & typeof stripeTriggerContracts & typeof teamsTriggerContracts & typeof trelloTriggerContracts & typeof vercelTriggerContracts & typeof webflowTriggerContracts & typeof whatsappTriggerContracts;
38
39
  export type IntegrationTriggerType = keyof TriggerContractMap;
39
40
  /** Canonical authoring configuration for one integration trigger type. */
40
41
  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.143.8",
3
+ "version": "0.144.0",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,6 +25,7 @@
25
25
  "./axiom": "./src/axiom/index.ts",
26
26
  "./asana": "./src/asana/index.ts",
27
27
  "./clickup": "./src/clickup/index.ts",
28
+ "./calendly": "./src/calendly/index.ts",
28
29
  "./brevo": "./src/brevo/index.ts",
29
30
  "./cloudflare": "./src/cloudflare/index.ts",
30
31
  "./meta-ads": "./src/meta-ads/index.ts",
@@ -65,7 +66,7 @@
65
66
  },
66
67
  "dependencies": {
67
68
  "@anthropic-ai/sdk": "0.123.0",
68
- "@automate.ax/codec": "0.143.8",
69
+ "@automate.ax/codec": "0.144.0",
69
70
  "@cfworker/json-schema": "^4.1.1",
70
71
  "@googleapis/calendar": "^16.0.0",
71
72
  "@googleapis/forms": "^6.0.1",
@@ -153,6 +154,11 @@
153
154
  "types": "./dist/clickup/index.d.ts",
154
155
  "default": "./dist/clickup/index.js"
155
156
  },
157
+ "./calendly": {
158
+ "bun": "./src/calendly/index.ts",
159
+ "types": "./dist/calendly/index.d.ts",
160
+ "default": "./dist/calendly/index.js"
161
+ },
156
162
  "./brevo": {
157
163
  "bun": "./src/brevo/index.ts",
158
164
  "types": "./dist/brevo/index.d.ts",
@@ -0,0 +1,216 @@
1
+ import type { Encodable } from "@automate.ax/codec"
2
+ import { encodableSchema } from "@automate.ax/codec"
3
+ import * as z from "zod"
4
+
5
+ const CALENDLY_API_BASE_URL = "https://api.calendly.com/"
6
+ const CALENDLY_API_ORIGIN = new URL(CALENDLY_API_BASE_URL).origin
7
+ const CALENDLY_OAUTH_SECRET_SCHEMA = z.object({
8
+ accessToken: z.string().min(1),
9
+ })
10
+
11
+ /** Resolved Calendly OAuth account accepted by the shared API client. */
12
+ export interface CalendlyResolvedAccount {
13
+ connectionMethodId: string
14
+ secret: Record<string, unknown>
15
+ serviceId: "calendly"
16
+ }
17
+
18
+ /** Options for one authenticated Calendly REST request. */
19
+ export interface CalendlyRequestOptions<TSchema extends z.ZodType> {
20
+ body?: Encodable
21
+ method?: "DELETE" | "GET" | "PATCH" | "POST"
22
+ query?: Record<string, boolean | number | string | undefined>
23
+ responseSchema: TSchema
24
+ }
25
+
26
+ /** Structured Calendly REST error. */
27
+ export class CalendlyApiError extends Error {
28
+ readonly body?: Encodable
29
+ readonly rateLimitReset?: number
30
+ readonly status: number
31
+
32
+ /**
33
+ * Creates a structured Calendly API error.
34
+ *
35
+ * @param options - Provider response details.
36
+ * @param options.body - Parsed provider error body.
37
+ * @param options.rateLimitReset - Provider retry delay, in seconds.
38
+ * @param options.status - HTTP response status.
39
+ */
40
+ constructor(options: {
41
+ body?: Encodable
42
+ rateLimitReset?: number
43
+ status: number
44
+ }) {
45
+ super(
46
+ getErrorMessage(options.body) ??
47
+ `Calendly API request failed with status ${options.status}.`,
48
+ )
49
+ this.name = "CalendlyApiError"
50
+ this.body = options.body
51
+ this.rateLimitReset = options.rateLimitReset
52
+ this.status = options.status
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Creates an authenticated Calendly REST client.
58
+ *
59
+ * @param account - Resolved Calendly OAuth account.
60
+ * @throws When the account method or secret is invalid.
61
+ */
62
+ export function getCalendlyApi(account: CalendlyResolvedAccount) {
63
+ if (account.connectionMethodId !== "oauth") {
64
+ throw new Error(
65
+ `Unsupported Calendly connection method: ${account.connectionMethodId}`,
66
+ )
67
+ }
68
+ const { accessToken } = CALENDLY_OAUTH_SECRET_SCHEMA.parse(account.secret)
69
+
70
+ /**
71
+ * Calls one Calendly endpoint and validates the response.
72
+ *
73
+ * @param path - Calendly API path.
74
+ * @param options - Request options and response schema.
75
+ */
76
+ async function request<TSchema extends z.ZodType>(
77
+ path: string,
78
+ options: CalendlyRequestOptions<TSchema>,
79
+ ): Promise<z.output<TSchema>> {
80
+ const url = new URL(path.replace(/^\//, ""), CALENDLY_API_BASE_URL)
81
+ if (url.origin !== CALENDLY_API_ORIGIN) {
82
+ throw new Error("Calendly API paths must use the Calendly API origin.")
83
+ }
84
+ for (const [name, value] of Object.entries(options.query ?? {})) {
85
+ if (value !== undefined)
86
+ url.searchParams.set(toSnakeCase(name), String(value))
87
+ }
88
+ const response = await fetch(url, {
89
+ body:
90
+ options.body === undefined
91
+ ? undefined
92
+ : JSON.stringify(toCalendly(options.body)),
93
+ headers: {
94
+ Accept: "application/json",
95
+ Authorization: `Bearer ${accessToken}`,
96
+ ...(options.body === undefined
97
+ ? {}
98
+ : { "Content-Type": "application/json" }),
99
+ },
100
+ method: options.method ?? "GET",
101
+ })
102
+ const text = await response.text()
103
+ const normalized = text ? fromCalendly(parseJson(text)) : undefined
104
+ if (!response.ok) {
105
+ const body = encodableSchema.safeParse(normalized)
106
+ throw new CalendlyApiError({
107
+ ...(body.success && { body: body.data }),
108
+ rateLimitReset: parseFiniteNumber(
109
+ response.headers.get("X-RateLimit-Reset"),
110
+ ),
111
+ status: response.status,
112
+ })
113
+ }
114
+ return options.responseSchema.parse(normalized)
115
+ }
116
+
117
+ return { request }
118
+ }
119
+
120
+ /**
121
+ * Converts public camelCase JSON to Calendly wire keys.
122
+ *
123
+ * @param value - Public action input.
124
+ */
125
+ export function toCalendly(value: Encodable): Encodable {
126
+ if (value instanceof Date) return value.toISOString()
127
+ if (Array.isArray(value)) return value.map(toCalendly)
128
+ if (!isPlainObject(value)) return value
129
+ return Object.fromEntries(
130
+ Object.entries(value).map(([key, item]) => [
131
+ toSnakeCase(key),
132
+ toCalendly(item),
133
+ ]),
134
+ )
135
+ }
136
+
137
+ /**
138
+ * Converts Calendly wire JSON to public camelCase keys.
139
+ *
140
+ * @param value - Parsed Calendly response value.
141
+ */
142
+ export function fromCalendly(value: unknown): unknown {
143
+ if (Array.isArray(value)) return value.map(fromCalendly)
144
+ if (!isPlainObject(value)) return value
145
+ return Object.fromEntries(
146
+ Object.entries(value).map(([key, item]) => [
147
+ toCamelCase(key),
148
+ fromCalendly(item),
149
+ ]),
150
+ )
151
+ }
152
+
153
+ /**
154
+ * Whether a value can be traversed as a plain JSON object.
155
+ *
156
+ * @param value - Candidate JSON value.
157
+ */
158
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
159
+ return typeof value === "object" && value !== null && !Array.isArray(value)
160
+ }
161
+
162
+ /**
163
+ * Parses a provider body without masking its HTTP failure.
164
+ *
165
+ * @param value - Provider response body.
166
+ */
167
+ function parseJson(value: string): unknown {
168
+ try {
169
+ return JSON.parse(value)
170
+ } catch {
171
+ return undefined
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Parses a finite numeric response header.
177
+ *
178
+ * @param value - Response header value.
179
+ */
180
+ function parseFiniteNumber(value: string | null) {
181
+ if (value === null) return undefined
182
+ const number = Number(value)
183
+ return Number.isFinite(number) ? number : undefined
184
+ }
185
+
186
+ /**
187
+ * Extracts Calendly's human-readable error message.
188
+ *
189
+ * @param body - Parsed provider error body.
190
+ */
191
+ function getErrorMessage(body: Encodable | undefined) {
192
+ if (!isPlainObject(body)) return undefined
193
+ if (typeof body.message === "string") return body.message
194
+ if (typeof body.title === "string") return body.title
195
+ return undefined
196
+ }
197
+
198
+ /**
199
+ * Converts one public field name to Calendly's wire convention.
200
+ *
201
+ * @param value - Public field name.
202
+ */
203
+ function toSnakeCase(value: string) {
204
+ return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
205
+ }
206
+
207
+ /**
208
+ * Converts one Calendly wire field name to the public convention.
209
+ *
210
+ * @param value - Calendly wire field name.
211
+ */
212
+ function toCamelCase(value: string) {
213
+ return value.replace(/_([a-z0-9])/g, (_, letter: string) =>
214
+ letter.toUpperCase(),
215
+ )
216
+ }
@@ -0,0 +1,69 @@
1
+ export * from "./api"
2
+ export * from "./schemas"
3
+
4
+ import * as z from "zod"
5
+ import {
6
+ CALENDLY_ORGANIZATION_TRIGGER_CONFIG_SCHEMA,
7
+ CALENDLY_PROVIDER_EVENT_NAMES,
8
+ CALENDLY_TRIGGER_CONFIG_SCHEMA,
9
+ CALENDLY_WEBHOOK_EVENT_SCHEMA,
10
+ } from "./schemas"
11
+
12
+ export const CALENDLY_EVENT_TYPE_BY_PROVIDER_EVENT = {
13
+ "event_type.created": "calendly.event-type.created",
14
+ "event_type.deleted": "calendly.event-type.deleted",
15
+ "event_type.updated": "calendly.event-type.updated",
16
+ "invitee.canceled": "calendly.invitee.canceled",
17
+ "invitee.created": "calendly.invitee.created",
18
+ "invitee_no_show.created": "calendly.invitee-no-show.created",
19
+ "invitee_no_show.deleted": "calendly.invitee-no-show.deleted",
20
+ "routing_form_submission.created": "calendly.routing-form-submission.created",
21
+ } as const satisfies Record<
22
+ (typeof CALENDLY_PROVIDER_EVENT_NAMES)[number],
23
+ string
24
+ >
25
+
26
+ /**
27
+ * Builds a semantic contract for one Calendly provider event.
28
+ *
29
+ * @param event - Calendly provider event name.
30
+ */
31
+ function semanticContract<
32
+ const TEvent extends keyof typeof CALENDLY_EVENT_TYPE_BY_PROVIDER_EVENT,
33
+ >(event: TEvent) {
34
+ return {
35
+ configSchema: CALENDLY_TRIGGER_CONFIG_SCHEMA,
36
+ eventSchema: CALENDLY_WEBHOOK_EVENT_SCHEMA.and(
37
+ z.object({ event: z.literal(event) }),
38
+ ),
39
+ }
40
+ }
41
+
42
+ export const calendlyTriggerContracts = {
43
+ "calendly.scheduling.event": {
44
+ configSchema: CALENDLY_ORGANIZATION_TRIGGER_CONFIG_SCHEMA,
45
+ eventSchema: CALENDLY_WEBHOOK_EVENT_SCHEMA,
46
+ },
47
+ "calendly.event-type.created": semanticContract("event_type.created"),
48
+ "calendly.event-type.deleted": semanticContract("event_type.deleted"),
49
+ "calendly.event-type.updated": semanticContract("event_type.updated"),
50
+ "calendly.invitee.canceled": semanticContract("invitee.canceled"),
51
+ "calendly.invitee.created": semanticContract("invitee.created"),
52
+ "calendly.invitee-no-show.created": semanticContract(
53
+ "invitee_no_show.created",
54
+ ),
55
+ "calendly.invitee-no-show.deleted": semanticContract(
56
+ "invitee_no_show.deleted",
57
+ ),
58
+ "calendly.routing-form-submission.created": {
59
+ configSchema: CALENDLY_ORGANIZATION_TRIGGER_CONFIG_SCHEMA,
60
+ eventSchema: CALENDLY_WEBHOOK_EVENT_SCHEMA.and(
61
+ z.object({ event: z.literal("routing_form_submission.created") }),
62
+ ),
63
+ },
64
+ } as const
65
+
66
+ export const CALENDLY_EVENT_TYPES = [
67
+ "calendly.scheduling.event",
68
+ ...Object.values(CALENDLY_EVENT_TYPE_BY_PROVIDER_EVENT),
69
+ ] as const