@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,581 @@
1
+ import { auth, forms } from "@googleapis/forms";
2
+ import { z } from "zod";
3
+ import { FORM_ITEM_SCHEMA, FORM_RESPONSE_SCHEMA, FORM_SCHEMA, } from "./schemas.js";
4
+ const GOOGLE_FORMS_URL_HOSTS = new Set(["docs.google.com", "forms.google.com"]);
5
+ const GOOGLE_FORMS_SECRET_SCHEMA = z.object({
6
+ accessToken: z.string().min(1),
7
+ });
8
+ const EMAIL_COLLECTION_TYPES = {
9
+ none: "DO_NOT_COLLECT",
10
+ respondentInput: "RESPONDER_INPUT",
11
+ verified: "VERIFIED",
12
+ };
13
+ const CHOICE_TYPES = {
14
+ checkbox: "CHECKBOX",
15
+ dropdown: "DROP_DOWN",
16
+ multipleChoice: "RADIO",
17
+ };
18
+ const RATING_ICON_TYPES = {
19
+ heart: "HEART",
20
+ star: "STAR",
21
+ thumbUp: "THUMB_UP",
22
+ };
23
+ const MEDIA_ALIGNMENTS = {
24
+ center: "CENTER",
25
+ left: "LEFT",
26
+ right: "RIGHT",
27
+ };
28
+ /**
29
+ * Creates the official Forms client from a resolved Google integration secret.
30
+ *
31
+ * @param secret - Refreshed Google integration secret.
32
+ */
33
+ export function getGoogleFormsApi(secret) {
34
+ const { accessToken } = GOOGLE_FORMS_SECRET_SCHEMA.parse(secret);
35
+ return forms({
36
+ auth: new auth.OAuth2({
37
+ credentials: { access_token: accessToken },
38
+ }),
39
+ version: "v1",
40
+ });
41
+ }
42
+ /**
43
+ * Accepts either a raw form ID or a standard Google Forms URL.
44
+ *
45
+ * @param form - Form ID or URL.
46
+ * @throws When a URL is not a recognizable Google Forms URL.
47
+ */
48
+ export function normalizeFormId(form) {
49
+ const value = form.trim();
50
+ if (!URL.canParse(value))
51
+ return value;
52
+ const url = new URL(value);
53
+ if (!GOOGLE_FORMS_URL_HOSTS.has(url.hostname)) {
54
+ throw new Error(`Expected a Google Forms URL, received "${value}".`);
55
+ }
56
+ const pathParts = url.pathname.split("/");
57
+ const dIndex = pathParts.indexOf("d", pathParts.indexOf("forms") + 1);
58
+ const formId = dIndex === -1
59
+ ? undefined
60
+ : pathParts[dIndex + 1] === "e"
61
+ ? pathParts[dIndex + 2]
62
+ : pathParts[dIndex + 1];
63
+ if (!formId) {
64
+ throw new Error(`Could not find a form ID in "${value}".`);
65
+ }
66
+ return formId;
67
+ }
68
+ /**
69
+ * Converts a code-first item definition to the provider request shape.
70
+ *
71
+ * @param input - Code-first form item.
72
+ */
73
+ export function toGoogleFormItem(input) {
74
+ const base = {
75
+ description: input.description,
76
+ itemId: input.itemId,
77
+ title: input.title,
78
+ };
79
+ switch (input.type) {
80
+ case "question":
81
+ return {
82
+ ...base,
83
+ questionItem: {
84
+ question: {
85
+ ...toGoogleQuestion(input.question),
86
+ grading: input.grading
87
+ ? {
88
+ correctAnswers: {
89
+ answers: input.grading.correctAnswers.map((value) => ({
90
+ value,
91
+ })),
92
+ },
93
+ generalFeedback: input.grading.generalFeedback
94
+ ? { text: input.grading.generalFeedback }
95
+ : undefined,
96
+ pointValue: input.grading.points,
97
+ whenRight: input.grading.whenRight
98
+ ? { text: input.grading.whenRight }
99
+ : undefined,
100
+ whenWrong: input.grading.whenWrong
101
+ ? { text: input.grading.whenWrong }
102
+ : undefined,
103
+ }
104
+ : undefined,
105
+ },
106
+ },
107
+ };
108
+ case "grid":
109
+ return {
110
+ ...base,
111
+ questionGroupItem: {
112
+ grid: {
113
+ columns: {
114
+ options: input.columns.map((value) => ({ value })),
115
+ type: input.gridType === "checkbox" ? "CHECKBOX" : "RADIO",
116
+ },
117
+ shuffleQuestions: input.shuffleRows,
118
+ },
119
+ questions: input.rows.map((row) => ({
120
+ questionId: row.questionId,
121
+ required: row.required,
122
+ rowQuestion: { title: row.title },
123
+ })),
124
+ },
125
+ };
126
+ case "pageBreak":
127
+ return { ...base, pageBreakItem: {} };
128
+ case "text":
129
+ return { ...base, textItem: {} };
130
+ case "image":
131
+ return {
132
+ ...base,
133
+ imageItem: {
134
+ image: {
135
+ altText: input.altText,
136
+ properties: {
137
+ alignment: input.alignment
138
+ ? MEDIA_ALIGNMENTS[input.alignment]
139
+ : undefined,
140
+ width: input.width,
141
+ },
142
+ sourceUri: input.sourceUrl,
143
+ },
144
+ },
145
+ };
146
+ case "video":
147
+ return {
148
+ ...base,
149
+ videoItem: {
150
+ caption: input.caption,
151
+ video: {
152
+ properties: {
153
+ alignment: input.alignment
154
+ ? MEDIA_ALIGNMENTS[input.alignment]
155
+ : undefined,
156
+ width: input.width,
157
+ },
158
+ youtubeUri: input.youtubeUrl,
159
+ },
160
+ },
161
+ };
162
+ }
163
+ }
164
+ /**
165
+ * Normalizes a provider form and all nested items.
166
+ *
167
+ * @param form - Google Forms resource.
168
+ * @throws When required provider identifiers are absent.
169
+ */
170
+ export function normalizeForm(form) {
171
+ if (!form.formId)
172
+ throw new Error("Google Forms returned no form ID.");
173
+ return FORM_SCHEMA.parse({
174
+ description: form.info?.description ?? undefined,
175
+ documentTitle: form.info?.documentTitle ?? undefined,
176
+ editUrl: `https://docs.google.com/forms/d/${form.formId}/edit`,
177
+ formId: form.formId,
178
+ items: (form.items ?? []).map(normalizeFormItem),
179
+ linkedSheetId: form.linkedSheetId ?? undefined,
180
+ publishSettings: form.publishSettings?.publishState
181
+ ? {
182
+ isAcceptingResponses: form.publishSettings.publishState.isAcceptingResponses ?? false,
183
+ isPublished: form.publishSettings.publishState.isPublished ?? false,
184
+ }
185
+ : undefined,
186
+ responderUrl: form.responderUri ?? undefined,
187
+ revisionId: form.revisionId ?? undefined,
188
+ settings: {
189
+ emailCollection: toPublicEmailCollectionType(form.settings?.emailCollectionType),
190
+ isQuiz: form.settings?.quizSettings?.isQuiz ?? false,
191
+ },
192
+ title: form.info?.title ?? "",
193
+ });
194
+ }
195
+ /**
196
+ * Normalizes a provider item at its display position.
197
+ *
198
+ * @param item - Google Forms item.
199
+ * @param index - Zero-based display position.
200
+ */
201
+ export function normalizeFormItem(item, index) {
202
+ return FORM_ITEM_SCHEMA.parse({
203
+ description: item.description ?? undefined,
204
+ index,
205
+ itemId: item.itemId ?? "",
206
+ questions: item.questionItem?.question
207
+ ? [normalizeQuestion(item.questionItem.question)]
208
+ : (item.questionGroupItem?.questions ?? []).map((question) => normalizeQuestion({
209
+ ...question,
210
+ choiceQuestion: item.questionGroupItem?.grid?.columns,
211
+ })),
212
+ title: item.title ?? "",
213
+ type: item.questionItem
214
+ ? "question"
215
+ : item.questionGroupItem
216
+ ? "grid"
217
+ : item.pageBreakItem
218
+ ? "pageBreak"
219
+ : item.textItem
220
+ ? "text"
221
+ : item.imageItem
222
+ ? "image"
223
+ : item.videoItem
224
+ ? "video"
225
+ : "unknown",
226
+ });
227
+ }
228
+ /**
229
+ * Collects question titles in form display order.
230
+ *
231
+ * @param form - Google Forms resource.
232
+ */
233
+ export function getQuestionMetadata(form) {
234
+ return (form.items ?? []).flatMap((item) => {
235
+ if (item.questionItem?.question?.questionId) {
236
+ return [
237
+ {
238
+ questionId: item.questionItem.question.questionId,
239
+ title: item.title ?? undefined,
240
+ },
241
+ ];
242
+ }
243
+ return (item.questionGroupItem?.questions ?? []).flatMap((question) => question.questionId
244
+ ? [
245
+ {
246
+ questionId: question.questionId,
247
+ title: question.rowQuestion?.title ?? item.title ?? undefined,
248
+ },
249
+ ]
250
+ : []);
251
+ });
252
+ }
253
+ /**
254
+ * Normalizes a submitted response and sorts answers in form display order.
255
+ *
256
+ * @param response - Google Forms response.
257
+ * @param formId - Form ID used when list responses omit it.
258
+ * @param questions - Optional form question metadata.
259
+ * @throws When the provider response omits its response ID.
260
+ */
261
+ export function normalizeFormResponse(response, formId, questions = []) {
262
+ if (!response.responseId) {
263
+ throw new Error("Google Forms returned a response without an ID.");
264
+ }
265
+ const questionOrder = new Map(questions.map(({ questionId }, index) => [questionId, index]));
266
+ const titles = new Map(questions.map(({ questionId, title }) => [questionId, title]));
267
+ // Keep this pipeline named so the response assembly remains readable.
268
+ const answers = Object.values(response.answers ?? {})
269
+ .filter((answer) => Boolean(answer.questionId))
270
+ .toSorted((left, right) => (questionOrder.get(left.questionId) ?? Number.MAX_SAFE_INTEGER) -
271
+ (questionOrder.get(right.questionId) ?? Number.MAX_SAFE_INTEGER))
272
+ .map((answer) => ({
273
+ files: (answer.fileUploadAnswers?.answers ?? []).flatMap((file) => file.fileId && file.fileName && file.mimeType
274
+ ? [
275
+ {
276
+ fileId: file.fileId,
277
+ fileName: file.fileName,
278
+ mimeType: file.mimeType,
279
+ },
280
+ ]
281
+ : []),
282
+ grade: answer.grade
283
+ ? {
284
+ correct: answer.grade.correct ?? undefined,
285
+ correctAnswers: [],
286
+ feedback: answer.grade.feedback?.text ?? undefined,
287
+ score: answer.grade.score ?? undefined,
288
+ }
289
+ : undefined,
290
+ questionId: answer.questionId,
291
+ questionTitle: titles.get(answer.questionId) ?? undefined,
292
+ values: (answer.textAnswers?.answers ?? []).flatMap(({ value }) => value == null ? [] : [value]),
293
+ }));
294
+ return FORM_RESPONSE_SCHEMA.parse({
295
+ answers,
296
+ createTime: response.createTime ?? undefined,
297
+ formId: response.formId ?? formId,
298
+ lastSubmittedTime: response.lastSubmittedTime ?? undefined,
299
+ respondentEmail: response.respondentEmail ?? undefined,
300
+ responseId: response.responseId,
301
+ totalScore: response.totalScore ?? undefined,
302
+ });
303
+ }
304
+ /**
305
+ * Resolves a one-based item position or immutable item ID to a zero-based
306
+ * index.
307
+ *
308
+ * @param formsApi - Authenticated Forms client.
309
+ * @param formId - Immutable form ID.
310
+ * @param reference - One-based position or item ID.
311
+ * @throws When the referenced item does not exist.
312
+ */
313
+ export async function resolveItemIndex(formsApi, formId, reference) {
314
+ if (typeof reference === "number")
315
+ return reference - 1;
316
+ const { data } = await formsApi.forms.get({ formId });
317
+ const index = data.items?.findIndex(({ itemId }) => itemId === reference) ?? -1;
318
+ if (index === -1) {
319
+ throw new Error(`Google Forms item "${reference}" was not found.`);
320
+ }
321
+ return index;
322
+ }
323
+ /**
324
+ * Resolves a one-based item position or immutable item ID with its form.
325
+ *
326
+ * @param formsApi - Authenticated Forms client.
327
+ * @param formId - Immutable form ID.
328
+ * @param reference - One-based position or item ID.
329
+ * @throws When the referenced item does not exist.
330
+ */
331
+ export async function resolveFormItem(formsApi, formId, reference) {
332
+ const { data: form } = await formsApi.forms.get({ formId });
333
+ const index = typeof reference === "number"
334
+ ? reference - 1
335
+ : (form.items?.findIndex(({ itemId }) => itemId === reference) ?? -1);
336
+ const item = form.items?.[index];
337
+ if (!item) {
338
+ throw new Error(`Google Forms item "${reference}" was not found.`);
339
+ }
340
+ return { form, index, item };
341
+ }
342
+ /**
343
+ * Preserves provider-generated item and question IDs during full replacement.
344
+ *
345
+ * @param next - Replacement item.
346
+ * @param current - Existing provider item.
347
+ */
348
+ export function preserveItemIds(next, current) {
349
+ return {
350
+ ...next,
351
+ itemId: next.itemId ?? current.itemId,
352
+ questionGroupItem: next.questionGroupItem
353
+ ? {
354
+ ...next.questionGroupItem,
355
+ questions: next.questionGroupItem.questions?.map((question, index) => ({
356
+ ...question,
357
+ questionId: question.questionId ??
358
+ current.questionGroupItem?.questions?.[index]?.questionId,
359
+ })),
360
+ }
361
+ : undefined,
362
+ questionItem: next.questionItem
363
+ ? {
364
+ ...next.questionItem,
365
+ question: next.questionItem.question
366
+ ? {
367
+ ...next.questionItem.question,
368
+ questionId: next.questionItem.question.questionId ??
369
+ current.questionItem?.question?.questionId,
370
+ }
371
+ : undefined,
372
+ }
373
+ : undefined,
374
+ };
375
+ }
376
+ /**
377
+ * Resolves an optional one-based insertion position, defaulting to the end.
378
+ *
379
+ * @param formsApi - Authenticated Forms client.
380
+ * @param formId - Immutable form ID.
381
+ * @param position - Optional one-based insertion position.
382
+ */
383
+ export async function resolveInsertionIndex(formsApi, formId, position) {
384
+ if (position !== undefined)
385
+ return position - 1;
386
+ const { data } = await formsApi.forms.get({ formId });
387
+ return data.items?.length ?? 0;
388
+ }
389
+ /**
390
+ * Converts public settings into Google Forms settings.
391
+ *
392
+ * @param settings - Code-first form settings.
393
+ * @param settings.emailCollection - Email collection behavior.
394
+ * @param settings.isQuiz - Whether the form is a quiz.
395
+ */
396
+ export function toGoogleFormSettings(settings) {
397
+ return {
398
+ emailCollectionType: settings.emailCollection
399
+ ? EMAIL_COLLECTION_TYPES[settings.emailCollection]
400
+ : undefined,
401
+ quizSettings: settings.isQuiz === undefined ? undefined : { isQuiz: settings.isQuiz },
402
+ };
403
+ }
404
+ /**
405
+ * Builds a comma-separated settings update mask.
406
+ *
407
+ * @param settings - Code-first form settings.
408
+ * @param settings.emailCollection - Email collection behavior.
409
+ * @param settings.isQuiz - Whether the form is a quiz.
410
+ */
411
+ export function getSettingsUpdateMask(settings) {
412
+ return [
413
+ settings.emailCollection === undefined ? undefined : "emailCollectionType",
414
+ settings.isQuiz === undefined ? undefined : "quizSettings.isQuiz",
415
+ ]
416
+ .filter((field) => Boolean(field))
417
+ .join(",");
418
+ }
419
+ /**
420
+ * Converts a code-first question to Google Forms' union representation.
421
+ *
422
+ * @param input - Code-first question definition.
423
+ */
424
+ function toGoogleQuestion(input) {
425
+ const base = {
426
+ questionId: input.questionId,
427
+ required: input.required,
428
+ };
429
+ switch (input.type) {
430
+ case "shortText":
431
+ return { ...base, textQuestion: { paragraph: false } };
432
+ case "paragraph":
433
+ return { ...base, textQuestion: { paragraph: true } };
434
+ case "multipleChoice":
435
+ case "checkbox":
436
+ case "dropdown":
437
+ return {
438
+ ...base,
439
+ choiceQuestion: {
440
+ options: input.options.map((option) => typeof option === "string"
441
+ ? { value: option }
442
+ : { isOther: true, value: option.value }),
443
+ shuffle: input.shuffle,
444
+ type: CHOICE_TYPES[input.type],
445
+ },
446
+ };
447
+ case "scale":
448
+ return {
449
+ ...base,
450
+ scaleQuestion: {
451
+ high: input.high,
452
+ highLabel: input.highLabel,
453
+ low: input.low,
454
+ lowLabel: input.lowLabel,
455
+ },
456
+ };
457
+ case "date":
458
+ return {
459
+ ...base,
460
+ dateQuestion: {
461
+ includeTime: input.includeTime,
462
+ includeYear: input.includeYear,
463
+ },
464
+ };
465
+ case "time":
466
+ return { ...base, timeQuestion: { duration: false } };
467
+ case "duration":
468
+ return { ...base, timeQuestion: { duration: true } };
469
+ case "rating":
470
+ return {
471
+ ...base,
472
+ ratingQuestion: {
473
+ iconType: RATING_ICON_TYPES[input.icon],
474
+ ratingScaleLevel: input.levels,
475
+ },
476
+ };
477
+ }
478
+ }
479
+ /**
480
+ * Normalizes one provider question.
481
+ *
482
+ * @param question - Google Forms question.
483
+ */
484
+ function normalizeQuestion(question) {
485
+ return {
486
+ choices: (question.choiceQuestion?.options ?? []).flatMap(({ value }) => value == null ? [] : [value]),
487
+ date: question.dateQuestion
488
+ ? {
489
+ includeTime: question.dateQuestion.includeTime ?? false,
490
+ includeYear: question.dateQuestion.includeYear ?? false,
491
+ }
492
+ : undefined,
493
+ fileUpload: question.fileUploadQuestion
494
+ ? {
495
+ folderId: question.fileUploadQuestion.folderId ?? undefined,
496
+ maxFileSizeBytes: question.fileUploadQuestion.maxFileSize ?? undefined,
497
+ maxFiles: question.fileUploadQuestion.maxFiles ?? undefined,
498
+ types: question.fileUploadQuestion.types ?? [],
499
+ }
500
+ : undefined,
501
+ grading: question.grading
502
+ ? {
503
+ correctAnswers: (question.grading.correctAnswers?.answers ?? []).flatMap(({ value }) => (value == null ? [] : [value])),
504
+ feedback: question.grading.generalFeedback?.text ?? undefined,
505
+ points: question.grading.pointValue ?? undefined,
506
+ whenRight: question.grading.whenRight?.text ?? undefined,
507
+ whenWrong: question.grading.whenWrong?.text ?? undefined,
508
+ }
509
+ : undefined,
510
+ questionId: question.questionId ?? "",
511
+ rating: question.ratingQuestion
512
+ ? {
513
+ icon: question.ratingQuestion.iconType ?? "",
514
+ levels: question.ratingQuestion.ratingScaleLevel ?? 0,
515
+ }
516
+ : undefined,
517
+ required: question.required ?? false,
518
+ rowTitle: question.rowQuestion?.title ?? undefined,
519
+ scale: question.scaleQuestion
520
+ ? {
521
+ high: question.scaleQuestion.high ?? 0,
522
+ highLabel: question.scaleQuestion.highLabel ?? undefined,
523
+ low: question.scaleQuestion.low ?? 0,
524
+ lowLabel: question.scaleQuestion.lowLabel ?? undefined,
525
+ }
526
+ : undefined,
527
+ type: question.rowQuestion
528
+ ? "gridRow"
529
+ : question.textQuestion
530
+ ? question.textQuestion.paragraph
531
+ ? "paragraph"
532
+ : "shortText"
533
+ : question.choiceQuestion
534
+ ? toPublicChoiceType(question.choiceQuestion.type)
535
+ : question.scaleQuestion
536
+ ? "scale"
537
+ : question.dateQuestion
538
+ ? "date"
539
+ : question.timeQuestion
540
+ ? question.timeQuestion.duration
541
+ ? "duration"
542
+ : "time"
543
+ : question.ratingQuestion
544
+ ? "rating"
545
+ : question.fileUploadQuestion
546
+ ? "fileUpload"
547
+ : "unknown",
548
+ };
549
+ }
550
+ /**
551
+ * Maps the provider email collection enum to the public enum.
552
+ *
553
+ * @param type - Google Forms enum value.
554
+ */
555
+ function toPublicEmailCollectionType(type) {
556
+ switch (type) {
557
+ case "VERIFIED":
558
+ return "verified";
559
+ case "RESPONDER_INPUT":
560
+ return "respondentInput";
561
+ default:
562
+ return "none";
563
+ }
564
+ }
565
+ /**
566
+ * Maps the provider choice enum to the public question kind.
567
+ *
568
+ * @param type - Google Forms enum value.
569
+ */
570
+ function toPublicChoiceType(type) {
571
+ switch (type) {
572
+ case "RADIO":
573
+ return "multipleChoice";
574
+ case "CHECKBOX":
575
+ return "checkbox";
576
+ case "DROP_DOWN":
577
+ return "dropdown";
578
+ default:
579
+ return "unknown";
580
+ }
581
+ }