From 10bd08991e4b02e193b77b3dcdd6309f3b5ae622 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Fri, 1 Nov 2024 13:36:41 -0500 Subject: [PATCH] Fix circular imports in forms package. This resolves a problem where Rollup's watch mode wasn't working. (#368) --- .../components/common/MovePatternDropdown.tsx | 5 +- .../components/common/PatternEditActions.tsx | 9 +- packages/forms/rollup.config.js | 5 + packages/forms/src/blueprint.ts | 537 +++++++++++++++ packages/forms/src/builder/builder.test.ts | 4 +- packages/forms/src/builder/index.ts | 25 +- packages/forms/src/components.ts | 3 +- packages/forms/src/documents/document.ts | 18 +- .../forms/src/documents/pdf/parsing-api.ts | 29 +- packages/forms/src/index.ts | 624 +----------------- packages/forms/src/pattern.ts | 25 +- .../forms/src/patterns/fieldset/config.ts | 11 +- packages/forms/src/patterns/fieldset/index.ts | 13 +- .../forms/src/patterns/fieldset/prompt.ts | 7 +- packages/forms/src/patterns/index.ts | 2 + packages/forms/src/patterns/input/builder.ts | 2 +- packages/forms/src/patterns/input/config.ts | 4 +- packages/forms/src/patterns/input/index.ts | 6 +- packages/forms/src/patterns/input/prompt.ts | 15 +- packages/forms/src/patterns/input/response.ts | 2 +- .../forms/src/patterns/page-set/prompt.ts | 15 +- packages/forms/src/patterns/page/prompt.ts | 4 +- packages/forms/src/response.ts | 12 +- .../forms/src/services/submit-form.test.ts | 15 +- packages/forms/src/services/submit-form.ts | 16 +- packages/forms/src/session.ts | 18 +- packages/forms/src/types.ts | 21 + 27 files changed, 702 insertions(+), 745 deletions(-) create mode 100644 packages/forms/src/blueprint.ts create mode 100644 packages/forms/src/types.ts diff --git a/packages/design/src/FormManager/FormEdit/components/common/MovePatternDropdown.tsx b/packages/design/src/FormManager/FormEdit/components/common/MovePatternDropdown.tsx index f52a6a95..da5ecff5 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/MovePatternDropdown.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/MovePatternDropdown.tsx @@ -1,6 +1,7 @@ import React, { useState, useRef, useEffect } from 'react'; import { useFormManagerStore } from '../../../store.js'; import styles from '../../formEditStyles.module.css'; +import type { Pattern } from '@atj/forms'; interface MovePatternDropdownProps { isFieldset: boolean; @@ -27,7 +28,9 @@ const MovePatternDropdown: React.FC = ({ const dropdownRef = useRef(null); const buttonRef = useRef(null); const pages = useFormManagerStore(state => - Object.values(state.session.form.patterns).filter(p => p.type === 'page') + Object.values(state.session.form.patterns).filter( + p => p.type === 'page' + ) ); const movePatternToPage = useFormManagerStore(state => state.movePattern); const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); diff --git a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx index bf343d7c..24f91e24 100644 --- a/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx +++ b/packages/design/src/FormManager/FormEdit/components/common/PatternEditActions.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames'; import { useFormManagerStore } from '../../../store.js'; import MovePatternDropdown from './MovePatternDropdown.js'; import styles from '../../formEditStyles.module.css'; +import type { Pattern } from '@atj/forms'; type PatternEditActionsProps = PropsWithChildren<{ children?: ReactElement; @@ -19,7 +20,7 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { state => state.focus?.pattern.type ); const patterns = useFormManagerStore(state => - Object.values(state.session.form.patterns) + Object.values(state.session.form.patterns) ); const focusPatternId = useFormManagerStore(state => state.focus?.pattern.id); const isPatternInFieldset = useMemo(() => { @@ -34,10 +35,12 @@ export const PatternEditActions = ({ children }: PatternEditActionsProps) => { copyPattern: state.copyPattern, })); const pages = useFormManagerStore(state => - Object.values(state.session.form.patterns).filter(p => p.type === 'page') + Object.values(state.session.form.patterns).filter( + p => p.type === 'page' + ) ); const fieldsets = useFormManagerStore(state => - Object.values(state.session.form.patterns).filter( + Object.values(state.session.form.patterns).filter( p => p.type === 'fieldset' ) ); diff --git a/packages/forms/rollup.config.js b/packages/forms/rollup.config.js index 626b023d..6671ba98 100644 --- a/packages/forms/rollup.config.js +++ b/packages/forms/rollup.config.js @@ -1,3 +1,4 @@ +import { watch } from 'rollup'; import { builtinModules } from 'module'; import { nodeResolve } from '@rollup/plugin-node-resolve'; @@ -51,4 +52,8 @@ export default { ]), ]; })(), + watch: { + include: 'src/**/*.ts', + clearScreen: false, + }, }; diff --git a/packages/forms/src/blueprint.ts b/packages/forms/src/blueprint.ts new file mode 100644 index 00000000..555a6862 --- /dev/null +++ b/packages/forms/src/blueprint.ts @@ -0,0 +1,537 @@ +import { + type FormConfig, + type Pattern, + type PatternId, + type PatternMap, + generatePatternId, + getPatternMap, + removeChildPattern, +} from './pattern'; +import { + type FieldsetPattern, + type PagePattern, + type PageSetPattern, + type SequencePattern, +} from './patterns'; +import { type Blueprint, type FormOutput, type FormSummary } from './types'; + +export const nullBlueprint: Blueprint = { + summary: { + title: '', + description: '', + }, + root: 'root', + patterns: { + root: { + type: 'sequence', + id: 'root', + data: { + patterns: [], + }, + } satisfies SequencePattern, + }, + outputs: [], +}; + +export const createOnePageBlueprint = (): Blueprint => { + const page1 = generatePatternId(); + return { + summary: { + title: '', + description: '', + }, + root: 'root', + patterns: { + root: { + type: 'page-set', + id: 'root', + data: { + pages: [page1], + }, + } satisfies PageSetPattern, + [page1]: { + type: 'page', + id: page1, + data: { + title: 'Page 1', + patterns: [], + }, + }, + }, + outputs: [], + }; +}; + +export const createForm = ( + summary: FormSummary, + initial: { + patterns: Pattern[]; + root: PatternId; + } = { + patterns: [ + { + id: 'root', + type: 'sequence', + data: { + patterns: [], + }, + } satisfies SequencePattern, + ], + root: 'root', + } +): Blueprint => { + return { + summary, + root: initial.root, + patterns: getPatternMap(initial.patterns), + outputs: [], + }; +}; + +export const getRootPattern = (form: Blueprint) => { + return form.patterns[form.root]; +}; + +export const addPatternMap = ( + form: Blueprint, + patterns: PatternMap, + root?: PatternId +) => { + return { + ...form, + patterns: { ...form.patterns, ...patterns }, + root: root !== undefined ? root : form.root, + }; +}; + +export const addPatterns = ( + form: Blueprint, + patterns: Pattern[], + root?: PatternId +) => { + const formPatternMap = getPatternMap(patterns); + return addPatternMap(form, formPatternMap, root); +}; + +export const addPatternToPage = ( + bp: Blueprint, + pagePatternId: PatternId, + pattern: Pattern, + index?: number +): Blueprint => { + const pagePattern = bp.patterns[pagePatternId] as PagePattern; + if (pagePattern.type !== 'page') { + throw new Error('Pattern is not a page.'); + } + + let updatedPagePattern: PatternId[]; + + if (index !== undefined) { + updatedPagePattern = [ + ...pagePattern.data.patterns.slice(0, index + 1), + pattern.id, + ...pagePattern.data.patterns.slice(index + 1), + ]; + } else { + updatedPagePattern = [...pagePattern.data.patterns, pattern.id]; + } + + return { + ...bp, + patterns: { + ...bp.patterns, + [pagePattern.id]: { + ...pagePattern, + data: { + ...pagePattern.data, + patterns: updatedPagePattern, + }, + } satisfies SequencePattern, + [pattern.id]: pattern, + }, + }; +}; + +export const movePatternBetweenPages = ( + bp: Blueprint, + sourcePageId: PatternId, + targetPageId: PatternId, + patternId: PatternId, + position: string +): Blueprint => { + const sourcePage = bp.patterns[sourcePageId] as PagePattern; + const targetPage = bp.patterns[targetPageId] as PagePattern; + + if (!sourcePage || !targetPage) { + throw new Error('Source or target page not found.'); + } + + if (sourcePage.type !== 'page' || targetPage.type !== 'page') { + throw new Error('Pattern is not a page.'); + } + + let updatedSourcePatterns: PatternId[]; + let updatedTargetPatterns: PatternId[]; + + if (sourcePageId === targetPageId) { + const sourcePagePatterns = sourcePage.data.patterns; + const indexToRemove = sourcePagePatterns.indexOf(patternId); + + if (indexToRemove === -1) { + throw new Error(`Pattern ID ${patternId} not found in the source page.`); + } + + updatedSourcePatterns = [ + ...sourcePagePatterns.slice(0, indexToRemove), + ...sourcePagePatterns.slice(indexToRemove + 1), + ]; + + updatedTargetPatterns = + position === 'top' + ? [patternId, ...updatedSourcePatterns] + : [...updatedSourcePatterns, patternId]; + } else { + const indexToRemove = sourcePage.data.patterns.indexOf(patternId); + + if (indexToRemove === -1) { + throw new Error(`Pattern ID ${patternId} not found in the source page.`); + } + + updatedSourcePatterns = [ + ...sourcePage.data.patterns.slice(0, indexToRemove), + ...sourcePage.data.patterns.slice(indexToRemove + 1), + ]; + + updatedTargetPatterns = + position === 'top' + ? [patternId, ...targetPage.data.patterns] + : [...targetPage.data.patterns, patternId]; + } + + return { + ...bp, + patterns: { + ...bp.patterns, + [sourcePageId]: { + ...sourcePage, + data: { + ...sourcePage.data, + patterns: updatedSourcePatterns, + }, + } satisfies PagePattern, + [targetPageId]: { + ...targetPage, + data: { + ...targetPage.data, + patterns: updatedTargetPatterns, + }, + } satisfies PagePattern, + }, + }; +}; + +export const copyPattern = ( + bp: Blueprint, + parentPatternId: PatternId, + patternId: PatternId +): { bp: Blueprint; pattern: Pattern } => { + const pattern = bp.patterns[patternId]; + if (!pattern) { + throw new Error(`Pattern with id ${patternId} not found`); + } + + const copySimplePattern = (pattern: Pattern): Pattern => { + const newId = generatePatternId(); + const currentDate = new Date(); + const dateString = currentDate.toLocaleString(); + const newPattern: Pattern = { + ...pattern, + id: newId, + data: { ...pattern.data }, + }; + + if (newPattern.type === 'form-summary') { + newPattern.data.title = `(Copy ${dateString}) ${newPattern.data.title || ''}`; + } else if ( + newPattern.type === 'input' || + newPattern.type === 'radio-group' || + newPattern.type === 'checkbox' + ) { + newPattern.data.label = `(Copy ${dateString}) ${newPattern.data.label || ''}`; + } else { + newPattern.data.text = `(Copy ${dateString}) ${newPattern.data.text || ''}`; + } + + return newPattern; + }; + + const copyFieldsetPattern = (pattern: Pattern): Pattern => { + const newId = generatePatternId(); + const currentDate = new Date(); + const dateString = currentDate.toLocaleString(); + const newPattern: Pattern = { + ...pattern, + id: newId, + data: { ...pattern.data }, + }; + + if (newPattern.type === 'fieldset') { + newPattern.data.legend = `(Copy ${dateString}) ${newPattern.data.legend || ''}`; + } + + return newPattern; + }; + + const findParentFieldset = ( + bp: Blueprint, + childId: PatternId + ): PatternId | null => { + for (const [id, pattern] of Object.entries(bp.patterns)) { + if ( + pattern.type === 'fieldset' && + pattern.data.patterns.includes(childId) + ) { + return id as PatternId; + } + } + return null; + }; + + const copyFieldsetContents = ( + bp: Blueprint, + originalFieldsetId: PatternId, + newFieldsetId: PatternId + ): Blueprint => { + const originalFieldset = bp.patterns[originalFieldsetId] as FieldsetPattern; + const newFieldset = bp.patterns[newFieldsetId] as FieldsetPattern; + let updatedBp = { ...bp }; + + const idMap = new Map(); + + for (const childId of originalFieldset.data.patterns) { + const childPattern = updatedBp.patterns[childId]; + if (childPattern) { + const newChildPattern = copyFieldsetPattern(childPattern); + idMap.set(childId, newChildPattern.id); + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newChildPattern.id]: newChildPattern, + }, + }; + + if (childPattern.type === 'fieldset') { + updatedBp = copyFieldsetContents( + updatedBp, + childId, + newChildPattern.id + ); + } + } + } + + newFieldset.data.patterns = originalFieldset.data.patterns.map( + id => idMap.get(id) || id + ); + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newFieldsetId]: newFieldset, + }, + }; + + return updatedBp; + }; + + let updatedBp = { ...bp }; + let newPattern = pattern; + + if (pattern.type === 'fieldset') { + newPattern = copyFieldsetPattern(pattern); + } else { + newPattern = copySimplePattern(pattern); + } + + const actualParentId = findParentFieldset(bp, patternId) || parentPatternId; + const actualParent = updatedBp.patterns[actualParentId]; + + if ( + !actualParent || + !actualParent.data || + !Array.isArray(actualParent.data.patterns) + ) { + throw new Error(`Invalid parent pattern with id ${actualParentId}`); + } + + const originalIndex = actualParent.data.patterns.indexOf(patternId); + if (originalIndex === -1) { + throw new Error( + `Pattern with id ${patternId} not found in parent's patterns` + ); + } + + actualParent.data.patterns = [ + ...actualParent.data.patterns.slice(0, originalIndex + 1), + newPattern.id, + ...actualParent.data.patterns.slice(originalIndex + 1), + ]; + + updatedBp = { + ...updatedBp, + patterns: { + ...updatedBp.patterns, + [newPattern.id]: newPattern, + [actualParentId]: actualParent, + }, + }; + + if (pattern.type === 'fieldset') { + updatedBp = copyFieldsetContents(updatedBp, patternId, newPattern.id); + } + + return { bp: updatedBp, pattern: newPattern }; +}; + +export const addPatternToFieldset = ( + bp: Blueprint, + fieldsetPatternId: PatternId, + pattern: Pattern, + index?: number +): Blueprint => { + const fieldsetPattern = bp.patterns[fieldsetPatternId] as FieldsetPattern; + if (fieldsetPattern.type !== 'fieldset') { + throw new Error('Pattern is not a page.'); + } + + let updatedPagePattern: PatternId[]; + + if (index !== undefined) { + updatedPagePattern = [ + ...fieldsetPattern.data.patterns.slice(0, index + 1), + pattern.id, + ...fieldsetPattern.data.patterns.slice(index + 1), + ]; + } else { + updatedPagePattern = [...fieldsetPattern.data.patterns, pattern.id]; + } + + return { + ...bp, + patterns: { + ...bp.patterns, + [fieldsetPattern.id]: { + ...fieldsetPattern, + data: { + ...fieldsetPattern.data, + patterns: updatedPagePattern, + }, + } satisfies FieldsetPattern, + [pattern.id]: pattern, + }, + }; +}; + +export const addPageToPageSet = ( + bp: Blueprint, + pattern: Pattern +): Blueprint => { + const pageSet = bp.patterns[bp.root] as PageSetPattern; + if (pageSet.type !== 'page-set') { + throw new Error('Root pattern is not a page set.'); + } + return { + ...bp, + patterns: { + ...bp.patterns, + [pageSet.id]: { + ...pageSet, + data: { + pages: [...pageSet.data.pages, pattern.id], + }, + } satisfies PageSetPattern, + [pattern.id]: pattern, + }, + }; +}; + +export const replacePatterns = ( + form: Blueprint, + patterns: Pattern[] +): Blueprint => { + return { + ...form, + patterns: patterns.reduce( + (acc, pattern) => { + acc[pattern.id] = pattern; + return acc; + }, + {} as Record + ), + }; +}; + +export const updatePatterns = ( + config: FormConfig, + form: Blueprint, + newPatterns: PatternMap +): Blueprint => { + const root = newPatterns[form.root]; + const targetPatterns: PatternMap = { + [root.id]: root, + }; + const patternConfig = config.patterns[root.type]; + const children = patternConfig.getChildren(root, newPatterns); + targetPatterns[root.id] = root; + children.forEach(child => (targetPatterns[child.id] = child)); + return { + ...form, + patterns: targetPatterns, + }; +}; + +export const addFormOutput = ( + form: Blueprint, + document: FormOutput +): Blueprint => { + return { + ...form, + outputs: [...form.outputs, document], + }; +}; + +export const updateFormSummary = ( + form: Blueprint, + summary: FormSummary +): Blueprint => { + return { + ...form, + summary, + }; +}; + +export const removePatternFromBlueprint = ( + config: FormConfig, + blueprint: Blueprint, + id: PatternId +) => { + // Iterate over each pattern in the blueprint, and remove the target pattern + // if it is a child. + const patterns = Object.values(blueprint.patterns).reduce( + (patterns, pattern) => { + patterns[pattern.id] = removeChildPattern(config, pattern, id); + return patterns; + }, + {} as PatternMap + ); + + // Remove the pattern itself + delete patterns[id]; + return { + ...blueprint, + patterns, + }; +}; diff --git a/packages/forms/src/builder/builder.test.ts b/packages/forms/src/builder/builder.test.ts index 4f677e25..8ee7c087 100644 --- a/packages/forms/src/builder/builder.test.ts +++ b/packages/forms/src/builder/builder.test.ts @@ -2,9 +2,9 @@ import { describe, expect, it } from 'vitest'; import { type Pattern, createForm, getPattern } from '../index.js'; import { defaultFormConfig } from '../patterns/index.js'; -import { type FieldsetPattern } from '../patterns/fieldset/index.js'; +import { type FieldsetPattern } from '../patterns/fieldset/config.js'; import { type FormSummaryPattern } from '../patterns/form-summary.js'; -import { type InputPattern } from '../patterns/input/index.js'; +import { type InputPattern } from '../patterns/input/config.js'; import { type PagePattern } from '../patterns/page/config.js'; import { type PageSetPattern } from '../patterns/page-set/config.js'; import { type RadioGroupPattern } from '../patterns/radio-group.js'; diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 09e49e7e..5f109702 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -1,27 +1,28 @@ import { type VoidResult } from '@atj/common'; import { - type Blueprint, - type FormConfig, - type FormErrors, - type FormSummary, - type Pattern, - type PatternId, - type PatternMap, - addDocument, addPageToPageSet, addPatternToFieldset, addPatternToPage, copyPattern, - createDefaultPattern, createOnePageBlueprint, - getPattern, movePatternBetweenPages, removePatternFromBlueprint, updateFormSummary, +} from '../blueprint.js'; +import { addDocument } from '../documents/document.js'; +import type { FormErrors } from '../error.js'; +import { + createDefaultPattern, + getPattern, updatePatternFromFormData, -} from '../index.js'; + type FormConfig, + type Pattern, + type PatternId, + type PatternMap, +} from '../pattern.js'; +import { type FieldsetPattern } from '../patterns/fieldset/config.js'; import { type PageSetPattern } from '../patterns/page-set/config.js'; -import { type FieldsetPattern } from '../patterns/fieldset/index.js'; +import type { Blueprint, FormSummary } from '../types.js'; export class BlueprintBuilder { bp: Blueprint; diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts index 307f41a7..4f074a97 100644 --- a/packages/forms/src/components.ts +++ b/packages/forms/src/components.ts @@ -1,4 +1,5 @@ -import { type FormError, getRootPattern } from './index.js'; +import { getRootPattern } from './blueprint.js'; +import { type FormError } from './error.js'; import { type FormConfig, type Pattern, diff --git a/packages/forms/src/documents/document.ts b/packages/forms/src/documents/document.ts index 4aba8c8f..2e455702 100644 --- a/packages/forms/src/documents/document.ts +++ b/packages/forms/src/documents/document.ts @@ -1,20 +1,22 @@ import { - Blueprint, - Pattern, addFormOutput, - addPatterns, addPatternMap, + addPatterns, updateFormSummary, -} from '../index.js'; -import { InputPattern } from '../patterns/input/index.js'; -import { SequencePattern } from '../patterns/sequence.js'; -import { PDFDocument, getDocumentFieldData } from './pdf/index.js'; +} from '../blueprint.js'; +import { type Pattern } from '../pattern.js'; +import { type InputPattern } from '../patterns/input/config.js'; +import { type SequencePattern } from '../patterns/sequence.js'; +import { type Blueprint } from '../types.js'; + +import { type PDFDocument, getDocumentFieldData } from './pdf/index.js'; import { type FetchPdfApiResponse, processApiResponse, fetchPdfApiResponse, } from './pdf/parsing-api.js'; -import { DocumentFieldMap } from './types.js'; + +import { type DocumentFieldMap } from './types.js'; export type DocumentTemplate = PDFDocument; diff --git a/packages/forms/src/documents/pdf/parsing-api.ts b/packages/forms/src/documents/pdf/parsing-api.ts index 1ca8a01c..82d44f15 100644 --- a/packages/forms/src/documents/pdf/parsing-api.ts +++ b/packages/forms/src/documents/pdf/parsing-api.ts @@ -1,26 +1,25 @@ import * as z from 'zod'; -import { - type FormConfig, - type FormErrors, - type Pattern, - type PatternId, - type PatternMap, - createPattern, - defaultFormConfig, -} from '../../index.js'; - -import { type FieldsetPattern } from '../../patterns/fieldset/index.js'; -import { type InputPattern } from '../../patterns/input/index.js'; +import { type FieldsetPattern } from '../../patterns/fieldset/config.js'; +import { type InputPattern } from '../../patterns/input/config.js'; +import { PagePattern } from '../../patterns/page/config.js'; +import { PageSetPattern } from '../../patterns/page-set/config.js'; import { type ParagraphPattern } from '../../patterns/paragraph.js'; import { type CheckboxPattern } from '../../patterns/checkbox.js'; import { type RadioGroupPattern } from '../../patterns/radio-group.js'; +import { RichTextPattern } from '../../patterns/rich-text.js'; import { uint8ArrayToBase64 } from '../util.js'; import { type DocumentFieldMap } from '../types.js'; -import { PagePattern } from '../../patterns/page/config.js'; -import { PageSetPattern } from '../../patterns/page-set/config.js'; -import { RichTextPattern } from '../../patterns/rich-text.js'; +import { + createPattern, + FormConfig, + Pattern, + PatternId, + PatternMap, +} from '../../pattern.js'; +import { FormErrors } from '../../error.js'; +import { defaultFormConfig } from '../../patterns/index.js'; const FormSummary = z.object({ component_type: z.literal('form_summary'), diff --git a/packages/forms/src/index.ts b/packages/forms/src/index.ts index cad04c39..98825238 100644 --- a/packages/forms/src/index.ts +++ b/packages/forms/src/index.ts @@ -1,29 +1,15 @@ -import { type DocumentFieldMap } from './documents/index.js'; -import { - type FormConfig, - type Pattern, - type PatternId, - type PatternMap, - getPatternMap, - removeChildPattern, - generatePatternId, -} from './pattern.js'; - -export * from './patterns/index.js'; +export * from './blueprint.js'; export * from './builder/index.js'; export * from './components.js'; export * from './documents/index.js'; export * from './error.js'; export * from './pattern.js'; +export * from './patterns/index.js'; export * from './response.js'; export * from './session.js'; +export * from './types.js'; export { type FormService, createFormService } from './services/index.js'; -export { defaultFormConfig } from './patterns/index.js'; -import { type PagePattern } from './patterns/page/config.js'; -import { type PageSetPattern } from './patterns/page-set/config.js'; export { type RichTextPattern } from './patterns/rich-text.js'; -import { type SequencePattern } from './patterns/sequence.js'; -import { FieldsetPattern } from './patterns/index.js'; export { type FormRepository, createFormsRepository, @@ -33,607 +19,3 @@ export { type RouteData, getRouteDataFromQueryString, } from './route-data.js'; - -export type Blueprint = { - summary: FormSummary; - root: PatternId; - patterns: PatternMap; - outputs: FormOutput[]; -}; - -export const nullBlueprint: Blueprint = { - summary: { - title: '', - description: '', - }, - root: 'root', - patterns: { - root: { - type: 'sequence', - id: 'root', - data: { - patterns: [], - }, - } satisfies SequencePattern, - }, - outputs: [], -}; - -export const createOnePageBlueprint = (): Blueprint => { - const page1 = generatePatternId(); - return { - summary: { - title: '', - description: '', - }, - root: 'root', - patterns: { - root: { - type: 'page-set', - id: 'root', - data: { - pages: [page1], - }, - } satisfies PageSetPattern, - [page1]: { - type: 'page', - id: page1, - data: { - title: 'Page 1', - patterns: [], - }, - }, - }, - outputs: [], - }; -}; - -export type FormSummary = { - title: string; - description: string; -}; - -export type FormOutput = { - data: Uint8Array; - path: string; - fields: DocumentFieldMap; - formFields: Record; -}; - -export const createForm = ( - summary: FormSummary, - initial: { - patterns: Pattern[]; - root: PatternId; - } = { - patterns: [ - { - id: 'root', - type: 'sequence', - data: { - patterns: [], - }, - } satisfies SequencePattern, - ], - root: 'root', - } -): Blueprint => { - return { - summary, - root: initial.root, - patterns: getPatternMap(initial.patterns), - outputs: [], - }; -}; - -export const getRootPattern = (form: Blueprint) => { - return form.patterns[form.root]; -}; - -/* -export const updateForm = (context: Session, id: PatternId, value: any) => { - if (!(id in context.form.patterns)) { - console.error(`Pattern "${id}" does not exist on form.`); - return context; - } - const nextForm = addValue(context, id, value); - const pattern = context.form.patterns[id]; - if (pattern.type === 'input') { - if (pattern.data.required && !value) { - return addError(nextForm, id, 'Required value not provided.'); - } - } - return nextForm; -}; - -const addValue = ( - form: FormSession, - id: PatternId, - value: PatternValue -): FormSession => ({ - ...form, - data: { - ...form.data, - values: { - ...form.data.values, - [id]: value, - }, - }, -}); - -const addError = ( - session: FormSession, - id: PatternId, - error: string -): FormSession => ({ - ...session, - data: { - ...session.data, - errors: { - ...session.data.errors, - [id]: error, - }, - }, -}); -*/ - -export const addPatternMap = ( - form: Blueprint, - patterns: PatternMap, - root?: PatternId -) => { - return { - ...form, - patterns: { ...form.patterns, ...patterns }, - root: root !== undefined ? root : form.root, - }; -}; - -export const addPatterns = ( - form: Blueprint, - patterns: Pattern[], - root?: PatternId -) => { - const formPatternMap = getPatternMap(patterns); - return addPatternMap(form, formPatternMap, root); -}; - -export const addPatternToPage = ( - bp: Blueprint, - pagePatternId: PatternId, - pattern: Pattern, - index?: number -): Blueprint => { - const pagePattern = bp.patterns[pagePatternId] as PagePattern; - if (pagePattern.type !== 'page') { - throw new Error('Pattern is not a page.'); - } - - let updatedPagePattern: PatternId[]; - - if (index !== undefined) { - updatedPagePattern = [ - ...pagePattern.data.patterns.slice(0, index + 1), - pattern.id, - ...pagePattern.data.patterns.slice(index + 1), - ]; - } else { - updatedPagePattern = [...pagePattern.data.patterns, pattern.id]; - } - - return { - ...bp, - patterns: { - ...bp.patterns, - [pagePattern.id]: { - ...pagePattern, - data: { - ...pagePattern.data, - patterns: updatedPagePattern, - }, - } satisfies SequencePattern, - [pattern.id]: pattern, - }, - }; -}; - -export const movePatternBetweenPages = ( - bp: Blueprint, - sourcePageId: PatternId, - targetPageId: PatternId, - patternId: PatternId, - position: string -): Blueprint => { - const sourcePage = bp.patterns[sourcePageId] as PagePattern; - const targetPage = bp.patterns[targetPageId] as PagePattern; - - if (!sourcePage || !targetPage) { - throw new Error('Source or target page not found.'); - } - - if (sourcePage.type !== 'page' || targetPage.type !== 'page') { - throw new Error('Pattern is not a page.'); - } - - let updatedSourcePatterns: PatternId[]; - let updatedTargetPatterns: PatternId[]; - - if (sourcePageId === targetPageId) { - const sourcePagePatterns = sourcePage.data.patterns; - const indexToRemove = sourcePagePatterns.indexOf(patternId); - - if (indexToRemove === -1) { - throw new Error(`Pattern ID ${patternId} not found in the source page.`); - } - - updatedSourcePatterns = [ - ...sourcePagePatterns.slice(0, indexToRemove), - ...sourcePagePatterns.slice(indexToRemove + 1), - ]; - - updatedTargetPatterns = - position === 'top' - ? [patternId, ...updatedSourcePatterns] - : [...updatedSourcePatterns, patternId]; - } else { - const indexToRemove = sourcePage.data.patterns.indexOf(patternId); - - if (indexToRemove === -1) { - throw new Error(`Pattern ID ${patternId} not found in the source page.`); - } - - updatedSourcePatterns = [ - ...sourcePage.data.patterns.slice(0, indexToRemove), - ...sourcePage.data.patterns.slice(indexToRemove + 1), - ]; - - updatedTargetPatterns = - position === 'top' - ? [patternId, ...targetPage.data.patterns] - : [...targetPage.data.patterns, patternId]; - } - - return { - ...bp, - patterns: { - ...bp.patterns, - [sourcePageId]: { - ...sourcePage, - data: { - ...sourcePage.data, - patterns: updatedSourcePatterns, - }, - } satisfies PagePattern, - [targetPageId]: { - ...targetPage, - data: { - ...targetPage.data, - patterns: updatedTargetPatterns, - }, - } satisfies PagePattern, - }, - }; -}; - -export const copyPattern = ( - bp: Blueprint, - parentPatternId: PatternId, - patternId: PatternId -): { bp: Blueprint; pattern: Pattern } => { - const pattern = bp.patterns[patternId]; - if (!pattern) { - throw new Error(`Pattern with id ${patternId} not found`); - } - - const copySimplePattern = (pattern: Pattern): Pattern => { - const newId = generatePatternId(); - const currentDate = new Date(); - const dateString = currentDate.toLocaleString(); - const newPattern: Pattern = { - ...pattern, - id: newId, - data: { ...pattern.data }, - }; - - if (newPattern.type === 'form-summary') { - newPattern.data.title = `(Copy ${dateString}) ${newPattern.data.title || ''}`; - } else if ( - newPattern.type === 'input' || - newPattern.type === 'radio-group' || - newPattern.type === 'checkbox' - ) { - newPattern.data.label = `(Copy ${dateString}) ${newPattern.data.label || ''}`; - } else { - newPattern.data.text = `(Copy ${dateString}) ${newPattern.data.text || ''}`; - } - - return newPattern; - }; - - const copyFieldsetPattern = (pattern: Pattern): Pattern => { - const newId = generatePatternId(); - const currentDate = new Date(); - const dateString = currentDate.toLocaleString(); - const newPattern: Pattern = { - ...pattern, - id: newId, - data: { ...pattern.data }, - }; - - if (newPattern.type === 'fieldset') { - newPattern.data.legend = `(Copy ${dateString}) ${newPattern.data.legend || ''}`; - } - - return newPattern; - }; - - const findParentFieldset = ( - bp: Blueprint, - childId: PatternId - ): PatternId | null => { - for (const [id, pattern] of Object.entries(bp.patterns)) { - if ( - pattern.type === 'fieldset' && - pattern.data.patterns.includes(childId) - ) { - return id as PatternId; - } - } - return null; - }; - - const copyFieldsetContents = ( - bp: Blueprint, - originalFieldsetId: PatternId, - newFieldsetId: PatternId - ): Blueprint => { - const originalFieldset = bp.patterns[originalFieldsetId] as FieldsetPattern; - const newFieldset = bp.patterns[newFieldsetId] as FieldsetPattern; - let updatedBp = { ...bp }; - - const idMap = new Map(); - - for (const childId of originalFieldset.data.patterns) { - const childPattern = updatedBp.patterns[childId]; - if (childPattern) { - const newChildPattern = copyFieldsetPattern(childPattern); - idMap.set(childId, newChildPattern.id); - - updatedBp = { - ...updatedBp, - patterns: { - ...updatedBp.patterns, - [newChildPattern.id]: newChildPattern, - }, - }; - - if (childPattern.type === 'fieldset') { - updatedBp = copyFieldsetContents( - updatedBp, - childId, - newChildPattern.id - ); - } - } - } - - newFieldset.data.patterns = originalFieldset.data.patterns.map( - id => idMap.get(id) || id - ); - - updatedBp = { - ...updatedBp, - patterns: { - ...updatedBp.patterns, - [newFieldsetId]: newFieldset, - }, - }; - - return updatedBp; - }; - - let updatedBp = { ...bp }; - let newPattern = pattern; - - if (pattern.type === 'fieldset') { - newPattern = copyFieldsetPattern(pattern); - } else { - newPattern = copySimplePattern(pattern); - } - - const actualParentId = findParentFieldset(bp, patternId) || parentPatternId; - const actualParent = updatedBp.patterns[actualParentId]; - - if ( - !actualParent || - !actualParent.data || - !Array.isArray(actualParent.data.patterns) - ) { - throw new Error(`Invalid parent pattern with id ${actualParentId}`); - } - - const originalIndex = actualParent.data.patterns.indexOf(patternId); - if (originalIndex === -1) { - throw new Error( - `Pattern with id ${patternId} not found in parent's patterns` - ); - } - - actualParent.data.patterns = [ - ...actualParent.data.patterns.slice(0, originalIndex + 1), - newPattern.id, - ...actualParent.data.patterns.slice(originalIndex + 1), - ]; - - updatedBp = { - ...updatedBp, - patterns: { - ...updatedBp.patterns, - [newPattern.id]: newPattern, - [actualParentId]: actualParent, - }, - }; - - if (pattern.type === 'fieldset') { - updatedBp = copyFieldsetContents(updatedBp, patternId, newPattern.id); - } - - return { bp: updatedBp, pattern: newPattern }; -}; - -export const addPatternToFieldset = ( - bp: Blueprint, - fieldsetPatternId: PatternId, - pattern: Pattern, - index?: number -): Blueprint => { - const fieldsetPattern = bp.patterns[fieldsetPatternId] as FieldsetPattern; - if (fieldsetPattern.type !== 'fieldset') { - throw new Error('Pattern is not a page.'); - } - - let updatedPagePattern: PatternId[]; - - if (index !== undefined) { - updatedPagePattern = [ - ...fieldsetPattern.data.patterns.slice(0, index + 1), - pattern.id, - ...fieldsetPattern.data.patterns.slice(index + 1), - ]; - } else { - updatedPagePattern = [...fieldsetPattern.data.patterns, pattern.id]; - } - - return { - ...bp, - patterns: { - ...bp.patterns, - [fieldsetPattern.id]: { - ...fieldsetPattern, - data: { - ...fieldsetPattern.data, - patterns: updatedPagePattern, - }, - } satisfies FieldsetPattern, - [pattern.id]: pattern, - }, - }; -}; - -export const addPageToPageSet = ( - bp: Blueprint, - pattern: Pattern -): Blueprint => { - const pageSet = bp.patterns[bp.root] as PageSetPattern; - if (pageSet.type !== 'page-set') { - throw new Error('Root pattern is not a page set.'); - } - return { - ...bp, - patterns: { - ...bp.patterns, - [pageSet.id]: { - ...pageSet, - data: { - pages: [...pageSet.data.pages, pattern.id], - }, - } satisfies PageSetPattern, - [pattern.id]: pattern, - }, - }; -}; - -export const replacePatterns = ( - form: Blueprint, - patterns: Pattern[] -): Blueprint => { - return { - ...form, - patterns: patterns.reduce( - (acc, pattern) => { - acc[pattern.id] = pattern; - return acc; - }, - {} as Record - ), - }; -}; - -export const updatePatterns = ( - config: FormConfig, - form: Blueprint, - newPatterns: PatternMap -): Blueprint => { - const root = newPatterns[form.root]; - const targetPatterns: PatternMap = { - [root.id]: root, - }; - const patternConfig = config.patterns[root.type]; - const children = patternConfig.getChildren(root, newPatterns); - targetPatterns[root.id] = root; - children.forEach(child => (targetPatterns[child.id] = child)); - return { - ...form, - patterns: targetPatterns, - }; -}; - -export const updatePattern = (form: Blueprint, pattern: Pattern): Blueprint => { - return { - ...form, - patterns: { - ...form.patterns, - [pattern.id]: pattern, - }, - }; -}; - -export const addFormOutput = ( - form: Blueprint, - document: FormOutput -): Blueprint => { - return { - ...form, - outputs: [...form.outputs, document], - }; -}; - -export const getPattern = ( - form: Blueprint, - id: PatternId -): T => { - return form.patterns[id] as T; -}; - -export const updateFormSummary = ( - form: Blueprint, - summary: FormSummary -): Blueprint => { - return { - ...form, - summary, - }; -}; - -export const removePatternFromBlueprint = ( - config: FormConfig, - blueprint: Blueprint, - id: PatternId -) => { - // Iterate over each pattern in the blueprint, and remove the target pattern - // if it is a child. - const patterns = Object.values(blueprint.patterns).reduce( - (patterns, pattern) => { - patterns[pattern.id] = removeChildPattern(config, pattern, id); - return patterns; - }, - {} as PatternMap - ); - - // Remove the pattern itself - delete patterns[id]; - return { - ...blueprint, - patterns, - }; -}; diff --git a/packages/forms/src/pattern.ts b/packages/forms/src/pattern.ts index b14e4c4c..40a1e601 100644 --- a/packages/forms/src/pattern.ts +++ b/packages/forms/src/pattern.ts @@ -1,12 +1,8 @@ import * as r from '@atj/common'; -import { - type Blueprint, - type FormError, - type FormErrors, - updatePattern, -} from './index.js'; import { type CreatePrompt } from './components.js'; +import { type FormError, type FormErrors } from './error.js'; +import { type Blueprint } from './types.js'; export type Pattern = { type: string; @@ -49,8 +45,11 @@ export abstract class PatternBuilder

{ abstract toPattern(): P; } -export const getPattern: GetPattern = (form, patternId) => { - return form.patterns[patternId]; +export const getPattern = ( + form: Blueprint, + id: PatternId +): T => { + return form.patterns[id] as T; }; export const getPatternSafely =

(opts: { @@ -70,6 +69,16 @@ export const getPatternSafely =

(opts: { return r.success(pattern as P); }; +export const updatePattern = (form: Blueprint, pattern: Pattern): Blueprint => { + return { + ...form, + patterns: { + ...form.patterns, + [pattern.id]: pattern, + }, + }; +}; + export type PatternConfig< ThisPattern extends Pattern = Pattern, PatternOutput = unknown, diff --git a/packages/forms/src/patterns/fieldset/config.ts b/packages/forms/src/patterns/fieldset/config.ts index cf09c85a..1c2225a9 100644 --- a/packages/forms/src/patterns/fieldset/config.ts +++ b/packages/forms/src/patterns/fieldset/config.ts @@ -1,7 +1,16 @@ import { z } from 'zod'; import { safeZodParseFormErrors } from '../../util/zod.js'; -import { ParsePatternConfigData } from '../../pattern.js'; +import { + type ParsePatternConfigData, + type Pattern, + type PatternId, +} from '../../pattern.js'; + +export type FieldsetPattern = Pattern<{ + legend?: string; + patterns: PatternId[]; +}>; const configSchema = z.object({ legend: z.string().min(1), diff --git a/packages/forms/src/patterns/fieldset/index.ts b/packages/forms/src/patterns/fieldset/index.ts index c095046c..2e4dd1a2 100644 --- a/packages/forms/src/patterns/fieldset/index.ts +++ b/packages/forms/src/patterns/fieldset/index.ts @@ -1,16 +1,7 @@ -import { - type Pattern, - type PatternConfig, - type PatternId, -} from '../../pattern.js'; -import { parseConfigData } from './config.js'; +import { type PatternConfig } from '../../pattern.js'; +import { type FieldsetPattern, parseConfigData } from './config.js'; import { createPrompt } from './prompt.js'; -export type FieldsetPattern = Pattern<{ - legend?: string; - patterns: PatternId[]; -}>; - export const fieldsetConfig: PatternConfig = { displayName: 'Fieldset', iconPath: 'block-icon.svg', diff --git a/packages/forms/src/patterns/fieldset/prompt.ts b/packages/forms/src/patterns/fieldset/prompt.ts index 977cb56d..045da198 100644 --- a/packages/forms/src/patterns/fieldset/prompt.ts +++ b/packages/forms/src/patterns/fieldset/prompt.ts @@ -1,10 +1,11 @@ -import { type FieldsetPattern } from './index.js'; import { type CreatePrompt, type FieldsetProps, createPromptForPattern, - getPattern, -} from '../../index.js'; +} from '../../components.js'; +import { getPattern } from '../../pattern.js'; + +import { type FieldsetPattern } from './config.js'; export const createPrompt: CreatePrompt = ( config, diff --git a/packages/forms/src/patterns/index.ts b/packages/forms/src/patterns/index.ts index f7d67765..f502ffc4 100644 --- a/packages/forms/src/patterns/index.ts +++ b/packages/forms/src/patterns/index.ts @@ -36,8 +36,10 @@ export const defaultFormConfig: FormConfig = { export * from './address/index.js'; export * from './checkbox.js'; export * from './fieldset/index.js'; +export { type FieldsetPattern } from './fieldset/config.js'; export * from './form-summary.js'; export * from './input/index.js'; +export { type InputPattern } from './input/config.js'; export * from './package-download/index.js'; export * from './page/index.js'; export { type PagePattern } from './page/config.js'; diff --git a/packages/forms/src/patterns/input/builder.ts b/packages/forms/src/patterns/input/builder.ts index 43b648a0..806a5745 100644 --- a/packages/forms/src/patterns/input/builder.ts +++ b/packages/forms/src/patterns/input/builder.ts @@ -1,5 +1,5 @@ import { PatternBuilder } from '../../pattern'; -import { type InputPattern } from '.'; +import { type InputPattern } from './config'; export class Input extends PatternBuilder { toPattern(): InputPattern { diff --git a/packages/forms/src/patterns/input/config.ts b/packages/forms/src/patterns/input/config.ts index a64c820a..2fdf3b7b 100644 --- a/packages/forms/src/patterns/input/config.ts +++ b/packages/forms/src/patterns/input/config.ts @@ -2,9 +2,11 @@ import { z } from 'zod'; import { enLocale as message } from '@atj/common'; -import { ParsePatternConfigData } from '../../pattern.js'; +import { type ParsePatternConfigData, type Pattern } from '../../pattern.js'; import { safeZodParseFormErrors } from '../../util/zod.js'; +export type InputPattern = Pattern; + const configSchema = z.object({ label: z.string().min(1, message.patterns.input.fieldLabelRequired), initial: z.string().optional(), diff --git a/packages/forms/src/patterns/input/index.ts b/packages/forms/src/patterns/input/index.ts index 1ed4f046..e332379d 100644 --- a/packages/forms/src/patterns/input/index.ts +++ b/packages/forms/src/patterns/input/index.ts @@ -1,13 +1,11 @@ import { enLocale as message } from '@atj/common'; -import { Pattern, type PatternConfig } from '../../pattern.js'; +import { type PatternConfig } from '../../pattern.js'; -import { parseConfigData, type InputConfigSchema } from './config.js'; +import { type InputPattern, parseConfigData } from './config.js'; import { createPrompt } from './prompt.js'; import { type InputPatternOutput, parseUserInput } from './response.js'; -export type InputPattern = Pattern; - export const inputConfig: PatternConfig = { displayName: message.patterns.input.displayName, iconPath: 'shortanswer-icon.svg', diff --git a/packages/forms/src/patterns/input/prompt.ts b/packages/forms/src/patterns/input/prompt.ts index ff92755e..a7357dab 100644 --- a/packages/forms/src/patterns/input/prompt.ts +++ b/packages/forms/src/patterns/input/prompt.ts @@ -1,13 +1,11 @@ -import { type InputPattern, inputConfig } from './index.js'; -import { - type CreatePrompt, - type TextInputProps, - getFormSessionValue, - validatePattern, -} from '../../index.js'; +import { type CreatePrompt, type TextInputProps } from '../../components.js'; +import { getPatternConfig, validatePattern } from '../../pattern.js'; +import { getFormSessionValue } from '../../session.js'; + +import { type InputPattern } from './config.js'; export const createPrompt: CreatePrompt = ( - _, + config, session, pattern, options @@ -15,6 +13,7 @@ export const createPrompt: CreatePrompt = ( const extraAttributes: Record = {}; const sessionValue = getFormSessionValue(session, pattern.id); if (options.validate) { + const inputConfig = getPatternConfig(config, pattern.type); const isValidResult = validatePattern(inputConfig, pattern, sessionValue); if (!isValidResult.success) { extraAttributes['error'] = isValidResult.error; diff --git a/packages/forms/src/patterns/input/response.ts b/packages/forms/src/patterns/input/response.ts index 30ac0b64..0866f948 100644 --- a/packages/forms/src/patterns/input/response.ts +++ b/packages/forms/src/patterns/input/response.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import { ParseUserInput } from '../../pattern.js'; import { safeZodParseToFormError } from '../../util/zod.js'; -import { type InputPattern } from './index.js'; +import { type InputPattern } from './config.js'; const createSchema = (data: InputPattern['data']) => { const schema = z.string().max(data.maxLength); diff --git a/packages/forms/src/patterns/page-set/prompt.ts b/packages/forms/src/patterns/page-set/prompt.ts index ab2337a6..a641776d 100644 --- a/packages/forms/src/patterns/page-set/prompt.ts +++ b/packages/forms/src/patterns/page-set/prompt.ts @@ -1,19 +1,20 @@ import { z } from 'zod'; +import { type RouteData } from '../../route-data.js'; +import { safeZodParseFormErrors } from '../../util/zod.js'; + +import { type PagePattern } from '../page/config.js'; +import { type ActionName, getActionString } from '../../submission.js'; import { type CreatePrompt, - type FormSession, type PageSetProps, type PromptAction, createPromptForPattern, - getPattern, -} from '../../index.js'; -import { type RouteData } from '../../route-data.js'; -import { safeZodParseFormErrors } from '../../util/zod.js'; +} from '../../components.js'; +import { getPattern } from '../../pattern.js'; +import { type FormSession } from '../../session.js'; import { type PageSetPattern } from './config.js'; -import { type PagePattern } from '../page/config.js'; -import { ActionName, getActionString } from '../../submission.js'; export const createPrompt: CreatePrompt = ( config, diff --git a/packages/forms/src/patterns/page/prompt.ts b/packages/forms/src/patterns/page/prompt.ts index ee0fb996..0427ec8b 100644 --- a/packages/forms/src/patterns/page/prompt.ts +++ b/packages/forms/src/patterns/page/prompt.ts @@ -2,8 +2,8 @@ import { type CreatePrompt, type PageProps, createPromptForPattern, - getPattern, -} from '../../index.js'; +} from '../../components.js'; +import { getPattern } from '../../pattern.js'; import { type PagePattern } from './config.js'; diff --git a/packages/forms/src/response.ts b/packages/forms/src/response.ts index 7018654a..b22296ad 100644 --- a/packages/forms/src/response.ts +++ b/packages/forms/src/response.ts @@ -1,17 +1,17 @@ import { type Result } from '@atj/common'; -import { - type FormConfig, - getPattern, - getPatternConfig, - validatePattern, -} from './index.js'; import { type PromptAction } from './components.js'; import { type FormErrorMap, type FormSession, updateSession, } from './session.js'; +import { + type FormConfig, + getPattern, + getPatternConfig, + validatePattern, +} from './pattern.js'; export type PromptResponse = { action: PromptAction['type']; diff --git a/packages/forms/src/services/submit-form.test.ts b/packages/forms/src/services/submit-form.test.ts index 8f59ab0d..5dba1149 100644 --- a/packages/forms/src/services/submit-form.test.ts +++ b/packages/forms/src/services/submit-form.test.ts @@ -1,20 +1,17 @@ import { describe, expect, it } from 'vitest'; -import { - createForm, - createFormSession, - type Blueprint, - type InputPattern, - type PagePattern, - type PageSetPattern, - type PatternValueMap, -} from '../index.js'; import { createTestFormServiceContext } from '../testing.js'; import { submitForm } from './submit-form.js'; import { createTestFormWithPDF, getMockFormData, } from '../documents/__tests__/test-documents.js'; +import { createFormSession } from '../session.js'; +import { createForm } from '../blueprint.js'; +import { type PageSetPattern } from '../patterns/page-set/config.js'; +import { type PagePattern } from '../patterns/page/config.js'; +import { type InputPattern } from '../patterns/input/config.js'; +import { type Blueprint } from '../types.js'; describe('submitForm', () => { it('fails with missing action string', async () => { diff --git a/packages/forms/src/services/submit-form.ts b/packages/forms/src/services/submit-form.ts index 3dd0d4e7..ef917b79 100644 --- a/packages/forms/src/services/submit-form.ts +++ b/packages/forms/src/services/submit-form.ts @@ -1,17 +1,17 @@ import { failure, success, type Result } from '@atj/common'; -import { - type Blueprint, - type FormSession, - type FormSessionId, - createFormSession, - defaultFormConfig, -} from '../index.js'; -import { FormServiceContext } from '../context/index.js'; +import { type FormServiceContext } from '../context/index.js'; import { submitPage } from '../patterns/page-set/submit'; import { downloadPackageHandler } from '../patterns/package-download/submit'; import { type FormRoute } from '../route-data.js'; import { SubmissionRegistry } from '../submission'; +import { + createFormSession, + type FormSession, + type FormSessionId, +} from '../session.js'; +import { defaultFormConfig } from '../patterns/index.js'; +import type { Blueprint } from '../types.js'; export type SubmitForm = ( ctx: FormServiceContext, diff --git a/packages/forms/src/session.ts b/packages/forms/src/session.ts index a1df98e2..76d5a49f 100644 --- a/packages/forms/src/session.ts +++ b/packages/forms/src/session.ts @@ -1,22 +1,16 @@ +import { type FormError } from './error.js'; +import { type SequencePattern } from './patterns/sequence.js'; import { - type Blueprint, type FormConfig, - type FormError, type Pattern, - getPatternConfig, - validatePattern, -} from './index.js'; -import { SequencePattern } from './patterns/sequence.js'; -import { type PatternId, type PatternValue, type PatternValueMap, + getPatternConfig, + validatePattern, } from './pattern.js'; -import { - type FormRoute, - type RouteData, - getRouteDataFromQueryString, -} from './route-data.js'; +import { type FormRoute, type RouteData } from './route-data.js'; +import { type Blueprint } from './types.js'; export type FormErrorMap = Record; diff --git a/packages/forms/src/types.ts b/packages/forms/src/types.ts new file mode 100644 index 00000000..e5147eb4 --- /dev/null +++ b/packages/forms/src/types.ts @@ -0,0 +1,21 @@ +import { type DocumentFieldMap } from './documents/types'; +import { type PatternId, type PatternMap } from './pattern'; + +export type Blueprint = { + summary: FormSummary; + root: PatternId; + patterns: PatternMap; + outputs: FormOutput[]; +}; + +export type FormSummary = { + title: string; + description: string; +}; + +export type FormOutput = { + data: Uint8Array; + path: string; + fields: DocumentFieldMap; + formFields: Record; +};