@pilotiq/pilotiq 0.24.1 → 0.24.2
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/CHANGELOG.md +33 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -225
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -194
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -704
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1243
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation primitives.
|
|
3
|
-
*
|
|
4
|
-
* A `Validator` is a function: `(value, ctx?) => string | null | Promise<string | null>`.
|
|
5
|
-
* It returns an error message when invalid, or `null` when the value
|
|
6
|
-
* passes. Validators may carry an optional `serialized` descriptor — a
|
|
7
|
-
* JSON-safe shape mirrored to the client via `Field.toMeta()` so the
|
|
8
|
-
* browser can run the same rule for live UX before submit.
|
|
9
|
-
*
|
|
10
|
-
* The runtime is server-of-truth: client-side mirroring is best-effort.
|
|
11
|
-
* If `serialized` is omitted, the validator runs only on the server.
|
|
12
|
-
*
|
|
13
|
-
* Async validators (e.g. `unique()` probing the DB) return a `Promise`;
|
|
14
|
-
* the pipeline awaits each result before collecting the next field's
|
|
15
|
-
* errors. They are NOT mirrored to the client — the descriptor is omitted
|
|
16
|
-
* or marks itself `async: true` so the client skips the live check.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/** JSON-safe descriptor of a built-in rule. Mirrored to the client. */
|
|
20
|
-
export interface SerializedRule {
|
|
21
|
-
rule: string
|
|
22
|
-
message?: string
|
|
23
|
-
[key: string]: unknown
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Optional context passed to a validator. `values` is the full submit
|
|
28
|
-
* payload — useful for cross-field rules (e.g. confirm-password). `record`
|
|
29
|
-
* is the persisted record under edit, when present.
|
|
30
|
-
*/
|
|
31
|
-
export interface ValidatorContext {
|
|
32
|
-
values?: Record<string, unknown>
|
|
33
|
-
record?: unknown
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type ValidatorFn = (
|
|
37
|
-
value: unknown,
|
|
38
|
-
ctx?: ValidatorContext,
|
|
39
|
-
) => string | null | Promise<string | null>
|
|
40
|
-
|
|
41
|
-
/** A validator function, optionally tagged with a serialized descriptor. */
|
|
42
|
-
export type Validator = ValidatorFn & { serialized?: SerializedRule }
|
|
43
|
-
|
|
44
|
-
/** Wrap a `ValidatorFn` and attach an optional serialized descriptor. */
|
|
45
|
-
export function makeValidator(fn: ValidatorFn, serialized?: SerializedRule): Validator {
|
|
46
|
-
const v = fn as Validator
|
|
47
|
-
if (serialized) v.serialized = serialized
|
|
48
|
-
return v
|
|
49
|
-
}
|
package/src/validation/index.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
makeValidator,
|
|
3
|
-
type Validator,
|
|
4
|
-
type ValidatorFn,
|
|
5
|
-
type ValidatorContext,
|
|
6
|
-
type SerializedRule,
|
|
7
|
-
} from './Validator.js'
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
required,
|
|
11
|
-
email,
|
|
12
|
-
minLength,
|
|
13
|
-
maxLength,
|
|
14
|
-
min,
|
|
15
|
-
max,
|
|
16
|
-
pattern,
|
|
17
|
-
} from './rules.js'
|
|
18
|
-
|
|
19
|
-
export {
|
|
20
|
-
validateSchema,
|
|
21
|
-
isValid,
|
|
22
|
-
type ValidationErrors,
|
|
23
|
-
} from './runValidators.js'
|
|
24
|
-
|
|
25
|
-
export {
|
|
26
|
-
unique,
|
|
27
|
-
type UniqueOptions,
|
|
28
|
-
} from './uniqueValidator.js'
|
package/src/validation/rules.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { makeValidator, type Validator } from './Validator.js'
|
|
2
|
-
|
|
3
|
-
const isEmpty = (v: unknown): boolean => v === undefined || v === null || v === ''
|
|
4
|
-
|
|
5
|
-
export function required(message = 'This field is required'): Validator {
|
|
6
|
-
return makeValidator(
|
|
7
|
-
v => (isEmpty(v) ? message : null),
|
|
8
|
-
{ rule: 'required', message },
|
|
9
|
-
)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
13
|
-
|
|
14
|
-
export function email(message = 'Must be a valid email'): Validator {
|
|
15
|
-
return makeValidator(
|
|
16
|
-
v => {
|
|
17
|
-
if (isEmpty(v)) return null
|
|
18
|
-
if (typeof v !== 'string' || !EMAIL_RE.test(v)) return message
|
|
19
|
-
return null
|
|
20
|
-
},
|
|
21
|
-
{ rule: 'email', message },
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function minLength(n: number, message = `Must be at least ${n} characters`): Validator {
|
|
26
|
-
return makeValidator(
|
|
27
|
-
v => {
|
|
28
|
-
if (isEmpty(v)) return null
|
|
29
|
-
if (typeof v !== 'string') return null
|
|
30
|
-
return v.length < n ? message : null
|
|
31
|
-
},
|
|
32
|
-
{ rule: 'minLength', value: n, message },
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function maxLength(n: number, message = `Must be at most ${n} characters`): Validator {
|
|
37
|
-
return makeValidator(
|
|
38
|
-
v => {
|
|
39
|
-
if (isEmpty(v)) return null
|
|
40
|
-
if (typeof v !== 'string') return null
|
|
41
|
-
return v.length > n ? message : null
|
|
42
|
-
},
|
|
43
|
-
{ rule: 'maxLength', value: n, message },
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function min(n: number, message = `Must be at least ${n}`): Validator {
|
|
48
|
-
return makeValidator(
|
|
49
|
-
v => {
|
|
50
|
-
if (isEmpty(v)) return null
|
|
51
|
-
if (typeof v !== 'number') return null
|
|
52
|
-
return v < n ? message : null
|
|
53
|
-
},
|
|
54
|
-
{ rule: 'min', value: n, message },
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function max(n: number, message = `Must be at most ${n}`): Validator {
|
|
59
|
-
return makeValidator(
|
|
60
|
-
v => {
|
|
61
|
-
if (isEmpty(v)) return null
|
|
62
|
-
if (typeof v !== 'number') return null
|
|
63
|
-
return v > n ? message : null
|
|
64
|
-
},
|
|
65
|
-
{ rule: 'max', value: n, message },
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function pattern(regex: RegExp, message = 'Invalid format'): Validator {
|
|
70
|
-
return makeValidator(
|
|
71
|
-
v => {
|
|
72
|
-
if (isEmpty(v)) return null
|
|
73
|
-
if (typeof v !== 'string') return null
|
|
74
|
-
return regex.test(v) ? null : message
|
|
75
|
-
},
|
|
76
|
-
{ rule: 'pattern', source: regex.source, flags: regex.flags, message },
|
|
77
|
-
)
|
|
78
|
-
}
|
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
import { Element } from '../schema/Element.js'
|
|
2
|
-
import { Field, type DistinctOptions } from '../fields/Field.js'
|
|
3
|
-
import { RepeaterField } from '../fields/RepeaterField.js'
|
|
4
|
-
import { BuilderField } from '../fields/BuilderField.js'
|
|
5
|
-
|
|
6
|
-
export interface ValidationErrors {
|
|
7
|
-
[fieldName: string]: string[]
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Walk an Element tree, run every Field's validators against the matching
|
|
12
|
-
* value in `values`, and return a `{ name -> errors[] }` map.
|
|
13
|
-
*
|
|
14
|
-
* The map is empty when every field passes. Fields with no validators
|
|
15
|
-
* (and no `required` flag) never appear in the output. The caller decides
|
|
16
|
-
* whether a non-empty map should reject the submit.
|
|
17
|
-
*
|
|
18
|
-
* `record` is the persisted record under edit, when present — it propagates
|
|
19
|
-
* to each validator's `ctx` for cross-field rules that need it.
|
|
20
|
-
*
|
|
21
|
-
* Plan #14 — Repeater fields validate each row's inner schema recursively.
|
|
22
|
-
* Per-row errors are flat-keyed as `${fieldName}.${i}.${childName}` so the
|
|
23
|
-
* client can surface them inline on the right row. `minItems` / `maxItems`
|
|
24
|
-
* land under the bare repeater name.
|
|
25
|
-
*
|
|
26
|
-
* Async — `Field.runValidators` is awaited per field, so async validators
|
|
27
|
-
* (e.g. `unique()` probing the DB) work transparently. The walker is still
|
|
28
|
-
* sequential per field within the same scope to preserve declaration order
|
|
29
|
-
* in the error output; cross-field parallelism would only matter on forms
|
|
30
|
-
* with many DB-probing validators and is left for a future profile.
|
|
31
|
-
*/
|
|
32
|
-
export async function validateSchema(
|
|
33
|
-
elements: Element[],
|
|
34
|
-
values: Record<string, unknown>,
|
|
35
|
-
record?: unknown,
|
|
36
|
-
): Promise<ValidationErrors> {
|
|
37
|
-
const errors: ValidationErrors = {}
|
|
38
|
-
const fields: Field[] = []
|
|
39
|
-
const repeaters: RepeaterField[] = []
|
|
40
|
-
const builders: BuilderField[] = []
|
|
41
|
-
|
|
42
|
-
// Two-pass to preserve order: collect first (sync walk), then await each.
|
|
43
|
-
walk(elements, el => {
|
|
44
|
-
if (el instanceof RepeaterField) { repeaters.push(el); return }
|
|
45
|
-
if (el instanceof BuilderField) { builders.push(el); return }
|
|
46
|
-
if (el instanceof Field) fields.push(el)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
for (const el of fields) {
|
|
50
|
-
const value = values[el.name]
|
|
51
|
-
const ctx: { values: Record<string, unknown>; record?: unknown } = { values }
|
|
52
|
-
if (record !== undefined) ctx.record = record
|
|
53
|
-
const fieldErrors = await el.runValidators(value, ctx)
|
|
54
|
-
if (fieldErrors.length > 0) errors[el.name] = fieldErrors
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
for (const rep of repeaters) {
|
|
58
|
-
const raw = values[rep.name]
|
|
59
|
-
const rows = Array.isArray(raw) ? raw : foldFlatRepeaterRows(values, rep.name)
|
|
60
|
-
await validateRepeater(rep, rows, record, errors)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
for (const bld of builders) {
|
|
64
|
-
const raw = values[bld.name]
|
|
65
|
-
const rows = Array.isArray(raw) ? raw : foldFlatBuilderRows(values, bld.name)
|
|
66
|
-
await validateBuilder(bld, rows, record, errors)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return errors
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function validateRepeater(
|
|
73
|
-
field: RepeaterField,
|
|
74
|
-
raw: unknown,
|
|
75
|
-
record: unknown,
|
|
76
|
-
errors: ValidationErrors,
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
const rows = Array.isArray(raw) ? raw : []
|
|
79
|
-
const baseErrors: string[] = []
|
|
80
|
-
|
|
81
|
-
const min = field.getMinItems()
|
|
82
|
-
if (min !== undefined && rows.length < min) {
|
|
83
|
-
baseErrors.push(min === 1
|
|
84
|
-
? 'At least 1 item is required'
|
|
85
|
-
: `At least ${min} items are required`)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const max = field.getMaxItems()
|
|
89
|
-
if (max !== undefined && rows.length > max) {
|
|
90
|
-
baseErrors.push(max === 1
|
|
91
|
-
? 'At most 1 item is allowed'
|
|
92
|
-
: `At most ${max} items are allowed`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (baseErrors.length > 0) errors[field.name] = baseErrors
|
|
96
|
-
|
|
97
|
-
const inner = field.getInnerSchema()
|
|
98
|
-
const simpleInner = field.getSimpleInnerField()
|
|
99
|
-
for (let i = 0; i < rows.length; i++) {
|
|
100
|
-
const row = rows[i]
|
|
101
|
-
// For `simple()` repeaters, wrap primitive entries inline so per-field
|
|
102
|
-
// validators run against the standard `{ <innerName>: v }` shape. A
|
|
103
|
-
// primitive in a non-simple Repeater is genuinely malformed and gets
|
|
104
|
-
// skipped (the prior posture).
|
|
105
|
-
let rowValues: Record<string, unknown> | null
|
|
106
|
-
if (row && typeof row === 'object' && !Array.isArray(row)) {
|
|
107
|
-
rowValues = row as Record<string, unknown>
|
|
108
|
-
} else if (simpleInner && row !== undefined) {
|
|
109
|
-
rowValues = { [simpleInner.name]: row }
|
|
110
|
-
} else {
|
|
111
|
-
rowValues = null
|
|
112
|
-
}
|
|
113
|
-
if (!rowValues) continue
|
|
114
|
-
const rowErrors = await validateSchema(inner, rowValues, record)
|
|
115
|
-
for (const [childName, msgs] of Object.entries(rowErrors)) {
|
|
116
|
-
errors[`${field.name}.${i}.${childName}`] = msgs
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
checkDistinctRows({
|
|
121
|
-
fieldName: field.name,
|
|
122
|
-
innerFields: collectDistinctFields(inner),
|
|
123
|
-
rows,
|
|
124
|
-
errorKey: (rowIdx, childName) => `${field.name}.${rowIdx}.${childName}`,
|
|
125
|
-
// `simple()` repeaters can hand bare primitive rows to the comparator;
|
|
126
|
-
// when the row matches the inner field, treat the primitive itself as
|
|
127
|
-
// the value. Object rows still read by child key as normal.
|
|
128
|
-
extractValue: (row, childName) => {
|
|
129
|
-
if (row && typeof row === 'object' && !Array.isArray(row)) {
|
|
130
|
-
return (row as Record<string, unknown>)[childName]
|
|
131
|
-
}
|
|
132
|
-
if (simpleInner && simpleInner.name === childName) return row
|
|
133
|
-
return undefined
|
|
134
|
-
},
|
|
135
|
-
rowMatches: () => true,
|
|
136
|
-
errors,
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Validate a single Builder field's rows. Per-row errors are flat-keyed
|
|
142
|
-
* `${field.name}.${i}.data.${childName}` so the client can surface them
|
|
143
|
-
* inline on the right row's inner field. `minItems` / `maxItems` /
|
|
144
|
-
* `Block.maxItems` (per-block-type ceiling) and unknown-block-type
|
|
145
|
-
* errors land under the bare builder name.
|
|
146
|
-
*/
|
|
147
|
-
async function validateBuilder(
|
|
148
|
-
field: BuilderField,
|
|
149
|
-
raw: unknown,
|
|
150
|
-
record: unknown,
|
|
151
|
-
errors: ValidationErrors,
|
|
152
|
-
): Promise<void> {
|
|
153
|
-
const rows = Array.isArray(raw) ? raw : []
|
|
154
|
-
const baseErrors: string[] = []
|
|
155
|
-
|
|
156
|
-
const min = field.getMinItems()
|
|
157
|
-
if (min !== undefined && rows.length < min) {
|
|
158
|
-
baseErrors.push(min === 1
|
|
159
|
-
? 'At least 1 item is required'
|
|
160
|
-
: `At least ${min} items are required`)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const max = field.getMaxItems()
|
|
164
|
-
if (max !== undefined && rows.length > max) {
|
|
165
|
-
baseErrors.push(max === 1
|
|
166
|
-
? 'At most 1 item is allowed'
|
|
167
|
-
: `At most ${max} items are allowed`)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Per-block-type ceiling (`Block.maxItems`) is enforced post-row-walk
|
|
171
|
-
// so we can count the rows of each type in one pass.
|
|
172
|
-
const typeCounts = new Map<string, number>()
|
|
173
|
-
for (let i = 0; i < rows.length; i++) {
|
|
174
|
-
const row = rows[i]
|
|
175
|
-
if (!row || typeof row !== 'object' || Array.isArray(row)) continue
|
|
176
|
-
const t = (row as Record<string, unknown>)['type']
|
|
177
|
-
if (typeof t === 'string' && t !== '') {
|
|
178
|
-
typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
for (const block of field.getBlocks()) {
|
|
182
|
-
const cap = block.getMaxItems()
|
|
183
|
-
if (cap === undefined) continue
|
|
184
|
-
const count = typeCounts.get(block.name) ?? 0
|
|
185
|
-
if (count > cap) {
|
|
186
|
-
baseErrors.push(cap === 1
|
|
187
|
-
? `At most 1 "${block.getLabel()}" block is allowed`
|
|
188
|
-
: `At most ${cap} "${block.getLabel()}" blocks are allowed`)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (let i = 0; i < rows.length; i++) {
|
|
193
|
-
const row = rows[i]
|
|
194
|
-
if (!row || typeof row !== 'object' || Array.isArray(row)) continue
|
|
195
|
-
const r = row as Record<string, unknown>
|
|
196
|
-
const t = r['type']
|
|
197
|
-
if (typeof t !== 'string' || t === '') {
|
|
198
|
-
errors[`${field.name}.${i}`] = ['Block type is required']
|
|
199
|
-
continue
|
|
200
|
-
}
|
|
201
|
-
const block = field.getBlock(t)
|
|
202
|
-
if (!block) {
|
|
203
|
-
errors[`${field.name}.${i}`] = [`Unknown block type "${t}"`]
|
|
204
|
-
continue
|
|
205
|
-
}
|
|
206
|
-
const dataRaw = r['data']
|
|
207
|
-
const data: Record<string, unknown> = (dataRaw && typeof dataRaw === 'object' && !Array.isArray(dataRaw))
|
|
208
|
-
? (dataRaw as Record<string, unknown>)
|
|
209
|
-
: {}
|
|
210
|
-
const rowErrors = await validateSchema(block.getSchema(), data, record)
|
|
211
|
-
for (const [childName, msgs] of Object.entries(rowErrors)) {
|
|
212
|
-
errors[`${field.name}.${i}.data.${childName}`] = msgs
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Distinct check is per-block-type — comparing a `heading.title` against a
|
|
217
|
-
// `paragraph.text` is meaningless (different schemas, different fields).
|
|
218
|
-
// We re-walk the rows once per block type so each pass only sees rows
|
|
219
|
-
// matching that type.
|
|
220
|
-
for (const block of field.getBlocks()) {
|
|
221
|
-
const distinctFields = collectDistinctFields(block.getSchema())
|
|
222
|
-
if (distinctFields.length === 0) continue
|
|
223
|
-
checkDistinctRows({
|
|
224
|
-
fieldName: field.name,
|
|
225
|
-
innerFields: distinctFields,
|
|
226
|
-
rows,
|
|
227
|
-
errorKey: (rowIdx, childName) => `${field.name}.${rowIdx}.data.${childName}`,
|
|
228
|
-
extractValue: (row, childName) => {
|
|
229
|
-
if (!row || typeof row !== 'object' || Array.isArray(row)) return undefined
|
|
230
|
-
const r = row as Record<string, unknown>
|
|
231
|
-
const data = r['data']
|
|
232
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) return undefined
|
|
233
|
-
return (data as Record<string, unknown>)[childName]
|
|
234
|
-
},
|
|
235
|
-
rowMatches: row => {
|
|
236
|
-
if (!row || typeof row !== 'object' || Array.isArray(row)) return false
|
|
237
|
-
return (row as Record<string, unknown>)['type'] === block.name
|
|
238
|
-
},
|
|
239
|
-
errors,
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (baseErrors.length > 0) errors[field.name] = baseErrors
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Walk the inner schema for fields with `.distinct()` set. Stops at any
|
|
248
|
-
* nested Repeater / Builder so the outer field's distinct pass doesn't
|
|
249
|
-
* accidentally walk into a child array's leaves (those rows are
|
|
250
|
-
* validated by their own array-field pass when they're submitted).
|
|
251
|
-
*/
|
|
252
|
-
function collectDistinctFields(elements: Element[]): Array<{ field: Field; opts: DistinctOptions }> {
|
|
253
|
-
const out: Array<{ field: Field; opts: DistinctOptions }> = []
|
|
254
|
-
const visit = (els: Element[]): void => {
|
|
255
|
-
for (const el of els) {
|
|
256
|
-
if (el instanceof RepeaterField) continue
|
|
257
|
-
if (el instanceof BuilderField) continue
|
|
258
|
-
if (el instanceof Field) {
|
|
259
|
-
const opts = el.getDistinct()
|
|
260
|
-
if (opts) out.push({ field: el, opts })
|
|
261
|
-
}
|
|
262
|
-
const children = el.getChildren()
|
|
263
|
-
if (children && children.length > 0) visit(children)
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
visit(elements)
|
|
267
|
-
return out
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Cross-row distinct comparison shared by Repeater + Builder. The first
|
|
272
|
-
* occurrence of each value passes; subsequent rows with the same value
|
|
273
|
-
* (after `caseInsensitive` / `ignoreNulls` normalization) get the
|
|
274
|
-
* configured rejection message under their flat-keyed error path.
|
|
275
|
-
*
|
|
276
|
-
* `rowMatches` lets the Builder caller scope the comparison to a single
|
|
277
|
-
* block type; Repeater always passes `() => true`.
|
|
278
|
-
*/
|
|
279
|
-
function checkDistinctRows(args: {
|
|
280
|
-
fieldName: string
|
|
281
|
-
innerFields: Array<{ field: Field; opts: DistinctOptions }>
|
|
282
|
-
rows: unknown[]
|
|
283
|
-
errorKey: (rowIdx: number, childName: string) => string
|
|
284
|
-
extractValue: (row: unknown, childName: string) => unknown
|
|
285
|
-
rowMatches: (row: unknown) => boolean
|
|
286
|
-
errors: ValidationErrors
|
|
287
|
-
}): void {
|
|
288
|
-
if (args.innerFields.length === 0) return
|
|
289
|
-
for (const { field: child, opts } of args.innerFields) {
|
|
290
|
-
const ignoreNulls = opts.ignoreNulls !== false // default true
|
|
291
|
-
const seen = new Map<unknown, number>()
|
|
292
|
-
for (let i = 0; i < args.rows.length; i++) {
|
|
293
|
-
const row = args.rows[i]
|
|
294
|
-
if (!args.rowMatches(row)) continue
|
|
295
|
-
const value = args.extractValue(row, child.name)
|
|
296
|
-
if (ignoreNulls && (value === undefined || value === null || value === '')) continue
|
|
297
|
-
const key = (opts.caseInsensitive && typeof value === 'string')
|
|
298
|
-
? value.toLowerCase()
|
|
299
|
-
: value
|
|
300
|
-
if (!seen.has(key)) {
|
|
301
|
-
seen.set(key, i)
|
|
302
|
-
continue
|
|
303
|
-
}
|
|
304
|
-
const errKey = args.errorKey(i, child.name)
|
|
305
|
-
const message = opts.message ?? 'Must be unique'
|
|
306
|
-
const existing = args.errors[errKey]
|
|
307
|
-
if (existing) existing.push(message)
|
|
308
|
-
else args.errors[errKey] = [message]
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Group flat-key form-encoded Builder rows into an array of row bodies
|
|
315
|
-
* shaped `{ __id?, type, data: {…} }`. Mirrors `coerceBuilderValue` in
|
|
316
|
-
* `dispatchForm.ts`. Validation runs before coercion so we can't reuse
|
|
317
|
-
* the coerced array directly.
|
|
318
|
-
*/
|
|
319
|
-
function foldFlatBuilderRows(
|
|
320
|
-
values: Record<string, unknown>,
|
|
321
|
-
fieldName: string,
|
|
322
|
-
): Array<Record<string, unknown>> {
|
|
323
|
-
const prefix = `${fieldName}.`
|
|
324
|
-
const grouped = new Map<number, { __id?: string; type: string; data: Record<string, unknown> }>()
|
|
325
|
-
let maxIdx = -1
|
|
326
|
-
for (const key of Object.keys(values)) {
|
|
327
|
-
if (!key.startsWith(prefix)) continue
|
|
328
|
-
const rest = key.slice(prefix.length)
|
|
329
|
-
const dot = rest.indexOf('.')
|
|
330
|
-
if (dot < 0) continue
|
|
331
|
-
const idxStr = rest.slice(0, dot)
|
|
332
|
-
const tail = rest.slice(dot + 1)
|
|
333
|
-
const idx = Number(idxStr)
|
|
334
|
-
if (!Number.isInteger(idx) || idx < 0) continue
|
|
335
|
-
if (idx > maxIdx) maxIdx = idx
|
|
336
|
-
let row = grouped.get(idx)
|
|
337
|
-
if (!row) { row = { type: '', data: {} }; grouped.set(idx, row) }
|
|
338
|
-
const value = values[key]
|
|
339
|
-
if (tail === '__id') {
|
|
340
|
-
if (typeof value === 'string') row.__id = value
|
|
341
|
-
} else if (tail === 'type') {
|
|
342
|
-
if (typeof value === 'string') row.type = value
|
|
343
|
-
} else if (tail.startsWith('data.')) {
|
|
344
|
-
row.data[tail.slice('data.'.length)] = value
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (maxIdx < 0) return []
|
|
348
|
-
const out: Array<Record<string, unknown>> = []
|
|
349
|
-
for (let i = 0; i <= maxIdx; i++) {
|
|
350
|
-
const row = grouped.get(i) ?? { type: '', data: {} }
|
|
351
|
-
const obj: Record<string, unknown> = { type: row.type, data: row.data }
|
|
352
|
-
if (typeof row.__id === 'string') obj['__id'] = row.__id
|
|
353
|
-
out.push(obj)
|
|
354
|
-
}
|
|
355
|
-
while (out.length > 0 && isBuilderRowEmpty(out[out.length - 1]!)) out.pop()
|
|
356
|
-
return out
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function isBuilderRowEmpty(row: Record<string, unknown>): boolean {
|
|
360
|
-
const data = row['data']
|
|
361
|
-
if (!data || typeof data !== 'object' || Array.isArray(data)) return true
|
|
362
|
-
for (const [, v] of Object.entries(data as Record<string, unknown>)) {
|
|
363
|
-
if (v === undefined || v === null || v === '') continue
|
|
364
|
-
return false
|
|
365
|
-
}
|
|
366
|
-
return true
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** True when no field returned any error. */
|
|
370
|
-
export function isValid(errors: ValidationErrors): boolean {
|
|
371
|
-
return Object.keys(errors).length === 0
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Group flat-key form-encoded Repeater rows into an array of row bodies.
|
|
376
|
-
* Mirrors the same fold path as `coerceRepeaterValue` in
|
|
377
|
-
* `dispatchForm.ts` but kept private here so validation works against
|
|
378
|
-
* raw bodies (validation runs before coercion in `dispatchFormSubmit`).
|
|
379
|
-
*
|
|
380
|
-
* Returns an empty array when no matching keys exist — the Repeater's
|
|
381
|
-
* `minItems` validator then surfaces the right error.
|
|
382
|
-
*/
|
|
383
|
-
function foldFlatRepeaterRows(
|
|
384
|
-
values: Record<string, unknown>,
|
|
385
|
-
fieldName: string,
|
|
386
|
-
): Array<Record<string, unknown>> {
|
|
387
|
-
const prefix = `${fieldName}.`
|
|
388
|
-
const grouped = new Map<number, Record<string, unknown>>()
|
|
389
|
-
let maxIdx = -1
|
|
390
|
-
for (const key of Object.keys(values)) {
|
|
391
|
-
if (!key.startsWith(prefix)) continue
|
|
392
|
-
const rest = key.slice(prefix.length)
|
|
393
|
-
const dot = rest.indexOf('.')
|
|
394
|
-
if (dot < 0) continue
|
|
395
|
-
const idxStr = rest.slice(0, dot)
|
|
396
|
-
const childKey = rest.slice(dot + 1)
|
|
397
|
-
const idx = Number(idxStr)
|
|
398
|
-
if (!Number.isInteger(idx) || idx < 0) continue
|
|
399
|
-
if (idx > maxIdx) maxIdx = idx
|
|
400
|
-
let row = grouped.get(idx)
|
|
401
|
-
if (!row) { row = {}; grouped.set(idx, row) }
|
|
402
|
-
row[childKey] = values[key]
|
|
403
|
-
}
|
|
404
|
-
if (maxIdx < 0) return []
|
|
405
|
-
// Trim trailing untouched rows so `minItems` lines up with what
|
|
406
|
-
// coercion will eventually persist.
|
|
407
|
-
const out: Array<Record<string, unknown>> = []
|
|
408
|
-
for (let i = 0; i <= maxIdx; i++) out.push(grouped.get(i) ?? {})
|
|
409
|
-
while (out.length > 0 && isRowEmpty(out[out.length - 1]!)) out.pop()
|
|
410
|
-
return out
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function isRowEmpty(row: Record<string, unknown>): boolean {
|
|
414
|
-
for (const [k, v] of Object.entries(row)) {
|
|
415
|
-
if (k === '__id') continue
|
|
416
|
-
if (v === undefined || v === null || v === '') continue
|
|
417
|
-
return false
|
|
418
|
-
}
|
|
419
|
-
return true
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
function walk(elements: Element[], visit: (el: Element) => void): void {
|
|
423
|
-
for (const el of elements) {
|
|
424
|
-
visit(el)
|
|
425
|
-
// Plan #14 — don't recurse into Repeater / Builder children. The
|
|
426
|
-
// visitor handles per-row validation by calling `validateSchema`
|
|
427
|
-
// recursively against the row's local values map (per-block schema
|
|
428
|
-
// for Builder). Recursing here would validate inner fields against
|
|
429
|
-
// the parent `values`, missing the row scope entirely.
|
|
430
|
-
if (el instanceof RepeaterField) continue
|
|
431
|
-
if (el instanceof BuilderField) continue
|
|
432
|
-
const children = el.getChildren()
|
|
433
|
-
if (children && children.length > 0) walk(children, visit)
|
|
434
|
-
}
|
|
435
|
-
}
|