@automate.ax/integration-contracts 0.115.6 → 0.118.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/gmail/index.d.ts +269 -0
- package/dist/gmail/index.js +13 -1
- package/dist/gmail/schemas.d.ts +204 -0
- package/dist/gmail/schemas.js +24 -0
- package/dist/google-calendar/event-schemas.d.ts +346 -0
- package/dist/google-calendar/event-schemas.js +14 -0
- package/dist/google-calendar/index.d.ts +350 -0
- package/dist/google-calendar/index.js +5 -1
- package/dist/millionverifier/api.d.ts +62 -0
- package/dist/millionverifier/api.js +114 -0
- package/dist/millionverifier/events.d.ts +126 -0
- package/dist/millionverifier/events.js +103 -0
- package/dist/millionverifier/index.d.ts +77 -0
- package/dist/millionverifier/index.js +17 -0
- package/dist/millionverifier/schemas.d.ts +226 -0
- package/dist/millionverifier/schemas.js +188 -0
- package/dist/tidycal/api.d.ts +62 -0
- package/dist/tidycal/api.js +192 -0
- package/dist/tidycal/index.d.ts +2 -0
- package/dist/tidycal/index.js +2 -0
- package/dist/tidycal/schemas.d.ts +121 -0
- package/dist/tidycal/schemas.js +95 -0
- package/dist/triggers.d.ts +2 -1
- package/package.json +14 -2
- package/src/gmail/index.ts +18 -1
- package/src/gmail/schemas.ts +26 -0
- package/src/google-calendar/event-schemas.ts +18 -0
- package/src/google-calendar/index.ts +8 -1
- package/src/millionverifier/api.ts +151 -0
- package/src/millionverifier/events.ts +110 -0
- package/src/millionverifier/index.ts +33 -0
- package/src/millionverifier/schemas.ts +207 -0
- package/src/tidycal/api.ts +239 -0
- package/src/tidycal/index.ts +2 -0
- package/src/tidycal/schemas.ts +105 -0
- package/src/triggers.ts +2 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { Encodable } from "@automate.ax/codec"
|
|
2
|
+
import { encodableSchema } from "@automate.ax/codec"
|
|
3
|
+
import * as z from "zod"
|
|
4
|
+
|
|
5
|
+
const TIDYCAL_API_BASE_URL = "https://tidycal.com/api/"
|
|
6
|
+
const TIDYCAL_API_ORIGIN = new URL(TIDYCAL_API_BASE_URL).origin
|
|
7
|
+
const TIDYCAL_PERSONAL_ACCESS_TOKEN_SECRET_SCHEMA = z.object({
|
|
8
|
+
apiKey: z.string().min(1),
|
|
9
|
+
})
|
|
10
|
+
const TIDYCAL_OAUTH_SECRET_SCHEMA = z.object({
|
|
11
|
+
accessToken: z.string().min(1),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
/** Resolved TidyCal account accepted by the shared API client. */
|
|
15
|
+
export interface TidyCalResolvedAccount {
|
|
16
|
+
connectionMethodId: string
|
|
17
|
+
secret: Record<string, unknown>
|
|
18
|
+
serviceId: "tidycal"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Options for one authenticated TidyCal REST request. */
|
|
22
|
+
export interface TidyCalRequestOptions<TSchema extends z.ZodType> {
|
|
23
|
+
/** Public camelCase JSON body. */
|
|
24
|
+
body?: Encodable
|
|
25
|
+
|
|
26
|
+
/** HTTP verb. Defaults to `GET`. */
|
|
27
|
+
method?: "DELETE" | "GET" | "PATCH" | "POST"
|
|
28
|
+
|
|
29
|
+
/** Public camelCase query parameters. */
|
|
30
|
+
query?: Record<string, boolean | number | string | undefined>
|
|
31
|
+
|
|
32
|
+
/** Schema for the normalized provider response. */
|
|
33
|
+
responseSchema: TSchema
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Structured TidyCal REST error. */
|
|
37
|
+
export class TidyCalApiError extends Error {
|
|
38
|
+
/** Normalized provider error payload, when it was valid JSON. */
|
|
39
|
+
readonly body?: Encodable
|
|
40
|
+
|
|
41
|
+
/** Retry delay in seconds, when TidyCal returned one. */
|
|
42
|
+
readonly retryAfter?: number
|
|
43
|
+
|
|
44
|
+
/** HTTP status returned by TidyCal. */
|
|
45
|
+
readonly status: number
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a structured error from one TidyCal response.
|
|
49
|
+
*
|
|
50
|
+
* @param options - Response status, provider body, and retry metadata.
|
|
51
|
+
* @param options.body - Normalized provider error payload.
|
|
52
|
+
* @param options.retryAfter - Retry delay in seconds.
|
|
53
|
+
* @param options.status - HTTP response status.
|
|
54
|
+
*/
|
|
55
|
+
constructor(options: {
|
|
56
|
+
body?: Encodable
|
|
57
|
+
retryAfter?: number
|
|
58
|
+
status: number
|
|
59
|
+
}) {
|
|
60
|
+
super(
|
|
61
|
+
getErrorMessage(options.body) ??
|
|
62
|
+
`TidyCal API request failed with status ${options.status}.`,
|
|
63
|
+
)
|
|
64
|
+
this.name = "TidyCalApiError"
|
|
65
|
+
this.body = options.body
|
|
66
|
+
this.retryAfter = options.retryAfter
|
|
67
|
+
this.status = options.status
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Creates an authenticated TidyCal REST client.
|
|
73
|
+
*
|
|
74
|
+
* @param account - Resolved OAuth or personal-access-token account.
|
|
75
|
+
* @throws When the connection method or secret shape is unsupported.
|
|
76
|
+
*/
|
|
77
|
+
export function getTidyCalApi(account: TidyCalResolvedAccount) {
|
|
78
|
+
const accessToken =
|
|
79
|
+
account.connectionMethodId === "oauth"
|
|
80
|
+
? TIDYCAL_OAUTH_SECRET_SCHEMA.parse(account.secret).accessToken
|
|
81
|
+
: account.connectionMethodId === "personal-access-token"
|
|
82
|
+
? TIDYCAL_PERSONAL_ACCESS_TOKEN_SECRET_SCHEMA.parse(account.secret)
|
|
83
|
+
.apiKey
|
|
84
|
+
: undefined
|
|
85
|
+
if (!accessToken) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Unsupported TidyCal connection method: ${account.connectionMethodId}`,
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Runs one request and returns its normalized response.
|
|
93
|
+
*
|
|
94
|
+
* @param path - TidyCal API path relative to the API root.
|
|
95
|
+
* @param options - Method, parameters, and response schema.
|
|
96
|
+
*/
|
|
97
|
+
async function request<TSchema extends z.ZodType>(
|
|
98
|
+
path: string,
|
|
99
|
+
options: TidyCalRequestOptions<TSchema>,
|
|
100
|
+
): Promise<z.output<TSchema>> {
|
|
101
|
+
const url = new URL(path.replace(/^\//, ""), TIDYCAL_API_BASE_URL)
|
|
102
|
+
if (url.origin !== TIDYCAL_API_ORIGIN) {
|
|
103
|
+
throw new Error("TidyCal API paths must use the TidyCal API origin.")
|
|
104
|
+
}
|
|
105
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
106
|
+
if (value !== undefined)
|
|
107
|
+
url.searchParams.set(toSnakeCase(name), String(value))
|
|
108
|
+
}
|
|
109
|
+
const response = await fetch(url, {
|
|
110
|
+
body:
|
|
111
|
+
options.body === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: JSON.stringify(toTidyCal(options.body)),
|
|
114
|
+
headers: {
|
|
115
|
+
Accept: "application/json",
|
|
116
|
+
Authorization: `Bearer ${accessToken}`,
|
|
117
|
+
...(options.body === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { "Content-Type": "application/json" }),
|
|
120
|
+
},
|
|
121
|
+
method: options.method ?? "GET",
|
|
122
|
+
})
|
|
123
|
+
const normalized = fromTidyCal(parseJson(await response.text()))
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const body = encodableSchema.safeParse(normalized)
|
|
126
|
+
throw new TidyCalApiError({
|
|
127
|
+
...(body.success && { body: body.data }),
|
|
128
|
+
retryAfter: parseFiniteNumber(response.headers.get("Retry-After")),
|
|
129
|
+
status: response.status,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
return options.responseSchema.parse(normalized)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { request }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Converts public camelCase JSON to TidyCal wire keys.
|
|
140
|
+
*
|
|
141
|
+
* @param value - Codec-safe public value.
|
|
142
|
+
*/
|
|
143
|
+
export function toTidyCal(value: Encodable): Encodable {
|
|
144
|
+
if (value instanceof Date) return value.toISOString()
|
|
145
|
+
if (Array.isArray(value)) return value.map(toTidyCal)
|
|
146
|
+
if (!isPlainObject(value)) return value
|
|
147
|
+
return Object.fromEntries(
|
|
148
|
+
Object.entries(value).map(([key, item]) => [
|
|
149
|
+
toSnakeCase(key),
|
|
150
|
+
toTidyCal(item),
|
|
151
|
+
]),
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Converts TidyCal wire JSON to public camelCase keys.
|
|
157
|
+
*
|
|
158
|
+
* @param value - Untrusted provider JSON value.
|
|
159
|
+
*/
|
|
160
|
+
export function fromTidyCal(value: unknown): unknown {
|
|
161
|
+
if (Array.isArray(value)) return value.map(fromTidyCal)
|
|
162
|
+
if (!isPlainObject(value)) return value
|
|
163
|
+
return Object.fromEntries(
|
|
164
|
+
Object.entries(value).map(([key, item]) => [
|
|
165
|
+
toCamelCase(key),
|
|
166
|
+
fromTidyCal(item),
|
|
167
|
+
]),
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Returns whether a value is a plain JSON object.
|
|
173
|
+
*
|
|
174
|
+
* @param value - Value to inspect.
|
|
175
|
+
*/
|
|
176
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Parses JSON without obscuring the eventual structured API error.
|
|
182
|
+
*
|
|
183
|
+
* @param value - Response text to parse.
|
|
184
|
+
*/
|
|
185
|
+
function parseJson(value: string): unknown {
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(value)
|
|
188
|
+
} catch {
|
|
189
|
+
return undefined
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Parses a finite numeric response header.
|
|
195
|
+
*
|
|
196
|
+
* @param value - Header value.
|
|
197
|
+
*/
|
|
198
|
+
function parseFiniteNumber(value: string | null) {
|
|
199
|
+
if (value === null) return undefined
|
|
200
|
+
const number = Number(value)
|
|
201
|
+
return Number.isFinite(number) ? number : undefined
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extracts the provider's primary error text without weakening its payload.
|
|
206
|
+
*
|
|
207
|
+
* @param body - Normalized provider error body.
|
|
208
|
+
*/
|
|
209
|
+
function getErrorMessage(body: Encodable | undefined) {
|
|
210
|
+
if (!isPlainObject(body)) return undefined
|
|
211
|
+
if (typeof body.message === "string") return body.message
|
|
212
|
+
const errors = body.errors
|
|
213
|
+
if (!isPlainObject(errors)) return undefined
|
|
214
|
+
const first = Object.values(errors)[0]
|
|
215
|
+
if (typeof first === "string") return first
|
|
216
|
+
return Array.isArray(first) && typeof first[0] === "string"
|
|
217
|
+
? first[0]
|
|
218
|
+
: undefined
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Converts one camelCase public key to snake_case.
|
|
223
|
+
*
|
|
224
|
+
* @param value - Public key.
|
|
225
|
+
*/
|
|
226
|
+
function toSnakeCase(value: string) {
|
|
227
|
+
return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Converts one snake_case provider key to camelCase.
|
|
232
|
+
*
|
|
233
|
+
* @param value - Provider key.
|
|
234
|
+
*/
|
|
235
|
+
function toCamelCase(value: string) {
|
|
236
|
+
return value.replace(/_([a-z0-9])/g, (_, letter: string) =>
|
|
237
|
+
letter.toUpperCase(),
|
|
238
|
+
)
|
|
239
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as z from "zod"
|
|
2
|
+
|
|
3
|
+
const DATE_TIME_SCHEMA = z.iso.datetime({ offset: true })
|
|
4
|
+
const ID_SCHEMA = z.number().int().positive()
|
|
5
|
+
|
|
6
|
+
export const TIDYCAL_ACCOUNT_SCHEMA = z.object({
|
|
7
|
+
currencySymbol: z.string(),
|
|
8
|
+
email: z.email(),
|
|
9
|
+
language: z.string(),
|
|
10
|
+
lifetimeProAt: DATE_TIME_SCHEMA.nullable(),
|
|
11
|
+
name: z.string(),
|
|
12
|
+
profilePictureUrl: z.url().nullable(),
|
|
13
|
+
vanityPath: z.string(),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const TIDYCAL_CONTACT_SCHEMA = z.object({
|
|
17
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
18
|
+
email: z.email(),
|
|
19
|
+
id: ID_SCHEMA,
|
|
20
|
+
ipAddress: z.string().nullable().optional(),
|
|
21
|
+
name: z.string(),
|
|
22
|
+
phoneNumber: z.string().nullable().optional(),
|
|
23
|
+
timezone: z.string().nullable().optional(),
|
|
24
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const TIDYCAL_PAYMENT_SCHEMA = z.object({
|
|
28
|
+
amount: z.number().nonnegative(),
|
|
29
|
+
bookingId: ID_SCHEMA,
|
|
30
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
31
|
+
currency: z.string(),
|
|
32
|
+
id: ID_SCHEMA,
|
|
33
|
+
paymentId: z.string(),
|
|
34
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const TIDYCAL_QUESTION_SCHEMA = z.object({
|
|
38
|
+
answer: z.string(),
|
|
39
|
+
bookingId: ID_SCHEMA,
|
|
40
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
41
|
+
id: ID_SCHEMA,
|
|
42
|
+
question: z.string(),
|
|
43
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
export const TIDYCAL_BOOKING_SCHEMA = z.object({
|
|
47
|
+
bookingTypeId: ID_SCHEMA,
|
|
48
|
+
cancelledAt: DATE_TIME_SCHEMA.nullable(),
|
|
49
|
+
contact: TIDYCAL_CONTACT_SCHEMA,
|
|
50
|
+
contactId: ID_SCHEMA,
|
|
51
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
52
|
+
endsAt: DATE_TIME_SCHEMA,
|
|
53
|
+
id: ID_SCHEMA,
|
|
54
|
+
meetingId: z.string().nullable().optional(),
|
|
55
|
+
meetingUrl: z.url().nullable().optional(),
|
|
56
|
+
payment: TIDYCAL_PAYMENT_SCHEMA.nullable().optional(),
|
|
57
|
+
questions: TIDYCAL_QUESTION_SCHEMA.array(),
|
|
58
|
+
startsAt: DATE_TIME_SCHEMA,
|
|
59
|
+
timezone: z.string(),
|
|
60
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export const TIDYCAL_BOOKING_TYPE_SCHEMA = z.object({
|
|
64
|
+
bookingThresholdMinutes: z.number().int().min(0),
|
|
65
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
66
|
+
currencyCode: z.string().nullable().optional(),
|
|
67
|
+
description: z.string(),
|
|
68
|
+
disabledAt: DATE_TIME_SCHEMA.nullable(),
|
|
69
|
+
durationMinutes: z.number().int().positive(),
|
|
70
|
+
id: ID_SCHEMA,
|
|
71
|
+
latestAvailabilityDays: z.number().int().min(0),
|
|
72
|
+
maxBookings: z.number().int().positive(),
|
|
73
|
+
paddingMinutes: z.number().int().min(0),
|
|
74
|
+
paymentPlatform: z.enum(["paypal", "stripe", "tidycal"]).nullable(),
|
|
75
|
+
paymentPlatformId: z.string().nullable().optional(),
|
|
76
|
+
price: z.number().nonnegative(),
|
|
77
|
+
private: z.boolean(),
|
|
78
|
+
redirectUrl: z.url().nullable(),
|
|
79
|
+
title: z.string(),
|
|
80
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
81
|
+
url: z.url(),
|
|
82
|
+
urlSlug: z.string(),
|
|
83
|
+
userId: ID_SCHEMA,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
export const TIDYCAL_TIMESLOT_SCHEMA = z.object({
|
|
87
|
+
availableBookings: z.number().int().nonnegative(),
|
|
88
|
+
endsAt: DATE_TIME_SCHEMA,
|
|
89
|
+
startsAt: DATE_TIME_SCHEMA,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
export const TIDYCAL_TEAM_SCHEMA = z.object({
|
|
93
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
94
|
+
id: ID_SCHEMA,
|
|
95
|
+
name: z.string(),
|
|
96
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
export const TIDYCAL_TEAM_USER_SCHEMA = z.object({
|
|
100
|
+
createdAt: DATE_TIME_SCHEMA,
|
|
101
|
+
email: z.email(),
|
|
102
|
+
id: ID_SCHEMA,
|
|
103
|
+
name: z.string(),
|
|
104
|
+
updatedAt: DATE_TIME_SCHEMA,
|
|
105
|
+
})
|
package/src/triggers.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { googleFormsTriggerContracts } from "./google-forms"
|
|
|
12
12
|
import type { googleMeetTriggerContracts } from "./google-meet"
|
|
13
13
|
import type { googleSheetsTriggerContracts } from "./google-sheets"
|
|
14
14
|
import type { linearTriggerContracts } from "./linear"
|
|
15
|
+
import type { millionVerifierTriggerContracts } from "./millionverifier"
|
|
15
16
|
import type { notionTriggerContracts } from "./notion"
|
|
16
17
|
import type { outlookTriggerContracts } from "./outlook"
|
|
17
18
|
import type { resendTriggerContracts } from "./resend"
|
|
@@ -38,6 +39,7 @@ export type TriggerContractMap = typeof airtableTriggerContracts &
|
|
|
38
39
|
typeof googleMeetTriggerContracts &
|
|
39
40
|
typeof googleSheetsTriggerContracts &
|
|
40
41
|
typeof linearTriggerContracts &
|
|
42
|
+
typeof millionVerifierTriggerContracts &
|
|
41
43
|
typeof notionTriggerContracts &
|
|
42
44
|
typeof outlookTriggerContracts &
|
|
43
45
|
typeof resendTriggerContracts &
|