@automate.ax/integration-contracts 0.120.0 → 0.122.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/integration-contracts",
3
- "version": "0.120.0",
3
+ "version": "0.122.0",
4
4
  "description": "Shared integration payload contracts and provider primitives for Automate.ax.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,10 +27,12 @@
27
27
  "./close": "./src/close/index.ts",
28
28
  "./gmail": "./src/gmail/index.ts",
29
29
  "./google-calendar": "./src/google-calendar/index.ts",
30
+ "./google-drive": "./src/google-drive/index.ts",
30
31
  "./google-forms": "./src/google-forms/index.ts",
31
32
  "./google-meet": "./src/google-meet/index.ts",
32
33
  "./google-sheets": "./src/google-sheets/index.ts",
33
34
  "./github": "./src/github/index.ts",
35
+ "./hubspot": "./src/hubspot/index.ts",
34
36
  "./jobnimbus": "./src/jobnimbus/index.ts",
35
37
  "./linear": "./src/linear/index.ts",
36
38
  "./millionverifier": "./src/millionverifier/index.ts",
@@ -53,7 +55,7 @@
53
55
  }
54
56
  },
55
57
  "dependencies": {
56
- "@automate.ax/codec": "0.120.0",
58
+ "@automate.ax/codec": "0.122.0",
57
59
  "@cfworker/json-schema": "^4.1.1",
58
60
  "@googleapis/calendar": "^15.0.0",
59
61
  "@googleapis/forms": "^6.0.1",
@@ -86,7 +88,7 @@
86
88
  },
87
89
  "scripts": {
88
90
  "generate:github": "bun scripts/generate-github-webhook-schemas.ts",
89
- "typecheck": "tsc --noEmit",
91
+ "typecheck": "resource-broker run --pool automate-ax-validation --limit 2 --weight 1 -- tsc --noEmit",
90
92
  "lint": "oxlint --type-aware --threads=2 && bun --bun eslint . --cache --cache-strategy content",
91
93
  "lint:fix": "oxlint --type-aware --threads=2 --fix --fix-suggestions && bun --bun eslint . --fix --cache --cache-strategy content",
92
94
  "check": "bun run typecheck && bun run lint",
@@ -149,6 +151,11 @@
149
151
  "types": "./dist/google-calendar/index.d.ts",
150
152
  "default": "./dist/google-calendar/index.js"
151
153
  },
154
+ "./google-drive": {
155
+ "bun": "./src/google-drive/index.ts",
156
+ "types": "./dist/google-drive/index.d.ts",
157
+ "default": "./dist/google-drive/index.js"
158
+ },
152
159
  "./google-forms": {
153
160
  "bun": "./src/google-forms/index.ts",
154
161
  "types": "./dist/google-forms/index.d.ts",
@@ -169,6 +176,11 @@
169
176
  "types": "./dist/github/index.d.ts",
170
177
  "default": "./dist/github/index.js"
171
178
  },
