@automate.ax/integration-contracts 0.89.2 → 0.91.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.
@@ -0,0 +1,71 @@
1
+ import { type Encodable } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ declare const JOB_NIMBUS_API_ORIGINS: {
4
+ readonly accountsReceivable: "https://api.jobnimbus.com/accounts-receivable/v1/";
5
+ readonly activities: "https://api.jobnimbus.com/activities/v1/";
6
+ readonly files: "https://api.jobnimbus.com/files/v1/";
7
+ readonly identity: "https://api.jobnimbus.com/identity/v1/";
8
+ readonly public: "https://app.jobnimbus.com/api1/";
9
+ };
10
+ export declare const JOB_NIMBUS_VALUE_SCHEMA: z.ZodCustom<Encodable, Encodable>;
11
+ export declare const JOB_NIMBUS_RESPONSE_SCHEMA: z.ZodUnion<readonly [z.ZodCustom<Encodable, Encodable>, z.ZodPipe<z.ZodUndefined, z.ZodTransform<null, undefined>>]>;
12
+ export type JobNimbusApi = keyof typeof JOB_NIMBUS_API_ORIGINS;
13
+ interface JobNimbusRequestOptions<TSchema extends z.ZodType> {
14
+ body?: Encodable;
15
+ contentType?: string;
16
+ headers?: Record<string, string | undefined>;
17
+ method?: "DELETE" | "GET" | "PATCH" | "POST" | "PUT";
18
+ query?: Record<string, boolean | number | string | readonly (boolean | number | string)[] | null | undefined>;
19
+ responseSchema: TSchema;
20
+ }
21
+ /** Error returned by a rejected JobNimbus API request. */
22
+ export declare class JobNimbusApiError extends Error {
23
+ readonly details: Encodable;
24
+ readonly status: number;
25
+ /**
26
+ * Creates an error from a rejected JobNimbus response.
27
+ *
28
+ * @param status - HTTP response status.
29
+ * @param details - Codec-safe response details.
30
+ */
31
+ constructor(status: number, details: Encodable);
32
+ }
33
+ /**
34
+ * Creates an authenticated client for one documented JobNimbus API root.
35
+ *
36
+ * @param secret - Stored JobNimbus API key.
37
+ * @param api - Public API or one current Platform service.
38
+ */
39
+ export declare function getJobNimbusApi(secret: unknown, api: JobNimbusApi): {
40
+ /**
41
+ * Sends one JSON request below the selected JobNimbus API root.
42
+ *
43
+ * @param path - Relative path below the selected root.
44
+ * @param options - Request method, query, body, headers, and response
45
+ * schema.
46
+ */
47
+ request<TSchema extends z.ZodType>(path: string, options: JobNimbusRequestOptions<TSchema>): Promise<z.output<TSchema>>;
48
+ };
49
+ /**
50
+ * Downloads one JobNimbus attachment from its documented non-API route.
51
+ *
52
+ * @param secret - Stored JobNimbus API key.
53
+ * @param fileId - Attachment JNID.
54
+ */
55
+ export declare function downloadJobNimbusFile(secret: unknown, fileId: string): Promise<import("buffer").Blob>;
56
+ /**
57
+ * Converts public camelCase JSON into JobNimbus's provider-native keys.
58
+ *
59
+ * @param value - Codec-safe public value.
60
+ */
61
+ export declare function toJobNimbus(value: Encodable): Encodable;
62
+ /**
63
+ * Converts JobNimbus JSON into public camelCase values.
64
+ *
65
+ * Documented legacy Unix date fields become Date instances. Unknown fields are
66
+ * preserved so account-defined CRM fields survive normalization.
67
+ *
68
+ * @param value - Raw provider value.
69
+ */
70
+ export declare function fromJobNimbus(value: unknown): Encodable;
71
+ export {};
@@ -0,0 +1,231 @@
1
+ import { isEncodable } from "@automate.ax/codec";
2
+ import * as z from "zod";
3
+ const JOB_NIMBUS_API_ORIGINS = {
4
+ accountsReceivable: "https://api.jobnimbus.com/accounts-receivable/v1/",
5
+ activities: "https://api.jobnimbus.com/activities/v1/",
6
+ files: "https://api.jobnimbus.com/files/v1/",
7
+ identity: "https://api.jobnimbus.com/identity/v1/",
8
+ public: "https://app.jobnimbus.com/api1/",
9
+ };
10
+ const JOB_NIMBUS_SECRET_SCHEMA = z.object({
11
+ apiKey: z.string().trim().min(1),
12
+ });
13
+ export const JOB_NIMBUS_VALUE_SCHEMA = z.custom(isEncodable, {
14
+ message: "Expected a codec-safe JobNimbus value.",
15
+ });
16
+ export const JOB_NIMBUS_RESPONSE_SCHEMA = z.union([
17
+ JOB_NIMBUS_VALUE_SCHEMA,
18
+ z.undefined().transform(() => null),
19
+ ]);
20
+ /** Error returned by a rejected JobNimbus API request. */
21
+ export class JobNimbusApiError extends Error {
22
+ details;
23
+ status;
24
+ /**
25
+ * Creates an error from a rejected JobNimbus response.
26
+ *
27
+ * @param status - HTTP response status.
28
+ * @param details - Codec-safe response details.
29
+ */
30
+ constructor(status, details) {
31
+ super(`JobNimbus API request failed (${status}).`);
32
+ this.name = "JobNimbusApiError";
33
+ this.details = details;
34
+ this.status = status;
35
+ }
36
+ }
37
+ /**
38
+ * Creates an authenticated client for one documented JobNimbus API root.
39
+ *
40
+ * @param secret - Stored JobNimbus API key.
41
+ * @param api - Public API or one current Platform service.
42
+ */
43
+ export function getJobNimbusApi(secret, api) {
44
+ const { apiKey } = JOB_NIMBUS_SECRET_SCHEMA.parse(secret);
45
+ const origin = JOB_NIMBUS_API_ORIGINS[api];
46
+ const root = new URL(origin);
47
+ return {
48
+ /**
49
+ * Sends one JSON request below the selected JobNimbus API root.
50
+ *
51
+ * @param path - Relative path below the selected root.
52
+ * @param options - Request method, query, body, headers, and response
53
+ * schema.
54
+ */
55
+ async request(path, options) {
56
+ const normalizedPath = path.replace(/^\/+/, "");
57
+ if (!normalizedPath ||
58
+ normalizedPath.includes("://") ||
59
+ normalizedPath.includes("\\")) {
60
+ throw new TypeError("JobNimbus API paths must be relative.");
61
+ }
62
+ const url = new URL(normalizedPath, origin);
63
+ if (url.origin !== root.origin ||
64
+ !url.pathname.startsWith(root.pathname)) {
65
+ throw new TypeError("JobNimbus API paths must remain below the selected root.");
66
+ }
67
+ for (const [key, value] of Object.entries(options.query ?? {})) {
68
+ if (value == null)
69
+ continue;
70
+ if (Array.isArray(value)) {
71
+ for (const item of value)
72
+ url.searchParams.append(key, String(item));
73
+ }
74
+ else {
75
+ url.searchParams.set(key, String(value));
76
+ }
77
+ }
78
+ const headers = new Headers({
79
+ Accept: "application/json",
80
+ Authorization: `Bearer ${apiKey}`,
81
+ });
82
+ for (const [key, value] of Object.entries(options.headers ?? {})) {
83
+ if (value !== undefined)
84
+ headers.set(key, value);
85
+ }
86
+ if (options.body !== undefined) {
87
+ headers.set("Content-Type", options.contentType ?? "application/json");
88
+ }
89
+ const response = await fetch(url, {
90
+ body: options.body === undefined
91
+ ? undefined
92
+ : JSON.stringify(api === "public" ? toJobNimbus(options.body) : options.body),
93
+ headers,
94
+ method: options.method ?? "GET",
95
+ });
96
+ const text = await response.text();
97
+ const parsed = JOB_NIMBUS_RESPONSE_SCHEMA.safeParse(fromJobNimbus(text ? parseJson(text) : null));
98
+ if (!response.ok || !parsed.success) {
99
+ throw new JobNimbusApiError(response.status, parsed.success ? parsed.data : { response: text });
100
+ }
101
+ return options.responseSchema.parse(parsed.data);
102
+ },
103
+ };
104
+ }
105
+ /**
106
+ * Downloads one JobNimbus attachment from its documented non-API route.
107
+ *
108
+ * @param secret - Stored JobNimbus API key.
109
+ * @param fileId - Attachment JNID.
110
+ */
111
+ export async function downloadJobNimbusFile(secret, fileId) {
112
+ const { apiKey } = JOB_NIMBUS_SECRET_SCHEMA.parse(secret);
113
+ const response = await fetch(new URL(encodeURIComponent(z.string().trim().min(1).parse(fileId)), "https://app.jobnimbus.com/files/"), { headers: { Authorization: `Bearer ${apiKey}` } });
114
+ if (!response.ok) {
115
+ throw new JobNimbusApiError(response.status, {
116
+ response: await response.text(),
117
+ });
118
+ }
119
+ return response.blob();
120
+ }
121
+ /**
122
+ * Converts public camelCase JSON into JobNimbus's provider-native keys.
123
+ *
124
+ * @param value - Codec-safe public value.
125
+ */
126
+ export function toJobNimbus(value) {
127
+ if (value instanceof Date)
128
+ return value.toISOString();
129
+ if (Array.isArray(value))
130
+ return value.map(toJobNimbus);
131
+ if (!isPlainObject(value))
132
+ return value;
133
+ return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => key === "customFields" && isPlainObject(item)
134
+ ? Object.entries(item).map(([field, fieldValue]) => [
135
+ field,
136
+ toJobNimbus(fieldValue),
137
+ ])
138
+ : [[toSnakeCase(key), toJobNimbus(item)]]));
139
+ }
140
+ /**
141
+ * Converts JobNimbus JSON into public camelCase values.
142
+ *
143
+ * Documented legacy Unix date fields become Date instances. Unknown fields are
144
+ * preserved so account-defined CRM fields survive normalization.
145
+ *
146
+ * @param value - Raw provider value.
147
+ */
148
+ export function fromJobNimbus(value) {
149
+ return normalizeJobNimbusValue(value);
150
+ }
151
+ /**
152
+ * Recursively normalizes one raw provider value and its original key.
153
+ *
154
+ * @param value - Raw provider value.
155
+ * @param providerKey - Original provider field name, when nested in an object.
156
+ */
157
+ function normalizeJobNimbusValue(value, providerKey) {
158
+ if (typeof value === "number" && providerKey?.startsWith("date_")) {
159
+ return new Date(value * 1_000);
160
+ }
161
+ if (Array.isArray(value)) {
162
+ return value.map((item) => normalizeJobNimbusValue(item));
163
+ }
164
+ if (!isPlainObject(value))
165
+ return JOB_NIMBUS_VALUE_SCHEMA.parse(value);
166
+ const customFields = {};
167
+ return Object.fromEntries([
168
+ ...Object.entries(value).flatMap(([key, item]) => {
169
+ if (/^cf_(?:boolean|date|double|long|string)_\d+$/.test(key)) {
170
+ customFields[key] = normalizeJobNimbusValue(item, key);
171
+ return [];
172
+ }
173
+ return [
174
+ [
175
+ key === "extermal_id"
176
+ ? "externalId"
177
+ : key === "jnid"
178
+ ? "id"
179
+ : key === "recid"
180
+ ? "recordId"
181
+ : toCamelCase(key),
182
+ normalizeJobNimbusValue(item, key),
183
+ ],
184
+ ];
185
+ }),
186
+ ...(Object.keys(customFields).length > 0
187
+ ? [["customFields", customFields]]
188
+ : []),
189
+ ]);
190
+ }
191
+ /**
192
+ * Parses a response body while preserving non-JSON diagnostics.
193
+ *
194
+ * @param value - Raw response body.
195
+ */
196
+ function parseJson(value) {
197
+ try {
198
+ return JSON.parse(value);
199
+ }
200
+ catch {
201
+ return { response: value };
202
+ }
203
+ }
204
+ /**
205
+ * Checks whether a value can be traversed as a JSON-style object.
206
+ *
207
+ * @param value - Candidate object value.
208
+ */
209
+ function isPlainObject(value) {
210
+ return typeof value === "object" && value !== null && !Array.isArray(value);
211
+ }
212
+ /**
213
+ * Converts one provider snake-case key to camel case.
214
+ *
215
+ * @param value - Provider field name.
216
+ */
217
+ function toCamelCase(value) {
218
+ return value
219
+ .replace(/_([a-z0-9])/g, (_match, character) => character.toUpperCase())
220
+ .replace(/^[A-Z]/, (character) => character.toLowerCase());
221
+ }
222
+ /**
223
+ * Converts one public camel-case key to provider snake case.
224
+ *
225
+ * @param value - Public field name.
226
+ */
227
+ function toSnakeCase(value) {
228
+ return value
229
+ .replace(/([a-zA-Z])([0-9])/g, "$1_$2")
230
+ .replace(/[A-Z]/g, (character) => `_${character.toLowerCase()}`);
231
+ }
@@ -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";