@automate.ax/integration-contracts 0.125.0 → 0.127.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.
Files changed (49) hide show
  1. package/dist/axiom/api.d.ts +60 -0
  2. package/dist/axiom/api.js +192 -0
  3. package/dist/axiom/events.d.ts +104 -0
  4. package/dist/axiom/events.js +25 -0
  5. package/dist/axiom/index.d.ts +3 -0
  6. package/dist/axiom/index.js +3 -0
  7. package/dist/axiom/schemas.d.ts +1520 -0
  8. package/dist/axiom/schemas.js +580 -0
  9. package/dist/brevo/index.d.ts +2 -2
  10. package/dist/brevo/schemas.d.ts +3 -3
  11. package/dist/close/events.d.ts +9 -9
  12. package/dist/close/schemas.d.ts +3 -3
  13. package/dist/cloudflare/schemas.d.ts +4 -4
  14. package/dist/convex/index.d.ts +8 -8
  15. package/dist/convex/schemas.d.ts +4 -4
  16. package/dist/gmail/schemas.d.ts +3 -3
  17. package/dist/google-calendar/schemas.d.ts +2 -2
  18. package/dist/google-drive/index.d.ts +3 -3
  19. package/dist/google-forms/event-schemas.d.ts +2 -2
  20. package/dist/google-forms/google-forms.d.ts +4 -4
  21. package/dist/google-forms/index.d.ts +2 -2
  22. package/dist/google-forms/schemas.d.ts +7 -7
  23. package/dist/hubspot/events.d.ts +3 -3
  24. package/dist/linear/schemas.d.ts +12 -12
  25. package/dist/notion/schemas.d.ts +6 -6
  26. package/dist/outlook/index.d.ts +9 -9
  27. package/dist/outlook/schemas.d.ts +6 -6
  28. package/dist/reddit/api.d.ts +56 -0
  29. package/dist/reddit/api.js +135 -0
  30. package/dist/reddit/events.d.ts +439 -0
  31. package/dist/reddit/events.js +53 -0
  32. package/dist/reddit/index.d.ts +3 -0
  33. package/dist/reddit/index.js +3 -0
  34. package/dist/reddit/schemas.d.ts +816 -0
  35. package/dist/reddit/schemas.js +398 -0
  36. package/dist/trello/schemas.d.ts +2 -2
  37. package/dist/triggers.d.ts +3 -1
  38. package/dist/whatsapp/index.d.ts +4 -4
  39. package/dist/whatsapp/schemas.d.ts +8 -8
  40. package/package.json +14 -2
  41. package/src/axiom/api.ts +252 -0
  42. package/src/axiom/events.ts +31 -0
  43. package/src/axiom/index.ts +3 -0
  44. package/src/axiom/schemas.ts +614 -0
  45. package/src/reddit/api.ts +183 -0
  46. package/src/reddit/events.ts +68 -0
  47. package/src/reddit/index.ts +3 -0
  48. package/src/reddit/schemas.ts +442 -0
  49. package/src/triggers.ts +4 -0
