@aglyn/plugins-forms 1.0.0-beta.256 → 1.0.0-beta.257

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-forms",
3
- "version": "1.0.0-beta.256",
3
+ "version": "1.0.0-beta.257",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,19 +25,19 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.256",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.256",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.256",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.256",
32
- "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.256",
33
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.256",
34
- "@aglyn/shared-util-email": "1.0.0-beta.256",
35
- "@aglyn/shared-util-http": "1.0.0-beta.256",
36
- "@aglyn/shared-util-timestamp": "1.0.0-beta.256",
37
- "@aglyn/shared-util-tools": "1.0.0-beta.256",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.256",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.256",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.256",
28
+ "@aglyn/aglyn": "1.0.0-beta.257",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.257",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.257",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.257",
32
+ "@aglyn/shared-ui-jsx-forms": "1.0.0-beta.257",
33
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.257",
34
+ "@aglyn/shared-util-email": "1.0.0-beta.257",
35
+ "@aglyn/shared-util-http": "1.0.0-beta.257",
36
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.257",
37
+ "@aglyn/shared-util-tools": "1.0.0-beta.257",
38
+ "@aglyn/tenant-data-admin": "1.0.0-beta.257",
39
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.257",
40
+ "@aglyn/tenant-runtime": "1.0.0-beta.257",
41
41
  "@swc/helpers": "0.5.23"
42
42
  },
