@automate.ax/integration-contracts 0.55.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 (136) hide show
  1. package/dist/airtable/api.d.ts +19 -0
  2. package/dist/airtable/api.js +44 -0
  3. package/dist/airtable/index.d.ts +353 -0
  4. package/dist/airtable/index.js +32 -0
  5. package/dist/airtable/schemas.d.ts +716 -0
  6. package/dist/airtable/schemas.js +184 -0
  7. package/dist/asana/api.d.ts +70 -0
  8. package/dist/asana/api.js +119 -0
  9. package/dist/asana/index.d.ts +384 -0
  10. package/dist/asana/index.js +38 -0
  11. package/dist/asana/schemas.d.ts +1060 -0
  12. package/dist/asana/schemas.js +512 -0
  13. package/dist/brevo/api.d.ts +31 -0
  14. package/dist/brevo/api.js +64 -0
  15. package/dist/brevo/index.d.ts +822 -0
  16. package/dist/brevo/index.js +51 -0
  17. package/dist/brevo/schemas.d.ts +1217 -0
  18. package/dist/brevo/schemas.js +332 -0
  19. package/dist/github/index.d.ts +37 -0
  20. package/dist/github/index.js +106 -0
  21. package/dist/github/schemas.d.ts +8014 -0
  22. package/dist/github/schemas.js +113 -0
  23. package/dist/gmail/gmail.d.ts +172 -0
  24. package/dist/gmail/gmail.js +405 -0
  25. package/dist/gmail/index.d.ts +262 -0
  26. package/dist/gmail/index.js +23 -0
  27. package/dist/gmail/schemas.d.ts +444 -0
  28. package/dist/gmail/schemas.js +242 -0
  29. package/dist/google-calendar/event-schemas.d.ts +117 -0
  30. package/dist/google-calendar/event-schemas.js +13 -0
  31. package/dist/google-calendar/google-calendar.d.ts +147 -0
  32. package/dist/google-calendar/google-calendar.js +583 -0
  33. package/dist/google-calendar/index.d.ts +365 -0
  34. package/dist/google-calendar/index.js +24 -0
  35. package/dist/google-calendar/schemas.d.ts +592 -0
  36. package/dist/google-calendar/schemas.js +379 -0
  37. package/dist/google-forms/event-schemas.d.ts +105 -0
  38. package/dist/google-forms/event-schemas.js +20 -0
  39. package/dist/google-forms/google-forms.d.ts +243 -0
  40. package/dist/google-forms/google-forms.js +581 -0
  41. package/dist/google-forms/index.d.ts +141 -0
  42. package/dist/google-forms/index.js +20 -0
  43. package/dist/google-forms/schemas.d.ts +618 -0
  44. package/dist/google-forms/schemas.js +383 -0
  45. package/dist/linear/api.d.ts +63 -0
  46. package/dist/linear/api.js +88 -0
  47. package/dist/linear/index.d.ts +1338 -0
  48. package/dist/linear/index.js +55 -0
  49. package/dist/linear/schemas.d.ts +1795 -0
  50. package/dist/linear/schemas.js +760 -0
  51. package/dist/outlook/graph.d.ts +34 -0
  52. package/dist/outlook/graph.js +98 -0
  53. package/dist/outlook/index.d.ts +115 -0
  54. package/dist/outlook/index.js +11 -0
  55. package/dist/outlook/schemas.d.ts +529 -0
  56. package/dist/outlook/schemas.js +312 -0
  57. package/dist/resend/index.d.ts +707 -0
  58. package/dist/resend/index.js +54 -0
  59. package/dist/resend/schemas.d.ts +1033 -0
  60. package/dist/resend/schemas.js +377 -0
  61. package/dist/slack/index.d.ts +151 -0
  62. package/dist/slack/index.js +22 -0
  63. package/dist/slack/schemas.d.ts +641 -0
  64. package/dist/slack/schemas.js +581 -0
  65. package/dist/teams/graph.d.ts +33 -0
  66. package/dist/teams/graph.js +95 -0
  67. package/dist/teams/index.d.ts +164 -0
  68. package/dist/teams/index.js +21 -0
  69. package/dist/teams/schemas.d.ts +675 -0
  70. package/dist/teams/schemas.js +365 -0
  71. package/dist/trello/api.d.ts +69 -0
  72. package/dist/trello/api.js +140 -0
  73. package/dist/trello/event-schemas.d.ts +1101 -0
  74. package/dist/trello/event-schemas.js +233 -0
  75. package/dist/trello/index.d.ts +721 -0
  76. package/dist/trello/index.js +35 -0
  77. package/dist/trello/schemas.d.ts +283 -0
  78. package/dist/trello/schemas.js +401 -0
  79. package/dist/triggers.d.ts +26 -0
  80. package/dist/triggers.js +33 -0
  81. package/dist/vercel/index.d.ts +1065 -0
  82. package/dist/vercel/index.js +114 -0
  83. package/dist/vercel/schemas.d.ts +1518 -0
  84. package/dist/vercel/schemas.js +335 -0
  85. package/dist/whatsapp/api.d.ts +62 -0
  86. package/dist/whatsapp/api.js +121 -0
  87. package/dist/whatsapp/index.d.ts +614 -0
  88. package/dist/whatsapp/index.js +35 -0
  89. package/dist/whatsapp/schemas.d.ts +926 -0
  90. package/dist/whatsapp/schemas.js +326 -0
  91. package/package.json +169 -0
  92. package/src/airtable/api.ts +66 -0
  93. package/src/airtable/index.ts +41 -0
  94. package/src/airtable/schemas.ts +231 -0
  95. package/src/asana/api.ts +171 -0
  96. package/src/asana/index.ts +49 -0
  97. package/src/asana/schemas.ts +655 -0
  98. package/src/brevo/api.ts +89 -0
  99. package/src/brevo/index.ts +66 -0
  100. package/src/brevo/schemas.ts +449 -0
  101. package/src/github/index.ts +217 -0
  102. package/src/github/schemas.ts +435 -0
  103. package/src/gmail/gmail.ts +577 -0
  104. package/src/gmail/index.ts +26 -0
  105. package/src/gmail/schemas.ts +327 -0
  106. package/src/google-calendar/event-schemas.ts +16 -0
  107. package/src/google-calendar/google-calendar.ts +727 -0
  108. package/src/google-calendar/index.ts +27 -0
  109. package/src/google-calendar/schemas.ts +529 -0
  110. package/src/google-forms/event-schemas.ts +26 -0
  111. package/src/google-forms/google-forms.ts +683 -0
  112. package/src/google-forms/index.ts +23 -0
  113. package/src/google-forms/schemas.ts +499 -0
  114. package/src/linear/api.ts +141 -0
  115. package/src/linear/index.ts +71 -0
  116. package/src/linear/schemas.ts +1060 -0
  117. package/src/outlook/graph.ts +132 -0
  118. package/src/outlook/index.ts +14 -0
  119. package/src/outlook/schemas.ts +388 -0
  120. package/src/resend/index.ts +70 -0
  121. package/src/resend/schemas.ts +462 -0
  122. package/src/slack/index.ts +29 -0
  123. package/src/slack/schemas.ts +737 -0
  124. package/src/teams/graph.ts +130 -0
  125. package/src/teams/index.ts +27 -0
  126. package/src/teams/schemas.ts +463 -0
  127. package/src/trello/api.ts +190 -0
  128. package/src/trello/event-schemas.ts +290 -0
  129. package/src/trello/index.ts +45 -0
  130. package/src/trello/schemas.ts +507 -0
  131. package/src/triggers.ts +68 -0
  132. package/src/vercel/index.ts +147 -0
  133. package/src/vercel/schemas.ts +594 -0
  134. package/src/whatsapp/api.ts +171 -0
  135. package/src/whatsapp/index.ts +46 -0
  136. package/src/whatsapp/schemas.ts +341 -0