@@ -0,0 +1,252 @@
1
+ import { encodableSchema, type Encodable } from "@automate.ax/codec"
2
+ import * as z from "zod"
3
+
4
+ const AXIOM_API_ORIGIN = "https://api.axiom.co"
5
+ const AXIOM_SECRET_SCHEMA = z.object({ apiKey: z.string().trim().min(1) })
6
+
7
+ export interface AxiomResolvedAccount {
8
+ connectionMethodId: string
9
+ secret: Record<string, unknown>
10
+ serviceId: "axiom"
11
+ }
12
+
13
+ export interface AxiomRequestOptions<TSchema extends z.ZodType> {
14
+ body?: Encodable
15
+ headers?: Record<string, string>
16
+ method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT"
17
+ query?: Record<string, boolean | number | string | undefined>
18
+ responseSchema: TSchema
19
+ version?: "v1" | "v2"
20
+ }
21
+
22
+ /** Structured Axiom REST error. */
23
+ export class AxiomApiError extends Error {
24
+ readonly details: Encodable
25
+ readonly ingestLimitReset?: number
26
+ readonly queryLimitReset?: number
27
+ readonly rateLimitReset?: number
28
+ readonly retryAfter?: number
29
+ readonly status: number
30
+
31
+ /**
32
+ * Creates a structured provider error.
33
+ *
34
+ * @param options - Error response details and rate-limit metadata.
35
+ * @param options.details - Parsed provider response body.
36
+ * @param options.ingestLimitReset - Ingest-limit reset timestamp.
37
+ * @param options.queryLimitReset - Query-limit reset timestamp.
38
+ * @param options.rateLimitReset - Request-rate-limit reset timestamp.
39
+ * @param options.retryAfter - Suggested retry delay in seconds.
40
+ * @param options.status - HTTP response status.
41
+ */
42
+ constructor(options: {
43
+ details: Encodable
44
+ ingestLimitReset?: number
45
+ queryLimitReset?: number
46
+ rateLimitReset?: number
47
+ retryAfter?: number
48
+ status: number
49
+ }) {
50
+ const message =
51
+ isPlainObject(options.details) &&
52
+ typeof options.details.message === "string"
53
+ ? options.details.message
54
+ : undefined
55
+ super(
56
+ message
57
+ ? `Axiom API error (${options.status}): ${message}`
58
+ : `Axiom API request failed (${options.status}).`,
59
+ )
60
+ this.name = "AxiomApiError"
61
+ this.details = options.details
62
+ this.ingestLimitReset = options.ingestLimitReset
63
+ this.queryLimitReset = options.queryLimitReset
64
+ this.rateLimitReset = options.rateLimitReset
65
+ this.retryAfter = options.retryAfter
66
+ this.status = options.status
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Creates an authenticated, origin-confined Axiom API client.
72
+ *
73
+ * @param account - Resolved Axiom integration account.
74
+ * @param edgeUrl - Optional Axiom edge origin used by version-one endpoints.
75
+ * @throws When the account method, secret, or edge origin is invalid.
76
+ */
77
+ export function getAxiomApi(
78
+ account: AxiomResolvedAccount,
79
+ edgeUrl = AXIOM_API_ORIGIN,
80
+ ) {
81
+ if (account.connectionMethodId !== "api-token") {
82
+ throw new Error(
83
+ `Unsupported Axiom connection method: ${account.connectionMethodId}`,
84
+ )
85
+ }
86
+ const { apiKey } = AXIOM_SECRET_SCHEMA.parse(account.secret)
87
+ const edgeOrigin = validateAxiomOrigin(edgeUrl)
88
+
89
+ /**
90
+ * Sends one authenticated request and validates its response.
91
+ *
92
+ * @param path - Relative Axiom API path.
93
+ * @param options - Request and response-validation options.
94
+ * @throws {AxiomApiError} When Axiom returns an unsuccessful response.
95
+ */
96
+ async function request<TSchema extends z.ZodType>(
97
+ path: string,
98
+ options: AxiomRequestOptions<TSchema>,
99
+ ): Promise<z.output<TSchema>> {
100
+ const version = options.version ?? "v2"
101
+ const url = confinedUrl(
102
+ version === "v1" ? edgeOrigin : AXIOM_API_ORIGIN,
103
+ version,
104
+ path,
105
+ )
106
+ for (const [name, value] of Object.entries(options.query ?? {})) {
107
+ if (value !== undefined) url.searchParams.set(name, String(value))
108
+ }
109
+
110
+ const response = await fetch(url, {
111
+ body:
112
+ options.body === undefined
113
+ ? undefined
114
+ : JSON.stringify(encodableSchema.parse(options.body)),
115
+ headers: {
116
+ Accept: "application/json",
117
+ Authorization: `Bearer ${apiKey}`,
118
+ ...(options.body === undefined
119
+ ? {}
120
+ : { "Content-Type": "application/json" }),
121
+ ...options.headers,
122
+ },
123
+ method: options.method ?? "GET",
124
+ redirect: "manual",
125
+ })
126
+ const parsed = parseResponse(await response.text())
127
+ if (!response.ok || response.status >= 300) {
128
+ throw new AxiomApiError({
129
+ details: parsed,
130
+ ingestLimitReset: parseFiniteNumber(
131
+ response.headers.get("X-IngestLimit-Reset"),
132
+ ),
133
+ queryLimitReset: parseFiniteNumber(
134
+ response.headers.get("X-QueryLimit-Reset"),
135
+ ),
136
+ rateLimitReset: parseFiniteNumber(
137
+ response.headers.get("X-RateLimit-Reset"),
138
+ ),
139
+ retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
140
+ status: response.status,
141
+ })
142
+ }
143
+ return options.responseSchema.parse(parsed)
144
+ }
145
+
146
+ return { request }
147
+ }
148
+
149
+ /**
150
+ * Restricts edge traffic to Axiom-owned HTTPS origins on the standard port.
151
+ *
152
+ * @param value - Candidate edge URL.
153
+ * @throws {TypeError} When the URL is not a confined Axiom HTTPS origin.
154
+ */
155
+ export function validateAxiomOrigin(value: string) {
156
+ const url = new URL(value)
157
+ const isAxiomHost =
158
+ url.hostname === "api.axiom.co" || url.hostname.endsWith(".edge.axiom.co")
159
+ if (
160
+ url.protocol !== "https:" ||
161
+ url.port !== "" ||
162
+ !isAxiomHost ||
163
+ url.username ||
164
+ url.password ||
165
+ url.search ||
166
+ url.hash
167
+ ) {
168
+ throw new TypeError(
169
+ "Axiom edge URLs must use api.axiom.co or an HTTPS *.edge.axiom.co host.",
170
+ )
171
+ }
172
+ return url.origin
173
+ }
174
+
175
+ /**
176
+ * Resolves a relative path below one confined API-version root.
177
+ *
178
+ * @param origin - Validated Axiom origin.
179
+ * @param version - API version path segment.
180
+ * @param path - Relative request path.
181
+ * @throws {TypeError} When the path escapes its API-version root.
182
+ */
183
+ function confinedUrl(origin: string, version: "v1" | "v2", path: string) {
184
+ const normalizedPath = path.replace(/^\/+/, "")
185
+ if (
186
+ !normalizedPath ||
187
+ normalizedPath.includes("://") ||
188
+ normalizedPath.includes("\\")
189
+ ) {
190
+ throw new TypeError("Axiom API paths must be relative.")
191
+ }
192
+ const root = new URL(`/${version}/`, origin)
193
+ const url = new URL(normalizedPath, root)
194
+ if (url.origin !== root.origin || !url.pathname.startsWith(root.pathname)) {
195
+ throw new TypeError("Axiom API paths must remain below their API version.")
196
+ }
197
+ return url
198
+ }
199
+
200
+ /**
201
+ * Parses an Axiom response into an encodable value.
202
+ *
203
+ * @param text - Raw response body.
204
+ */
205
+ function parseResponse(text: string): Encodable {
206
+ if (!text) return null
207
+ try {
208
+ return encodableSchema.parse(JSON.parse(text))
209
+ } catch {
210
+ return { response: text }
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Parses a finite numeric response header.
216
+ *
217
+ * @param value - Raw header value.
218
+ */
219
+ function parseFiniteNumber(value: string | null) {
220
+ if (value === null) return undefined
221
+ const number = Number(value)
222
+ return Number.isFinite(number) ? number : undefined
223
+ }
224
+
225
+ /**
226
+ * Parses a Retry-After header into seconds.
227
+ *
228
+ * @param value - Raw header value.
229
+ */
230
+ function parseRetryAfter(value: string | null) {
231
+ if (value === null) return undefined
232
+ const seconds = Number(value)
233
+ if (Number.isFinite(seconds)) return seconds
234
+ const timestamp = Date.parse(value)
235
+ return Number.isFinite(timestamp)
236
+ ? Math.max(0, Math.ceil((timestamp - Date.now()) / 1_000))
237
+ : undefined
238
+ }
239
+
240
+ /**
241
+ * Checks whether an encodable value is a plain object.
242
+ *
243
+ * @param value - Value to inspect.
244
+ */
245
+ function isPlainObject(value: Encodable): value is Record<string, Encodable> {
246
+ return (
247
+ typeof value === "object" &&
248
+ value !== null &&
249
+ !Array.isArray(value) &&
250
+ Object.getPrototypeOf(value) === Object.prototype
251
+ )
252
+ }
@@ -0,0 +1,31 @@
1
+ import * as z from "zod"
2
+ import { AXIOM_ID_SCHEMA, AXIOM_MONITOR_NOTIFICATION_SCHEMA } from "./schemas"
3
+
4
+ export const AXIOM_TRIGGER_CONFIG_SCHEMA = z.object({
5
+ monitorId: AXIOM_ID_SCHEMA,
6
+ })
7
+
8
+ export const AXIOM_MONITOR_OPENED_SCHEMA =
9
+ AXIOM_MONITOR_NOTIFICATION_SCHEMA.extend({
10
+ action: z.literal("Open"),
11
+ })
12
+
13
+ export const AXIOM_MONITOR_CLOSED_SCHEMA =
14
+ AXIOM_MONITOR_NOTIFICATION_SCHEMA.extend({
15
+ action: z.literal("Closed"),
16
+ })
17
+
18
+ export const axiomTriggerContracts = {
19
+ "axiom.monitorNotification": {
20
+ configSchema: AXIOM_TRIGGER_CONFIG_SCHEMA,
21
+ eventSchema: AXIOM_MONITOR_NOTIFICATION_SCHEMA,
22
+ },
23
+ "axiom.monitorOpened": {
24
+ configSchema: AXIOM_TRIGGER_CONFIG_SCHEMA,
25
+ eventSchema: AXIOM_MONITOR_OPENED_SCHEMA,
26
+ },
27
+ "axiom.monitorClosed": {
28
+ configSchema: AXIOM_TRIGGER_CONFIG_SCHEMA,
29
+ eventSchema: AXIOM_MONITOR_CLOSED_SCHEMA,
30
+ },
31
+ } as const
@@ -0,0 +1,3 @@
1
+ export * from "./api"
2
+ export * from "./events"
3
+ export * from "./schemas"