43
43
  "peerDependencies": {
@@ -46,6 +46,8 @@ export declare const FORM_FIELD_ELEMENT_IDS: {
46
46
  readonly checkbox: "muiCheckbox";
47
47
  readonly choiceLabel: "muiFormControlLabel";
48
48
  readonly rating: "muiRating";
49
+ readonly datePicker: "muiDatePicker";
50
+ readonly timePicker: "muiTimePicker";
49
51
  };
50
52
  /**
51
53
  * A choice list as the all-in-one element split it: newline- or
@@ -62,7 +62,9 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
62
62
  radio: 'muiRadio',
63
63
  checkbox: 'muiCheckbox',
64
64
  choiceLabel: 'muiFormControlLabel',
65
- rating: 'muiRating'
65
+ rating: 'muiRating',
66
+ datePicker: 'muiDatePicker',
67
+ timePicker: 'muiTimePicker'
66
68
  };
67
69
  const LEGACY_FIELD_TYPES = new Set([
68
70
  'text',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-field-elements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * ONE ALL-IN-ONE FORM FIELD, REBUILT FROM MUI'S SEPARATE FORM ELEMENTS.\n *\n * Every piece of a field is its own Besigner element now (a Form Control\n * holding a Form Label and an Input, a Select of Select Options, a Radio Group\n * of Radios in Choice Labels, Checkboxes in Choice Labels, a Rating), so an\n * author selects and styles each piece with the ordinary Styles panel. The\n * all-in-one `formField` is still registered so a document nobody has migrated\n * keeps drawing, and this module is how each one is moved off it: the seeds,\n * the presets and `tools/scripts/backfill-formfield-elements.mjs` all build the\n * same tree from the same rules.\n *\n * What must survive the move, exactly:\n *\n * - **The field's id.** It stays on the Form Control wrapper, so a parent's\n * `nodes` list, a reveal target and every placement override keyed by it\n * still find the field. The wrapper takes the field's `sx` with it.\n * - **The name it submits under.** Contact-field mappings, dataset bindings\n * and the consent field all key on it, so it is copied verbatim.\n * - **Everything a placement overrode.** A placed form's `attrOverrides`\n * named the field's id and a prop of the all-in-one element; the slice is\n * re-keyed onto the piece that now owns that prop.\n *\n * It lives with the forms plugin, which owns the all-in-one element, rather\n * than in core: a form's vocabulary is the plugin's domain (docs/PACKAGES.md\n * rule 3).\n *\n * Pure and dependency-free (no imports at all) on purpose: the backfill is a\n * plain Node script that imports this file directly from its TypeScript\n * source, and an import here would drag a package graph into it.\n */\n\n/** A node as it is stored: a flat map entry with its children by id. */\nexport interface StoredFormNode {\n $id?: string\n componentId?: string\n pluginId?: string\n parentId?: string | null\n props?: Record<string, unknown>\n nodes?: string[]\n sx?: unknown\n [key: string]: unknown\n}\n\n/** The all-in-one element this module migrates away from. Persisted; never rename. */\nexport const LEGACY_FORM_FIELD_COMPONENT_ID = 'formField'\n\n/** The bundle that registers every element a field is rebuilt from. */\nexport const FORM_FIELD_ELEMENTS_PLUGIN_ID = 'mui'\n\n/**\n * MUI's form elements, by persisted component id — restated rather than\n * imported (see the module note; a plugin never imports another plugin).\n * `form-field-elements.spec.ts` holds this table equal to core's\n * `FORM_INPUT_ELEMENT_IDS` in `forms.ts`.\n */\nexport const FORM_FIELD_ELEMENT_IDS = {\n formControl: 'muiFormControl',\n formLabel: 'muiFormLabel',\n input: 'muiInput',\n select: 'muiSelect',\n selectOption: 'muiSelectOption',\n radioGroup: 'muiRadioGroup',\n radio: 'muiRadio',\n checkbox: 'muiCheckbox',\n choiceLabel: 'muiFormControlLabel',\n rating: 'muiRating',\n} as const\n\n/** The all-in-one element's types; anything else drew as a one-line text box. */\ntype LegacyFieldType =\n | 'text'\n | 'email'\n | 'textarea'\n | 'select'\n | 'radio'\n | 'checkbox'\n | 'rating'\n\nconst LEGACY_FIELD_TYPES = new Set<string>([\n 'text',\n 'email',\n 'textarea',\n 'select',\n 'radio',\n 'checkbox',\n 'rating',\n])\n\n/** The props the all-in-one element read; anything else rode onto its root. */\nconst LEGACY_FIELD_PROPS = new Set<string>([\n 'fieldName',\n 'datasetFieldId',\n 'label',\n 'placeholder',\n 'fieldType',\n 'options',\n 'required',\n])\n\n/**\n * A choice list as the all-in-one element split it: newline- or\n * comma-separated, trimmed, empties dropped (its own `parseFieldOptions`).\n */\nexport function parseLegacyFieldOptions(options: unknown): string[] {\n return (typeof options === 'string' ? options : '')\n .split(/[\\n,]/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n}\n\n/** Which new node owns each prop the all-in-one element had. */\nexport interface FormFieldElementsLayout {\n /** The Form Control; the field's own id. */\n rootId: string\n /** The Form Label, when the field has one (not a lone checkbox). */\n labelId?: string\n /** Every node that submits the answer and carries `name`. */\n answerIds: string[]\n /** The nodes a placeholder belongs on (Input, Select). */\n placeholderIds: string[]\n /** The nodes `required` is enforced on. */\n requiredIds: string[]\n}\n\nexport interface FormFieldElements extends FormFieldElementsLayout {\n /** The wrapper and every new node under it, keyed by id. */\n nodes: Record<string, StoredFormNode>\n}\n\n/** Child ids of a stored node, in order. */\nconst childIds = (node: StoredFormNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as string[]) : []\n\n/**\n * The all-in-one `formField` node `node`, rebuilt as MUI's separate form\n * elements — or `null` when it is not one.\n *\n * `nodes` is the tree it sits in, read only so a new id never collides with\n * one already there. New ids are derived from the field's own\n * (`<id>-label`, `<id>-input`, `<id>-opt-0`, `<id>-opt-0-input`), so a\n * second run over the same field mints the same tree.\n */\nexport function formFieldToElements(\n node: StoredFormNode | undefined | null,\n nodes: Record<string, StoredFormNode | undefined> = {},\n): FormFieldElements | null {\n if (!node || node.componentId !== LEGACY_FORM_FIELD_COMPONENT_ID) return null\n const id = String(node.$id ?? '')\n if (!id) return null\n\n const props = (node.props ?? {}) as Record<string, unknown>\n const rawName = props['fieldName']\n // Exactly what the element submitted under: `fieldName || 'field'`.\n const name = rawName === undefined || rawName === null || rawName === ''\n ? 'field'\n : String(rawName)\n const label =\n String(props['label'] || '') || String(rawName || '') || 'Field'\n const rawType = String(props['fieldType'] ?? 'text')\n const fieldType = (\n LEGACY_FIELD_TYPES.has(rawType) ? rawType : 'text'\n ) as LegacyFieldType\n const placeholder =\n typeof props['placeholder'] === 'string' && props['placeholder'].trim()\n ? (props['placeholder'] as string)\n : undefined\n const required = Boolean(props['required'])\n const datasetFieldId =\n typeof props['datasetFieldId'] === 'string' && props['datasetFieldId'].trim()\n ? (props['datasetFieldId'] as string)\n : undefined\n const options = parseLegacyFieldOptions(props['options'])\n\n // Whatever else the author's props held rode onto the element's root, so\n // it rides onto the wrapper.\n const rest: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(props)) {\n if (!LEGACY_FIELD_PROPS.has(key)) rest[key] = value\n }\n\n const taken = new Set<string>(Object.keys(nodes))\n taken.delete(id)\n const mint = (base: string): string => {\n let candidate = base\n for (let n = 2; taken.has(candidate); n += 1) candidate = `${base}-${n}`\n taken.add(candidate)\n return candidate\n }\n\n const out: Record<string, StoredFormNode> = {}\n const typed = node.type !== undefined ? { type: node.type } : {}\n const add = (\n nodeId: string,\n parentId: string,\n componentId: string,\n nodeProps: Record<string, unknown>,\n ): string => {\n out[nodeId] = {\n $id: nodeId,\n ...typed,\n componentId,\n pluginId: FORM_FIELD_ELEMENTS_PLUGIN_ID,\n parentId,\n props: nodeProps,\n }\n const parent = out[parentId]\n if (parent) parent.nodes = [...childIds(parent), nodeId]\n return nodeId\n }\n const mapping = datasetFieldId ? { datasetFieldId } : {}\n const req = required ? { required: true } : {}\n\n const fullWidth =\n fieldType === 'text' ||\n fieldType === 'email' ||\n fieldType === 'textarea' ||\n fieldType === 'select'\n const { nodes: _ownChildren, props: _props, ...shell } = node\n out[id] = {\n ...shell,\n componentId: FORM_FIELD_ELEMENT_IDS.formControl,\n pluginId: FORM_FIELD_ELEMENTS_PLUGIN_ID,\n props: {\n ...rest,\n ...(fullWidth ? { fullWidth: true } : {}),\n ...req,\n },\n nodes: [],\n }\n\n const layout: FormFieldElementsLayout = {\n rootId: id,\n answerIds: [],\n placeholderIds: [],\n requiredIds: [],\n }\n const addLabel = () => {\n layout.labelId = add(mint(`${id}-label`), id, FORM_FIELD_ELEMENT_IDS.formLabel, {\n children: label,\n })\n }\n\n switch (fieldType) {\n case 'select': {\n addLabel()\n const select = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.select, {\n name,\n ...(placeholder ? { placeholder } : {}),\n ...req,\n fullWidth: true,\n ...mapping,\n })\n options.forEach((option, index) => {\n add(mint(`${id}-opt-${index}`), select, FORM_FIELD_ELEMENT_IDS.selectOption, {\n children: option,\n })\n })\n layout.answerIds.push(select)\n layout.placeholderIds.push(select)\n layout.requiredIds.push(select)\n break\n }\n case 'radio': {\n addLabel()\n const group = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.radioGroup, {\n name,\n ...mapping,\n })\n options.forEach((option, index) => {\n const choice = add(\n mint(`${id}-opt-${index}`),\n group,\n FORM_FIELD_ELEMENT_IDS.choiceLabel,\n { label: option },\n )\n const radio = add(mint(`${id}-opt-${index}-input`), choice, FORM_FIELD_ELEMENT_IDS.radio, {\n value: option,\n ...req,\n })\n layout.requiredIds.push(radio)\n })\n layout.answerIds.push(group)\n break\n }\n case 'checkbox': {\n // A lone box is its own question: the words beside it say it all. A\n // group of boxes keeps the question above them.\n if (options.length !== 1) addLabel()\n options.forEach((option, index) => {\n const choice = add(\n mint(`${id}-opt-${index}`),\n id,\n FORM_FIELD_ELEMENT_IDS.choiceLabel,\n { label: option },\n )\n // `value` is the option text, as the all-in-one element posted it —\n // which is also what makes a single-option marketing consent box\n // read as ticked (`isConsentCheckboxTicked`). `required` only on a\n // lone box: on a group it meant \"at least one\", which a box of its\n // own cannot say.\n const box = add(mint(`${id}-opt-${index}-input`), choice, FORM_FIELD_ELEMENT_IDS.checkbox, {\n name,\n value: option,\n ...(options.length === 1 ? req : {}),\n ...mapping,\n })\n layout.answerIds.push(box)\n if (options.length === 1) layout.requiredIds.push(box)\n })\n break\n }\n case 'rating': {\n addLabel()\n const rating = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.rating, {\n name,\n ...mapping,\n })\n layout.answerIds.push(rating)\n break\n }\n default: {\n addLabel()\n const input = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.input, {\n name,\n type: fieldType === 'email' ? 'email' : 'text',\n ...(fieldType === 'textarea' ? { multiline: true, minRows: 3 } : {}),\n ...(placeholder ? { placeholder } : {}),\n ...req,\n fullWidth: true,\n ...mapping,\n })\n layout.answerIds.push(input)\n layout.placeholderIds.push(input)\n layout.requiredIds.push(input)\n }\n }\n\n // A self-closing field never had children, but one that somehow did keeps\n // them rather than orphaning them.\n const wrapper = out[id] as StoredFormNode\n wrapper.nodes = [...childIds(wrapper), ...childIds(node)]\n return { ...layout, nodes: out }\n}\n\n/**\n * Every all-in-one field in a tree, rebuilt — or `null` when the tree holds\n * none. The tree is not mutated; the result is a new map with each field\n * replaced in place (same id) and its new pieces added.\n */\nexport function migrateFormFieldNodes(\n nodes: Record<string, StoredFormNode | undefined> | null | undefined,\n): { nodes: Record<string, StoredFormNode>; converted: string[] } | null {\n if (!nodes || typeof nodes !== 'object') return null\n const next: Record<string, StoredFormNode> = { ...(nodes as Record<string, StoredFormNode>) }\n const converted: string[] = []\n for (const [key, node] of Object.entries(nodes)) {\n if (!node || node.componentId !== LEGACY_FORM_FIELD_COMPONENT_ID) continue\n const elements = formFieldToElements({ ...node, $id: node.$id ?? key }, next)\n if (!elements) continue\n Object.assign(next, elements.nodes)\n converted.push(key)\n }\n return converted.length ? { nodes: next, converted } : null\n}\n\n/**\n * Which piece owns each prop of the field at `id` in a DEFINITION tree,\n * whether that tree still holds the all-in-one element or already holds its\n * replacement — so re-keying a placement works before and after the design\n * itself was migrated, and a re-run never double-moves anything.\n */\nexport function formFieldLayoutIn(\n definition: Record<string, StoredFormNode | undefined>,\n id: string,\n): FormFieldElementsLayout | null {\n const node = definition[id]\n if (!node) return null\n if (node.componentId === LEGACY_FORM_FIELD_COMPONENT_ID) {\n const elements = formFieldToElements({ ...node, $id: node.$id ?? id }, definition)\n if (!elements) return null\n const { nodes: _nodes, ...layout } = elements\n return layout\n }\n if (node.componentId !== FORM_FIELD_ELEMENT_IDS.formControl) return null\n\n const layout: FormFieldElementsLayout = {\n rootId: id,\n answerIds: [],\n placeholderIds: [],\n requiredIds: [],\n }\n const checkboxes: string[] = []\n const stack = [...childIds(node)].reverse()\n const seen = new Set<string>([id])\n while (stack.length) {\n const childId = stack.pop() as string\n if (seen.has(childId)) continue\n seen.add(childId)\n const child = definition[childId]\n if (!child) continue\n switch (child.componentId) {\n case FORM_FIELD_ELEMENT_IDS.formLabel:\n if (!layout.labelId && child.parentId === id) layout.labelId = childId\n break\n case FORM_FIELD_ELEMENT_IDS.input:\n case FORM_FIELD_ELEMENT_IDS.select:\n layout.answerIds.push(childId)\n layout.placeholderIds.push(childId)\n layout.requiredIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.radioGroup:\n case FORM_FIELD_ELEMENT_IDS.rating:\n layout.answerIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.radio:\n layout.requiredIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.checkbox:\n layout.answerIds.push(childId)\n checkboxes.push(childId)\n break\n default:\n break\n }\n const children = childIds(child)\n for (let index = children.length - 1; index >= 0; index -= 1) {\n stack.push(children[index] as string)\n }\n }\n if (checkboxes.length === 1) layout.requiredIds.push(checkboxes[0] as string)\n return layout\n}\n\n/** A placement override that had nowhere to go, reported rather than kept. */\nexport interface DroppedFormFieldOverride {\n key: string\n prop: string\n}\n\n/**\n * A placement's `attrOverrides` re-keyed for MUI's separate form elements, or\n * `null` when nothing in it named an all-in-one field's props.\n *\n * `overrides` is keyed by DEFINITION node ids (a placed form's design, a\n * reusable component's definition). A slice keyed by a field's id moves:\n * `label` onto the Form Label (as its text), `placeholder` onto the Input or\n * Select, `fieldName` onto every answer element (as `name`), `required` onto\n * the elements that enforce it and the wrapper, `datasetFieldId` onto the\n * answer elements. Anything else in the slice (`sx` among it) stays on the\n * field's id, which is the wrapper. `fieldType` and `options` re-shape the\n * field itself and have no single new home; they are dropped and returned so\n * the caller can report them.\n */\nexport function rekeyFormFieldOverrides(\n overrides: unknown,\n definition: Record<string, StoredFormNode | undefined> | null | undefined,\n): {\n attrOverrides: Record<string, Record<string, unknown>>\n moved: number\n dropped: DroppedFormFieldOverride[]\n} | null {\n if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) return null\n if (!definition) return null\n const source = overrides as Record<string, unknown>\n const next: Record<string, Record<string, unknown>> = {}\n for (const [key, slice] of Object.entries(source)) {\n if (slice && typeof slice === 'object' && !Array.isArray(slice)) {\n next[key] = { ...(slice as Record<string, unknown>) }\n }\n }\n const put = (targetId: string, prop: string, value: unknown) => {\n next[targetId] = { ...(next[targetId] ?? {}), [prop]: value }\n }\n let moved = 0\n const dropped: DroppedFormFieldOverride[] = []\n for (const [key, slice] of Object.entries(source)) {\n if (!slice || typeof slice !== 'object' || Array.isArray(slice)) continue\n const own = slice as Record<string, unknown>\n if (!Object.keys(own).some((prop) => LEGACY_FIELD_PROPS.has(prop))) continue\n const layout = formFieldLayoutIn(definition, key)\n if (!layout) continue\n const kept: Record<string, unknown> = {}\n for (const [prop, value] of Object.entries(own)) {\n switch (prop) {\n case 'label':\n if (layout.labelId) put(layout.labelId, 'children', value)\n else dropped.push({ key, prop })\n break\n case 'placeholder':\n if (layout.placeholderIds.length) {\n for (const target of layout.placeholderIds) put(target, 'placeholder', value)\n } else dropped.push({ key, prop })\n break\n case 'fieldName':\n if (layout.answerIds.length) {\n for (const target of layout.answerIds) put(target, 'name', value)\n } else dropped.push({ key, prop })\n break\n case 'datasetFieldId':\n if (layout.answerIds.length) {\n for (const target of layout.answerIds) put(target, 'datasetFieldId', value)\n } else dropped.push({ key, prop })\n break\n case 'required':\n kept['required'] = value\n for (const target of layout.requiredIds) put(target, 'required', value)\n break\n case 'fieldType':\n case 'options':\n dropped.push({ key, prop })\n break\n default:\n kept[prop] = value\n }\n }\n moved += 1\n // The wrapper's slice is rebuilt from what stayed, merged over anything\n // a re-key of ANOTHER field put there (none can — ids are per field —\n // but the merge keeps the rule obvious).\n const { label: _l, placeholder: _p, fieldName: _f, datasetFieldId: _d, fieldType: _t, options: _o, required: _r, ...others } =\n next[key] ?? {}\n const wrapper = { ...others, ...kept }\n if (Object.keys(wrapper).length) next[key] = wrapper\n else delete next[key]\n }\n return moved ? { attrOverrides: next, moved, dropped } : null\n}\n\n/**\n * The MUI class names a stored `sx` (or a style override slice) selects, such\n * as `& .MuiInputLabel-root` or `& .MuiOutlinedInput-notchedOutline`.\n *\n * The all-in-one element drew a TextField, so an author's styles could reach\n * into its inner classes. After the move a field is separate elements whose\n * own classes differ (`MuiFormLabel`, not `MuiInputLabel`), so such a\n * selector may match nothing; the backfill lists every one for a person to\n * restate on the right element.\n */\nexport function muiClassSelectorsIn(sx: unknown): string[] {\n const found = new Set<string>()\n const visit = (value: unknown, depth: number) => {\n if (!value || typeof value !== 'object' || depth > 8) return\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n for (const match of key.matchAll(/Mui[A-Z][A-Za-z]*(?:-[A-Za-z]+)?/g)) {\n found.add(match[0])\n }\n visit(entry, depth + 1)\n }\n }\n visit(sx, 0)\n return [...found].sort()\n}\n"],"names":["LEGACY_FORM_FIELD_COMPONENT_ID","FORM_FIELD_ELEMENTS_PLUGIN_ID","FORM_FIELD_ELEMENT_IDS","formControl","formLabel","input","select","selectOption","radioGroup","radio","checkbox","choiceLabel","rating","LEGACY_FIELD_TYPES","Set","LEGACY_FIELD_PROPS","parseLegacyFieldOptions","options","split","map","entry","trim","filter","Boolean","childIds","node","Array","isArray","nodes","formFieldToElements","props","componentId","id","String","$id","rawName","name","undefined","label","rawType","fieldType","has","placeholder","required","datasetFieldId","rest","key","value","Object","entries","taken","keys","delete","mint","base","candidate","n","add","out","typed","type","nodeId","parentId","nodeProps","pluginId","parent","mapping","req","fullWidth","_ownChildren","_props","shell","layout","rootId","answerIds","placeholderIds","requiredIds","addLabel","labelId","children","forEach","option","index","push","group","choice","length","box","multiline","minRows","wrapper","migrateFormFieldNodes","next","converted","elements","assign","formFieldLayoutIn","definition","_nodes","checkboxes","stack","reverse","seen","childId","pop","child","rekeyFormFieldOverrides","overrides","source","slice","put","targetId","prop","moved","dropped","own","some","kept","target","_l","_p","fieldName","_f","_d","_t","_o","_r","others","attrOverrides","muiClassSelectorsIn","sx","found","visit","depth","match","matchAll","sort"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/model/form-field-elements.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*\n * ONE ALL-IN-ONE FORM FIELD, REBUILT FROM MUI'S SEPARATE FORM ELEMENTS.\n *\n * Every piece of a field is its own Besigner element now (a Form Control\n * holding a Form Label and an Input, a Select of Select Options, a Radio Group\n * of Radios in Choice Labels, Checkboxes in Choice Labels, a Rating), so an\n * author selects and styles each piece with the ordinary Styles panel. The\n * all-in-one `formField` is still registered so a document nobody has migrated\n * keeps drawing, and this module is how each one is moved off it: the seeds,\n * the presets and `tools/scripts/backfill-formfield-elements.mjs` all build the\n * same tree from the same rules.\n *\n * What must survive the move, exactly:\n *\n * - **The field's id.** It stays on the Form Control wrapper, so a parent's\n * `nodes` list, a reveal target and every placement override keyed by it\n * still find the field. The wrapper takes the field's `sx` with it.\n * - **The name it submits under.** Contact-field mappings, dataset bindings\n * and the consent field all key on it, so it is copied verbatim.\n * - **Everything a placement overrode.** A placed form's `attrOverrides`\n * named the field's id and a prop of the all-in-one element; the slice is\n * re-keyed onto the piece that now owns that prop.\n *\n * It lives with the forms plugin, which owns the all-in-one element, rather\n * than in core: a form's vocabulary is the plugin's domain (docs/PACKAGES.md\n * rule 3).\n *\n * Pure and dependency-free (no imports at all) on purpose: the backfill is a\n * plain Node script that imports this file directly from its TypeScript\n * source, and an import here would drag a package graph into it.\n */\n\n/** A node as it is stored: a flat map entry with its children by id. */\nexport interface StoredFormNode {\n $id?: string\n componentId?: string\n pluginId?: string\n parentId?: string | null\n props?: Record<string, unknown>\n nodes?: string[]\n sx?: unknown\n [key: string]: unknown\n}\n\n/** The all-in-one element this module migrates away from. Persisted; never rename. */\nexport const LEGACY_FORM_FIELD_COMPONENT_ID = 'formField'\n\n/** The bundle that registers every element a field is rebuilt from. */\nexport const FORM_FIELD_ELEMENTS_PLUGIN_ID = 'mui'\n\n/**\n * MUI's form elements, by persisted component id — restated rather than\n * imported (see the module note; a plugin never imports another plugin).\n * `form-field-elements.spec.ts` holds this table equal to core's\n * `FORM_INPUT_ELEMENT_IDS` in `forms.ts`.\n */\nexport const FORM_FIELD_ELEMENT_IDS = {\n formControl: 'muiFormControl',\n formLabel: 'muiFormLabel',\n input: 'muiInput',\n select: 'muiSelect',\n selectOption: 'muiSelectOption',\n radioGroup: 'muiRadioGroup',\n radio: 'muiRadio',\n checkbox: 'muiCheckbox',\n choiceLabel: 'muiFormControlLabel',\n rating: 'muiRating',\n datePicker: 'muiDatePicker',\n timePicker: 'muiTimePicker',\n} as const\n\n/** The all-in-one element's types; anything else drew as a one-line text box. */\ntype LegacyFieldType =\n | 'text'\n | 'email'\n | 'textarea'\n | 'select'\n | 'radio'\n | 'checkbox'\n | 'rating'\n\nconst LEGACY_FIELD_TYPES = new Set<string>([\n 'text',\n 'email',\n 'textarea',\n 'select',\n 'radio',\n 'checkbox',\n 'rating',\n])\n\n/** The props the all-in-one element read; anything else rode onto its root. */\nconst LEGACY_FIELD_PROPS = new Set<string>([\n 'fieldName',\n 'datasetFieldId',\n 'label',\n 'placeholder',\n 'fieldType',\n 'options',\n 'required',\n])\n\n/**\n * A choice list as the all-in-one element split it: newline- or\n * comma-separated, trimmed, empties dropped (its own `parseFieldOptions`).\n */\nexport function parseLegacyFieldOptions(options: unknown): string[] {\n return (typeof options === 'string' ? options : '')\n .split(/[\\n,]/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n}\n\n/** Which new node owns each prop the all-in-one element had. */\nexport interface FormFieldElementsLayout {\n /** The Form Control; the field's own id. */\n rootId: string\n /** The Form Label, when the field has one (not a lone checkbox). */\n labelId?: string\n /** Every node that submits the answer and carries `name`. */\n answerIds: string[]\n /** The nodes a placeholder belongs on (Input, Select). */\n placeholderIds: string[]\n /** The nodes `required` is enforced on. */\n requiredIds: string[]\n}\n\nexport interface FormFieldElements extends FormFieldElementsLayout {\n /** The wrapper and every new node under it, keyed by id. */\n nodes: Record<string, StoredFormNode>\n}\n\n/** Child ids of a stored node, in order. */\nconst childIds = (node: StoredFormNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as string[]) : []\n\n/**\n * The all-in-one `formField` node `node`, rebuilt as MUI's separate form\n * elements — or `null` when it is not one.\n *\n * `nodes` is the tree it sits in, read only so a new id never collides with\n * one already there. New ids are derived from the field's own\n * (`<id>-label`, `<id>-input`, `<id>-opt-0`, `<id>-opt-0-input`), so a\n * second run over the same field mints the same tree.\n */\nexport function formFieldToElements(\n node: StoredFormNode | undefined | null,\n nodes: Record<string, StoredFormNode | undefined> = {},\n): FormFieldElements | null {\n if (!node || node.componentId !== LEGACY_FORM_FIELD_COMPONENT_ID) return null\n const id = String(node.$id ?? '')\n if (!id) return null\n\n const props = (node.props ?? {}) as Record<string, unknown>\n const rawName = props['fieldName']\n // Exactly what the element submitted under: `fieldName || 'field'`.\n const name = rawName === undefined || rawName === null || rawName === ''\n ? 'field'\n : String(rawName)\n const label =\n String(props['label'] || '') || String(rawName || '') || 'Field'\n const rawType = String(props['fieldType'] ?? 'text')\n const fieldType = (\n LEGACY_FIELD_TYPES.has(rawType) ? rawType : 'text'\n ) as LegacyFieldType\n const placeholder =\n typeof props['placeholder'] === 'string' && props['placeholder'].trim()\n ? (props['placeholder'] as string)\n : undefined\n const required = Boolean(props['required'])\n const datasetFieldId =\n typeof props['datasetFieldId'] === 'string' && props['datasetFieldId'].trim()\n ? (props['datasetFieldId'] as string)\n : undefined\n const options = parseLegacyFieldOptions(props['options'])\n\n // Whatever else the author's props held rode onto the element's root, so\n // it rides onto the wrapper.\n const rest: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(props)) {\n if (!LEGACY_FIELD_PROPS.has(key)) rest[key] = value\n }\n\n const taken = new Set<string>(Object.keys(nodes))\n taken.delete(id)\n const mint = (base: string): string => {\n let candidate = base\n for (let n = 2; taken.has(candidate); n += 1) candidate = `${base}-${n}`\n taken.add(candidate)\n return candidate\n }\n\n const out: Record<string, StoredFormNode> = {}\n const typed = node.type !== undefined ? { type: node.type } : {}\n const add = (\n nodeId: string,\n parentId: string,\n componentId: string,\n nodeProps: Record<string, unknown>,\n ): string => {\n out[nodeId] = {\n $id: nodeId,\n ...typed,\n componentId,\n pluginId: FORM_FIELD_ELEMENTS_PLUGIN_ID,\n parentId,\n props: nodeProps,\n }\n const parent = out[parentId]\n if (parent) parent.nodes = [...childIds(parent), nodeId]\n return nodeId\n }\n const mapping = datasetFieldId ? { datasetFieldId } : {}\n const req = required ? { required: true } : {}\n\n const fullWidth =\n fieldType === 'text' ||\n fieldType === 'email' ||\n fieldType === 'textarea' ||\n fieldType === 'select'\n const { nodes: _ownChildren, props: _props, ...shell } = node\n out[id] = {\n ...shell,\n componentId: FORM_FIELD_ELEMENT_IDS.formControl,\n pluginId: FORM_FIELD_ELEMENTS_PLUGIN_ID,\n props: {\n ...rest,\n ...(fullWidth ? { fullWidth: true } : {}),\n ...req,\n },\n nodes: [],\n }\n\n const layout: FormFieldElementsLayout = {\n rootId: id,\n answerIds: [],\n placeholderIds: [],\n requiredIds: [],\n }\n const addLabel = () => {\n layout.labelId = add(mint(`${id}-label`), id, FORM_FIELD_ELEMENT_IDS.formLabel, {\n children: label,\n })\n }\n\n switch (fieldType) {\n case 'select': {\n addLabel()\n const select = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.select, {\n name,\n ...(placeholder ? { placeholder } : {}),\n ...req,\n fullWidth: true,\n ...mapping,\n })\n options.forEach((option, index) => {\n add(mint(`${id}-opt-${index}`), select, FORM_FIELD_ELEMENT_IDS.selectOption, {\n children: option,\n })\n })\n layout.answerIds.push(select)\n layout.placeholderIds.push(select)\n layout.requiredIds.push(select)\n break\n }\n case 'radio': {\n addLabel()\n const group = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.radioGroup, {\n name,\n ...mapping,\n })\n options.forEach((option, index) => {\n const choice = add(\n mint(`${id}-opt-${index}`),\n group,\n FORM_FIELD_ELEMENT_IDS.choiceLabel,\n { label: option },\n )\n const radio = add(mint(`${id}-opt-${index}-input`), choice, FORM_FIELD_ELEMENT_IDS.radio, {\n value: option,\n ...req,\n })\n layout.requiredIds.push(radio)\n })\n layout.answerIds.push(group)\n break\n }\n case 'checkbox': {\n // A lone box is its own question: the words beside it say it all. A\n // group of boxes keeps the question above them.\n if (options.length !== 1) addLabel()\n options.forEach((option, index) => {\n const choice = add(\n mint(`${id}-opt-${index}`),\n id,\n FORM_FIELD_ELEMENT_IDS.choiceLabel,\n { label: option },\n )\n // `value` is the option text, as the all-in-one element posted it —\n // which is also what makes a single-option marketing consent box\n // read as ticked (`isConsentCheckboxTicked`). `required` only on a\n // lone box: on a group it meant \"at least one\", which a box of its\n // own cannot say.\n const box = add(mint(`${id}-opt-${index}-input`), choice, FORM_FIELD_ELEMENT_IDS.checkbox, {\n name,\n value: option,\n ...(options.length === 1 ? req : {}),\n ...mapping,\n })\n layout.answerIds.push(box)\n if (options.length === 1) layout.requiredIds.push(box)\n })\n break\n }\n case 'rating': {\n addLabel()\n const rating = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.rating, {\n name,\n ...mapping,\n })\n layout.answerIds.push(rating)\n break\n }\n default: {\n addLabel()\n const input = add(mint(`${id}-input`), id, FORM_FIELD_ELEMENT_IDS.input, {\n name,\n type: fieldType === 'email' ? 'email' : 'text',\n ...(fieldType === 'textarea' ? { multiline: true, minRows: 3 } : {}),\n ...(placeholder ? { placeholder } : {}),\n ...req,\n fullWidth: true,\n ...mapping,\n })\n layout.answerIds.push(input)\n layout.placeholderIds.push(input)\n layout.requiredIds.push(input)\n }\n }\n\n // A self-closing field never had children, but one that somehow did keeps\n // them rather than orphaning them.\n const wrapper = out[id] as StoredFormNode\n wrapper.nodes = [...childIds(wrapper), ...childIds(node)]\n return { ...layout, nodes: out }\n}\n\n/**\n * Every all-in-one field in a tree, rebuilt — or `null` when the tree holds\n * none. The tree is not mutated; the result is a new map with each field\n * replaced in place (same id) and its new pieces added.\n */\nexport function migrateFormFieldNodes(\n nodes: Record<string, StoredFormNode | undefined> | null | undefined,\n): { nodes: Record<string, StoredFormNode>; converted: string[] } | null {\n if (!nodes || typeof nodes !== 'object') return null\n const next: Record<string, StoredFormNode> = { ...(nodes as Record<string, StoredFormNode>) }\n const converted: string[] = []\n for (const [key, node] of Object.entries(nodes)) {\n if (!node || node.componentId !== LEGACY_FORM_FIELD_COMPONENT_ID) continue\n const elements = formFieldToElements({ ...node, $id: node.$id ?? key }, next)\n if (!elements) continue\n Object.assign(next, elements.nodes)\n converted.push(key)\n }\n return converted.length ? { nodes: next, converted } : null\n}\n\n/**\n * Which piece owns each prop of the field at `id` in a DEFINITION tree,\n * whether that tree still holds the all-in-one element or already holds its\n * replacement — so re-keying a placement works before and after the design\n * itself was migrated, and a re-run never double-moves anything.\n */\nexport function formFieldLayoutIn(\n definition: Record<string, StoredFormNode | undefined>,\n id: string,\n): FormFieldElementsLayout | null {\n const node = definition[id]\n if (!node) return null\n if (node.componentId === LEGACY_FORM_FIELD_COMPONENT_ID) {\n const elements = formFieldToElements({ ...node, $id: node.$id ?? id }, definition)\n if (!elements) return null\n const { nodes: _nodes, ...layout } = elements\n return layout\n }\n if (node.componentId !== FORM_FIELD_ELEMENT_IDS.formControl) return null\n\n const layout: FormFieldElementsLayout = {\n rootId: id,\n answerIds: [],\n placeholderIds: [],\n requiredIds: [],\n }\n const checkboxes: string[] = []\n const stack = [...childIds(node)].reverse()\n const seen = new Set<string>([id])\n while (stack.length) {\n const childId = stack.pop() as string\n if (seen.has(childId)) continue\n seen.add(childId)\n const child = definition[childId]\n if (!child) continue\n switch (child.componentId) {\n case FORM_FIELD_ELEMENT_IDS.formLabel:\n if (!layout.labelId && child.parentId === id) layout.labelId = childId\n break\n case FORM_FIELD_ELEMENT_IDS.input:\n case FORM_FIELD_ELEMENT_IDS.select:\n layout.answerIds.push(childId)\n layout.placeholderIds.push(childId)\n layout.requiredIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.radioGroup:\n case FORM_FIELD_ELEMENT_IDS.rating:\n layout.answerIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.radio:\n layout.requiredIds.push(childId)\n break\n case FORM_FIELD_ELEMENT_IDS.checkbox:\n layout.answerIds.push(childId)\n checkboxes.push(childId)\n break\n default:\n break\n }\n const children = childIds(child)\n for (let index = children.length - 1; index >= 0; index -= 1) {\n stack.push(children[index] as string)\n }\n }\n if (checkboxes.length === 1) layout.requiredIds.push(checkboxes[0] as string)\n return layout\n}\n\n/** A placement override that had nowhere to go, reported rather than kept. */\nexport interface DroppedFormFieldOverride {\n key: string\n prop: string\n}\n\n/**\n * A placement's `attrOverrides` re-keyed for MUI's separate form elements, or\n * `null` when nothing in it named an all-in-one field's props.\n *\n * `overrides` is keyed by DEFINITION node ids (a placed form's design, a\n * reusable component's definition). A slice keyed by a field's id moves:\n * `label` onto the Form Label (as its text), `placeholder` onto the Input or\n * Select, `fieldName` onto every answer element (as `name`), `required` onto\n * the elements that enforce it and the wrapper, `datasetFieldId` onto the\n * answer elements. Anything else in the slice (`sx` among it) stays on the\n * field's id, which is the wrapper. `fieldType` and `options` re-shape the\n * field itself and have no single new home; they are dropped and returned so\n * the caller can report them.\n */\nexport function rekeyFormFieldOverrides(\n overrides: unknown,\n definition: Record<string, StoredFormNode | undefined> | null | undefined,\n): {\n attrOverrides: Record<string, Record<string, unknown>>\n moved: number\n dropped: DroppedFormFieldOverride[]\n} | null {\n if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides)) return null\n if (!definition) return null\n const source = overrides as Record<string, unknown>\n const next: Record<string, Record<string, unknown>> = {}\n for (const [key, slice] of Object.entries(source)) {\n if (slice && typeof slice === 'object' && !Array.isArray(slice)) {\n next[key] = { ...(slice as Record<string, unknown>) }\n }\n }\n const put = (targetId: string, prop: string, value: unknown) => {\n next[targetId] = { ...(next[targetId] ?? {}), [prop]: value }\n }\n let moved = 0\n const dropped: DroppedFormFieldOverride[] = []\n for (const [key, slice] of Object.entries(source)) {\n if (!slice || typeof slice !== 'object' || Array.isArray(slice)) continue\n const own = slice as Record<string, unknown>\n if (!Object.keys(own).some((prop) => LEGACY_FIELD_PROPS.has(prop))) continue\n const layout = formFieldLayoutIn(definition, key)\n if (!layout) continue\n const kept: Record<string, unknown> = {}\n for (const [prop, value] of Object.entries(own)) {\n switch (prop) {\n case 'label':\n if (layout.labelId) put(layout.labelId, 'children', value)\n else dropped.push({ key, prop })\n break\n case 'placeholder':\n if (layout.placeholderIds.length) {\n for (const target of layout.placeholderIds) put(target, 'placeholder', value)\n } else dropped.push({ key, prop })\n break\n case 'fieldName':\n if (layout.answerIds.length) {\n for (const target of layout.answerIds) put(target, 'name', value)\n } else dropped.push({ key, prop })\n break\n case 'datasetFieldId':\n if (layout.answerIds.length) {\n for (const target of layout.answerIds) put(target, 'datasetFieldId', value)\n } else dropped.push({ key, prop })\n break\n case 'required':\n kept['required'] = value\n for (const target of layout.requiredIds) put(target, 'required', value)\n break\n case 'fieldType':\n case 'options':\n dropped.push({ key, prop })\n break\n default:\n kept[prop] = value\n }\n }\n moved += 1\n // The wrapper's slice is rebuilt from what stayed, merged over anything\n // a re-key of ANOTHER field put there (none can — ids are per field —\n // but the merge keeps the rule obvious).\n const { label: _l, placeholder: _p, fieldName: _f, datasetFieldId: _d, fieldType: _t, options: _o, required: _r, ...others } =\n next[key] ?? {}\n const wrapper = { ...others, ...kept }\n if (Object.keys(wrapper).length) next[key] = wrapper\n else delete next[key]\n }\n return moved ? { attrOverrides: next, moved, dropped } : null\n}\n\n/**\n * The MUI class names a stored `sx` (or a style override slice) selects, such\n * as `& .MuiInputLabel-root` or `& .MuiOutlinedInput-notchedOutline`.\n *\n * The all-in-one element drew a TextField, so an author's styles could reach\n * into its inner classes. After the move a field is separate elements whose\n * own classes differ (`MuiFormLabel`, not `MuiInputLabel`), so such a\n * selector may match nothing; the backfill lists every one for a person to\n * restate on the right element.\n */\nexport function muiClassSelectorsIn(sx: unknown): string[] {\n const found = new Set<string>()\n const visit = (value: unknown, depth: number) => {\n if (!value || typeof value !== 'object' || depth > 8) return\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n for (const match of key.matchAll(/Mui[A-Z][A-Za-z]*(?:-[A-Za-z]+)?/g)) {\n found.add(match[0])\n }\n visit(entry, depth + 1)\n }\n }\n visit(sx, 0)\n return [...found].sort()\n}\n"],"names":["LEGACY_FORM_FIELD_COMPONENT_ID","FORM_FIELD_ELEMENTS_PLUGIN_ID","FORM_FIELD_ELEMENT_IDS","formControl","formLabel","input","select","selectOption","radioGroup","radio","checkbox","choiceLabel","rating","datePicker","timePicker","LEGACY_FIELD_TYPES","Set","LEGACY_FIELD_PROPS","parseLegacyFieldOptions","options","split","map","entry","trim","filter","Boolean","childIds","node","Array","isArray","nodes","formFieldToElements","props","componentId","id","String","$id","rawName","name","undefined","label","rawType","fieldType","has","placeholder","required","datasetFieldId","rest","key","value","Object","entries","taken","keys","delete","mint","base","candidate","n","add","out","typed","type","nodeId","parentId","nodeProps","pluginId","parent","mapping","req","fullWidth","_ownChildren","_props","shell","layout","rootId","answerIds","placeholderIds","requiredIds","addLabel","labelId","children","forEach","option","index","push","group","choice","length","box","multiline","minRows","wrapper","migrateFormFieldNodes","next","converted","elements","assign","formFieldLayoutIn","definition","_nodes","checkboxes","stack","reverse","seen","childId","pop","child","rekeyFormFieldOverrides","overrides","source","slice","put","targetId","prop","moved","dropped","own","some","kept","target","_l","_p","fieldName","_f","_d","_t","_o","_r","others","attrOverrides","muiClassSelectorsIn","sx","found","visit","depth","match","matchAll","sort"],"mappings":"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"}
@@ -106,6 +106,8 @@ const PAGE_ROWS_MAX = 500;
106
106
  return 'longText';
107
107
  case 'rating':
108
108
  return 'integer';
109
+ case 'date':
110
+ return 'date';
109
111
  default:
110
112
  return 'text';
111
113
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/transfer/form-submissions-transfer.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FORMS_MAX_PER_HOST,\n type FormFieldDecl,\n type FormFieldType,\n} from '@aglyn/aglyn/app-utils/forms'\nimport {\n TRANSFER_ID_FIELD,\n buildMatchLookup,\n transferIdField,\n type TransferCatalogInput,\n type TransferField,\n type TransferFieldGroup,\n type TransferFieldType,\n} from '@aglyn/aglyn/data-transfer'\nimport type {\n TransferReadOptions,\n TransferRecordsHooks,\n TransferReadPage,\n TransferResourceContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { FieldPath, Timestamp } from 'firebase-admin/firestore'\nimport { FORM_SUBMISSIONS_MATCH_KEYS } from './form-submissions-transfer-key'\n\n/*==========================================\n * A SITE'S FORM SUBMISSIONS, AS AN EXPORT.\n *\n * `hosts/{hostId}/formSubmissions/{id}`, written only by this plugin's door\n * (`server/form-submit.ts`): the answers a visitor sent, keyed by the form\n * field's `fieldName`, with when, from which page and through which form.\n * The record as a reader may rely on it is `model/form-submission-record.ts`'s;\n * this module reads it the same way.\n *\n * ## Export only\n *\n * The declaration carries `exportOnly`: a submission is what a visitor sent\n * through a published form, and a file cannot have sent one. So the resource\n * answers the catalog, the reads, the count and a lookup by id, and registers\n * no `apply` or `revert` — the upload route refuses it before a byte is\n * stored.\n *\n * ## The columns follow the form\n *\n * A submission's answers have no fixed shape: each form asks its own\n * questions, and a form edited since asks different ones than its older\n * submissions answered. So the catalog is read from the site's forms, and\n * from the list filter the dialog was opened on (`ctx.filter`):\n *\n * - one form (`{ formId }`): that form's questions, each `answer:<fieldName>`,\n * labeled with the question and typed from the field; then \"Other\n * answers\" — keys its most recent submissions carry and today's form no\n * longer declares, so an answer to a removed question is still exported.\n * - the whole site: every form's questions, each form its own group. A\n * `fieldName` two forms share is one column (the first form's label), so\n * an \"email\" asked by every form lands in one place.\n *\n * The submission's own details — the Aglyn ID, when it arrived, the form, the\n * page, read, replied, campaigns, where it was filed — are read-only, and the\n * platform-written ones are `system`.\n *\n * ## The reads are queries, never a loaded window\n *\n * `{ formId }` and `{ read }` are equalities on the stored row, ordered by\n * `createdAt` newest first with the document id breaking ties, so a page\n * boundary between two submissions of the same instant neither repeats nor\n * skips one. `formId ↑ createdAt ↓` and `read ↑ createdAt ↓` are the\n * collection's existing indexes (the console's per-form list and the Inbox's\n * Read filter); no filter rides the single-field `createdAt` index. Both at\n * once needs `formId ↑ read ↑ createdAt ↓`, which the export dialog never\n * asks for today.\n *\n * ## Scope\n *\n * A host resource: the export route admits a reader only on the site, with\n * `data.manage` there. Submissions carry no `visibleTo`, so `scopeTokens` —\n * a site-scoped collaborator's tokens — narrows nothing here: a collaborator\n * the route let onto this site may read this site's submissions, as the\n * Inbox lets them.\n *=========================================*/\n\nconst SUBMISSIONS = 'formSubmissions'\nconst FORMS = 'forms'\n\n/** The prefix of an answer's field id; the rest is the form field's `fieldName`. */\nexport const FORM_SUBMISSION_ANSWER_PREFIX = 'answer:'\n\n/** How many of the most recent submissions the catalog reads for answers today's forms no longer ask. */\nexport const FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS = 200\n\n/**\n * The most \"other answers\" columns the catalog offers. A submission's keys\n * come from the visitor's request (up to twenty, each at most 64 characters),\n * so a sample can hold keys no form ever asked; the most frequent are kept.\n */\nexport const FORM_SUBMISSIONS_OTHER_ANSWERS_MAX = 50\n\nconst PAGE_ROWS_DEFAULT = 200\nconst PAGE_ROWS_MAX = 500\n/** Documents one `getAll` reads at a time, for a selection. */\nconst READ_BY_ID_CHUNK = 300\n\n/** The group the submission's own details are listed under. */\nexport const FORM_SUBMISSION_DETAILS_GROUP: TransferFieldGroup = { id: 'submission', label: 'Submission' }\n/** The group of answers no form declares today. */\nexport const FORM_SUBMISSION_OTHER_ANSWERS_GROUP: TransferFieldGroup = {\n id: 'otherAnswers',\n label: 'Other answers',\n}\n\n/** A form's questions are listed under `form:<formId>`. */\nexport function formAnswersGroupId(formId: string): string {\n return `form:${formId}`\n}\n\n/** The field id an answer to `fieldName` is exported under. */\nexport function formSubmissionAnswerFieldId(fieldName: string): string {\n return `${FORM_SUBMISSION_ANSWER_PREFIX}${fieldName}`\n}\n\n/** What an answer is, from the form field that asked it. */\nexport function formAnswerTransferType(fieldType: FormFieldType | undefined): TransferFieldType {\n switch (fieldType) {\n case 'email':\n return 'email'\n case 'textarea':\n return 'longText'\n case 'rating':\n return 'integer'\n default:\n return 'text'\n }\n}\n\n/*==========================================\n * THE FILTER\n *=========================================*/\n\n/** The list filter, in this resource's terms: one form, and read or unread. */\nexport interface FormSubmissionsFilter {\n formId?: string\n read?: boolean\n}\n\n/**\n * The filter a dialog or a route handed over, or why it cannot be used.\n * Refused rather than ignored: a filter dropped without a word exports more\n * than the person asked for.\n */\nexport function readFormSubmissionsFilter(value: unknown): FormSubmissionsFilter {\n if (value === null || value === undefined) return {}\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('A form submissions filter is an object naming formId and read.')\n }\n const filter: FormSubmissionsFilter = {}\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n if (raw === undefined || raw === null) continue\n if (key === 'formId') {\n const formId = typeof raw === 'string' ? raw.trim() : ''\n if (!formId || formId.includes('/')) throw new Error('The form submissions filter names no usable form.')\n filter.formId = formId\n } else if (key === 'read') {\n if (raw === true || raw === 'true') filter.read = true\n else if (raw === false || raw === 'false') filter.read = false\n else throw new Error('The form submissions filter says read as true or false.')\n } else {\n throw new Error(`A form submissions filter names formId and read; \"${key}\" is not one.`)\n }\n }\n return filter\n}\n\nfunction matchesFilter(data: Readonly<Record<string, unknown>>, filter: FormSubmissionsFilter): boolean {\n if (filter.formId !== undefined && data['formId'] !== filter.formId) return false\n if (filter.read !== undefined && (data['read'] === true) !== filter.read) return false\n return true\n}\n\n/*==========================================\n * THE CATALOG\n *=========================================*/\n\n/** The submission's own details, in the order a person reads them. */\nexport function formSubmissionDetailFields(): TransferField[] {\n const group = FORM_SUBMISSION_DETAILS_GROUP.id\n return [\n { ...transferIdField(), group },\n {\n id: 'createdAt',\n label: 'Submitted at',\n group,\n type: 'datetime',\n readOnly: true,\n system: true,\n aliases: ['submitted', 'created', 'date'],\n description: 'When the submission arrived, by the server’s clock.',\n },\n {\n id: 'formId',\n label: 'Form ID',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n description: 'The form it was sent through; blank on submissions older than the form itself.',\n },\n {\n id: 'formName',\n label: 'Form name',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n aliases: ['form'],\n description: 'What the form was called when the submission arrived.',\n },\n {\n id: 'path',\n label: 'Page path',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n aliases: ['page', 'path'],\n description: 'The page of the site it was sent from.',\n },\n {\n id: 'read',\n label: 'Read',\n group,\n type: 'boolean',\n readOnly: true,\n description: 'Whether someone has marked it read in the Inbox.',\n },\n {\n id: 'repliedAt',\n label: 'Replied at',\n group,\n type: 'datetime',\n readOnly: true,\n description: 'When it was last answered from the Inbox.',\n },\n {\n id: 'campaignIds',\n label: 'Campaign IDs',\n group,\n type: 'tags',\n readOnly: true,\n system: true,\n description: 'The campaigns the form was filed under when the submission arrived.',\n },\n {\n id: 'routing',\n label: 'Where it was filed',\n group,\n type: 'json',\n readOnly: true,\n system: true,\n description: 'Where the submission was also filed beyond the Inbox, such as a dataset, or why it was not.',\n },\n {\n id: 'rateDegraded',\n label: 'Arrived while rate limits were unchecked',\n group,\n type: 'boolean',\n readOnly: true,\n system: true,\n description: 'Yes when it arrived during a moment the site’s submission rate limit could not be checked.',\n },\n {\n id: 'hostId',\n label: 'Site ID',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n },\n ]\n}\n\n/** One form as the catalog reads it: its id, its name, and the questions it declares. */\nexport interface FormQuestions {\n id: string\n name: string\n fields: readonly FormFieldDecl[]\n}\n\nfunction formQuestionsOf(id: string, data: Readonly<Record<string, unknown>> | undefined): FormQuestions {\n const declared = Array.isArray(data?.['fields']) ? (data?.['fields'] as unknown[]) : []\n const displayName = data?.['displayName']\n return {\n id,\n name: typeof displayName === 'string' && displayName.trim() ? displayName.trim() : id,\n fields: declared.filter(\n (field): field is FormFieldDecl =>\n Boolean(field) &&\n typeof field === 'object' &&\n typeof (field as FormFieldDecl).fieldName === 'string' &&\n (field as FormFieldDecl).fieldName.trim() !== '',\n ),\n }\n}\n\nfunction answerField(decl: FormFieldDecl, group: string): TransferField {\n const label = typeof decl.label === 'string' && decl.label.trim() ? decl.label.trim() : decl.fieldName\n return {\n id: formSubmissionAnswerFieldId(decl.fieldName),\n label,\n group,\n type: formAnswerTransferType(decl.fieldType),\n ...(label !== decl.fieldName ? { aliases: [decl.fieldName] } : {}),\n }\n}\n\n/**\n * The catalog, from the forms read and the answer keys sampled (see the\n * block header). `scoped` is true when the dialog was opened on one form.\n */\nexport function formSubmissionsCatalog(input: {\n forms: readonly FormQuestions[]\n sampledKeys: readonly string[]\n scoped: boolean\n}): TransferCatalogInput {\n const answers: TransferField[] = []\n const groups: TransferFieldGroup[] = [FORM_SUBMISSION_DETAILS_GROUP]\n const taken = new Set<string>()\n for (const form of input.forms) {\n const group: TransferFieldGroup = {\n id: formAnswersGroupId(form.id),\n label: input.scoped ? 'Answers' : `Answers: ${form.name}`,\n }\n let added = false\n for (const decl of form.fields) {\n if (taken.has(decl.fieldName)) continue\n taken.add(decl.fieldName)\n answers.push(answerField(decl, group.id))\n added = true\n }\n if (added) groups.push(group)\n }\n const others = input.sampledKeys.filter((key) => !taken.has(key)).slice(0, FORM_SUBMISSIONS_OTHER_ANSWERS_MAX)\n for (const key of others) {\n answers.push({\n id: formSubmissionAnswerFieldId(key),\n label: key,\n group: FORM_SUBMISSION_OTHER_ANSWERS_GROUP.id,\n type: 'text',\n description: input.scoped\n ? 'An answer earlier submissions to this form carry that the form no longer asks for.'\n : 'An answer earlier submissions carry that no form on this site asks for today.',\n })\n }\n if (others.length) groups.push(FORM_SUBMISSION_OTHER_ANSWERS_GROUP)\n return { standard: [...formSubmissionDetailFields(), ...answers], groups }\n}\n\n/** The answer keys the sampled submissions carry, most frequent first, then first seen. */\nexport function sampledAnswerKeys(rows: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>): string[] {\n const seen = new Map<string, { count: number; order: number }>()\n for (const data of rows) {\n const fields = data?.['fields']\n if (!fields || typeof fields !== 'object' || Array.isArray(fields)) continue\n for (const key of Object.keys(fields)) {\n if (!key.trim()) continue\n const entry = seen.get(key)\n if (entry) entry.count += 1\n else seen.set(key, { count: 1, order: seen.size })\n }\n }\n return [...seen.entries()]\n .sort(([, a], [, b]) => b.count - a.count || a.order - b.order)\n .map(([key]) => key)\n}\n\n/*==========================================\n * ONE ROW\n *=========================================*/\n\nfunction textOrNull(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null\n}\n\n/** A stored instant as ISO text, so a CSV reads it: a Firestore Timestamp, a Date, or milliseconds. */\nfunction isoOf(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'number') return Number.isFinite(value) ? new Date(value).toISOString() : null\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString()\n const toDate = (value as { toDate?: () => Date }).toDate\n if (typeof toDate === 'function') return isoOf(toDate.call(value))\n return null\n}\n\nconst DETAIL_READERS: Readonly<Record<string, (data: Readonly<Record<string, unknown>>, id: string) => unknown>> = {\n [TRANSFER_ID_FIELD]: (_data, id) => id,\n createdAt: (data) => isoOf(data['createdAt']),\n formId: (data) => textOrNull(data['formId']),\n formName: (data) => textOrNull(data['formName']),\n path: (data) => textOrNull(data['path']),\n read: (data) => data['read'] === true,\n repliedAt: (data) => isoOf(data['repliedAtMs']),\n campaignIds: (data) =>\n Array.isArray(data['campaignIds'])\n ? (data['campaignIds'] as unknown[]).filter((one): one is string => typeof one === 'string' && one !== '')\n : [],\n routing: (data) =>\n data['routing'] && typeof data['routing'] === 'object' && !Array.isArray(data['routing'])\n ? data['routing']\n : null,\n rateDegraded: (data) => data['rateDegraded'] === true,\n hostId: (data) => textOrNull(data['hostId']),\n}\n\n/** Form id → the names of its rating questions, whose answers are numbers. */\nexport type FormRatingQuestions = ReadonlyMap<string, ReadonlySet<string>>\n\n/** The rating questions of each form (AGL-3548). */\nexport function formRatingQuestions(forms: readonly FormQuestions[]): Map<string, Set<string>> {\n return new Map(\n forms.map((form) => [\n form.id,\n new Set(form.fields.filter((field) => field.fieldType === 'rating').map((field) => field.fieldName)),\n ]),\n )\n}\n\nconst RATING_TEXT = /^-?\\d+(?:\\.\\d+)?$/\n\n/**\n * One submission as an export row: only `fieldIds`, keyed by field id. An\n * answer is the value as the visitor sent it — but an answer to a rating\n * question of the submission's form (`ratings`) that reads as a number is\n * that number, so a JSON file holds `4` and a CSV writes it as one, not as\n * text. A question they did not answer, or a field id this resource does\n * not know, is `null`.\n */\nexport function formSubmissionTransferRow(\n id: string,\n data: Readonly<Record<string, unknown>>,\n fieldIds: readonly string[],\n ratings?: FormRatingQuestions,\n): Record<string, unknown> {\n const answers =\n data['fields'] && typeof data['fields'] === 'object' && !Array.isArray(data['fields'])\n ? (data['fields'] as Readonly<Record<string, unknown>>)\n : {}\n const formId = typeof data['formId'] === 'string' ? data['formId'] : ''\n const rated = ratings?.get(formId)\n const row: Record<string, unknown> = {}\n for (const fieldId of fieldIds) {\n const reader = DETAIL_READERS[fieldId]\n if (reader) row[fieldId] = reader(data, id)\n else if (fieldId.startsWith(FORM_SUBMISSION_ANSWER_PREFIX)) {\n const name = fieldId.slice(FORM_SUBMISSION_ANSWER_PREFIX.length)\n const answer = answers[name]\n row[fieldId] =\n answer === undefined\n ? null\n : rated?.has(name) && typeof answer === 'string' && RATING_TEXT.test(answer.trim())\n ? Number(answer.trim())\n : answer\n } else row[fieldId] = null\n }\n return row\n}\n\n/*==========================================\n * THE CURSOR\n *=========================================*/\n\n/**\n * Where the next page of a query starts: the last row's `createdAt` (seconds\n * and nanoseconds, so two submissions a microsecond apart stay apart) and\n * its id. A selection's cursor is the offset into the ids.\n */\nexport function formSubmissionsCursor(createdAt: unknown, id: string): string {\n const at = createdAt as { seconds?: unknown; nanoseconds?: unknown } | null | undefined\n if (typeof at?.seconds !== 'number' || typeof at?.nanoseconds !== 'number') {\n throw new Error(`Form submission ${id} has no submission time to page from.`)\n }\n return `${at.seconds}:${at.nanoseconds}:${id}`\n}\n\nfunction readQueryCursor(cursor: string): { createdAt: Timestamp; id: string } {\n const found = /^(-?\\d+):(\\d+):([^]+)$/.exec(cursor)\n if (!found) throw new Error('The export cursor could not be read.')\n return { createdAt: new Timestamp(Number(found[1]), Number(found[2])), id: found[3] as string }\n}\n\nfunction readOffsetCursor(cursor: string | null): number {\n if (cursor === null) return 0\n if (!/^\\d+$/.test(cursor)) throw new Error('The export cursor could not be read.')\n return Number(cursor)\n}\n\n/*==========================================\n * THE RESOURCE\n *=========================================*/\n\nexport interface FormSubmissionsTransferDeps {\n firestore: FirebaseFirestore.Firestore\n}\n\n/** The hooks an export-only records resource answers with: no apply or revert. */\nexport type FormSubmissionsTransferResource = Required<\n Pick<TransferRecordsHooks, 'matchKeys' | 'fields' | 'count' | 'readPage' | 'lookup'>\n>\n\nfunction pageSizeOf(options: TransferReadOptions | undefined): number {\n const asked = Math.floor(Number(options?.pageSize) || PAGE_ROWS_DEFAULT)\n return Math.max(1, Math.min(PAGE_ROWS_MAX, asked))\n}\n\n/** The ids of a selection, each once, in the order given. */\nfunction selectionIds(ids: readonly string[]): string[] {\n return [...new Set(ids.filter((id) => typeof id === 'string' && id !== '' && !id.includes('/')))]\n}\n\n/**\n * The form submissions resource's server half (see the block header).\n * Firestore is handed in, so a spec runs it over an in-memory one.\n */\nexport function createFormSubmissionsTransferResource(\n deps: FormSubmissionsTransferDeps,\n): FormSubmissionsTransferResource {\n const { firestore } = deps\n const site = (hostId: string) => firestore.collection('hosts').doc(hostId)\n const submissions = (hostId: string) => site(hostId).collection(SUBMISSIONS)\n\n /** The filter as Firestore equalities, over the collection's existing indexes. */\n const filtered = (hostId: string, filter: FormSubmissionsFilter): FirebaseFirestore.Query => {\n let query: FirebaseFirestore.Query = submissions(hostId)\n if (filter.formId !== undefined) query = query.where('formId', '==', filter.formId)\n if (filter.read !== undefined) query = query.where('read', '==', filter.read)\n return query\n }\n const newestFirst = (query: FirebaseFirestore.Query): FirebaseFirestore.Query =>\n query.orderBy('createdAt', 'desc').orderBy(FieldPath.documentId(), 'desc')\n\n /** The selection's documents that exist and keep the filter, in the order given. */\n const readByIds = async (\n hostId: string,\n ids: readonly string[],\n filter: FormSubmissionsFilter,\n ): Promise<FirebaseFirestore.DocumentSnapshot[]> => {\n const found: FirebaseFirestore.DocumentSnapshot[] = []\n for (let at = 0; at < ids.length; at += READ_BY_ID_CHUNK) {\n const refs = ids.slice(at, at + READ_BY_ID_CHUNK).map((id) => submissions(hostId).doc(id))\n if (!refs.length) continue\n const snapshots = await firestore.getAll(...refs)\n for (const snapshot of snapshots) {\n const data = snapshot.data()\n if (snapshot.exists && data && matchesFilter(data, filter)) found.push(snapshot)\n }\n }\n return found\n }\n\n const readForms = async (hostId: string, formId: string | undefined): Promise<FormQuestions[]> => {\n if (formId !== undefined) {\n const snapshot = await site(hostId).collection(FORMS).doc(formId).get()\n return snapshot.exists ? [formQuestionsOf(snapshot.id, snapshot.data())] : []\n }\n const snapshot = await site(hostId).collection(FORMS).limit(FORMS_MAX_PER_HOST).get()\n return snapshot.docs\n .map((document) => formQuestionsOf(document.id, document.data()))\n .sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))\n }\n\n const readPage = async (\n ctx: TransferResourceContext,\n cursor: string | null,\n fieldIds: readonly string[],\n options?: TransferReadOptions,\n ): Promise<TransferReadPage> => {\n const hostId = ctx.hostId\n if (!hostId) return { rows: [], next: null }\n const filter = readFormSubmissionsFilter(options?.filter)\n const size = pageSizeOf(options)\n // Which answers are ratings, read only when an answer is asked for.\n const ratings = fieldIds.some((fieldId) => fieldId.startsWith(FORM_SUBMISSION_ANSWER_PREFIX))\n ? formRatingQuestions(await readForms(hostId, filter.formId))\n : undefined\n if (options?.ids) {\n const ids = selectionIds(options.ids)\n const from = readOffsetCursor(cursor)\n const to = from + size\n const snapshots = await readByIds(hostId, ids.slice(from, to), filter)\n return {\n rows: snapshots.map((snapshot) =>\n formSubmissionTransferRow(snapshot.id, snapshot.data() ?? {}, fieldIds, ratings),\n ),\n next: to < ids.length ? String(to) : null,\n }\n }\n let query = newestFirst(filtered(hostId, filter))\n if (cursor !== null) {\n const after = readQueryCursor(cursor)\n query = query.startAfter(after.createdAt, after.id)\n }\n // One past the page, so the last page says it is the last.\n const snapshot = await query.limit(size + 1).get()\n const page = snapshot.docs.slice(0, size)\n const last = page[page.length - 1]\n return {\n rows: page.map((document) => formSubmissionTransferRow(document.id, document.data(), fieldIds, ratings)),\n next: snapshot.docs.length > size && last ? formSubmissionsCursor(last.get('createdAt'), last.id) : null,\n }\n }\n\n return {\n matchKeys: FORM_SUBMISSIONS_MATCH_KEYS,\n\n async fields(ctx) {\n const hostId = ctx.hostId\n if (!hostId) return formSubmissionsCatalog({ forms: [], sampledKeys: [], scoped: false })\n const { formId } = readFormSubmissionsFilter(ctx.filter)\n const [forms, sample] = await Promise.all([\n readForms(hostId, formId),\n newestFirst(filtered(hostId, formId === undefined ? {} : { formId }))\n .limit(FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS)\n .get(),\n ])\n return formSubmissionsCatalog({\n forms,\n sampledKeys: sampledAnswerKeys(sample.docs.map((document) => document.data())),\n scoped: formId !== undefined,\n })\n },\n\n async count(ctx, options) {\n const hostId = ctx.hostId\n if (!hostId) return 0\n const filter = readFormSubmissionsFilter(options.filter)\n if (options.ids) return (await readByIds(hostId, selectionIds(options.ids), filter)).length\n const counted = await filtered(hostId, filter).count().get()\n return counted.data().count\n },\n\n readPage,\n\n /**\n * What a submission holds now, by its Aglyn ID — the one key this\n * resource answers, since nothing ever imports into it.\n */\n async lookup(ctx, requests) {\n const byId = requests.filter((request) => request.fieldId === TRANSFER_ID_FIELD)\n const ids = byId.flatMap((request) => request.values)\n const found = ctx.hostId ? await readByIds(ctx.hostId, selectionIds(ids), {}) : []\n const records = new Map<string, Record<string, unknown>>()\n for (const document of found) {\n const data = document.data() ?? {}\n const answers = data['fields'] && typeof data['fields'] === 'object' ? Object.keys(data['fields']) : []\n records.set(\n document.id,\n formSubmissionTransferRow(document.id, data, [\n ...Object.keys(DETAIL_READERS),\n ...answers.map(formSubmissionAnswerFieldId),\n ]),\n )\n }\n const lookup = buildMatchLookup(\n [...records].map(([id, values]) => ({ id, values })),\n byId,\n )\n return { lookup, records }\n },\n }\n}\n"],"names":["FORMS_MAX_PER_HOST","TRANSFER_ID_FIELD","buildMatchLookup","transferIdField","FieldPath","Timestamp","FORM_SUBMISSIONS_MATCH_KEYS","SUBMISSIONS","FORMS","FORM_SUBMISSION_ANSWER_PREFIX","FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS","FORM_SUBMISSIONS_OTHER_ANSWERS_MAX","PAGE_ROWS_DEFAULT","PAGE_ROWS_MAX","READ_BY_ID_CHUNK","FORM_SUBMISSION_DETAILS_GROUP","id","label","FORM_SUBMISSION_OTHER_ANSWERS_GROUP","formAnswersGroupId","formId","formSubmissionAnswerFieldId","fieldName","formAnswerTransferType","fieldType","readFormSubmissionsFilter","value","undefined","Array","isArray","Error","filter","key","raw","Object","entries","trim","includes","read","matchesFilter","data","formSubmissionDetailFields","group","type","readOnly","system","aliases","description","formQuestionsOf","declared","displayName","name","fields","field","Boolean","answerField","decl","formSubmissionsCatalog","input","answers","groups","taken","Set","form","forms","scoped","added","has","add","push","others","sampledKeys","slice","length","standard","sampledAnswerKeys","rows","seen","Map","keys","entry","get","count","set","order","size","sort","a","b","map","textOrNull","isoOf","Number","isFinite","Date","toISOString","isNaN","getTime","toDate","call","DETAIL_READERS","_data","createdAt","formName","path","repliedAt","campaignIds","one","routing","rateDegraded","hostId","formRatingQuestions","RATING_TEXT","formSubmissionTransferRow","fieldIds","ratings","rated","row","fieldId","reader","startsWith","answer","test","formSubmissionsCursor","at","seconds","nanoseconds","readQueryCursor","cursor","found","exec","readOffsetCursor","pageSizeOf","options","asked","Math","floor","pageSize","max","min","selectionIds","ids","createFormSubmissionsTransferResource","deps","firestore","site","collection","doc","submissions","filtered","query","where","newestFirst","orderBy","documentId","readByIds","refs","snapshots","getAll","snapshot","exists","readForms","limit","docs","document","localeCompare","readPage","ctx","next","some","from","to","String","after","startAfter","page","last","matchKeys","sample","Promise","all","counted","lookup","requests","byId","request","flatMap","values","records"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,kBAAkB,QAGb,+BAA8B;AACrC,SACEC,iBAAiB,EACjBC,gBAAgB,EAChBC,eAAe,QAKV,6BAA4B;AAOnC,SAASC,SAAS,EAAEC,SAAS,QAAQ,2BAA0B;AAC/D,SAASC,2BAA2B,QAAQ,qCAAiC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAsD2C,GAE3C,MAAMC,cAAc;AACpB,MAAMC,QAAQ;AAEd,kFAAkF,GAClF,OAAO,MAAMC,gCAAgC,UAAS;AAEtD,uGAAuG,GACvG,OAAO,MAAMC,sCAAsC,IAAG;AAEtD;;;;CAIC,GACD,OAAO,MAAMC,qCAAqC,GAAE;AAEpD,MAAMC,oBAAoB;AAC1B,MAAMC,gBAAgB;AACtB,6DAA6D,GAC7D,MAAMC,mBAAmB;AAEzB,6DAA6D,GAC7D,OAAO,MAAMC,gCAAoD;IAAEC,IAAI;IAAcC,OAAO;AAAa,EAAC;AAC1G,iDAAiD,GACjD,OAAO,MAAMC,sCAA0D;IACrEF,IAAI;IACJC,OAAO;AACT,EAAC;AAED,yDAAyD,GACzD,OAAO,SAASE,mBAAmBC,MAAc;IAC/C,OAAO,CAAC,KAAK,EAAEA,QAAQ;AACzB;AAEA,6DAA6D,GAC7D,OAAO,SAASC,4BAA4BC,SAAiB;IAC3D,OAAO,GAAGb,gCAAgCa,WAAW;AACvD;AAEA,0DAA0D,GAC1D,OAAO,SAASC,uBAAuBC,SAAoC;IACzE,OAAQA;QACN,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT;YACE,OAAO;IACX;AACF;AAYA;;;;CAIC,GACD,OAAO,SAASC,0BAA0BC,KAAc;IACtD,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO,CAAC;IACnD,IAAI,OAAOD,UAAU,YAAYE,MAAMC,OAAO,CAACH,QAAQ;QACrD,MAAM,IAAII,MAAM;IAClB;IACA,MAAMC,SAAgC,CAAC;IACvC,KAAK,MAAM,CAACC,KAAKC,IAAI,IAAIC,OAAOC,OAAO,CAACT,OAAmC;QACzE,IAAIO,QAAQN,aAAaM,QAAQ,MAAM;QACvC,IAAID,QAAQ,UAAU;YACpB,MAAMZ,SAAS,OAAOa,QAAQ,WAAWA,IAAIG,IAAI,KAAK;YACtD,IAAI,CAAChB,UAAUA,OAAOiB,QAAQ,CAAC,MAAM,MAAM,IAAIP,MAAM;YACrDC,OAAOX,MAAM,GAAGA;QAClB,OAAO,IAAIY,QAAQ,QAAQ;YACzB,IAAIC,QAAQ,QAAQA,QAAQ,QAAQF,OAAOO,IAAI,GAAG;iBAC7C,IAAIL,QAAQ,SAASA,QAAQ,SAASF,OAAOO,IAAI,GAAG;iBACpD,MAAM,IAAIR,MAAM;QACvB,OAAO;YACL,MAAM,IAAIA,MAAM,CAAC,kDAAkD,EAAEE,IAAI,aAAa,CAAC;QACzF;IACF;IACA,OAAOD;AACT;AAEA,SAASQ,cAAcC,IAAuC,EAAET,MAA6B;IAC3F,IAAIA,OAAOX,MAAM,KAAKO,aAAaa,IAAI,CAAC,SAAS,KAAKT,OAAOX,MAAM,EAAE,OAAO;IAC5E,IAAIW,OAAOO,IAAI,KAAKX,aAAa,AAACa,IAAI,CAAC,OAAO,KAAK,SAAUT,OAAOO,IAAI,EAAE,OAAO;IACjF,OAAO;AACT;AAEA;;2CAE2C,GAE3C,oEAAoE,GACpE,OAAO,SAASG;IACd,MAAMC,QAAQ3B,8BAA8BC,EAAE;IAC9C,OAAO;QACL,aAAKb;YAAmBuC;;QACxB;YACE1B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRC,SAAS;gBAAC;gBAAa;gBAAW;aAAO;YACzCC,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRE,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRC,SAAS;gBAAC;aAAO;YACjBC,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRC,SAAS;gBAAC;gBAAQ;aAAO;YACzBC,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVG,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVG,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRE,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRE,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;YACRE,aAAa;QACf;QACA;YACE/B,IAAI;YACJC,OAAO;YACPyB;YACAC,MAAM;YACNC,UAAU;YACVC,QAAQ;QACV;KACD;AACH;AASA,SAASG,gBAAgBhC,EAAU,EAAEwB,IAAmD;IACtF,MAAMS,WAAWrB,MAAMC,OAAO,CAACW,wBAAAA,IAAM,CAAC,SAAS,IAAKA,wBAAAA,IAAM,CAAC,SAAS,GAAiB,EAAE;IACvF,MAAMU,cAAcV,wBAAAA,IAAM,CAAC,cAAc;IACzC,OAAO;QACLxB;QACAmC,MAAM,OAAOD,gBAAgB,YAAYA,YAAYd,IAAI,KAAKc,YAAYd,IAAI,KAAKpB;QACnFoC,QAAQH,SAASlB,MAAM,CACrB,CAACsB,QACCC,QAAQD,UACR,OAAOA,UAAU,YACjB,OAAO,AAACA,MAAwB/B,SAAS,KAAK,YAC9C,AAAC+B,MAAwB/B,SAAS,CAACc,IAAI,OAAO;IAEpD;AACF;AAEA,SAASmB,YAAYC,IAAmB,EAAEd,KAAa;IACrD,MAAMzB,QAAQ,OAAOuC,KAAKvC,KAAK,KAAK,YAAYuC,KAAKvC,KAAK,CAACmB,IAAI,KAAKoB,KAAKvC,KAAK,CAACmB,IAAI,KAAKoB,KAAKlC,SAAS;IACtG,OAAO;QACLN,IAAIK,4BAA4BmC,KAAKlC,SAAS;QAC9CL;QACAyB;QACAC,MAAMpB,uBAAuBiC,KAAKhC,SAAS;OACvCP,UAAUuC,KAAKlC,SAAS,GAAG;QAAEwB,SAAS;YAACU,KAAKlC,SAAS;SAAC;IAAC,IAAI,CAAC;AAEpE;AAEA;;;CAGC,GACD,OAAO,SAASmC,uBAAuBC,KAItC;IACC,MAAMC,UAA2B,EAAE;IACnC,MAAMC,SAA+B;QAAC7C;KAA8B;IACpE,MAAM8C,QAAQ,IAAIC;IAClB,KAAK,MAAMC,QAAQL,MAAMM,KAAK,CAAE;QAC9B,MAAMtB,QAA4B;YAChC1B,IAAIG,mBAAmB4C,KAAK/C,EAAE;YAC9BC,OAAOyC,MAAMO,MAAM,GAAG,YAAY,CAAC,SAAS,EAAEF,KAAKZ,IAAI,EAAE;QAC3D;QACA,IAAIe,QAAQ;QACZ,KAAK,MAAMV,QAAQO,KAAKX,MAAM,CAAE;YAC9B,IAAIS,MAAMM,GAAG,CAACX,KAAKlC,SAAS,GAAG;YAC/BuC,MAAMO,GAAG,CAACZ,KAAKlC,SAAS;YACxBqC,QAAQU,IAAI,CAACd,YAAYC,MAAMd,MAAM1B,EAAE;YACvCkD,QAAQ;QACV;QACA,IAAIA,OAAON,OAAOS,IAAI,CAAC3B;IACzB;IACA,MAAM4B,SAASZ,MAAMa,WAAW,CAACxC,MAAM,CAAC,CAACC,MAAQ,CAAC6B,MAAMM,GAAG,CAACnC,MAAMwC,KAAK,CAAC,GAAG7D;IAC3E,KAAK,MAAMqB,OAAOsC,OAAQ;QACxBX,QAAQU,IAAI,CAAC;YACXrD,IAAIK,4BAA4BW;YAChCf,OAAOe;YACPU,OAAOxB,oCAAoCF,EAAE;YAC7C2B,MAAM;YACNI,aAAaW,MAAMO,MAAM,GACrB,uFACA;QACN;IACF;IACA,IAAIK,OAAOG,MAAM,EAAEb,OAAOS,IAAI,CAACnD;IAC/B,OAAO;QAAEwD,UAAU;eAAIjC;eAAiCkB;SAAQ;QAAEC;IAAO;AAC3E;AAEA,yFAAyF,GACzF,OAAO,SAASe,kBAAkBC,IAAkE;IAClG,MAAMC,OAAO,IAAIC;IACjB,KAAK,MAAMtC,QAAQoC,KAAM;QACvB,MAAMxB,SAASZ,wBAAAA,IAAM,CAAC,SAAS;QAC/B,IAAI,CAACY,UAAU,OAAOA,WAAW,YAAYxB,MAAMC,OAAO,CAACuB,SAAS;QACpE,KAAK,MAAMpB,OAAOE,OAAO6C,IAAI,CAAC3B,QAAS;YACrC,IAAI,CAACpB,IAAII,IAAI,IAAI;YACjB,MAAM4C,QAAQH,KAAKI,GAAG,CAACjD;YACvB,IAAIgD,OAAOA,MAAME,KAAK,IAAI;iBACrBL,KAAKM,GAAG,CAACnD,KAAK;gBAAEkD,OAAO;gBAAGE,OAAOP,KAAKQ,IAAI;YAAC;QAClD;IACF;IACA,OAAO;WAAIR,KAAK1C,OAAO;KAAG,CACvBmD,IAAI,CAAC,CAAC,GAAGC,EAAE,EAAE,GAAGC,EAAE,GAAKA,EAAEN,KAAK,GAAGK,EAAEL,KAAK,IAAIK,EAAEH,KAAK,GAAGI,EAAEJ,KAAK,EAC7DK,GAAG,CAAC,CAAC,CAACzD,IAAI,GAAKA;AACpB;AAEA;;2CAE2C,GAE3C,SAAS0D,WAAWhE,KAAc;IAChC,OAAO,OAAOA,UAAU,YAAYA,UAAU,KAAKA,QAAQ;AAC7D;AAEA,qGAAqG,GACrG,SAASiE,MAAMjE,KAAc;IAC3B,IAAIA,UAAU,QAAQA,UAAUC,WAAW,OAAO;IAClD,IAAI,OAAOD,UAAU,UAAU,OAAOkE,OAAOC,QAAQ,CAACnE,SAAS,IAAIoE,KAAKpE,OAAOqE,WAAW,KAAK;IAC/F,IAAIrE,iBAAiBoE,MAAM,OAAOF,OAAOI,KAAK,CAACtE,MAAMuE,OAAO,MAAM,OAAOvE,MAAMqE,WAAW;IAC1F,MAAMG,SAAS,AAACxE,MAAkCwE,MAAM;IACxD,IAAI,OAAOA,WAAW,YAAY,OAAOP,MAAMO,OAAOC,IAAI,CAACzE;IAC3D,OAAO;AACT;AAEA,MAAM0E,iBAA6G;IACjH,CAACnG,kBAAkB,EAAE,CAACoG,OAAOrF,KAAOA;IACpCsF,WAAW,CAAC9D,OAASmD,MAAMnD,IAAI,CAAC,YAAY;IAC5CpB,QAAQ,CAACoB,OAASkD,WAAWlD,IAAI,CAAC,SAAS;IAC3C+D,UAAU,CAAC/D,OAASkD,WAAWlD,IAAI,CAAC,WAAW;IAC/CgE,MAAM,CAAChE,OAASkD,WAAWlD,IAAI,CAAC,OAAO;IACvCF,MAAM,CAACE,OAASA,IAAI,CAAC,OAAO,KAAK;IACjCiE,WAAW,CAACjE,OAASmD,MAAMnD,IAAI,CAAC,cAAc;IAC9CkE,aAAa,CAAClE,OACZZ,MAAMC,OAAO,CAACW,IAAI,CAAC,cAAc,IAC7B,AAACA,IAAI,CAAC,cAAc,CAAeT,MAAM,CAAC,CAAC4E,MAAuB,OAAOA,QAAQ,YAAYA,QAAQ,MACrG,EAAE;IACRC,SAAS,CAACpE,OACRA,IAAI,CAAC,UAAU,IAAI,OAAOA,IAAI,CAAC,UAAU,KAAK,YAAY,CAACZ,MAAMC,OAAO,CAACW,IAAI,CAAC,UAAU,IACpFA,IAAI,CAAC,UAAU,GACf;IACNqE,cAAc,CAACrE,OAASA,IAAI,CAAC,eAAe,KAAK;IACjDsE,QAAQ,CAACtE,OAASkD,WAAWlD,IAAI,CAAC,SAAS;AAC7C;AAKA,kDAAkD,GAClD,OAAO,SAASuE,oBAAoB/C,KAA+B;IACjE,OAAO,IAAIc,IACTd,MAAMyB,GAAG,CAAC,CAAC1B,OAAS;YAClBA,KAAK/C,EAAE;YACP,IAAI8C,IAAIC,KAAKX,MAAM,CAACrB,MAAM,CAAC,CAACsB,QAAUA,MAAM7B,SAAS,KAAK,UAAUiE,GAAG,CAAC,CAACpC,QAAUA,MAAM/B,SAAS;SACnG;AAEL;AAEA,MAAM0F,cAAc;AAEpB;;;;;;;CAOC,GACD,OAAO,SAASC,0BACdjG,EAAU,EACVwB,IAAuC,EACvC0E,QAA2B,EAC3BC,OAA6B;IAE7B,MAAMxD,UACJnB,IAAI,CAAC,SAAS,IAAI,OAAOA,IAAI,CAAC,SAAS,KAAK,YAAY,CAACZ,MAAMC,OAAO,CAACW,IAAI,CAAC,SAAS,IAChFA,IAAI,CAAC,SAAS,GACf,CAAC;IACP,MAAMpB,SAAS,OAAOoB,IAAI,CAAC,SAAS,KAAK,WAAWA,IAAI,CAAC,SAAS,GAAG;IACrE,MAAM4E,QAAQD,2BAAAA,QAASlC,GAAG,CAAC7D;IAC3B,MAAMiG,MAA+B,CAAC;IACtC,KAAK,MAAMC,WAAWJ,SAAU;QAC9B,MAAMK,SAASnB,cAAc,CAACkB,QAAQ;QACtC,IAAIC,QAAQF,GAAG,CAACC,QAAQ,GAAGC,OAAO/E,MAAMxB;aACnC,IAAIsG,QAAQE,UAAU,CAAC/G,gCAAgC;YAC1D,MAAM0C,OAAOmE,QAAQ9C,KAAK,CAAC/D,8BAA8BgE,MAAM;YAC/D,MAAMgD,SAAS9D,OAAO,CAACR,KAAK;YAC5BkE,GAAG,CAACC,QAAQ,GACVG,WAAW9F,YACP,OACAyF,CAAAA,yBAAAA,MAAOjD,GAAG,CAAChB,UAAS,OAAOsE,WAAW,YAAYT,YAAYU,IAAI,CAACD,OAAOrF,IAAI,MAC5EwD,OAAO6B,OAAOrF,IAAI,MAClBqF;QACV,OAAOJ,GAAG,CAACC,QAAQ,GAAG;IACxB;IACA,OAAOD;AACT;AAEA;;2CAE2C,GAE3C;;;;CAIC,GACD,OAAO,SAASM,sBAAsBrB,SAAkB,EAAEtF,EAAU;IAClE,MAAM4G,KAAKtB;IACX,IAAI,QAAOsB,sBAAAA,GAAIC,OAAO,MAAK,YAAY,QAAOD,sBAAAA,GAAIE,WAAW,MAAK,UAAU;QAC1E,MAAM,IAAIhG,MAAM,CAAC,gBAAgB,EAAEd,GAAG,qCAAqC,CAAC;IAC9E;IACA,OAAO,GAAG4G,GAAGC,OAAO,CAAC,CAAC,EAAED,GAAGE,WAAW,CAAC,CAAC,EAAE9G,IAAI;AAChD;AAEA,SAAS+G,gBAAgBC,MAAc;IACrC,MAAMC,QAAQ,yBAAyBC,IAAI,CAACF;IAC5C,IAAI,CAACC,OAAO,MAAM,IAAInG,MAAM;IAC5B,OAAO;QAAEwE,WAAW,IAAIjG,UAAUuF,OAAOqC,KAAK,CAAC,EAAE,GAAGrC,OAAOqC,KAAK,CAAC,EAAE;QAAIjH,IAAIiH,KAAK,CAAC,EAAE;IAAW;AAChG;AAEA,SAASE,iBAAiBH,MAAqB;IAC7C,IAAIA,WAAW,MAAM,OAAO;IAC5B,IAAI,CAAC,QAAQN,IAAI,CAACM,SAAS,MAAM,IAAIlG,MAAM;IAC3C,OAAO8D,OAAOoC;AAChB;AAeA,SAASI,WAAWC,OAAwC;IAC1D,MAAMC,QAAQC,KAAKC,KAAK,CAAC5C,OAAOyC,2BAAAA,QAASI,QAAQ,KAAK7H;IACtD,OAAO2H,KAAKG,GAAG,CAAC,GAAGH,KAAKI,GAAG,CAAC9H,eAAeyH;AAC7C;AAEA,2DAA2D,GAC3D,SAASM,aAAaC,GAAsB;IAC1C,OAAO;WAAI,IAAI/E,IAAI+E,IAAI9G,MAAM,CAAC,CAACf,KAAO,OAAOA,OAAO,YAAYA,OAAO,MAAM,CAACA,GAAGqB,QAAQ,CAAC;KAAO;AACnG;AAEA;;;CAGC,GACD,OAAO,SAASyG,sCACdC,IAAiC;IAEjC,MAAM,EAAEC,SAAS,EAAE,GAAGD;IACtB,MAAME,OAAO,CAACnC,SAAmBkC,UAAUE,UAAU,CAAC,SAASC,GAAG,CAACrC;IACnE,MAAMsC,cAAc,CAACtC,SAAmBmC,KAAKnC,QAAQoC,UAAU,CAAC3I;IAEhE,gFAAgF,GAChF,MAAM8I,WAAW,CAACvC,QAAgB/E;QAChC,IAAIuH,QAAiCF,YAAYtC;QACjD,IAAI/E,OAAOX,MAAM,KAAKO,WAAW2H,QAAQA,MAAMC,KAAK,CAAC,UAAU,MAAMxH,OAAOX,MAAM;QAClF,IAAIW,OAAOO,IAAI,KAAKX,WAAW2H,QAAQA,MAAMC,KAAK,CAAC,QAAQ,MAAMxH,OAAOO,IAAI;QAC5E,OAAOgH;IACT;IACA,MAAME,cAAc,CAACF,QACnBA,MAAMG,OAAO,CAAC,aAAa,QAAQA,OAAO,CAACrJ,UAAUsJ,UAAU,IAAI;IAErE,kFAAkF,GAClF,MAAMC,YAAY,OAChB7C,QACA+B,KACA9G;QAEA,MAAMkG,QAA8C,EAAE;QACtD,IAAK,IAAIL,KAAK,GAAGA,KAAKiB,IAAIpE,MAAM,EAAEmD,MAAM9G,iBAAkB;YACxD,MAAM8I,OAAOf,IAAIrE,KAAK,CAACoD,IAAIA,KAAK9G,kBAAkB2E,GAAG,CAAC,CAACzE,KAAOoI,YAAYtC,QAAQqC,GAAG,CAACnI;YACtF,IAAI,CAAC4I,KAAKnF,MAAM,EAAE;YAClB,MAAMoF,YAAY,MAAMb,UAAUc,MAAM,IAAIF;YAC5C,KAAK,MAAMG,YAAYF,UAAW;gBAChC,MAAMrH,OAAOuH,SAASvH,IAAI;gBAC1B,IAAIuH,SAASC,MAAM,IAAIxH,QAAQD,cAAcC,MAAMT,SAASkG,MAAM5D,IAAI,CAAC0F;YACzE;QACF;QACA,OAAO9B;IACT;IAEA,MAAMgC,YAAY,OAAOnD,QAAgB1F;QACvC,IAAIA,WAAWO,WAAW;YACxB,MAAMoI,WAAW,MAAMd,KAAKnC,QAAQoC,UAAU,CAAC1I,OAAO2I,GAAG,CAAC/H,QAAQ6D,GAAG;YACrE,OAAO8E,SAASC,MAAM,GAAG;gBAAChH,gBAAgB+G,SAAS/I,EAAE,EAAE+I,SAASvH,IAAI;aAAI,GAAG,EAAE;QAC/E;QACA,MAAMuH,WAAW,MAAMd,KAAKnC,QAAQoC,UAAU,CAAC1I,OAAO0J,KAAK,CAAClK,oBAAoBiF,GAAG;QACnF,OAAO8E,SAASI,IAAI,CACjB1E,GAAG,CAAC,CAAC2E,WAAapH,gBAAgBoH,SAASpJ,EAAE,EAAEoJ,SAAS5H,IAAI,KAC5D8C,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAEpC,IAAI,CAACkH,aAAa,CAAC7E,EAAErC,IAAI,KAAKoC,EAAEvE,EAAE,CAACqJ,aAAa,CAAC7E,EAAExE,EAAE;IAC3E;IAEA,MAAMsJ,WAAW,OACfC,KACAvC,QACAd,UACAmB;QAEA,MAAMvB,SAASyD,IAAIzD,MAAM;QACzB,IAAI,CAACA,QAAQ,OAAO;YAAElC,MAAM,EAAE;YAAE4F,MAAM;QAAK;QAC3C,MAAMzI,SAASN,0BAA0B4G,2BAAAA,QAAStG,MAAM;QACxD,MAAMsD,OAAO+C,WAAWC;QACxB,oEAAoE;QACpE,MAAMlB,UAAUD,SAASuD,IAAI,CAAC,CAACnD,UAAYA,QAAQE,UAAU,CAAC/G,kCAC1DsG,oBAAoB,MAAMkD,UAAUnD,QAAQ/E,OAAOX,MAAM,KACzDO;QACJ,IAAI0G,2BAAAA,QAASQ,GAAG,EAAE;YAChB,MAAMA,MAAMD,aAAaP,QAAQQ,GAAG;YACpC,MAAM6B,OAAOvC,iBAAiBH;YAC9B,MAAM2C,KAAKD,OAAOrF;YAClB,MAAMwE,YAAY,MAAMF,UAAU7C,QAAQ+B,IAAIrE,KAAK,CAACkG,MAAMC,KAAK5I;YAC/D,OAAO;gBACL6C,MAAMiF,UAAUpE,GAAG,CAAC,CAACsE;wBACoBA;2BAAvC9C,0BAA0B8C,SAAS/I,EAAE,GAAE+I,iBAAAA,SAASvH,IAAI,cAAbuH,iBAAmB,CAAC,GAAG7C,UAAUC;;gBAE1EqD,MAAMG,KAAK9B,IAAIpE,MAAM,GAAGmG,OAAOD,MAAM;YACvC;QACF;QACA,IAAIrB,QAAQE,YAAYH,SAASvC,QAAQ/E;QACzC,IAAIiG,WAAW,MAAM;YACnB,MAAM6C,QAAQ9C,gBAAgBC;YAC9BsB,QAAQA,MAAMwB,UAAU,CAACD,MAAMvE,SAAS,EAAEuE,MAAM7J,EAAE;QACpD;QACA,2DAA2D;QAC3D,MAAM+I,WAAW,MAAMT,MAAMY,KAAK,CAAC7E,OAAO,GAAGJ,GAAG;QAChD,MAAM8F,OAAOhB,SAASI,IAAI,CAAC3F,KAAK,CAAC,GAAGa;QACpC,MAAM2F,OAAOD,IAAI,CAACA,KAAKtG,MAAM,GAAG,EAAE;QAClC,OAAO;YACLG,MAAMmG,KAAKtF,GAAG,CAAC,CAAC2E,WAAanD,0BAA0BmD,SAASpJ,EAAE,EAAEoJ,SAAS5H,IAAI,IAAI0E,UAAUC;YAC/FqD,MAAMT,SAASI,IAAI,CAAC1F,MAAM,GAAGY,QAAQ2F,OAAOrD,sBAAsBqD,KAAK/F,GAAG,CAAC,cAAc+F,KAAKhK,EAAE,IAAI;QACtG;IACF;IAEA,OAAO;QACLiK,WAAW3K;QAEX,MAAM8C,QAAOmH,GAAG;YACd,MAAMzD,SAASyD,IAAIzD,MAAM;YACzB,IAAI,CAACA,QAAQ,OAAOrD,uBAAuB;gBAAEO,OAAO,EAAE;gBAAEO,aAAa,EAAE;gBAAEN,QAAQ;YAAM;YACvF,MAAM,EAAE7C,MAAM,EAAE,GAAGK,0BAA0B8I,IAAIxI,MAAM;YACvD,MAAM,CAACiC,OAAOkH,OAAO,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBACxCnB,UAAUnD,QAAQ1F;gBAClBoI,YAAYH,SAASvC,QAAQ1F,WAAWO,YAAY,CAAC,IAAI;oBAAEP;gBAAO,IAC/D8I,KAAK,CAACxJ,qCACNuE,GAAG;aACP;YACD,OAAOxB,uBAAuB;gBAC5BO;gBACAO,aAAaI,kBAAkBuG,OAAOf,IAAI,CAAC1E,GAAG,CAAC,CAAC2E,WAAaA,SAAS5H,IAAI;gBAC1EyB,QAAQ7C,WAAWO;YACrB;QACF;QAEA,MAAMuD,OAAMqF,GAAG,EAAElC,OAAO;YACtB,MAAMvB,SAASyD,IAAIzD,MAAM;YACzB,IAAI,CAACA,QAAQ,OAAO;YACpB,MAAM/E,SAASN,0BAA0B4G,QAAQtG,MAAM;YACvD,IAAIsG,QAAQQ,GAAG,EAAE,OAAO,AAAC,CAAA,MAAMc,UAAU7C,QAAQ8B,aAAaP,QAAQQ,GAAG,GAAG9G,OAAM,EAAG0C,MAAM;YAC3F,MAAM4G,UAAU,MAAMhC,SAASvC,QAAQ/E,QAAQmD,KAAK,GAAGD,GAAG;YAC1D,OAAOoG,QAAQ7I,IAAI,GAAG0C,KAAK;QAC7B;QAEAoF;QAEA;;;KAGC,GACD,MAAMgB,QAAOf,GAAG,EAAEgB,QAAQ;YACxB,MAAMC,OAAOD,SAASxJ,MAAM,CAAC,CAAC0J,UAAYA,QAAQnE,OAAO,KAAKrH;YAC9D,MAAM4I,MAAM2C,KAAKE,OAAO,CAAC,CAACD,UAAYA,QAAQE,MAAM;YACpD,MAAM1D,QAAQsC,IAAIzD,MAAM,GAAG,MAAM6C,UAAUY,IAAIzD,MAAM,EAAE8B,aAAaC,MAAM,CAAC,KAAK,EAAE;YAClF,MAAM+C,UAAU,IAAI9G;YACpB,KAAK,MAAMsF,YAAYnC,MAAO;oBACfmC;gBAAb,MAAM5H,QAAO4H,iBAAAA,SAAS5H,IAAI,cAAb4H,iBAAmB,CAAC;gBACjC,MAAMzG,UAAUnB,IAAI,CAAC,SAAS,IAAI,OAAOA,IAAI,CAAC,SAAS,KAAK,WAAWN,OAAO6C,IAAI,CAACvC,IAAI,CAAC,SAAS,IAAI,EAAE;gBACvGoJ,QAAQzG,GAAG,CACTiF,SAASpJ,EAAE,EACXiG,0BAA0BmD,SAASpJ,EAAE,EAAEwB,MAAM;uBACxCN,OAAO6C,IAAI,CAACqB;uBACZzC,QAAQ8B,GAAG,CAACpE;iBAChB;YAEL;YACA,MAAMiK,SAASpL,iBACb;mBAAI0L;aAAQ,CAACnG,GAAG,CAAC,CAAC,CAACzE,IAAI2K,OAAO,GAAM,CAAA;oBAAE3K;oBAAI2K;gBAAO,CAAA,IACjDH;YAEF,OAAO;gBAAEF;gBAAQM;YAAQ;QAC3B;IACF;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/transfer/form-submissions-transfer.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n FORMS_MAX_PER_HOST,\n type FormFieldDecl,\n type FormFieldType,\n} from '@aglyn/aglyn/app-utils/forms'\nimport {\n TRANSFER_ID_FIELD,\n buildMatchLookup,\n transferIdField,\n type TransferCatalogInput,\n type TransferField,\n type TransferFieldGroup,\n type TransferFieldType,\n} from '@aglyn/aglyn/data-transfer'\nimport type {\n TransferReadOptions,\n TransferRecordsHooks,\n TransferReadPage,\n TransferResourceContext,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { FieldPath, Timestamp } from 'firebase-admin/firestore'\nimport { FORM_SUBMISSIONS_MATCH_KEYS } from './form-submissions-transfer-key'\n\n/*==========================================\n * A SITE'S FORM SUBMISSIONS, AS AN EXPORT.\n *\n * `hosts/{hostId}/formSubmissions/{id}`, written only by this plugin's door\n * (`server/form-submit.ts`): the answers a visitor sent, keyed by the form\n * field's `fieldName`, with when, from which page and through which form.\n * The record as a reader may rely on it is `model/form-submission-record.ts`'s;\n * this module reads it the same way.\n *\n * ## Export only\n *\n * The declaration carries `exportOnly`: a submission is what a visitor sent\n * through a published form, and a file cannot have sent one. So the resource\n * answers the catalog, the reads, the count and a lookup by id, and registers\n * no `apply` or `revert` — the upload route refuses it before a byte is\n * stored.\n *\n * ## The columns follow the form\n *\n * A submission's answers have no fixed shape: each form asks its own\n * questions, and a form edited since asks different ones than its older\n * submissions answered. So the catalog is read from the site's forms, and\n * from the list filter the dialog was opened on (`ctx.filter`):\n *\n * - one form (`{ formId }`): that form's questions, each `answer:<fieldName>`,\n * labeled with the question and typed from the field; then \"Other\n * answers\" — keys its most recent submissions carry and today's form no\n * longer declares, so an answer to a removed question is still exported.\n * - the whole site: every form's questions, each form its own group. A\n * `fieldName` two forms share is one column (the first form's label), so\n * an \"email\" asked by every form lands in one place.\n *\n * The submission's own details — the Aglyn ID, when it arrived, the form, the\n * page, read, replied, campaigns, where it was filed — are read-only, and the\n * platform-written ones are `system`.\n *\n * ## The reads are queries, never a loaded window\n *\n * `{ formId }` and `{ read }` are equalities on the stored row, ordered by\n * `createdAt` newest first with the document id breaking ties, so a page\n * boundary between two submissions of the same instant neither repeats nor\n * skips one. `formId ↑ createdAt ↓` and `read ↑ createdAt ↓` are the\n * collection's existing indexes (the console's per-form list and the Inbox's\n * Read filter); no filter rides the single-field `createdAt` index. Both at\n * once needs `formId ↑ read ↑ createdAt ↓`, which the export dialog never\n * asks for today.\n *\n * ## Scope\n *\n * A host resource: the export route admits a reader only on the site, with\n * `data.manage` there. Submissions carry no `visibleTo`, so `scopeTokens` —\n * a site-scoped collaborator's tokens — narrows nothing here: a collaborator\n * the route let onto this site may read this site's submissions, as the\n * Inbox lets them.\n *=========================================*/\n\nconst SUBMISSIONS = 'formSubmissions'\nconst FORMS = 'forms'\n\n/** The prefix of an answer's field id; the rest is the form field's `fieldName`. */\nexport const FORM_SUBMISSION_ANSWER_PREFIX = 'answer:'\n\n/** How many of the most recent submissions the catalog reads for answers today's forms no longer ask. */\nexport const FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS = 200\n\n/**\n * The most \"other answers\" columns the catalog offers. A submission's keys\n * come from the visitor's request (up to twenty, each at most 64 characters),\n * so a sample can hold keys no form ever asked; the most frequent are kept.\n */\nexport const FORM_SUBMISSIONS_OTHER_ANSWERS_MAX = 50\n\nconst PAGE_ROWS_DEFAULT = 200\nconst PAGE_ROWS_MAX = 500\n/** Documents one `getAll` reads at a time, for a selection. */\nconst READ_BY_ID_CHUNK = 300\n\n/** The group the submission's own details are listed under. */\nexport const FORM_SUBMISSION_DETAILS_GROUP: TransferFieldGroup = { id: 'submission', label: 'Submission' }\n/** The group of answers no form declares today. */\nexport const FORM_SUBMISSION_OTHER_ANSWERS_GROUP: TransferFieldGroup = {\n id: 'otherAnswers',\n label: 'Other answers',\n}\n\n/** A form's questions are listed under `form:<formId>`. */\nexport function formAnswersGroupId(formId: string): string {\n return `form:${formId}`\n}\n\n/** The field id an answer to `fieldName` is exported under. */\nexport function formSubmissionAnswerFieldId(fieldName: string): string {\n return `${FORM_SUBMISSION_ANSWER_PREFIX}${fieldName}`\n}\n\n/** What an answer is, from the form field that asked it. */\nexport function formAnswerTransferType(fieldType: FormFieldType | undefined): TransferFieldType {\n switch (fieldType) {\n case 'email':\n return 'email'\n case 'textarea':\n return 'longText'\n case 'rating':\n return 'integer'\n case 'date':\n return 'date'\n default:\n return 'text'\n }\n}\n\n/*==========================================\n * THE FILTER\n *=========================================*/\n\n/** The list filter, in this resource's terms: one form, and read or unread. */\nexport interface FormSubmissionsFilter {\n formId?: string\n read?: boolean\n}\n\n/**\n * The filter a dialog or a route handed over, or why it cannot be used.\n * Refused rather than ignored: a filter dropped without a word exports more\n * than the person asked for.\n */\nexport function readFormSubmissionsFilter(value: unknown): FormSubmissionsFilter {\n if (value === null || value === undefined) return {}\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('A form submissions filter is an object naming formId and read.')\n }\n const filter: FormSubmissionsFilter = {}\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n if (raw === undefined || raw === null) continue\n if (key === 'formId') {\n const formId = typeof raw === 'string' ? raw.trim() : ''\n if (!formId || formId.includes('/')) throw new Error('The form submissions filter names no usable form.')\n filter.formId = formId\n } else if (key === 'read') {\n if (raw === true || raw === 'true') filter.read = true\n else if (raw === false || raw === 'false') filter.read = false\n else throw new Error('The form submissions filter says read as true or false.')\n } else {\n throw new Error(`A form submissions filter names formId and read; \"${key}\" is not one.`)\n }\n }\n return filter\n}\n\nfunction matchesFilter(data: Readonly<Record<string, unknown>>, filter: FormSubmissionsFilter): boolean {\n if (filter.formId !== undefined && data['formId'] !== filter.formId) return false\n if (filter.read !== undefined && (data['read'] === true) !== filter.read) return false\n return true\n}\n\n/*==========================================\n * THE CATALOG\n *=========================================*/\n\n/** The submission's own details, in the order a person reads them. */\nexport function formSubmissionDetailFields(): TransferField[] {\n const group = FORM_SUBMISSION_DETAILS_GROUP.id\n return [\n { ...transferIdField(), group },\n {\n id: 'createdAt',\n label: 'Submitted at',\n group,\n type: 'datetime',\n readOnly: true,\n system: true,\n aliases: ['submitted', 'created', 'date'],\n description: 'When the submission arrived, by the server’s clock.',\n },\n {\n id: 'formId',\n label: 'Form ID',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n description: 'The form it was sent through; blank on submissions older than the form itself.',\n },\n {\n id: 'formName',\n label: 'Form name',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n aliases: ['form'],\n description: 'What the form was called when the submission arrived.',\n },\n {\n id: 'path',\n label: 'Page path',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n aliases: ['page', 'path'],\n description: 'The page of the site it was sent from.',\n },\n {\n id: 'read',\n label: 'Read',\n group,\n type: 'boolean',\n readOnly: true,\n description: 'Whether someone has marked it read in the Inbox.',\n },\n {\n id: 'repliedAt',\n label: 'Replied at',\n group,\n type: 'datetime',\n readOnly: true,\n description: 'When it was last answered from the Inbox.',\n },\n {\n id: 'campaignIds',\n label: 'Campaign IDs',\n group,\n type: 'tags',\n readOnly: true,\n system: true,\n description: 'The campaigns the form was filed under when the submission arrived.',\n },\n {\n id: 'routing',\n label: 'Where it was filed',\n group,\n type: 'json',\n readOnly: true,\n system: true,\n description: 'Where the submission was also filed beyond the Inbox, such as a dataset, or why it was not.',\n },\n {\n id: 'rateDegraded',\n label: 'Arrived while rate limits were unchecked',\n group,\n type: 'boolean',\n readOnly: true,\n system: true,\n description: 'Yes when it arrived during a moment the site’s submission rate limit could not be checked.',\n },\n {\n id: 'hostId',\n label: 'Site ID',\n group,\n type: 'text',\n readOnly: true,\n system: true,\n },\n ]\n}\n\n/** One form as the catalog reads it: its id, its name, and the questions it declares. */\nexport interface FormQuestions {\n id: string\n name: string\n fields: readonly FormFieldDecl[]\n}\n\nfunction formQuestionsOf(id: string, data: Readonly<Record<string, unknown>> | undefined): FormQuestions {\n const declared = Array.isArray(data?.['fields']) ? (data?.['fields'] as unknown[]) : []\n const displayName = data?.['displayName']\n return {\n id,\n name: typeof displayName === 'string' && displayName.trim() ? displayName.trim() : id,\n fields: declared.filter(\n (field): field is FormFieldDecl =>\n Boolean(field) &&\n typeof field === 'object' &&\n typeof (field as FormFieldDecl).fieldName === 'string' &&\n (field as FormFieldDecl).fieldName.trim() !== '',\n ),\n }\n}\n\nfunction answerField(decl: FormFieldDecl, group: string): TransferField {\n const label = typeof decl.label === 'string' && decl.label.trim() ? decl.label.trim() : decl.fieldName\n return {\n id: formSubmissionAnswerFieldId(decl.fieldName),\n label,\n group,\n type: formAnswerTransferType(decl.fieldType),\n ...(label !== decl.fieldName ? { aliases: [decl.fieldName] } : {}),\n }\n}\n\n/**\n * The catalog, from the forms read and the answer keys sampled (see the\n * block header). `scoped` is true when the dialog was opened on one form.\n */\nexport function formSubmissionsCatalog(input: {\n forms: readonly FormQuestions[]\n sampledKeys: readonly string[]\n scoped: boolean\n}): TransferCatalogInput {\n const answers: TransferField[] = []\n const groups: TransferFieldGroup[] = [FORM_SUBMISSION_DETAILS_GROUP]\n const taken = new Set<string>()\n for (const form of input.forms) {\n const group: TransferFieldGroup = {\n id: formAnswersGroupId(form.id),\n label: input.scoped ? 'Answers' : `Answers: ${form.name}`,\n }\n let added = false\n for (const decl of form.fields) {\n if (taken.has(decl.fieldName)) continue\n taken.add(decl.fieldName)\n answers.push(answerField(decl, group.id))\n added = true\n }\n if (added) groups.push(group)\n }\n const others = input.sampledKeys.filter((key) => !taken.has(key)).slice(0, FORM_SUBMISSIONS_OTHER_ANSWERS_MAX)\n for (const key of others) {\n answers.push({\n id: formSubmissionAnswerFieldId(key),\n label: key,\n group: FORM_SUBMISSION_OTHER_ANSWERS_GROUP.id,\n type: 'text',\n description: input.scoped\n ? 'An answer earlier submissions to this form carry that the form no longer asks for.'\n : 'An answer earlier submissions carry that no form on this site asks for today.',\n })\n }\n if (others.length) groups.push(FORM_SUBMISSION_OTHER_ANSWERS_GROUP)\n return { standard: [...formSubmissionDetailFields(), ...answers], groups }\n}\n\n/** The answer keys the sampled submissions carry, most frequent first, then first seen. */\nexport function sampledAnswerKeys(rows: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>): string[] {\n const seen = new Map<string, { count: number; order: number }>()\n for (const data of rows) {\n const fields = data?.['fields']\n if (!fields || typeof fields !== 'object' || Array.isArray(fields)) continue\n for (const key of Object.keys(fields)) {\n if (!key.trim()) continue\n const entry = seen.get(key)\n if (entry) entry.count += 1\n else seen.set(key, { count: 1, order: seen.size })\n }\n }\n return [...seen.entries()]\n .sort(([, a], [, b]) => b.count - a.count || a.order - b.order)\n .map(([key]) => key)\n}\n\n/*==========================================\n * ONE ROW\n *=========================================*/\n\nfunction textOrNull(value: unknown): string | null {\n return typeof value === 'string' && value !== '' ? value : null\n}\n\n/** A stored instant as ISO text, so a CSV reads it: a Firestore Timestamp, a Date, or milliseconds. */\nfunction isoOf(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'number') return Number.isFinite(value) ? new Date(value).toISOString() : null\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString()\n const toDate = (value as { toDate?: () => Date }).toDate\n if (typeof toDate === 'function') return isoOf(toDate.call(value))\n return null\n}\n\nconst DETAIL_READERS: Readonly<Record<string, (data: Readonly<Record<string, unknown>>, id: string) => unknown>> = {\n [TRANSFER_ID_FIELD]: (_data, id) => id,\n createdAt: (data) => isoOf(data['createdAt']),\n formId: (data) => textOrNull(data['formId']),\n formName: (data) => textOrNull(data['formName']),\n path: (data) => textOrNull(data['path']),\n read: (data) => data['read'] === true,\n repliedAt: (data) => isoOf(data['repliedAtMs']),\n campaignIds: (data) =>\n Array.isArray(data['campaignIds'])\n ? (data['campaignIds'] as unknown[]).filter((one): one is string => typeof one === 'string' && one !== '')\n : [],\n routing: (data) =>\n data['routing'] && typeof data['routing'] === 'object' && !Array.isArray(data['routing'])\n ? data['routing']\n : null,\n rateDegraded: (data) => data['rateDegraded'] === true,\n hostId: (data) => textOrNull(data['hostId']),\n}\n\n/** Form id → the names of its rating questions, whose answers are numbers. */\nexport type FormRatingQuestions = ReadonlyMap<string, ReadonlySet<string>>\n\n/** The rating questions of each form (AGL-3548). */\nexport function formRatingQuestions(forms: readonly FormQuestions[]): Map<string, Set<string>> {\n return new Map(\n forms.map((form) => [\n form.id,\n new Set(form.fields.filter((field) => field.fieldType === 'rating').map((field) => field.fieldName)),\n ]),\n )\n}\n\nconst RATING_TEXT = /^-?\\d+(?:\\.\\d+)?$/\n\n/**\n * One submission as an export row: only `fieldIds`, keyed by field id. An\n * answer is the value as the visitor sent it — but an answer to a rating\n * question of the submission's form (`ratings`) that reads as a number is\n * that number, so a JSON file holds `4` and a CSV writes it as one, not as\n * text. A question they did not answer, or a field id this resource does\n * not know, is `null`.\n */\nexport function formSubmissionTransferRow(\n id: string,\n data: Readonly<Record<string, unknown>>,\n fieldIds: readonly string[],\n ratings?: FormRatingQuestions,\n): Record<string, unknown> {\n const answers =\n data['fields'] && typeof data['fields'] === 'object' && !Array.isArray(data['fields'])\n ? (data['fields'] as Readonly<Record<string, unknown>>)\n : {}\n const formId = typeof data['formId'] === 'string' ? data['formId'] : ''\n const rated = ratings?.get(formId)\n const row: Record<string, unknown> = {}\n for (const fieldId of fieldIds) {\n const reader = DETAIL_READERS[fieldId]\n if (reader) row[fieldId] = reader(data, id)\n else if (fieldId.startsWith(FORM_SUBMISSION_ANSWER_PREFIX)) {\n const name = fieldId.slice(FORM_SUBMISSION_ANSWER_PREFIX.length)\n const answer = answers[name]\n row[fieldId] =\n answer === undefined\n ? null\n : rated?.has(name) && typeof answer === 'string' && RATING_TEXT.test(answer.trim())\n ? Number(answer.trim())\n : answer\n } else row[fieldId] = null\n }\n return row\n}\n\n/*==========================================\n * THE CURSOR\n *=========================================*/\n\n/**\n * Where the next page of a query starts: the last row's `createdAt` (seconds\n * and nanoseconds, so two submissions a microsecond apart stay apart) and\n * its id. A selection's cursor is the offset into the ids.\n */\nexport function formSubmissionsCursor(createdAt: unknown, id: string): string {\n const at = createdAt as { seconds?: unknown; nanoseconds?: unknown } | null | undefined\n if (typeof at?.seconds !== 'number' || typeof at?.nanoseconds !== 'number') {\n throw new Error(`Form submission ${id} has no submission time to page from.`)\n }\n return `${at.seconds}:${at.nanoseconds}:${id}`\n}\n\nfunction readQueryCursor(cursor: string): { createdAt: Timestamp; id: string } {\n const found = /^(-?\\d+):(\\d+):([^]+)$/.exec(cursor)\n if (!found) throw new Error('The export cursor could not be read.')\n return { createdAt: new Timestamp(Number(found[1]), Number(found[2])), id: found[3] as string }\n}\n\nfunction readOffsetCursor(cursor: string | null): number {\n if (cursor === null) return 0\n if (!/^\\d+$/.test(cursor)) throw new Error('The export cursor could not be read.')\n return Number(cursor)\n}\n\n/*==========================================\n * THE RESOURCE\n *=========================================*/\n\nexport interface FormSubmissionsTransferDeps {\n firestore: FirebaseFirestore.Firestore\n}\n\n/** The hooks an export-only records resource answers with: no apply or revert. */\nexport type FormSubmissionsTransferResource = Required<\n Pick<TransferRecordsHooks, 'matchKeys' | 'fields' | 'count' | 'readPage' | 'lookup'>\n>\n\nfunction pageSizeOf(options: TransferReadOptions | undefined): number {\n const asked = Math.floor(Number(options?.pageSize) || PAGE_ROWS_DEFAULT)\n return Math.max(1, Math.min(PAGE_ROWS_MAX, asked))\n}\n\n/** The ids of a selection, each once, in the order given. */\nfunction selectionIds(ids: readonly string[]): string[] {\n return [...new Set(ids.filter((id) => typeof id === 'string' && id !== '' && !id.includes('/')))]\n}\n\n/**\n * The form submissions resource's server half (see the block header).\n * Firestore is handed in, so a spec runs it over an in-memory one.\n */\nexport function createFormSubmissionsTransferResource(\n deps: FormSubmissionsTransferDeps,\n): FormSubmissionsTransferResource {\n const { firestore } = deps\n const site = (hostId: string) => firestore.collection('hosts').doc(hostId)\n const submissions = (hostId: string) => site(hostId).collection(SUBMISSIONS)\n\n /** The filter as Firestore equalities, over the collection's existing indexes. */\n const filtered = (hostId: string, filter: FormSubmissionsFilter): FirebaseFirestore.Query => {\n let query: FirebaseFirestore.Query = submissions(hostId)\n if (filter.formId !== undefined) query = query.where('formId', '==', filter.formId)\n if (filter.read !== undefined) query = query.where('read', '==', filter.read)\n return query\n }\n const newestFirst = (query: FirebaseFirestore.Query): FirebaseFirestore.Query =>\n query.orderBy('createdAt', 'desc').orderBy(FieldPath.documentId(), 'desc')\n\n /** The selection's documents that exist and keep the filter, in the order given. */\n const readByIds = async (\n hostId: string,\n ids: readonly string[],\n filter: FormSubmissionsFilter,\n ): Promise<FirebaseFirestore.DocumentSnapshot[]> => {\n const found: FirebaseFirestore.DocumentSnapshot[] = []\n for (let at = 0; at < ids.length; at += READ_BY_ID_CHUNK) {\n const refs = ids.slice(at, at + READ_BY_ID_CHUNK).map((id) => submissions(hostId).doc(id))\n if (!refs.length) continue\n const snapshots = await firestore.getAll(...refs)\n for (const snapshot of snapshots) {\n const data = snapshot.data()\n if (snapshot.exists && data && matchesFilter(data, filter)) found.push(snapshot)\n }\n }\n return found\n }\n\n const readForms = async (hostId: string, formId: string | undefined): Promise<FormQuestions[]> => {\n if (formId !== undefined) {\n const snapshot = await site(hostId).collection(FORMS).doc(formId).get()\n return snapshot.exists ? [formQuestionsOf(snapshot.id, snapshot.data())] : []\n }\n const snapshot = await site(hostId).collection(FORMS).limit(FORMS_MAX_PER_HOST).get()\n return snapshot.docs\n .map((document) => formQuestionsOf(document.id, document.data()))\n .sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))\n }\n\n const readPage = async (\n ctx: TransferResourceContext,\n cursor: string | null,\n fieldIds: readonly string[],\n options?: TransferReadOptions,\n ): Promise<TransferReadPage> => {\n const hostId = ctx.hostId\n if (!hostId) return { rows: [], next: null }\n const filter = readFormSubmissionsFilter(options?.filter)\n const size = pageSizeOf(options)\n // Which answers are ratings, read only when an answer is asked for.\n const ratings = fieldIds.some((fieldId) => fieldId.startsWith(FORM_SUBMISSION_ANSWER_PREFIX))\n ? formRatingQuestions(await readForms(hostId, filter.formId))\n : undefined\n if (options?.ids) {\n const ids = selectionIds(options.ids)\n const from = readOffsetCursor(cursor)\n const to = from + size\n const snapshots = await readByIds(hostId, ids.slice(from, to), filter)\n return {\n rows: snapshots.map((snapshot) =>\n formSubmissionTransferRow(snapshot.id, snapshot.data() ?? {}, fieldIds, ratings),\n ),\n next: to < ids.length ? String(to) : null,\n }\n }\n let query = newestFirst(filtered(hostId, filter))\n if (cursor !== null) {\n const after = readQueryCursor(cursor)\n query = query.startAfter(after.createdAt, after.id)\n }\n // One past the page, so the last page says it is the last.\n const snapshot = await query.limit(size + 1).get()\n const page = snapshot.docs.slice(0, size)\n const last = page[page.length - 1]\n return {\n rows: page.map((document) => formSubmissionTransferRow(document.id, document.data(), fieldIds, ratings)),\n next: snapshot.docs.length > size && last ? formSubmissionsCursor(last.get('createdAt'), last.id) : null,\n }\n }\n\n return {\n matchKeys: FORM_SUBMISSIONS_MATCH_KEYS,\n\n async fields(ctx) {\n const hostId = ctx.hostId\n if (!hostId) return formSubmissionsCatalog({ forms: [], sampledKeys: [], scoped: false })\n const { formId } = readFormSubmissionsFilter(ctx.filter)\n const [forms, sample] = await Promise.all([\n readForms(hostId, formId),\n newestFirst(filtered(hostId, formId === undefined ? {} : { formId }))\n .limit(FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS)\n .get(),\n ])\n return formSubmissionsCatalog({\n forms,\n sampledKeys: sampledAnswerKeys(sample.docs.map((document) => document.data())),\n scoped: formId !== undefined,\n })\n },\n\n async count(ctx, options) {\n const hostId = ctx.hostId\n if (!hostId) return 0\n const filter = readFormSubmissionsFilter(options.filter)\n if (options.ids) return (await readByIds(hostId, selectionIds(options.ids), filter)).length\n const counted = await filtered(hostId, filter).count().get()\n return counted.data().count\n },\n\n readPage,\n\n /**\n * What a submission holds now, by its Aglyn ID — the one key this\n * resource answers, since nothing ever imports into it.\n */\n async lookup(ctx, requests) {\n const byId = requests.filter((request) => request.fieldId === TRANSFER_ID_FIELD)\n const ids = byId.flatMap((request) => request.values)\n const found = ctx.hostId ? await readByIds(ctx.hostId, selectionIds(ids), {}) : []\n const records = new Map<string, Record<string, unknown>>()\n for (const document of found) {\n const data = document.data() ?? {}\n const answers = data['fields'] && typeof data['fields'] === 'object' ? Object.keys(data['fields']) : []\n records.set(\n document.id,\n formSubmissionTransferRow(document.id, data, [\n ...Object.keys(DETAIL_READERS),\n ...answers.map(formSubmissionAnswerFieldId),\n ]),\n )\n }\n const lookup = buildMatchLookup(\n [...records].map(([id, values]) => ({ id, values })),\n byId,\n )\n return { lookup, records }\n },\n }\n}\n"],"names":["FORMS_MAX_PER_HOST","TRANSFER_ID_FIELD","buildMatchLookup","transferIdField","FieldPath","Timestamp","FORM_SUBMISSIONS_MATCH_KEYS","SUBMISSIONS","FORMS","FORM_SUBMISSION_ANSWER_PREFIX","FORM_SUBMISSIONS_ANSWER_SAMPLE_ROWS","FORM_SUBMISSIONS_OTHER_ANSWERS_MAX","PAGE_ROWS_DEFAULT","PAGE_ROWS_MAX","READ_BY_ID_CHUNK","FORM_SUBMISSION_DETAILS_GROUP","id","label","FORM_SUBMISSION_OTHER_ANSWERS_GROUP","formAnswersGroupId","formId","formSubmissionAnswerFieldId","fieldName","formAnswerTransferType","fieldType","readFormSubmissionsFilter","value","undefined","Array","isArray","Error","filter","key","raw","Object","entries","trim","includes","read","matchesFilter","data","formSubmissionDetailFields","group","type","readOnly","system","aliases","description","formQuestionsOf","declared","displayName","name","fields","field","Boolean","answerField","decl","formSubmissionsCatalog","input","answers","groups","taken","Set","form","forms","scoped","added","has","add","push","others","sampledKeys","slice","length","standard","sampledAnswerKeys","rows","seen","Map","keys","entry","get","count","set","order","size","sort","a","b","map","textOrNull","isoOf","Number","isFinite","Date","toISOString","isNaN","getTime","toDate","call","DETAIL_READERS","_data","createdAt","formName","path","repliedAt","campaignIds","one","routing","rateDegraded","hostId","formRatingQuestions","RATING_TEXT","formSubmissionTransferRow","fieldIds","ratings","rated","row","fieldId","reader","startsWith","answer","test","formSubmissionsCursor","at","seconds","nanoseconds","readQueryCursor","cursor","found","exec","readOffsetCursor","pageSizeOf","options","asked","Math","floor","pageSize","max","min","selectionIds","ids","createFormSubmissionsTransferResource","deps","firestore","site","collection","doc","submissions","filtered","query","where","newestFirst","orderBy","documentId","readByIds","refs","snapshots","getAll","snapshot","exists","readForms","limit","docs","document","localeCompare","readPage","ctx","next","some","from","to","String","after","startAfter","page","last","matchKeys","sample","Promise","all","counted","lookup","requests","byId","request","flatMap","values","records"],"mappings":"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"}