@automate.ax/integration-contracts 0.109.1 → 0.111.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.
@@ -9,6 +9,8 @@ export interface AsanaApiCallOptions<TSchema extends z.ZodType> {
9
9
  * envelope.
10
10
  */
11
11
  body?: JsonObject;
12
+ /** Provider-native multipart body for attachment uploads. */
13
+ formData?: FormData;
12
14
  /** HTTP method. Defaults to `POST` with a body and `GET` otherwise. */
13
15
  httpMethod?: "DELETE" | "GET" | "POST" | "PUT";
14
16
  /** Provider-native URL query parameters. */
package/dist/asana/api.js CHANGED
@@ -58,31 +58,58 @@ export function getAsanaApi(secret) {
58
58
  const { accessToken } = ASANA_SECRET_SCHEMA.parse(secret);
59
59
  return {
60
60
  call: async (path, options) => {
61
+ if (options.body && options.formData) {
62
+ throw new Error("Asana requests cannot include JSON and multipart bodies.");
63
+ }
61
64
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
62
- const url = new URL(`${ASANA_API_BASE_URL}${normalizedPath}`);
65
+ const url = new URL(path.replace(/^\/+/, ""), `${ASANA_API_BASE_URL}/`);
66
+ if (url.origin !== new URL(ASANA_API_BASE_URL).origin ||
67
+ !url.pathname.startsWith("/api/1.0/")) {
68
+ throw new Error("Asana API paths must remain under /api/1.0.");
69
+ }
63
70
  for (const [name, value] of Object.entries(options.query ?? {})) {
64
71
  if (value !== undefined)
65
72
  url.searchParams.set(name, String(value));
66
73
  }
67
74
  const response = await fetch(url, {
68
- body: options.body ? JSON.stringify({ data: options.body }) : undefined,
75
+ body: options.formData
76
+ ? options.formData
77
+ : options.body
78
+ ? JSON.stringify({ data: options.body })
79
+ : undefined,
69
80
  headers: {
70
81
  Accept: "application/json",
71
82
  Authorization: `Bearer ${accessToken}`,
72
83
  ...(options.body && { "Content-Type": "application/json" }),
73
84
  },
74
- method: options.httpMethod ?? (options.body === undefined ? "GET" : "POST"),
85
+ method: options.httpMethod ??
86
+ (options.body === undefined && options.formData === undefined
87
+ ? "GET"
88
+ : "POST"),
75
89
  });
76
90
  const responseBody = await response.text();
77
91
  let payload;
78
- try {
79
- payload = JSON.parse(responseBody);
92
+ if (responseBody === "") {
93
+ payload = undefined;
94
+ }
95
+ else {
96
+ try {
97
+ payload = JSON.parse(responseBody);
98
+ }
99
+ catch (cause) {
100
+ if (response.ok)
101
+ throw cause;
102
+ throw new AsanaApiError({
103
+ message: responseBody.trim() || `HTTP ${response.status}`,
104
+ path: normalizedPath,
105
+ retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
106
+ status: response.status,
107
+ });
108
+ }
80
109
  }
81
- catch (cause) {
82
- if (response.ok)
83
- throw cause;
110
+ if (!response.ok && responseBody === "") {
84
111
  throw new AsanaApiError({
85
- message: responseBody.trim() || `HTTP ${response.status}`,
112
+ message: `HTTP ${response.status}`,
86
113
  path: normalizedPath,
87
114
  retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
88
115
  status: response.status,
@@ -0,0 +1,38 @@
1
+ import * as z from "zod";
2
+ export declare const ASANA_RESOURCE_SCHEMA: z.ZodObject<{
3
+ id: z.ZodString;
4
+ name: z.ZodOptional<z.ZodString>;
5
+ type: z.ZodOptional<z.ZodString>;
6
+ }, z.core.$strip>;
7
+ export declare const ASANA_PAGE_INFO_SCHEMA: z.ZodObject<{
8
+ nextOffset: z.ZodOptional<z.ZodString>;
9
+ }, z.core.$strip>;
10
+ export declare const ASANA_PROVIDER_RESOURCE_SCHEMA: z.ZodObject<{
11
+ gid: z.ZodString;
12
+ name: z.ZodOptional<z.ZodString>;
13
+ resource_subtype: z.ZodOptional<z.ZodString>;
14
+ resource_type: z.ZodOptional<z.ZodString>;
15
+ }, z.core.$loose>;
16
+ export declare const ASANA_PROVIDER_PAGE_SCHEMA: z.ZodObject<{
17
+ next_page: z.ZodOptional<z.ZodNullable<z.ZodObject<{
18
+ offset: z.ZodString;
19
+ }, z.core.$loose>>>;
20
+ }, z.core.$loose>;
21
+ /**
22
+ * Converts a provider resource identity to the public shape.
23
+ *
24
+ * @param value - Provider resource identity.
25
+ */
26
+ export declare function toAsanaResource(value: z.output<typeof ASANA_PROVIDER_RESOURCE_SCHEMA>): {
27
+ id: string;
28
+ name: string | undefined;
29
+ type: string | undefined;
30
+ };
31
+ /**
32
+ * Converts provider pagination metadata to the public shape.
33
+ *
34
+ * @param value - Provider page envelope.
35
+ */
36
+ export declare function toAsanaPageInfo(value: z.output<typeof ASANA_PROVIDER_PAGE_SCHEMA>): {
37
+ nextOffset: string | undefined;
38
+ };
@@ -0,0 +1,42 @@
1
+ import * as z from "zod";
2
+ export const ASANA_RESOURCE_SCHEMA = z.object({
3
+ /** Stable Asana globally unique identifier. */
4
+ id: z.string(),
5
+ /** Human-readable resource name, when available. */
6
+ name: z.string().optional(),
7
+ /** Provider resource type. */
8
+ type: z.string().optional(),
9
+ });
10
+ export const ASANA_PAGE_INFO_SCHEMA = z.object({
11
+ /** Opaque token for the next page, when more results are available. */
12
+ nextOffset: z.string().optional(),
13
+ });
14
+ export const ASANA_PROVIDER_RESOURCE_SCHEMA = z.looseObject({
15
+ gid: z.string(),
16
+ name: z.string().optional(),
17
+ resource_subtype: z.string().optional(),
18
+ resource_type: z.string().optional(),
19
+ });
20
+ export const ASANA_PROVIDER_PAGE_SCHEMA = z.looseObject({
21
+ next_page: z.looseObject({ offset: z.string() }).nullable().optional(),
22
+ });
23
+ /**
24
+ * Converts a provider resource identity to the public shape.
25
+ *
26
+ * @param value - Provider resource identity.
27
+ */
28
+ export function toAsanaResource(value) {
29
+ return {
30
+ id: value.gid,
31
+ name: value.name,
32
+ type: value.resource_type,
33
+ };
34
+ }
35
+ /**
36
+ * Converts provider pagination metadata to the public shape.
37
+ *
38
+ * @param value - Provider page envelope.
39
+ */
40
+ export function toAsanaPageInfo(value) {
41
+ return { nextOffset: value.next_page?.offset };
42
+ }