@automate.ax/integration-contracts 0.116.0 → 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.
@@ -7,6 +7,356 @@ export declare const GOOGLE_CALENDAR_TRIGGER_CONFIG_SCHEMA: z.ZodObject<{
7
7
  calendarId: z.ZodString;
8
8
  }, z.core.$strip>;
9
9
  export declare const googleCalendarTriggerContracts: {
10
+ readonly "googleCalendar.event.changed": {
11
+ readonly configSchema: z.ZodObject<{
12
+ calendarId: z.ZodString;
13
+ }, z.core.$strip>;
14
+ readonly eventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
15
+ changeType: z.ZodLiteral<"created">;
16
+ event: z.ZodObject<{
17
+ attachments: z.ZodArray<z.ZodObject<{
18
+ fileId: z.ZodOptional<z.ZodString>;
19
+ fileUrl: z.ZodURL;
20
+ mimeType: z.ZodOptional<z.ZodString>;
21
+ title: z.ZodOptional<z.ZodString>;
22
+ iconLink: z.ZodOptional<z.ZodString>;
23
+ }, z.core.$strip>>;
24
+ attendees: z.ZodArray<z.ZodObject<{
25
+ additionalGuests: z.ZodOptional<z.ZodNumber>;
26
+ comment: z.ZodOptional<z.ZodString>;
27
+ email: z.ZodEmail;
28
+ displayName: z.ZodOptional<z.ZodString>;
29
+ optional: z.ZodOptional<z.ZodBoolean>;
30
+ resource: z.ZodOptional<z.ZodBoolean>;
31
+ responseStatus: z.ZodOptional<z.ZodEnum<{
32
+ tentative: "tentative";
33
+ needsAction: "needsAction";
34
+ declined: "declined";
35
+ accepted: "accepted";
36
+ }>>;
37
+ organizer: z.ZodBoolean;
38
+ self: z.ZodBoolean;
39
+ }, z.core.$strip>>;
40
+ calendarId: z.ZodString;
41
+ colorId: z.ZodOptional<z.ZodString>;
42
+ conference: z.ZodOptional<z.ZodObject<{
43
+ conferenceId: z.ZodOptional<z.ZodString>;
44
+ creationStatus: z.ZodOptional<z.ZodEnum<{
45
+ success: "success";
46
+ failure: "failure";
47
+ pending: "pending";
48
+ }>>;
49
+ entryPoints: z.ZodArray<z.ZodObject<{
50
+ label: z.ZodOptional<z.ZodString>;
51
+ passcode: z.ZodOptional<z.ZodString>;
52
+ type: z.ZodString;
53
+ uri: z.ZodString;
54
+ }, z.core.$strip>>;
55
+ name: z.ZodOptional<z.ZodString>;
56
+ }, z.core.$strip>>;
57
+ createdAt: z.ZodOptional<z.ZodString>;
58
+ creator: z.ZodOptional<z.ZodObject<{
59
+ displayName: z.ZodOptional<z.ZodString>;
60
+ email: z.ZodOptional<z.ZodString>;
61
+ self: z.ZodBoolean;
62
+ }, z.core.$strip>>;
63
+ description: z.ZodOptional<z.ZodString>;
64
+ end: z.ZodOptional<z.ZodObject<{
65
+ date: z.ZodOptional<z.ZodISODate>;
66
+ dateTime: z.ZodOptional<z.ZodString>;
67
+ timeZone: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>>;
69
+ extendedProperties: z.ZodObject<{
70
+ private: z.ZodRecord<z.ZodString, z.ZodString>;
71
+ shared: z.ZodRecord<z.ZodString, z.ZodString>;
72
+ }, z.core.$strip>;
73
+ guestsCanInviteOthers: z.ZodBoolean;
74
+ guestsCanModify: z.ZodBoolean;
75
+ guestsCanSeeOtherGuests: z.ZodBoolean;
76
+ hangoutLink: z.ZodOptional<z.ZodString>;
77
+ htmlLink: z.ZodOptional<z.ZodString>;
78
+ id: z.ZodString;
79
+ iCalUID: z.ZodOptional<z.ZodString>;
80
+ location: z.ZodOptional<z.ZodString>;
81
+ originalStart: z.ZodOptional<z.ZodObject<{
82
+ date: z.ZodOptional<z.ZodISODate>;
83
+ dateTime: z.ZodOptional<z.ZodString>;
84
+ timeZone: z.ZodOptional<z.ZodString>;
85
+ }, z.core.$strip>>;
86
+ organizer: z.ZodOptional<z.ZodObject<{
87
+ displayName: z.ZodOptional<z.ZodString>;
88
+ email: z.ZodOptional<z.ZodString>;
89
+ self: z.ZodBoolean;
90
+ }, z.core.$strip>>;
91
+ recurrence: z.ZodArray<z.ZodString>;
92
+ recurringEventId: z.ZodOptional<z.ZodString>;
93
+ reminders: z.ZodObject<{
94
+ overrides: z.ZodArray<z.ZodObject<{
95
+ minutes: z.ZodNumber;
96
+ method: z.ZodEnum<{
97
+ email: "email";
98
+ popup: "popup";
99
+ }>;
100
+ }, z.core.$strip>>;
101
+ useDefault: z.ZodBoolean;
102
+ }, z.core.$strip>;
103
+ sequence: z.ZodNumber;
104
+ start: z.ZodOptional<z.ZodObject<{
105
+ date: z.ZodOptional<z.ZodISODate>;
106
+ dateTime: z.ZodOptional<z.ZodString>;
107
+ timeZone: z.ZodOptional<z.ZodString>;
108
+ }, z.core.$strip>>;
109
+ status: z.ZodEnum<{
110
+ cancelled: "cancelled";
111
+ confirmed: "confirmed";
112
+ tentative: "tentative";
113
+ }>;
114
+ summary: z.ZodString;
115
+ transparency: z.ZodEnum<{
116
+ opaque: "opaque";
117
+ transparent: "transparent";
118
+ }>;
119
+ type: z.ZodString;
120
+ updatedAt: z.ZodOptional<z.ZodString>;
121
+ visibility: z.ZodEnum<{
122
+ default: "default";
123
+ public: "public";
124
+ private: "private";
125
+ confidential: "confidential";
126
+ }>;
127
+ }, z.core.$strip>;
128
+ }, z.core.$strip>, z.ZodObject<{
129
+ changeType: z.ZodLiteral<"updated">;
130
+ event: z.ZodObject<{
131
+ attachments: z.ZodArray<z.ZodObject<{
132
+ fileId: z.ZodOptional<z.ZodString>;
133
+ fileUrl: z.ZodURL;
134
+ mimeType: z.ZodOptional<z.ZodString>;
135
+ title: z.ZodOptional<z.ZodString>;
136
+ iconLink: z.ZodOptional<z.ZodString>;
137
+ }, z.core.$strip>>;
138
+ attendees: z.ZodArray<z.ZodObject<{
139
+ additionalGuests: z.ZodOptional<z.ZodNumber>;
140
+ comment: z.ZodOptional<z.ZodString>;
141
+ email: z.ZodEmail;
142
+ displayName: z.ZodOptional<z.ZodString>;
143
+ optional: z.ZodOptional<z.ZodBoolean>;
144
+ resource: z.ZodOptional<z.ZodBoolean>;
145
+ responseStatus: z.ZodOptional<z.ZodEnum<{
146
+ tentative: "tentative";
147
+ needsAction: "needsAction";
148
+ declined: "declined";
149
+ accepted: "accepted";
150
+ }>>;
151
+ organizer: z.ZodBoolean;
152
+ self: z.ZodBoolean;
153
+ }, z.core.$strip>>;
154
+ calendarId: z.ZodString;
155
+ colorId: z.ZodOptional<z.ZodString>;
156
+ conference: z.ZodOptional<z.ZodObject<{
157
+ conferenceId: z.ZodOptional<z.ZodString>;
158
+ creationStatus: z.ZodOptional<z.ZodEnum<{
159
+ success: "success";
160
+ failure: "failure";
161
+ pending: "pending";
162
+ }>>;
163
+ entryPoints: z.ZodArray<z.ZodObject<{
164
+ label: z.ZodOptional<z.ZodString>;
165
+ passcode: z.ZodOptional<z.ZodString>;
166
+ type: z.ZodString;
167
+ uri: z.ZodString;
168
+ }, z.core.$strip>>;
169
+ name: z.ZodOptional<z.ZodString>;
170
+ }, z.core.$strip>>;
171
+ createdAt: z.ZodOptional<z.ZodString>;
172
+ creator: z.ZodOptional<z.ZodObject<{
173
+ displayName: z.ZodOptional<z.ZodString>;
174
+ email: z.ZodOptional<z.ZodString>;
175
+ self: z.ZodBoolean;
176
+ }, z.core.$strip>>;
177
+ description: z.ZodOptional<z.ZodString>;
178
+ end: z.ZodOptional<z.ZodObject<{
179
+ date: z.ZodOptional<z.ZodISODate>;
180
+ dateTime: z.ZodOptional<z.ZodString>;
181
+ timeZone: z.ZodOptional<z.ZodString>;
182
+ }, z.core.$strip>>;
183
+ extendedProperties: z.ZodObject<{
184
+ private: z.ZodRecord<z.ZodString, z.ZodString>;
185
+ shared: z.ZodRecord<z.ZodString, z.ZodString>;
186
+ }, z.core.$strip>;
187
+ guestsCanInviteOthers: z.ZodBoolean;
188
+ guestsCanModify: z.ZodBoolean;
189
+ guestsCanSeeOtherGuests: z.ZodBoolean;
190
+ hangoutLink: z.ZodOptional<z.ZodString>;
191
+ htmlLink: z.ZodOptional<z.ZodString>;
192
+ id: z.ZodString;
193
+ iCalUID: z.ZodOptional<z.ZodString>;
194
+ location: z.ZodOptional<z.ZodString>;
195
+ originalStart: z.ZodOptional<z.ZodObject<{
196
+ date: z.ZodOptional<z.ZodISODate>;
197
+ dateTime: z.ZodOptional<z.ZodString>;
198
+ timeZone: z.ZodOptional<z.ZodString>;
199
+ }, z.core.$strip>>;
200
+ organizer: z.ZodOptional<z.ZodObject<{
201
+ displayName: z.ZodOptional<z.ZodString>;
202
+ email: z.ZodOptional<z.ZodString>;
203
+ self: z.ZodBoolean;
204
+ }, z.core.$strip>>;
205
+ recurrence: z.ZodArray<z.ZodString>;
206
+ recurringEventId: z.ZodOptional<z.ZodString>;
207
+ reminders: z.ZodObject<{
208
+ overrides: z.ZodArray<z.ZodObject<{
209
+ minutes: z.ZodNumber;
210
+ method: z.ZodEnum<{
211
+ email: "email";
212
+ popup: "popup";
213
+ }>;
214
+ }, z.core.$strip>>;
215
+ useDefault: z.ZodBoolean;
216
+ }, z.core.$strip>;
217
+ sequence: z.ZodNumber;
218
+ start: z.ZodOptional<z.ZodObject<{
219
+ date: z.ZodOptional<z.ZodISODate>;
220
+ dateTime: z.ZodOptional<z.ZodString>;
221
+ timeZone: z.ZodOptional<z.ZodString>;
222
+ }, z.core.$strip>>;
223
+ status: z.ZodEnum<{
224
+ cancelled: "cancelled";
225
+ confirmed: "confirmed";
226
+ tentative: "tentative";
227
+ }>;
228
+ summary: z.ZodString;
229
+ transparency: z.ZodEnum<{
230
+ opaque: "opaque";
231
+ transparent: "transparent";
232
+ }>;
233
+ type: z.ZodString;
234
+ updatedAt: z.ZodOptional<z.ZodString>;
235
+ visibility: z.ZodEnum<{
236
+ default: "default";
237
+ public: "public";
238
+ private: "private";
239
+ confidential: "confidential";
240
+ }>;
241
+ }, z.core.$strip>;
242
+ }, z.core.$strip>, z.ZodObject<{
243
+ calendarId: z.ZodString;
244
+ event: z.ZodOptional<z.ZodObject<{
245
+ attachments: z.ZodArray<z.ZodObject<{
246
+ fileId: z.ZodOptional<z.ZodString>;
247
+ fileUrl: z.ZodURL;
248
+ mimeType: z.ZodOptional<z.ZodString>;
249
+ title: z.ZodOptional<z.ZodString>;
250
+ iconLink: z.ZodOptional<z.ZodString>;
251
+ }, z.core.$strip>>;
252
+ attendees: z.ZodArray<z.ZodObject<{
253
+ additionalGuests: z.ZodOptional<z.ZodNumber>;
254
+ comment: z.ZodOptional<z.ZodString>;
255
+ email: z.ZodEmail;
256
+ displayName: z.ZodOptional<z.ZodString>;
257
+ optional: z.ZodOptional<z.ZodBoolean>;
258
+ resource: z.ZodOptional<z.ZodBoolean>;
259
+ responseStatus: z.ZodOptional<z.ZodEnum<{
260
+ tentative: "tentative";
261
+ needsAction: "needsAction";
262
+ declined: "declined";
263
+ accepted: "accepted";
264
+ }>>;
265
+ organizer: z.ZodBoolean;
266
+ self: z.ZodBoolean;
267
+ }, z.core.$strip>>;
268
+ calendarId: z.ZodString;
269
+ colorId: z.ZodOptional<z.ZodString>;
270
+ conference: z.ZodOptional<z.ZodObject<{
271
+ conferenceId: z.ZodOptional<z.ZodString>;
272
+ creationStatus: z.ZodOptional<z.ZodEnum<{
273
+ success: "success";
274
+ failure: "failure";
275
+ pending: "pending";
276
+ }>>;
277
+ entryPoints: z.ZodArray<z.ZodObject<{
278
+ label: z.ZodOptional<z.ZodString>;
279
+ passcode: z.ZodOptional<z.ZodString>;
280
+ type: z.ZodString;
281
+ uri: z.ZodString;
282
+ }, z.core.$strip>>;
283
+ name: z.ZodOptional<z.ZodString>;
284
+ }, z.core.$strip>>;
285
+ createdAt: z.ZodOptional<z.ZodString>;
286
+ creator: z.ZodOptional<z.ZodObject<{
287
+ displayName: z.ZodOptional<z.ZodString>;
288
+ email: z.ZodOptional<z.ZodString>;
289
+ self: z.ZodBoolean;
290
+ }, z.core.$strip>>;
291
+ description: z.ZodOptional<z.ZodString>;
292
+ end: z.ZodOptional<z.ZodObject<{
293
+ date: z.ZodOptional<z.ZodISODate>;
294
+ dateTime: z.ZodOptional<z.ZodString>;
295
+ timeZone: z.ZodOptional<z.ZodString>;
296
+ }, z.core.$strip>>;
297
+ extendedProperties: z.ZodObject<{
298
+ private: z.ZodRecord<z.ZodString, z.ZodString>;
299
+ shared: z.ZodRecord<z.ZodString, z.ZodString>;
300
+ }, z.core.$strip>;
301
+ guestsCanInviteOthers: z.ZodBoolean;
302
+ guestsCanModify: z.ZodBoolean;
303
+ guestsCanSeeOtherGuests: z.ZodBoolean;
304
+ hangoutLink: z.ZodOptional<z.ZodString>;
305
+ htmlLink: z.ZodOptional<z.ZodString>;
306
+ id: z.ZodString;
307
+ iCalUID: z.ZodOptional<z.ZodString>;
308
+ location: z.ZodOptional<z.ZodString>;
309
+ originalStart: z.ZodOptional<z.ZodObject<{
310
+ date: z.ZodOptional<z.ZodISODate>;
311
+ dateTime: z.ZodOptional<z.ZodString>;
312
+ timeZone: z.ZodOptional<z.ZodString>;
313
+ }, z.core.$strip>>;
314
+ organizer: z.ZodOptional<z.ZodObject<{
315
+ displayName: z.ZodOptional<z.ZodString>;
316
+ email: z.ZodOptional<z.ZodString>;
317
+ self: z.ZodBoolean;
318
+ }, z.core.$strip>>;
319
+ recurrence: z.ZodArray<z.ZodString>;
320
+ recurringEventId: z.ZodOptional<z.ZodString>;
321
+ reminders: z.ZodObject<{
322
+ overrides: z.ZodArray<z.ZodObject<{
323
+ minutes: z.ZodNumber;
324
+ method: z.ZodEnum<{
325
+ email: "email";
326
+ popup: "popup";
327
+ }>;
328
+ }, z.core.$strip>>;
329
+ useDefault: z.ZodBoolean;
330
+ }, z.core.$strip>;
331
+ sequence: z.ZodNumber;
332
+ start: z.ZodOptional<z.ZodObject<{
333
+ date: z.ZodOptional<z.ZodISODate>;
334
+ dateTime: z.ZodOptional<z.ZodString>;
335
+ timeZone: z.ZodOptional<z.ZodString>;
336
+ }, z.core.$strip>>;
337
+ status: z.ZodEnum<{
338
+ cancelled: "cancelled";
339
+ confirmed: "confirmed";
340
+ tentative: "tentative";
341
+ }>;
342
+ summary: z.ZodString;
343
+ transparency: z.ZodEnum<{
344
+ opaque: "opaque";
345
+ transparent: "transparent";
346
+ }>;
347
+ type: z.ZodString;
348
+ updatedAt: z.ZodOptional<z.ZodString>;
349
+ visibility: z.ZodEnum<{
350
+ default: "default";
351
+ public: "public";
352
+ private: "private";
353
+ confidential: "confidential";
354
+ }>;
355
+ }, z.core.$strip>>;
356
+ eventId: z.ZodString;
357
+ changeType: z.ZodLiteral<"deleted">;
358
+ }, z.core.$strip>], "changeType">;
359
+ };
10
360
  readonly "googleCalendar.event.created": {
11
361
  readonly configSchema: z.ZodObject<{
12
362
  calendarId: z.ZodString;
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod";
2
- import { GOOGLE_CALENDAR_EVENT_DELETED_SCHEMA } from "./event-schemas.js";
2
+ import { GOOGLE_CALENDAR_EVENT_CHANGED_SCHEMA, GOOGLE_CALENDAR_EVENT_DELETED_SCHEMA, } from "./event-schemas.js";
3
3
  import { CALENDAR_EVENT_SCHEMA } from "./schemas.js";
4
4
  export * from "./event-schemas.js";
5
5
  export * from "./google-calendar.js";
@@ -9,6 +9,10 @@ export const GOOGLE_CALENDAR_TRIGGER_CONFIG_SCHEMA = z.object({
9
9
  calendarId: z.string(),
10
10
  });
11
11
  export const googleCalendarTriggerContracts = {
12
+ "googleCalendar.event.changed": {
13
+ configSchema: GOOGLE_CALENDAR_TRIGGER_CONFIG_SCHEMA,
14
+ eventSchema: GOOGLE_CALENDAR_EVENT_CHANGED_SCHEMA,
15
+ },
12
16
  "googleCalendar.event.created": {
13
17
  configSchema: GOOGLE_CALENDAR_TRIGGER_CONFIG_SCHEMA,
14
18
  eventSchema: CALENDAR_EVENT_SCHEMA,
@@ -0,0 +1,62 @@
1
+ import type { Encodable } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ /** Resolved TidyCal account accepted by the shared API client. */
4
+ export interface TidyCalResolvedAccount {
5
+ connectionMethodId: string;
6
+ secret: Record<string, unknown>;
7
+ serviceId: "tidycal";
8
+ }
9
+ /** Options for one authenticated TidyCal REST request. */
10
+ export interface TidyCalRequestOptions<TSchema extends z.ZodType> {
11
+ /** Public camelCase JSON body. */
12
+ body?: Encodable;
13
+ /** HTTP verb. Defaults to `GET`. */
14
+ method?: "DELETE" | "GET" | "PATCH" | "POST";
15
+ /** Public camelCase query parameters. */
16
+ query?: Record<string, boolean | number | string | undefined>;
17
+ /** Schema for the normalized provider response. */
18
+ responseSchema: TSchema;
19
+ }
20
+ /** Structured TidyCal REST error. */
21
+ export declare class TidyCalApiError extends Error {
22
+ /** Normalized provider error payload, when it was valid JSON. */
23
+ readonly body?: Encodable;
24
+ /** Retry delay in seconds, when TidyCal returned one. */
25
+ readonly retryAfter?: number;
26
+ /** HTTP status returned by TidyCal. */
27
+ readonly status: number;
28
+ /**
29
+ * Creates a structured error from one TidyCal response.
30
+ *
31
+ * @param options - Response status, provider body, and retry metadata.
32
+ * @param options.body - Normalized provider error payload.
33
+ * @param options.retryAfter - Retry delay in seconds.
34
+ * @param options.status - HTTP response status.
35
+ */
36
+ constructor(options: {
37
+ body?: Encodable;
38
+ retryAfter?: number;
39
+ status: number;
40
+ });
41
+ }
42
+ /**
43
+ * Creates an authenticated TidyCal REST client.
44
+ *
45
+ * @param account - Resolved OAuth or personal-access-token account.
46
+ * @throws When the connection method or secret shape is unsupported.
47
+ */
48
+ export declare function getTidyCalApi(account: TidyCalResolvedAccount): {
49
+ request: <TSchema extends z.ZodType>(path: string, options: TidyCalRequestOptions<TSchema>) => Promise<z.output<TSchema>>;
50
+ };
51
+ /**
52
+ * Converts public camelCase JSON to TidyCal wire keys.
53
+ *
54
+ * @param value - Codec-safe public value.
55
+ */
56
+ export declare function toTidyCal(value: Encodable): Encodable;
57
+ /**
58
+ * Converts TidyCal wire JSON to public camelCase keys.
59
+ *
60
+ * @param value - Untrusted provider JSON value.
61
+ */
62
+ export declare function fromTidyCal(value: unknown): unknown;
@@ -0,0 +1,192 @@
1
+ import { encodableSchema } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ const TIDYCAL_API_BASE_URL = "https://tidycal.com/api/";
4
+ const TIDYCAL_API_ORIGIN = new URL(TIDYCAL_API_BASE_URL).origin;
5
+ const TIDYCAL_PERSONAL_ACCESS_TOKEN_SECRET_SCHEMA = z.object({
6
+ apiKey: z.string().min(1),
7
+ });
8
+ const TIDYCAL_OAUTH_SECRET_SCHEMA = z.object({
9
+ accessToken: z.string().min(1),
10
+ });
11
+ /** Structured TidyCal REST error. */
12
+ export class TidyCalApiError extends Error {
13
+ /** Normalized provider error payload, when it was valid JSON. */
14
+ body;
15
+ /** Retry delay in seconds, when TidyCal returned one. */
16
+ retryAfter;
17
+ /** HTTP status returned by TidyCal. */
18
+ status;
19
+ /**
20
+ * Creates a structured error from one TidyCal response.
21
+ *
22
+ * @param options - Response status, provider body, and retry metadata.
23
+ * @param options.body - Normalized provider error payload.
24
+ * @param options.retryAfter - Retry delay in seconds.
25
+ * @param options.status - HTTP response status.
26
+ */
27
+ constructor(options) {
28
+ super(getErrorMessage(options.body) ??
29
+ `TidyCal API request failed with status ${options.status}.`);
30
+ this.name = "TidyCalApiError";
31
+ this.body = options.body;
32
+ this.retryAfter = options.retryAfter;
33
+ this.status = options.status;
34
+ }
35
+ }
36
+ /**
37
+ * Creates an authenticated TidyCal REST client.
38
+ *
39
+ * @param account - Resolved OAuth or personal-access-token account.
40
+ * @throws When the connection method or secret shape is unsupported.
41
+ */
42
+ export function getTidyCalApi(account) {
43
+ const accessToken = account.connectionMethodId === "oauth"
44
+ ? TIDYCAL_OAUTH_SECRET_SCHEMA.parse(account.secret).accessToken
45
+ : account.connectionMethodId === "personal-access-token"
46
+ ? TIDYCAL_PERSONAL_ACCESS_TOKEN_SECRET_SCHEMA.parse(account.secret)
47
+ .apiKey
48
+ : undefined;
49
+ if (!accessToken) {
50
+ throw new Error(`Unsupported TidyCal connection method: ${account.connectionMethodId}`);
51
+ }
52
+ /**
53
+ * Runs one request and returns its normalized response.
54
+ *
55
+ * @param path - TidyCal API path relative to the API root.
56
+ * @param options - Method, parameters, and response schema.
57
+ */
58
+ async function request(path, options) {
59
+ const url = new URL(path.replace(/^\//, ""), TIDYCAL_API_BASE_URL);
60
+ if (url.origin !== TIDYCAL_API_ORIGIN) {
61
+ throw new Error("TidyCal API paths must use the TidyCal API origin.");
62
+ }
63
+ for (const [name, value] of Object.entries(options.query ?? {})) {
64
+ if (value !== undefined)
65
+ url.searchParams.set(toSnakeCase(name), String(value));
66
+ }
67
+ const response = await fetch(url, {
68
+ body: options.body === undefined
69
+ ? undefined
70
+ : JSON.stringify(toTidyCal(options.body)),
71
+ headers: {
72
+ Accept: "application/json",
73
+ Authorization: `Bearer ${accessToken}`,
74
+ ...(options.body === undefined
75
+ ? {}
76
+ : { "Content-Type": "application/json" }),
77
+ },
78
+ method: options.method ?? "GET",
79
+ });
80
+ const normalized = fromTidyCal(parseJson(await response.text()));
81
+ if (!response.ok) {
82
+ const body = encodableSchema.safeParse(normalized);
83
+ throw new TidyCalApiError({
84
+ ...(body.success && { body: body.data }),
85
+ retryAfter: parseFiniteNumber(response.headers.get("Retry-After")),
86
+ status: response.status,
87
+ });
88
+ }
89
+ return options.responseSchema.parse(normalized);
90
+ }
91
+ return { request };
92
+ }
93
+ /**
94
+ * Converts public camelCase JSON to TidyCal wire keys.
95
+ *
96
+ * @param value - Codec-safe public value.
97
+ */
98
+ export function toTidyCal(value) {
99
+ if (value instanceof Date)
100
+ return value.toISOString();
101
+ if (Array.isArray(value))
102
+ return value.map(toTidyCal);
103
+ if (!isPlainObject(value))
104
+ return value;
105
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
106
+ toSnakeCase(key),
107
+ toTidyCal(item),
108
+ ]));
109
+ }
110
+ /**
111
+ * Converts TidyCal wire JSON to public camelCase keys.
112
+ *
113
+ * @param value - Untrusted provider JSON value.
114
+ */
115
+ export function fromTidyCal(value) {
116
+ if (Array.isArray(value))
117
+ return value.map(fromTidyCal);
118
+ if (!isPlainObject(value))
119
+ return value;
120
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
121
+ toCamelCase(key),
122
+ fromTidyCal(item),
123
+ ]));
124
+ }
125
+ /**
126
+ * Returns whether a value is a plain JSON object.
127
+ *
128
+ * @param value - Value to inspect.
129
+ */
130
+ function isPlainObject(value) {
131
+ return typeof value === "object" && value !== null && !Array.isArray(value);
132
+ }
133
+ /**
134
+ * Parses JSON without obscuring the eventual structured API error.
135
+ *
136
+ * @param value - Response text to parse.
137
+ */
138
+ function parseJson(value) {
139
+ try {
140
+ return JSON.parse(value);
141
+ }
142
+ catch {
143
+ return undefined;
144
+ }
145
+ }
146
+ /**
147
+ * Parses a finite numeric response header.
148
+ *
149
+ * @param value - Header value.
150
+ */
151
+ function parseFiniteNumber(value) {
152
+ if (value === null)
153
+ return undefined;
154
+ const number = Number(value);
155
+ return Number.isFinite(number) ? number : undefined;
156
+ }
157
+ /**
158
+ * Extracts the provider's primary error text without weakening its payload.
159
+ *
160
+ * @param body - Normalized provider error body.
161
+ */
162
+ function getErrorMessage(body) {
163
+ if (!isPlainObject(body))
164
+ return undefined;
165
+ if (typeof body.message === "string")
166
+ return body.message;
167
+ const errors = body.errors;
168
+ if (!isPlainObject(errors))
169
+ return undefined;
170
+ const first = Object.values(errors)[0];
171
+ if (typeof first === "string")
172
+ return first;
173
+ return Array.isArray(first) && typeof first[0] === "string"
174
+ ? first[0]
175
+ : undefined;
176
+ }
177
+ /**
178
+ * Converts one camelCase public key to snake_case.
179
+ *
180
+ * @param value - Public key.
181
+ */
182
+ function toSnakeCase(value) {
183
+ return value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
184
+ }
185
+ /**
186
+ * Converts one snake_case provider key to camelCase.
187
+ *
188
+ * @param value - Provider key.
189
+ */
190
+ function toCamelCase(value) {
191
+ return value.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
192
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./api.js";
2
+ export * from "./schemas.js";
@@ -0,0 +1,2 @@
1
+ export * from "./api.js";
2
+ export * from "./schemas.js";