@automate.ax/integration-contracts 0.146.0 → 0.147.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/dist/tally/api.d.ts +63 -0
- package/dist/tally/api.js +181 -0
- package/dist/tally/events.d.ts +202 -0
- package/dist/tally/events.js +79 -0
- package/dist/tally/index.d.ts +3 -0
- package/dist/tally/index.js +3 -0
- package/dist/tally/openapi-schemas.generated.json +5479 -0
- package/dist/tally/openapi-types.generated.d.ts +2567 -0
- package/dist/tally/openapi-types.generated.js +1 -0
- package/dist/tally/schemas.d.ts +17 -0
- package/dist/tally/schemas.js +23 -0
- package/dist/triggers.d.ts +2 -1
- package/package.json +9 -2
- package/src/tally/api.ts +233 -0
- package/src/tally/events.ts +88 -0
- package/src/tally/index.ts +3 -0
- package/src/tally/openapi-schemas.generated.json +5479 -0
- package/src/tally/openapi-types.generated.ts +2858 -0
- package/src/tally/schemas.ts +58 -0
- package/src/triggers.ts +2 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Encodable } from "@automate.ax/codec";
|
|
2
|
+
import { type TallySchemaName, type TallySchemaValue } from "./schemas.js";
|
|
3
|
+
/** Resolved account accepted by the shared Tally API client. */
|
|
4
|
+
export interface TallyResolvedAccount {
|
|
5
|
+
connectionMethodId: string;
|
|
6
|
+
secret: Record<string, unknown>;
|
|
7
|
+
serviceId: "tally";
|
|
8
|
+
}
|
|
9
|
+
/** Options for one authenticated Tally REST request. */
|
|
10
|
+
export interface TallyRequestOptions {
|
|
11
|
+
/** Tally request body. */
|
|
12
|
+
body?: Encodable;
|
|
13
|
+
/** HTTP verb. Defaults to `GET`. */
|
|
14
|
+
method?: "DELETE" | "GET" | "PATCH" | "POST";
|
|
15
|
+
/** Query parameters accepted by the selected endpoint. */
|
|
16
|
+
query?: Record<string, number | string | string[] | null | undefined>;
|
|
17
|
+
}
|
|
18
|
+
/** Options for a Tally request with a JSON response. */
|
|
19
|
+
export interface TallyJsonRequestOptions<TName extends TallySchemaName> extends TallyRequestOptions {
|
|
20
|
+
/** Frozen OpenAPI response schema name. */
|
|
21
|
+
responseSchema: TName;
|
|
22
|
+
}
|
|
23
|
+
/** Structured error returned by a rejected Tally request. */
|
|
24
|
+
export declare class TallyApiError extends Error {
|
|
25
|
+
readonly body?: Encodable;
|
|
26
|
+
readonly retryAfter?: number;
|
|
27
|
+
readonly status: number;
|
|
28
|
+
/**
|
|
29
|
+
* Creates an error from one rejected Tally response.
|
|
30
|
+
*
|
|
31
|
+
* @param options Rejected response details.
|
|
32
|
+
* @param options.body Parsed provider response body.
|
|
33
|
+
* @param options.retryAfter Provider retry delay in seconds.
|
|
34
|
+
* @param options.status HTTP response status.
|
|
35
|
+
*/
|
|
36
|
+
constructor(options: {
|
|
37
|
+
body?: Encodable;
|
|
38
|
+
retryAfter?: number;
|
|
39
|
+
status: number;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates an authenticated Tally API client.
|
|
44
|
+
*
|
|
45
|
+
* @param account Resolved Tally API-key account.
|
|
46
|
+
* @throws When the account method or API key is invalid.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getTallyApi(account: TallyResolvedAccount): {
|
|
49
|
+
/**
|
|
50
|
+
* Sends a request and validates its JSON response.
|
|
51
|
+
*
|
|
52
|
+
* @param path Relative Tally API path.
|
|
53
|
+
* @param options Request and response-schema options.
|
|
54
|
+
*/
|
|
55
|
+
request<TName extends TallySchemaName>(path: string, options: TallyJsonRequestOptions<TName>): Promise<TallySchemaValue<TName>>;
|
|
56
|
+
/**
|
|
57
|
+
* Sends a request whose successful response has no body.
|
|
58
|
+
*
|
|
59
|
+
* @param path Relative Tally API path.
|
|
60
|
+
* @param options Request options.
|
|
61
|
+
*/
|
|
62
|
+
requestVoid(path: string, options: TallyRequestOptions): Promise<void>;
|
|
63
|
+
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { encodableSchema } from "@automate.ax/codec";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
import { tallySchema, } from "./schemas.js";
|
|
4
|
+
const TALLY_API_BASE_URL = "https://api.tally.so/";
|
|
5
|
+
const TALLY_API_ORIGIN = new URL(TALLY_API_BASE_URL).origin;
|
|
6
|
+
const TALLY_API_VERSION = "2026-08-04";
|
|
7
|
+
const TALLY_API_KEY_SECRET_SCHEMA = z.object({
|
|
8
|
+
apiKey: z.string().trim().min(1),
|
|
9
|
+
});
|
|
10
|
+
/** Structured error returned by a rejected Tally request. */
|
|
11
|
+
export class TallyApiError extends Error {
|
|
12
|
+
body;
|
|
13
|
+
retryAfter;
|
|
14
|
+
status;
|
|
15
|
+
/**
|
|
16
|
+
* Creates an error from one rejected Tally response.
|
|
17
|
+
*
|
|
18
|
+
* @param options Rejected response details.
|
|
19
|
+
* @param options.body Parsed provider response body.
|
|
20
|
+
* @param options.retryAfter Provider retry delay in seconds.
|
|
21
|
+
* @param options.status HTTP response status.
|
|
22
|
+
*/
|
|
23
|
+
constructor(options) {
|
|
24
|
+
super(getErrorMessage(options.body) ??
|
|
25
|
+
`Tally API request failed with status ${options.status}.`);
|
|
26
|
+
this.name = "TallyApiError";
|
|
27
|
+
this.body = options.body;
|
|
28
|
+
this.retryAfter = options.retryAfter;
|
|
29
|
+
this.status = options.status;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates an authenticated Tally API client.
|
|
34
|
+
*
|
|
35
|
+
* @param account Resolved Tally API-key account.
|
|
36
|
+
* @throws When the account method or API key is invalid.
|
|
37
|
+
*/
|
|
38
|
+
export function getTallyApi(account) {
|
|
39
|
+
if (account.connectionMethodId !== "api-key") {
|
|
40
|
+
throw new Error(`Unsupported Tally connection method: ${account.connectionMethodId}`);
|
|
41
|
+
}
|
|
42
|
+
const { apiKey } = TALLY_API_KEY_SECRET_SCHEMA.parse(account.secret);
|
|
43
|
+
return {
|
|
44
|
+
/**
|
|
45
|
+
* Sends a request and validates its JSON response.
|
|
46
|
+
*
|
|
47
|
+
* @param path Relative Tally API path.
|
|
48
|
+
* @param options Request and response-schema options.
|
|
49
|
+
*/
|
|
50
|
+
async request(path, options) {
|
|
51
|
+
return tallySchema(options.responseSchema).parse(await sendTallyRequest(apiKey, path, options));
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Sends a request whose successful response has no body.
|
|
55
|
+
*
|
|
56
|
+
* @param path Relative Tally API path.
|
|
57
|
+
* @param options Request options.
|
|
58
|
+
*/
|
|
59
|
+
async requestVoid(path, options) {
|
|
60
|
+
await sendTallyRequest(apiKey, path, options);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Sends one authenticated request while enforcing the Tally API origin.
|
|
66
|
+
*
|
|
67
|
+
* @param apiKey Tally API key.
|
|
68
|
+
* @param path Relative Tally API path.
|
|
69
|
+
* @param options Request options.
|
|
70
|
+
*/
|
|
71
|
+
async function sendTallyRequest(apiKey, path, options) {
|
|
72
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
73
|
+
if (!normalizedPath ||
|
|
74
|
+
normalizedPath.includes("://") ||
|
|
75
|
+
normalizedPath.includes("\\") ||
|
|
76
|
+
normalizedPath.includes("?") ||
|
|
77
|
+
normalizedPath.includes("#") ||
|
|
78
|
+
normalizedPath.split("/").some(isTraversalSegment)) {
|
|
79
|
+
throw new TypeError("Tally API paths must be relative.");
|
|
80
|
+
}
|
|
81
|
+
const url = new URL(normalizedPath, TALLY_API_BASE_URL);
|
|
82
|
+
if (url.origin !== TALLY_API_ORIGIN) {
|
|
83
|
+
throw new TypeError("Tally API paths must remain on api.tally.so.");
|
|
84
|
+
}
|
|
85
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
86
|
+
if (value == null)
|
|
87
|
+
continue;
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
for (const item of value)
|
|
90
|
+
url.searchParams.append(name, item);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
url.searchParams.set(name, String(value));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const response = await fetch(url, {
|
|
97
|
+
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
98
|
+
headers: {
|
|
99
|
+
Accept: "application/json",
|
|
100
|
+
Authorization: `Bearer ${apiKey}`,
|
|
101
|
+
"tally-version": TALLY_API_VERSION,
|
|
102
|
+
...(options.body === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { "Content-Type": "application/json" }),
|
|
105
|
+
},
|
|
106
|
+
method: options.method ?? "GET",
|
|
107
|
+
redirect: "error",
|
|
108
|
+
});
|
|
109
|
+
const text = await response.text();
|
|
110
|
+
const parsed = text ? parseJson(text) : undefined;
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const body = encodableSchema.safeParse(parsed);
|
|
113
|
+
throw new TallyApiError({
|
|
114
|
+
...(body.success && { body: body.data }),
|
|
115
|
+
retryAfter: parseRetryAfter(response.headers.get("Retry-After")),
|
|
116
|
+
status: response.status,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return parsed;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Detects direct or encoded path traversal segments.
|
|
123
|
+
*
|
|
124
|
+
* @param segment One URL path segment.
|
|
125
|
+
*/
|
|
126
|
+
function isTraversalSegment(segment) {
|
|
127
|
+
try {
|
|
128
|
+
const decoded = decodeURIComponent(segment);
|
|
129
|
+
return decoded === "." || decoded === ".." || decoded.includes("/");
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Parses one JSON response or reports a provider contract failure.
|
|
137
|
+
*
|
|
138
|
+
* @param text Provider response text.
|
|
139
|
+
* @throws When the provider response is not valid JSON.
|
|
140
|
+
*/
|
|
141
|
+
function parseJson(text) {
|
|
142
|
+
try {
|
|
143
|
+
return JSON.parse(text);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new Error("Tally API returned invalid JSON.");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parses Retry-After seconds or an HTTP date into a delay in seconds.
|
|
151
|
+
*
|
|
152
|
+
* @param value Retry-After header value.
|
|
153
|
+
*/
|
|
154
|
+
function parseRetryAfter(value) {
|
|
155
|
+
if (!value)
|
|
156
|
+
return undefined;
|
|
157
|
+
const seconds = Number(value);
|
|
158
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
159
|
+
return seconds;
|
|
160
|
+
const date = Date.parse(value);
|
|
161
|
+
return Number.isNaN(date)
|
|
162
|
+
? undefined
|
|
163
|
+
: Math.max(0, (date - Date.now()) / 1_000);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Extracts the provider's human-readable error message.
|
|
167
|
+
*
|
|
168
|
+
* @param body Parsed provider error response.
|
|
169
|
+
*/
|
|
170
|
+
function getErrorMessage(body) {
|
|
171
|
+
if (typeof body === "string")
|
|
172
|
+
return body;
|
|
173
|
+
if (!body || Array.isArray(body) || typeof body !== "object")
|
|
174
|
+
return undefined;
|
|
175
|
+
for (const key of ["message", "error"]) {
|
|
176
|
+
const value = Reflect.get(body, key);
|
|
177
|
+
if (typeof value === "string" && value)
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export declare const TALLY_FORM_RESPONSE_EVENT_TYPE: "FORM_RESPONSE";
|
|
3
|
+
/** Signed form-response webhook payload delivered by Tally. */
|
|
4
|
+
export declare const TALLY_FORM_RESPONSE_EVENT_SCHEMA: z.ZodObject<{
|
|
5
|
+
createdAt: z.ZodISODateTime;
|
|
6
|
+
data: z.ZodObject<{
|
|
7
|
+
createdAt: z.ZodISODateTime;
|
|
8
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
9
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
10
|
+
id: z.ZodString;
|
|
11
|
+
text: z.ZodString;
|
|
12
|
+
}, z.core.$strip>>>;
|
|
13
|
+
key: z.ZodString;
|
|
14
|
+
label: z.ZodString;
|
|
15
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
16
|
+
id: z.ZodString;
|
|
17
|
+
text: z.ZodString;
|
|
18
|
+
}, z.core.$strip>>>;
|
|
19
|
+
rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
20
|
+
id: z.ZodString;
|
|
21
|
+
text: z.ZodString;
|
|
22
|
+
}, z.core.$strip>>>;
|
|
23
|
+
type: z.ZodEnum<{
|
|
24
|
+
MATRIX: "MATRIX";
|
|
25
|
+
INPUT_TEXT: "INPUT_TEXT";
|
|
26
|
+
INPUT_NUMBER: "INPUT_NUMBER";
|
|
27
|
+
INPUT_EMAIL: "INPUT_EMAIL";
|
|
28
|
+
INPUT_LINK: "INPUT_LINK";
|
|
29
|
+
INPUT_PHONE_NUMBER: "INPUT_PHONE_NUMBER";
|
|
30
|
+
INPUT_DATE: "INPUT_DATE";
|
|
31
|
+
INPUT_TIME: "INPUT_TIME";
|
|
32
|
+
TEXTAREA: "TEXTAREA";
|
|
33
|
+
FILE_UPLOAD: "FILE_UPLOAD";
|
|
34
|
+
LINEAR_SCALE: "LINEAR_SCALE";
|
|
35
|
+
RATING: "RATING";
|
|
36
|
+
HIDDEN_FIELDS: "HIDDEN_FIELDS";
|
|
37
|
+
PAYMENT: "PAYMENT";
|
|
38
|
+
SIGNATURE: "SIGNATURE";
|
|
39
|
+
CALCULATED_FIELDS: "CALCULATED_FIELDS";
|
|
40
|
+
MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
|
|
41
|
+
CHECKBOXES: "CHECKBOXES";
|
|
42
|
+
DROPDOWN: "DROPDOWN";
|
|
43
|
+
RANKING: "RANKING";
|
|
44
|
+
MULTI_SELECT: "MULTI_SELECT";
|
|
45
|
+
}>;
|
|
46
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
47
|
+
id: z.ZodString;
|
|
48
|
+
mimeType: z.ZodString;
|
|
49
|
+
name: z.ZodString;
|
|
50
|
+
size: z.ZodNumber;
|
|
51
|
+
url: z.ZodURL;
|
|
52
|
+
}, z.core.$strip>>, z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>, z.ZodNull]>;
|
|
53
|
+
}, z.core.$strip>>;
|
|
54
|
+
formId: z.ZodString;
|
|
55
|
+
formName: z.ZodString;
|
|
56
|
+
respondentId: z.ZodString;
|
|
57
|
+
responseId: z.ZodString;
|
|
58
|
+
submissionId: z.ZodString;
|
|
59
|
+
submissionPdfUrl: z.ZodOptional<z.ZodURL>;
|
|
60
|
+
submissionPreviewUrl: z.ZodOptional<z.ZodURL>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
eventId: z.ZodString;
|
|
63
|
+
eventType: z.ZodLiteral<"FORM_RESPONSE">;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
export type TallyFormResponseEvent = z.output<typeof TALLY_FORM_RESPONSE_EVENT_SCHEMA>;
|
|
66
|
+
export declare const TALLY_TRIGGER_CONFIG_SCHEMA: z.ZodObject<{
|
|
67
|
+
formId: z.ZodString;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
export declare const tallyTriggerContracts: {
|
|
70
|
+
readonly "tally.form.event": {
|
|
71
|
+
readonly configSchema: z.ZodObject<{
|
|
72
|
+
formId: z.ZodString;
|
|
73
|
+
}, z.core.$strip>;
|
|
74
|
+
readonly eventSchema: z.ZodObject<{
|
|
75
|
+
createdAt: z.ZodISODateTime;
|
|
76
|
+
data: z.ZodObject<{
|
|
77
|
+
createdAt: z.ZodISODateTime;
|
|
78
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
79
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
80
|
+
id: z.ZodString;
|
|
81
|
+
text: z.ZodString;
|
|
82
|
+
}, z.core.$strip>>>;
|
|
83
|
+
key: z.ZodString;
|
|
84
|
+
label: z.ZodString;
|
|
85
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
86
|
+
id: z.ZodString;
|
|
87
|
+
text: z.ZodString;
|
|
88
|
+
}, z.core.$strip>>>;
|
|
89
|
+
rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
90
|
+
id: z.ZodString;
|
|
91
|
+
text: z.ZodString;
|
|
92
|
+
}, z.core.$strip>>>;
|
|
93
|
+
type: z.ZodEnum<{
|
|
94
|
+
MATRIX: "MATRIX";
|
|
95
|
+
INPUT_TEXT: "INPUT_TEXT";
|
|
96
|
+
INPUT_NUMBER: "INPUT_NUMBER";
|
|
97
|
+
INPUT_EMAIL: "INPUT_EMAIL";
|
|
98
|
+
INPUT_LINK: "INPUT_LINK";
|
|
99
|
+
INPUT_PHONE_NUMBER: "INPUT_PHONE_NUMBER";
|
|
100
|
+
INPUT_DATE: "INPUT_DATE";
|
|
101
|
+
INPUT_TIME: "INPUT_TIME";
|
|
102
|
+
TEXTAREA: "TEXTAREA";
|
|
103
|
+
FILE_UPLOAD: "FILE_UPLOAD";
|
|
104
|
+
LINEAR_SCALE: "LINEAR_SCALE";
|
|
105
|
+
RATING: "RATING";
|
|
106
|
+
HIDDEN_FIELDS: "HIDDEN_FIELDS";
|
|
107
|
+
PAYMENT: "PAYMENT";
|
|
108
|
+
SIGNATURE: "SIGNATURE";
|
|
109
|
+
CALCULATED_FIELDS: "CALCULATED_FIELDS";
|
|
110
|
+
MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
|
|
111
|
+
CHECKBOXES: "CHECKBOXES";
|
|
112
|
+
DROPDOWN: "DROPDOWN";
|
|
113
|
+
RANKING: "RANKING";
|
|
114
|
+
MULTI_SELECT: "MULTI_SELECT";
|
|
115
|
+
}>;
|
|
116
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
117
|
+
id: z.ZodString;
|
|
118
|
+
mimeType: z.ZodString;
|
|
119
|
+
name: z.ZodString;
|
|
120
|
+
size: z.ZodNumber;
|
|
121
|
+
url: z.ZodURL;
|
|
122
|
+
}, z.core.$strip>>, z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>, z.ZodNull]>;
|
|
123
|
+
}, z.core.$strip>>;
|
|
124
|
+
formId: z.ZodString;
|
|
125
|
+
formName: z.ZodString;
|
|
126
|
+
respondentId: z.ZodString;
|
|
127
|
+
responseId: z.ZodString;
|
|
128
|
+
submissionId: z.ZodString;
|
|
129
|
+
submissionPdfUrl: z.ZodOptional<z.ZodURL>;
|
|
130
|
+
submissionPreviewUrl: z.ZodOptional<z.ZodURL>;
|
|
131
|
+
}, z.core.$strip>;
|
|
132
|
+
eventId: z.ZodString;
|
|
133
|
+
eventType: z.ZodLiteral<"FORM_RESPONSE">;
|
|
134
|
+
}, z.core.$strip>;
|
|
135
|
+
};
|
|
136
|
+
readonly "tally.form.response": {
|
|
137
|
+
readonly configSchema: z.ZodObject<{
|
|
138
|
+
formId: z.ZodString;
|
|
139
|
+
}, z.core.$strip>;
|
|
140
|
+
readonly eventSchema: z.ZodObject<{
|
|
141
|
+
createdAt: z.ZodISODateTime;
|
|
142
|
+
data: z.ZodObject<{
|
|
143
|
+
createdAt: z.ZodISODateTime;
|
|
144
|
+
fields: z.ZodArray<z.ZodObject<{
|
|
145
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
146
|
+
id: z.ZodString;
|
|
147
|
+
text: z.ZodString;
|
|
148
|
+
}, z.core.$strip>>>;
|
|
149
|
+
key: z.ZodString;
|
|
150
|
+
label: z.ZodString;
|
|
151
|
+
options: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
152
|
+
id: z.ZodString;
|
|
153
|
+
text: z.ZodString;
|
|
154
|
+
}, z.core.$strip>>>;
|
|
155
|
+
rows: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
156
|
+
id: z.ZodString;
|
|
157
|
+
text: z.ZodString;
|
|
158
|
+
}, z.core.$strip>>>;
|
|
159
|
+
type: z.ZodEnum<{
|
|
160
|
+
MATRIX: "MATRIX";
|
|
161
|
+
INPUT_TEXT: "INPUT_TEXT";
|
|
162
|
+
INPUT_NUMBER: "INPUT_NUMBER";
|
|
163
|
+
INPUT_EMAIL: "INPUT_EMAIL";
|
|
164
|
+
INPUT_LINK: "INPUT_LINK";
|
|
165
|
+
INPUT_PHONE_NUMBER: "INPUT_PHONE_NUMBER";
|
|
166
|
+
INPUT_DATE: "INPUT_DATE";
|
|
167
|
+
INPUT_TIME: "INPUT_TIME";
|
|
168
|
+
TEXTAREA: "TEXTAREA";
|
|
169
|
+
FILE_UPLOAD: "FILE_UPLOAD";
|
|
170
|
+
LINEAR_SCALE: "LINEAR_SCALE";
|
|
171
|
+
RATING: "RATING";
|
|
172
|
+
HIDDEN_FIELDS: "HIDDEN_FIELDS";
|
|
173
|
+
PAYMENT: "PAYMENT";
|
|
174
|
+
SIGNATURE: "SIGNATURE";
|
|
175
|
+
CALCULATED_FIELDS: "CALCULATED_FIELDS";
|
|
176
|
+
MULTIPLE_CHOICE: "MULTIPLE_CHOICE";
|
|
177
|
+
CHECKBOXES: "CHECKBOXES";
|
|
178
|
+
DROPDOWN: "DROPDOWN";
|
|
179
|
+
RANKING: "RANKING";
|
|
180
|
+
MULTI_SELECT: "MULTI_SELECT";
|
|
181
|
+
}>;
|
|
182
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>, z.ZodArray<z.ZodObject<{
|
|
183
|
+
id: z.ZodString;
|
|
184
|
+
mimeType: z.ZodString;
|
|
185
|
+
name: z.ZodString;
|
|
186
|
+
size: z.ZodNumber;
|
|
187
|
+
url: z.ZodURL;
|
|
188
|
+
}, z.core.$strip>>, z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>, z.ZodNull]>;
|
|
189
|
+
}, z.core.$strip>>;
|
|
190
|
+
formId: z.ZodString;
|
|
191
|
+
formName: z.ZodString;
|
|
192
|
+
respondentId: z.ZodString;
|
|
193
|
+
responseId: z.ZodString;
|
|
194
|
+
submissionId: z.ZodString;
|
|
195
|
+
submissionPdfUrl: z.ZodOptional<z.ZodURL>;
|
|
196
|
+
submissionPreviewUrl: z.ZodOptional<z.ZodURL>;
|
|
197
|
+
}, z.core.$strip>;
|
|
198
|
+
eventId: z.ZodString;
|
|
199
|
+
eventType: z.ZodLiteral<"FORM_RESPONSE">;
|
|
200
|
+
}, z.core.$strip>;
|
|
201
|
+
};
|
|
202
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
export const TALLY_FORM_RESPONSE_EVENT_TYPE = "FORM_RESPONSE";
|
|
3
|
+
const TALLY_OPTION_SCHEMA = z.object({ id: z.string(), text: z.string() });
|
|
4
|
+
const TALLY_FILE_SCHEMA = z.object({
|
|
5
|
+
id: z.string(),
|
|
6
|
+
mimeType: z.string(),
|
|
7
|
+
name: z.string(),
|
|
8
|
+
size: z.number().int().nonnegative(),
|
|
9
|
+
url: z.url(),
|
|
10
|
+
});
|
|
11
|
+
const TALLY_FIELD_SCHEMA = z.object({
|
|
12
|
+
columns: TALLY_OPTION_SCHEMA.array().optional(),
|
|
13
|
+
key: z.string(),
|
|
14
|
+
label: z.string(),
|
|
15
|
+
options: TALLY_OPTION_SCHEMA.array().optional(),
|
|
16
|
+
rows: TALLY_OPTION_SCHEMA.array().optional(),
|
|
17
|
+
type: z.enum([
|
|
18
|
+
"CALCULATED_FIELDS",
|
|
19
|
+
"CHECKBOXES",
|
|
20
|
+
"DROPDOWN",
|
|
21
|
+
"FILE_UPLOAD",
|
|
22
|
+
"HIDDEN_FIELDS",
|
|
23
|
+
"INPUT_DATE",
|
|
24
|
+
"INPUT_EMAIL",
|
|
25
|
+
"INPUT_LINK",
|
|
26
|
+
"INPUT_NUMBER",
|
|
27
|
+
"INPUT_PHONE_NUMBER",
|
|
28
|
+
"INPUT_TEXT",
|
|
29
|
+
"INPUT_TIME",
|
|
30
|
+
"LINEAR_SCALE",
|
|
31
|
+
"MATRIX",
|
|
32
|
+
"MULTIPLE_CHOICE",
|
|
33
|
+
"MULTI_SELECT",
|
|
34
|
+
"PAYMENT",
|
|
35
|
+
"RANKING",
|
|
36
|
+
"RATING",
|
|
37
|
+
"SIGNATURE",
|
|
38
|
+
"TEXTAREA",
|
|
39
|
+
]),
|
|
40
|
+
value: z.union([
|
|
41
|
+
z.string(),
|
|
42
|
+
z.number(),
|
|
43
|
+
z.boolean(),
|
|
44
|
+
z.string().array(),
|
|
45
|
+
TALLY_FILE_SCHEMA.array(),
|
|
46
|
+
z.record(z.string(), z.string().array()),
|
|
47
|
+
z.null(),
|
|
48
|
+
]),
|
|
49
|
+
});
|
|
50
|
+
/** Signed form-response webhook payload delivered by Tally. */
|
|
51
|
+
export const TALLY_FORM_RESPONSE_EVENT_SCHEMA = z.object({
|
|
52
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
53
|
+
data: z.object({
|
|
54
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
55
|
+
fields: TALLY_FIELD_SCHEMA.array(),
|
|
56
|
+
formId: z.string(),
|
|
57
|
+
formName: z.string(),
|
|
58
|
+
respondentId: z.string(),
|
|
59
|
+
responseId: z.string(),
|
|
60
|
+
submissionId: z.string(),
|
|
61
|
+
submissionPdfUrl: z.url().optional(),
|
|
62
|
+
submissionPreviewUrl: z.url().optional(),
|
|
63
|
+
}),
|
|
64
|
+
eventId: z.string(),
|
|
65
|
+
eventType: z.literal(TALLY_FORM_RESPONSE_EVENT_TYPE),
|
|
66
|
+
});
|
|
67
|
+
export const TALLY_TRIGGER_CONFIG_SCHEMA = z.object({
|
|
68
|
+
formId: z.string().trim().min(1),
|
|
69
|
+
});
|
|
70
|
+
export const tallyTriggerContracts = {
|
|
71
|
+
"tally.form.event": {
|
|
72
|
+
configSchema: TALLY_TRIGGER_CONFIG_SCHEMA,
|
|
73
|
+
eventSchema: TALLY_FORM_RESPONSE_EVENT_SCHEMA,
|
|
74
|
+
},
|
|
75
|
+
"tally.form.response": {
|
|
76
|
+
configSchema: TALLY_TRIGGER_CONFIG_SCHEMA,
|
|
77
|
+
eventSchema: TALLY_FORM_RESPONSE_EVENT_SCHEMA,
|
|
78
|
+
},
|
|
79
|
+
};
|