@@ -0,0 +1,184 @@
1
+ import { z } from "zod";
2
+ const AIRTABLE_FIELDS_BY_ID_SCHEMA = z.record(z.string(), z.json());
3
+ const AIRTABLE_ACTION_METADATA_SCHEMA = z
4
+ .looseObject({
5
+ /** Airtable system that caused the transaction. */
6
+ source: z.string(),
7
+ /** Provider-specific actor or source details. */
8
+ sourceMetadata: z.record(z.string(), z.json()).optional(),
9
+ })
10
+ .catchall(z.json());
11
+ const AIRTABLE_CREATED_RECORD_SCHEMA = z.looseObject({
12
+ cellValuesByFieldId: AIRTABLE_FIELDS_BY_ID_SCHEMA,
13
+ createdTime: z.iso.datetime({ offset: true }),
14
+ });
15
+ const AIRTABLE_CHANGED_RECORD_SCHEMA = z.looseObject({
16
+ current: z.looseObject({
17
+ cellValuesByFieldId: AIRTABLE_FIELDS_BY_ID_SCHEMA,
18
+ }),
19
+ previous: z
20
+ .looseObject({ cellValuesByFieldId: AIRTABLE_FIELDS_BY_ID_SCHEMA })
21
+ .optional(),
22
+ unchanged: z
23
+ .looseObject({ cellValuesByFieldId: AIRTABLE_FIELDS_BY_ID_SCHEMA })
24
+ .optional(),
25
+ });
26
+ const AIRTABLE_VIEW_CHANGE_SCHEMA = z.looseObject({
27
+ changedRecordsById: z
28
+ .record(z.string(), AIRTABLE_CHANGED_RECORD_SCHEMA)
29
+ .optional(),
30
+ createdRecordsById: z
31
+ .record(z.string(), AIRTABLE_CREATED_RECORD_SCHEMA)
32
+ .optional(),
33
+ destroyedRecordIds: z.string().array().optional(),
34
+ });
35
+ const AIRTABLE_TABLE_CHANGE_SCHEMA = z.looseObject({
36
+ changedRecordsById: z
37
+ .record(z.string(), AIRTABLE_CHANGED_RECORD_SCHEMA)
38
+ .optional(),
39
+ changedViewsById: z
40
+ .record(z.string(), AIRTABLE_VIEW_CHANGE_SCHEMA)
41
+ .optional(),
42
+ createdRecordsById: z
43
+ .record(z.string(), AIRTABLE_CREATED_RECORD_SCHEMA)
44
+ .optional(),
45
+ destroyedRecordIds: z.string().array().optional(),
46
+ });
47
+ /** One provider-native Airtable webhook transaction payload. */
48
+ export const AIRTABLE_WEBHOOK_PAYLOAD_SCHEMA = z
49
+ .looseObject({
50
+ actionMetadata: AIRTABLE_ACTION_METADATA_SCHEMA,
51
+ baseTransactionNumber: z.number().int().nonnegative(),
52
+ changedTablesById: z
53
+ .record(z.string(), AIRTABLE_TABLE_CHANGE_SCHEMA)
54
+ .optional(),
55
+ code: z.string().optional(),
56
+ error: z.literal(true).optional(),
57
+ payloadFormat: z.string(),
58
+ timestamp: z.iso.datetime({ offset: true }),
59
+ })
60
+ .catchall(z.json());
61
+ const AIRTABLE_RECORD_EVENT_SCHEMA = z.object({
62
+ /** Airtable base containing the affected record. */
63
+ baseId: z.string(),
64
+ /** Complete provider webhook transaction. */
65
+ event: AIRTABLE_WEBHOOK_PAYLOAD_SCHEMA,
66
+ /** Time at which Airtable committed the transaction. */
67
+ occurredAt: z.iso.datetime({ offset: true }),
68
+ /** Stable Airtable record ID. */
69
+ recordId: z.string(),
70
+ /** Airtable system that caused the transaction. */
71
+ source: z.string(),
72
+ /** Provider-specific actor or source details. */
73
+ sourceMetadata: z.record(z.string(), z.json()).optional(),
74
+ /** Stable Airtable table ID. */
75
+ tableId: z.string(),
76
+ /** Base-scoped transaction number assigned by Airtable. */
77
+ transactionNumber: z.number().int().nonnegative(),
78
+ });
79
+ export const AIRTABLE_RECORD_CREATED_EVENT_SCHEMA = AIRTABLE_RECORD_EVENT_SCHEMA.extend({
80
+ /** Time at which Airtable originally created the record. */
81
+ createdAt: z.iso.datetime({ offset: true }),
82
+ /** Current cell values keyed by stable field ID. */
83
+ fields: AIRTABLE_FIELDS_BY_ID_SCHEMA,
84
+ });
85
+ export const AIRTABLE_RECORD_UPDATED_EVENT_SCHEMA = AIRTABLE_RECORD_EVENT_SCHEMA.extend({
86
+ /** Stable IDs of fields changed by this transaction. */
87
+ changedFieldIds: z.string().array(),
88
+ /** Current cell values keyed by stable field ID. */
89
+ fields: AIRTABLE_FIELDS_BY_ID_SCHEMA,
90
+ /** Cell values before this transaction, keyed by stable field ID. */
91
+ previousFields: AIRTABLE_FIELDS_BY_ID_SCHEMA,
92
+ });
93
+ export const AIRTABLE_RECORD_DELETED_EVENT_SCHEMA = AIRTABLE_RECORD_EVENT_SCHEMA;
94
+ export const AIRTABLE_RECORD_MOVED_INTO_VIEW_EVENT_SCHEMA = AIRTABLE_RECORD_CREATED_EVENT_SCHEMA.extend({
95
+ /** Stable ID of the view the record entered. */
96
+ viewId: z.string(),
97
+ });
98
+ export const AIRTABLE_RECORD_MOVED_OUT_OF_VIEW_EVENT_SCHEMA = AIRTABLE_RECORD_EVENT_SCHEMA.extend({
99
+ /** Stable ID of the view the record exited. */
100
+ viewId: z.string(),
101
+ });
102
+ export const AIRTABLE_RECORD_EVENT_TYPES = [
103
+ "airtable.record.created",
104
+ "airtable.record.updated",
105
+ "airtable.record.deleted",
106
+ "airtable.record.movedIntoView",
107
+ "airtable.record.movedOutOfView",
108
+ ];
109
+ /**
110
+ * Expands one Airtable webhook transaction into semantic record events.
111
+ *
112
+ * @param value - Untrusted provider payload returned by Airtable.
113
+ * @param baseId - Stable ID of the base that owns the webhook.
114
+ */
115
+ export function normalizeAirtableRecordEvents(value, baseId) {
116
+ const event = AIRTABLE_WEBHOOK_PAYLOAD_SCHEMA.parse(value);
117
+ return Object.entries(event.changedTablesById ?? {}).flatMap(([tableId, table]) => [
118
+ ...Object.entries(table.createdRecordsById ?? {}).map(([recordId, record]) => ({
119
+ eventType: "airtable.record.created",
120
+ payload: AIRTABLE_RECORD_CREATED_EVENT_SCHEMA.parse({
121
+ ...recordEventBase(event, baseId, tableId, recordId),
122
+ createdAt: record.createdTime,
123
+ fields: record.cellValuesByFieldId,
124
+ }),
125
+ })),
126
+ ...Object.entries(table.changedRecordsById ?? {}).map(([recordId, record]) => ({
127
+ eventType: "airtable.record.updated",
128
+ payload: AIRTABLE_RECORD_UPDATED_EVENT_SCHEMA.parse({
129
+ ...recordEventBase(event, baseId, tableId, recordId),
130
+ changedFieldIds: Object.keys(record.current.cellValuesByFieldId),
131
+ fields: {
132
+ ...record.unchanged?.cellValuesByFieldId,
133
+ ...record.current.cellValuesByFieldId,
134
+ },
135
+ previousFields: {
136
+ ...record.unchanged?.cellValuesByFieldId,
137
+ ...record.previous?.cellValuesByFieldId,
138
+ },
139
+ }),
140
+ })),
141
+ ...(table.destroyedRecordIds ?? []).map((recordId) => ({
142
+ eventType: "airtable.record.deleted",
143
+ payload: AIRTABLE_RECORD_DELETED_EVENT_SCHEMA.parse(recordEventBase(event, baseId, tableId, recordId)),
144
+ })),
145
+ ...Object.entries(table.changedViewsById ?? {}).flatMap(([viewId, view]) => [
146
+ ...Object.entries(view.createdRecordsById ?? {}).map(([recordId, record]) => ({
147
+ eventType: "airtable.record.movedIntoView",
148
+ payload: AIRTABLE_RECORD_MOVED_INTO_VIEW_EVENT_SCHEMA.parse({
149
+ ...recordEventBase(event, baseId, tableId, recordId),
150
+ createdAt: record.createdTime,
151
+ fields: record.cellValuesByFieldId,
152
+ viewId,
153
+ }),
154
+ })),
155
+ ...(view.destroyedRecordIds ?? []).map((recordId) => ({
156
+ eventType: "airtable.record.movedOutOfView",
157
+ payload: AIRTABLE_RECORD_MOVED_OUT_OF_VIEW_EVENT_SCHEMA.parse({
158
+ ...recordEventBase(event, baseId, tableId, recordId),
159
+ viewId,
160
+ }),
161
+ })),
162
+ ]),
163
+ ]);
164
+ }
165
+ /**
166
+ * Builds fields shared by every normalized Airtable record event.
167
+ *
168
+ * @param event - Parsed provider transaction.
169
+ * @param baseId - Base containing the changed record.
170
+ * @param tableId - Table containing the changed record.
171
+ * @param recordId - Stable ID of the changed record.
172
+ */
173
+ function recordEventBase(event, baseId, tableId, recordId) {
174
+ return {
175
+ baseId,
176
+ event,
177
+ occurredAt: event.timestamp,
178
+ recordId,
179
+ source: event.actionMetadata.source,
180
+ sourceMetadata: event.actionMetadata.sourceMetadata,
181
+ tableId,
182
+ transactionNumber: event.baseTransactionNumber,
183
+ };
184
+ }
@@ -0,0 +1,70 @@
1
+ import type { JsonObject } from "type-fest";
2
+ import { z } from "zod";
3
+ /** Scalar value serialized into one Asana query parameter. */
4
+ type AsanaQueryValue = boolean | number | string | undefined;
5
+ /** Options for an authenticated Asana REST API request. */
6
+ export interface AsanaApiCallOptions<TSchema extends z.ZodType> {
7
+ /**
8
+ * Provider-native JSON body. Automatically wrapped in Asana's `data`
9
+ * envelope.
10
+ */
11
+ body?: JsonObject;
12
+ /** HTTP method. Defaults to `POST` with a body and `GET` otherwise. */
13
+ httpMethod?: "DELETE" | "GET" | "POST" | "PUT";
14
+ /** Provider-native URL query parameters. */
15
+ query?: Record<string, AsanaQueryValue>;
16
+ /** Schema for the complete successful Asana response envelope. */
17
+ responseSchema: TSchema;
18
+ }
19
+ /** Authenticated Asana REST API helper for custom actions. */
20
+ export interface AsanaApi {
21
+ /**
22
+ * Calls an Asana REST endpoint and validates its successful response.
23
+ *
24
+ * @param path - API path such as `/tasks/123`.
25
+ * @param options - Request data and response schema.
26
+ */
27
+ call<TSchema extends z.ZodType>(path: string, options: AsanaApiCallOptions<TSchema>): Promise<z.output<TSchema>>;
28
+ }
29
+ /** Structured error returned by an authenticated Asana API request. */
30
+ export declare class AsanaApiError extends Error {
31
+ /** Asana troubleshooting link, when supplied. */
32
+ readonly help?: string;
33
+ /** API path that failed. */
34
+ readonly path: string;
35
+ /** Asana support phrase for provider-side errors. */
36
+ readonly phrase?: string;
37
+ /** Retry delay from Asana's `Retry-After` header, in seconds. */
38
+ readonly retryAfter?: number;
39
+ /** HTTP status returned by Asana. */
40
+ readonly status: number;
41
+ /**
42
+ * Creates a structured Asana API error.
43
+ *
44
+ * @param options - Provider and transport error details.
45
+ * @param options.help - Provider troubleshooting link.
46
+ * @param options.message - Human-readable error message.
47
+ * @param options.path - API path that failed.
48
+ * @param options.phrase - Asana support phrase.
49
+ * @param options.retryAfter - Retry delay in seconds.
50
+ * @param options.status - HTTP response status.
51
+ */
52
+ constructor(options: {
53
+ help?: string;
54
+ message: string;
55
+ path: string;
56
+ phrase?: string;
57
+ retryAfter?: number;
58
+ status: number;
59
+ });
60
+ }
61
+ /**
62
+ * Creates a raw authenticated Asana REST API helper.
63
+ *
64
+ * Use this escape hatch for endpoints without packaged actions. Callers provide
65
+ * a Zod schema so custom responses remain validated at the provider boundary.
66
+ *
67
+ * @param secret - Resolved Asana integration secret.
68
+ */
69
+ export declare function getAsanaApi(secret: Record<string, unknown>): AsanaApi;
70
+ export {};
@@ -0,0 +1,119 @@
1
+ import { z } from "zod";
2
+ const ASANA_API_BASE_URL = "https://app.asana.com/api/1.0";
3
+ const ASANA_SECRET_SCHEMA = z.object({
4
+ accessToken: z.string().min(1),
5
+ });
6
+ const ASANA_ERROR_RESPONSE_SCHEMA = z.looseObject({
7
+ errors: z
8
+ .looseObject({
9
+ help: z.string().optional(),
10
+ message: z.string(),
11
+ phrase: z.string().optional(),
12
+ })
13
+ .array()
14
+ .optional(),
15
+ });
16
+ /** Structured error returned by an authenticated Asana API request. */
17
+ export class AsanaApiError extends Error {
18
+ /** Asana troubleshooting link, when supplied. */
19
+ help;
20
+ /** API path that failed. */
21
+ path;
22
+ /** Asana support phrase for provider-side errors. */
23
+ phrase;
24
+ /** Retry delay from Asana's `Retry-After` header, in seconds. */
25
+ retryAfter;
26
+ /** HTTP status returned by Asana. */
27
+ status;
28
+ /**
29
+ * Creates a structured Asana API error.
30
+ *
31
+ * @param options - Provider and transport error details.
32
+ * @param options.help - Provider troubleshooting link.
33
+ * @param options.message - Human-readable error message.
34
+ * @param options.path - API path that failed.
35
+ * @param options.phrase - Asana support phrase.
36
+ * @param options.retryAfter - Retry delay in seconds.
37
+ * @param options.status - HTTP response status.
38
+ */
39
+ constructor(options) {
40
+ super(`Asana ${options.path} failed: ${options.message}`);
41
+ this.name = "AsanaApiError";
42
+ this.help = options.help;
43
+ this.path = options.path;
44
+ this.phrase = options.phrase;
45
+ this.retryAfter = options.retryAfter;
46
+ this.status = options.status;
47
+ }
48
+ }
49
+ /**
50
+ * Creates a raw authenticated Asana REST API helper.
51
+ *
52
+ * Use this escape hatch for endpoints without packaged actions. Callers provide
53
+ * a Zod schema so custom responses remain validated at the provider boundary.
54
+ *
55
+ * @param secret - Resolved Asana integration secret.
56
+ */
57
+ export function getAsanaApi(secret) {
58
+ const { accessToken } = ASANA_SECRET_SCHEMA.parse(secret);
59
+ return {
60
+ call: async (path, options) => {
61
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
62
+ const url = new URL(`${ASANA_API_BASE_URL}${normalizedPath}`);
63
+ for (const [name, value] of Object.entries(options.query ?? {})) {
64
+ if (value !== undefined)
65
+ url.searchParams.set(name, String(value));
66
+ }
67
+ const response = await fetch(url, {
68
+ body: options.body ? JSON.stringify({ data: options.body }) : undefined,
69
+ headers: {
70
+ Accept: "application/json",
71
+ Authorization: `Bearer ${accessToken}`,
72
+ ...(options.body && { "Content-Type": "application/json" }),
73
+ },
74
+ method: options.httpMethod ?? (options.body === undefined ? "GET" : "POST"),
75
+ });
76
+ const responseBody = await response.text();
77
+ let payload;
78
+ try {
79
+ payload = JSON.parse(responseBody);
80
+ }
81
+ catch (cause) {
82
+ if (response.ok)
83
+ throw cause;
84
+ throw new AsanaApiError({
85
+ message: responseBody.trim() || `HTTP ${response.status}`,
86
+ path: normalizedPath,
87
+ retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
88
+ status: response.status,
89
+ });
90
+ }
91
+ if (!response.ok) {
92
+ const errorResult = ASANA_ERROR_RESPONSE_SCHEMA.safeParse(payload);
93
+ const error = errorResult.success
94
+ ? errorResult.data.errors?.[0]
95
+ : undefined;
96
+ throw new AsanaApiError({
97
+ help: error?.help,
98
+ message: error?.message ?? `HTTP ${response.status}`,
99
+ path: normalizedPath,
100
+ phrase: error?.phrase,
101
+ retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
102
+ status: response.status,
103
+ });
104
+ }
105
+ return options.responseSchema.parse(payload);
106
+ },
107
+ };
108
+ }
109
+ /**
110
+ * Parses Asana's integer retry delay header.
111
+ *
112
+ * @param value - Raw `Retry-After` header.
113
+ */
114
+ function parseRetryAfter(value) {
115
+ if (value === null)
116
+ return undefined;
117
+ const seconds = Number(value);
118
+ return Number.isFinite(seconds) ? seconds : undefined;
119
+ }