179
+ "./hubspot": {
180
+ "bun": "./src/hubspot/index.ts",
181
+ "types": "./dist/hubspot/index.d.ts",
182
+ "default": "./dist/hubspot/index.js"
183
+ },
172
184
  "./jobnimbus": {
173
185
  "bun": "./src/jobnimbus/index.ts",
174
186
  "types": "./dist/jobnimbus/index.d.ts",
@@ -0,0 +1,329 @@
1
+ import { z } from "zod"
2
+
3
+ export const GOOGLE_DRIVE_TRIGGER_CONFIG_SCHEMA = z.object({
4
+ includeDescendants: z.boolean().optional(),
5
+ resourceId: z.string().min(1),
6
+ resourceType: z.enum(["file", "drive"]),
7
+ })
8
+
9
+ const GOOGLE_DRIVE_ACTION_SCHEMA = z.enum([
10
+ "cancelled",
11
+ "completed",
12
+ "contentChanged",
13
+ "created",
14
+ "deleted",
15
+ "edited",
16
+ "moved",
17
+ "reopened",
18
+ "renamed",
19
+ "reset",
20
+ "resolved",
21
+ "responded",
22
+ "reviewersChanged",
23
+ "trashed",
24
+ "untrashed",
25
+ ])
26
+
27
+ const GOOGLE_DRIVE_RESOURCE_TYPE_SCHEMA = z.enum([
28
+ "accessProposal",
29
+ "approval",
30
+ "comment",
31
+ "file",
32
+ "permission",
33
+ "reply",
34
+ ])
35
+
36
+ const GOOGLE_DRIVE_EVENT_BASE_SCHEMA = z.object({
37
+ action: GOOGLE_DRIVE_ACTION_SCHEMA,
38
+ assigneeEmailAddress: z.email().optional(),
39
+ commentId: z.string().optional(),
40
+ dueAt: z.date().optional(),
41
+ eventId: z.string(),
42
+ eventType: z.string(),
43
+ fileId: z.string().min(1),
44
+ initiatorEmailAddress: z.email().optional(),
45
+ mentionedEmailAddresses: z.email().array(),
46
+ mimeType: z.string().optional(),
47
+ occurredAt: z.date(),
48
+ parentId: z.string().optional(),
49
+ previousParentId: z.string().optional(),
50
+ resourceId: z.string().optional(),
51
+ resourceIds: z.string().array(),
52
+ resourceType: GOOGLE_DRIVE_RESOURCE_TYPE_SCHEMA,
53
+ reviewerEmailAddresses: z.email().array(),
54
+ reviewerResponse: z.string().optional(),
55
+ status: z.string().optional(),
56
+ version: z.string().optional(),
57
+ })
58
+
59
+ const GOOGLE_DRIVE_TRIGGER_TYPES = [
60
+ "googleDrive.accessProposal.created",
61
+ "googleDrive.accessProposal.resolved",
62
+ "googleDrive.approval.cancelled",
63
+ "googleDrive.approval.completed",
64
+ "googleDrive.approval.created",
65
+ "googleDrive.approval.reset",
66
+ "googleDrive.approval.responded",
67
+ "googleDrive.approval.reviewersChanged",
68
+ "googleDrive.comment.created",
69
+ "googleDrive.comment.deleted",
70
+ "googleDrive.comment.edited",
71
+ "googleDrive.comment.reopened",
72
+ "googleDrive.comment.resolved",
73
+ "googleDrive.file.contentChanged",
74
+ "googleDrive.file.created",
75
+ "googleDrive.file.deleted",
76
+ "googleDrive.file.moved",
77
+ "googleDrive.file.renamed",
78
+ "googleDrive.file.trashed",
79
+ "googleDrive.file.untrashed",
80
+ "googleDrive.permission.created",
81
+ "googleDrive.permission.deleted",
82
+ "googleDrive.permission.edited",
83
+ "googleDrive.reply.created",
84
+ "googleDrive.reply.deleted",
85
+ "googleDrive.reply.edited",
86
+ ] as const
87
+
88
+ export type GoogleDriveTriggerType = (typeof GOOGLE_DRIVE_TRIGGER_TYPES)[number]
89
+
90
+ export const GOOGLE_DRIVE_PROVIDER_EVENT_TYPES = {
91
+ "googleDrive.accessProposal.created":
92
+ "google.workspace.drive.accessproposal.v3.created",
93
+ "googleDrive.accessProposal.resolved":
94
+ "google.workspace.drive.accessproposal.v3.resolved",
95
+ "googleDrive.approval.cancelled":
96
+ "google.workspace.drive.approval.v3.cancelled",
97
+ "googleDrive.approval.completed":
98
+ "google.workspace.drive.approval.v3.completed",
99
+ "googleDrive.approval.created": "google.workspace.drive.approval.v3.created",
100
+ "googleDrive.approval.reset": "google.workspace.drive.approval.v3.reset",
101
+ "googleDrive.approval.responded":
102
+ "google.workspace.drive.approval.v3.responded",
103
+ "googleDrive.approval.reviewersChanged":
104
+ "google.workspace.drive.approval.v3.reviewersChanged",
105
+ "googleDrive.comment.created": "google.workspace.drive.comment.v3.created",
106
+ "googleDrive.comment.deleted": "google.workspace.drive.comment.v3.deleted",
107
+ "googleDrive.comment.edited": "google.workspace.drive.comment.v3.edited",
108
+ "googleDrive.comment.reopened": "google.workspace.drive.comment.v3.reopened",
109
+ "googleDrive.comment.resolved": "google.workspace.drive.comment.v3.resolved",
110
+ "googleDrive.file.contentChanged":
111
+ "google.workspace.drive.file.v3.contentChanged",
112
+ "googleDrive.file.created": "google.workspace.drive.file.v3.created",
113
+ "googleDrive.file.deleted": "google.workspace.drive.file.v3.deleted",
114
+ "googleDrive.file.moved": "google.workspace.drive.file.v3.moved",
115
+ "googleDrive.file.renamed": "google.workspace.drive.file.v3.renamed",
116
+ "googleDrive.file.trashed": "google.workspace.drive.file.v3.trashed",
117
+ "googleDrive.file.untrashed": "google.workspace.drive.file.v3.untrashed",
118
+ "googleDrive.permission.created":
119
+ "google.workspace.drive.permission.v3.created",
120
+ "googleDrive.permission.deleted":
121
+ "google.workspace.drive.permission.v3.deleted",
122
+ "googleDrive.permission.edited":
123
+ "google.workspace.drive.permission.v3.edited",
124
+ "googleDrive.reply.created": "google.workspace.drive.reply.v3.created",
125
+ "googleDrive.reply.deleted": "google.workspace.drive.reply.v3.deleted",
126
+ "googleDrive.reply.edited": "google.workspace.drive.reply.v3.edited",
127
+ } as const satisfies Record<GoogleDriveTriggerType, string>
128
+
129
+ export type GoogleDriveProviderEventType =
130
+ (typeof GOOGLE_DRIVE_PROVIDER_EVENT_TYPES)[GoogleDriveTriggerType]
131
+
132
+ export const GOOGLE_DRIVE_EVENT_SCHEMA = GOOGLE_DRIVE_EVENT_BASE_SCHEMA.extend({
133
+ eventType: z.enum(GOOGLE_DRIVE_TRIGGER_TYPES),
134
+ })
135
+
136
+ /**
137
+ * Builds one semantic Drive trigger contract with literal discriminants.
138
+ *
139
+ * @param eventType - Exact internal event type.
140
+ * @param resourceType - Exact event resource family.
141
+ * @param action - Exact event action.
142
+ */
143
+ function googleDriveSemanticTriggerContract<
144
+ const TEventType extends GoogleDriveTriggerType,
145
+ const TResourceType extends z.output<
146
+ typeof GOOGLE_DRIVE_RESOURCE_TYPE_SCHEMA
147
+ >,
148
+ const TAction extends z.output<typeof GOOGLE_DRIVE_ACTION_SCHEMA>,
149
+ >(eventType: TEventType, resourceType: TResourceType, action: TAction) {
150
+ return {
151
+ configSchema: GOOGLE_DRIVE_TRIGGER_CONFIG_SCHEMA,
152
+ eventSchema: GOOGLE_DRIVE_EVENT_BASE_SCHEMA.extend({
153
+ action: z.literal(action),
154
+ eventType: z.literal(eventType),
155
+ resourceType: z.literal(resourceType),
156
+ }),
157
+ }
158
+ }
159
+
160
+ /** Exact semantic trigger contract map used by authoring inference. */
161
+ const googleDriveSemanticTriggerContracts = {
162
+ "googleDrive.accessProposal.created": googleDriveSemanticTriggerContract(
163
+ "googleDrive.accessProposal.created",
164
+ "accessProposal",
165
+ "created",
166
+ ),
167
+ "googleDrive.accessProposal.resolved": googleDriveSemanticTriggerContract(
168
+ "googleDrive.accessProposal.resolved",
169
+ "accessProposal",
170
+ "resolved",
171
+ ),
172
+ "googleDrive.approval.cancelled": googleDriveSemanticTriggerContract(
173
+ "googleDrive.approval.cancelled",
174
+ "approval",
175
+ "cancelled",
176
+ ),
177
+ "googleDrive.approval.completed": googleDriveSemanticTriggerContract(
178
+ "googleDrive.approval.completed",
179
+ "approval",
180
+ "completed",
181
+ ),
182
+ "googleDrive.approval.created": googleDriveSemanticTriggerContract(
183
+ "googleDrive.approval.created",
184
+ "approval",
185
+ "created",
186
+ ),
187
+ "googleDrive.approval.reset": googleDriveSemanticTriggerContract(
188
+ "googleDrive.approval.reset",
189
+ "approval",
190
+ "reset",
191
+ ),
192
+ "googleDrive.approval.responded": googleDriveSemanticTriggerContract(
193
+ "googleDrive.approval.responded",
194
+ "approval",
195
+ "responded",
196
+ ),
197
+ "googleDrive.approval.reviewersChanged": googleDriveSemanticTriggerContract(
198
+ "googleDrive.approval.reviewersChanged",
199
+ "approval",
200
+ "reviewersChanged",
201
+ ),
202
+ "googleDrive.comment.created": googleDriveSemanticTriggerContract(
203
+ "googleDrive.comment.created",
204
+ "comment",
205
+ "created",
206
+ ),
207
+ "googleDrive.comment.deleted": googleDriveSemanticTriggerContract(
208
+ "googleDrive.comment.deleted",
209
+ "comment",
210
+ "deleted",
211
+ ),
212
+ "googleDrive.comment.edited": googleDriveSemanticTriggerContract(
213
+ "googleDrive.comment.edited",
214
+ "comment",
215
+ "edited",
216
+ ),
217
+ "googleDrive.comment.reopened": googleDriveSemanticTriggerContract(
218
+ "googleDrive.comment.reopened",
219
+ "comment",
220
+ "reopened",
221
+ ),
222
+ "googleDrive.comment.resolved": googleDriveSemanticTriggerContract(
223
+ "googleDrive.comment.resolved",
224
+ "comment",
225
+ "resolved",
226
+ ),
227
+ "googleDrive.file.contentChanged": googleDriveSemanticTriggerContract(
228
+ "googleDrive.file.contentChanged",
229
+ "file",
230
+ "contentChanged",
231
+ ),
232
+ "googleDrive.file.created": googleDriveSemanticTriggerContract(
233
+ "googleDrive.file.created",
234
+ "file",
235
+ "created",
236
+ ),
237
+ "googleDrive.file.deleted": googleDriveSemanticTriggerContract(
238
+ "googleDrive.file.deleted",
239
+ "file",
240
+ "deleted",
241
+ ),
242
+ "googleDrive.file.moved": googleDriveSemanticTriggerContract(
243
+ "googleDrive.file.moved",
244
+ "file",
245
+ "moved",
246
+ ),
247
+ "googleDrive.file.renamed": googleDriveSemanticTriggerContract(
248
+ "googleDrive.file.renamed",
249
+ "file",
250
+ "renamed",
251
+ ),
252
+ "googleDrive.file.trashed": googleDriveSemanticTriggerContract(
253
+ "googleDrive.file.trashed",
254
+ "file",
255
+ "trashed",
256
+ ),
257
+ "googleDrive.file.untrashed": googleDriveSemanticTriggerContract(
258
+ "googleDrive.file.untrashed",
259
+ "file",
260
+ "untrashed",
261
+ ),
262
+ "googleDrive.permission.created": googleDriveSemanticTriggerContract(
263
+ "googleDrive.permission.created",
264
+ "permission",
265
+ "created",
266
+ ),
267
+ "googleDrive.permission.deleted": googleDriveSemanticTriggerContract(
268
+ "googleDrive.permission.deleted",
269
+ "permission",
270
+ "deleted",
271
+ ),
272
+ "googleDrive.permission.edited": googleDriveSemanticTriggerContract(
273
+ "googleDrive.permission.edited",
274
+ "permission",
275
+ "edited",
276
+ ),
277
+ "googleDrive.reply.created": googleDriveSemanticTriggerContract(
278
+ "googleDrive.reply.created",
279
+ "reply",
280
+ "created",
281
+ ),
282
+ "googleDrive.reply.deleted": googleDriveSemanticTriggerContract(
283
+ "googleDrive.reply.deleted",
284
+ "reply",
285
+ "deleted",
286
+ ),
287
+ "googleDrive.reply.edited": googleDriveSemanticTriggerContract(
288
+ "googleDrive.reply.edited",
289
+ "reply",
290
+ "edited",
291
+ ),
292
+ } satisfies Record<GoogleDriveTriggerType, unknown>
293
+
294
+ export const googleDriveTriggerContracts = {
295
+ "googleDrive.event": {
296
+ configSchema: GOOGLE_DRIVE_TRIGGER_CONFIG_SCHEMA,
297
+ eventSchema: GOOGLE_DRIVE_EVENT_SCHEMA,
298
+ },
299
+ ...googleDriveSemanticTriggerContracts,
300
+ }
301
+
302
+ /**
303
+ * Resolves an internal semantic event type from a provider CloudEvent type.
304
+ *
305
+ * @param providerEventType - Provider CloudEvent type.
306
+ */
307
+ export function toGoogleDriveTriggerType(
308
+ providerEventType: string,
309
+ ): GoogleDriveTriggerType | undefined {
310
+ // Keep the candidate separate so Zod performs the only narrowing.
311
+ const candidate = Object.entries(GOOGLE_DRIVE_PROVIDER_EVENT_TYPES).find(
312
+ ([, value]) => value === providerEventType,
313
+ )?.[0]
314
+ const result = z.enum(GOOGLE_DRIVE_TRIGGER_TYPES).safeParse(candidate)
315
+ return result.success ? result.data : undefined
316
+ }
317
+
318
+ /**
319
+ * Splits one internal event type into its resource and action discriminants.
320
+ *
321
+ * @param eventType - Stable internal semantic event type.
322
+ */
323
+ export function parseGoogleDriveTriggerType(eventType: GoogleDriveTriggerType) {
324
+ const [, resourceType, ...actionParts] = eventType.split(".")
325
+ return {
326
+ action: GOOGLE_DRIVE_ACTION_SCHEMA.parse(actionParts.join(".")),
327
+ resourceType: GOOGLE_DRIVE_RESOURCE_TYPE_SCHEMA.parse(resourceType),
328
+ }
329
+ }
@@ -0,0 +1,147 @@
1
+ import { isEncodable, type Encodable } from "@automate.ax/codec"
2
+ import * as z from "zod"
3
+
4
+ const HUBSPOT_API_ORIGIN = "https://api.hubapi.com/"
5
+ const HUBSPOT_API_URL = new URL(HUBSPOT_API_ORIGIN)
6
+ const HUBSPOT_SECRET_SCHEMA = z.object({ accessToken: z.string().min(1) })
7
+ const HUBSPOT_ERROR_SCHEMA = z.looseObject({
8
+ category: z.string().optional(),
9
+ correlationId: z.string().optional(),
10
+ message: z.string().optional(),
11
+ subCategory: z.string().optional(),
12
+ })
13
+
14
+ export const HUBSPOT_VALUE_SCHEMA = z.custom<Encodable>(isEncodable, {
15
+ message: "Expected a codec-safe HubSpot value.",
16
+ })
17
+
18
+ interface HubSpotRequestOptions<TSchema extends z.ZodType> {
19
+ body?: Encodable
20
+ method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT"
21
+ query?: Record<
22
+ string,
23
+ boolean | number | string | readonly string[] | null | undefined
24
+ >
25
+ responseSchema: TSchema
26
+ }
27
+
28
+ /** Error returned by a rejected HubSpot API request. */
29
+ export class HubSpotApiError extends Error {
30
+ readonly category?: string
31
+ readonly correlationId?: string
32
+ readonly details: Encodable
33
+ readonly retryAfter?: string
34
+ readonly status: number
35
+ readonly subCategory?: string
36
+
37
+ /**
38
+ * Creates an error from one rejected HubSpot response.
39
+ *
40
+ * @param status - HTTP response status.
41
+ * @param details - Codec-safe provider response.
42
+ * @param retryAfter - Provider retry timing, when present.
43
+ */
44
+ constructor(status: number, details: Encodable, retryAfter?: string) {
45
+ const providerError = HUBSPOT_ERROR_SCHEMA.safeParse(details)
46
+ super(
47
+ providerError.success && providerError.data.message
48
+ ? `HubSpot API request failed (${status}): ${providerError.data.message}`
49
+ : `HubSpot API request failed (${status}).`,
50
+ )
51
+ this.name = "HubSpotApiError"
52
+ this.category = providerError.success
53
+ ? providerError.data.category
54
+ : undefined
55
+ this.correlationId = providerError.success
56
+ ? providerError.data.correlationId
57
+ : undefined
58
+ this.details = details
59
+ this.retryAfter = retryAfter
60
+ this.status = status
61
+ this.subCategory = providerError.success
62
+ ? providerError.data.subCategory
63
+ : undefined
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Creates a minimal authenticated HubSpot REST client.
69
+ *
70
+ * @param secret - Stored OAuth or private-app token.
71
+ */
72
+ export function getHubSpotApi(secret: unknown) {
73
+ const { accessToken } = HUBSPOT_SECRET_SCHEMA.parse(secret)
74
+
75
+ return {
76
+ /**
77
+ * Sends one request below HubSpot's API origin.
78
+ *
79
+ * @param path - Relative provider API path.
80
+ * @param options - Method, query, body, and response schema.
81
+ */
82
+ async request<TSchema extends z.ZodType>(
83
+ path: string,
84
+ options: HubSpotRequestOptions<TSchema>,
85
+ ): Promise<z.output<TSchema>> {
86
+ const normalizedPath = path.replace(/^\/+/, "")
87
+ if (
88
+ !normalizedPath ||
89
+ normalizedPath.includes("://") ||
90
+ normalizedPath.includes("\\")
91
+ ) {
92
+ throw new TypeError("HubSpot API paths must be relative.")
93
+ }
94
+
95
+ const url = new URL(normalizedPath, HUBSPOT_API_ORIGIN)
96
+ if (url.origin !== HUBSPOT_API_URL.origin) {
97
+ throw new TypeError("HubSpot API paths must remain on api.hubapi.com.")
98
+ }
99
+ for (const [key, value] of Object.entries(options.query ?? {})) {
100
+ if (value == null) continue
101
+ url.searchParams.set(
102
+ key,
103
+ Array.isArray(value) ? value.join(",") : String(value),
104
+ )
105
+ }
106
+
107
+ const headers = new Headers({
108
+ Accept: "application/json",
109
+ Authorization: `Bearer ${accessToken}`,
110
+ })
111
+ if (options.body !== undefined) {
112
+ headers.set("Content-Type", "application/json")
113
+ }
114
+ const response = await fetch(url, {
115
+ body:
116
+ options.body === undefined ? undefined : JSON.stringify(options.body),
117
+ headers,
118
+ method: options.method ?? "GET",
119
+ })
120
+ const text = await response.text()
121
+ const parsed = text ? parseJson(text) : undefined
122
+ if (response.ok && !text) return options.responseSchema.parse(undefined)
123
+ const encodable = HUBSPOT_VALUE_SCHEMA.safeParse(parsed)
124
+ if (!response.ok || !encodable.success) {
125
+ throw new HubSpotApiError(
126
+ response.status,
127
+ encodable.success ? encodable.data : { response: text },
128
+ response.headers.get("Retry-After") ?? undefined,
129
+ )
130
+ }
131
+ return options.responseSchema.parse(parsed)
132
+ },
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Parses a response body while preserving non-JSON error text.
138
+ *
139
+ * @param value - Raw response body.
140
+ */
141
+ function parseJson(value: string): unknown {
142
+ try {
143
+ return JSON.parse(value)
144
+ } catch {
145
+ return { response: value }
146
+ }
147
+ }