@pilotiq/pilotiq 0.24.1 → 0.24.3
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 +57 -0
- package/boost/guidelines.md +571 -0
- package/boost/skills/pilotiq-actions/SKILL.md +49 -0
- package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
- package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
- package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -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/dist/Pilotiq.d.ts +31 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +3 -1
- package/dist/Pilotiq.js.map +1 -1
- package/dist/PilotiqRegistry.d.ts +13 -0
- package/dist/PilotiqRegistry.d.ts.map +1 -1
- package/dist/PilotiqRegistry.js +15 -0
- package/dist/PilotiqRegistry.js.map +1 -1
- package/dist/pageData/misc.d.ts.map +1 -1
- package/dist/pageData/misc.js +6 -0
- package/dist/pageData/misc.js.map +1 -1
- package/dist/pageData/navigation.d.ts +1 -0
- package/dist/pageData/navigation.d.ts.map +1 -1
- package/dist/pageData/navigation.js +3 -0
- package/dist/pageData/navigation.js.map +1 -1
- package/dist/pageData/relationPages.d.ts.map +1 -1
- package/dist/pageData/relationPages.js +3 -0
- package/dist/pageData/relationPages.js.map +1 -1
- package/dist/pageData/resourcePages.d.ts.map +1 -1
- package/dist/pageData/resourcePages.js +8 -0
- package/dist/pageData/resourcePages.js.map +1 -1
- package/dist/react/AppShell.d.ts +8 -0
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/react/layouts/SidebarLayout.js +10 -2
- package/dist/react/layouts/SidebarLayout.js.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
- package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +25 -18
- package/dist/routes/relations.js.map +1 -1
- package/dist/routes/resources.js.map +1 -1
- package/package.json +10 -5
- 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,398 +0,0 @@
|
|
|
1
|
-
import { Filter, type FilterKind, type FilterMeta } from './Filter.js'
|
|
2
|
-
import type { ModelQuery } from '../orm/modelDefaults.js'
|
|
3
|
-
import {
|
|
4
|
-
type Constraint,
|
|
5
|
-
type ConstraintOperatorName,
|
|
6
|
-
} from './queryBuilder/Constraint.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Wire shape of a single condition row in the QueryBuilder tree.
|
|
10
|
-
*
|
|
11
|
-
* - `constraint` — the `Constraint.name` to dispatch against
|
|
12
|
-
* - `operator` — one of the constraint's advertised operator names
|
|
13
|
-
* - `value` — operator-dependent payload (string / number / array)
|
|
14
|
-
*
|
|
15
|
-
* Rules whose `constraint` doesn't resolve at apply time are silently
|
|
16
|
-
* skipped — config drift (renaming a column) shouldn't 500 the page.
|
|
17
|
-
*/
|
|
18
|
-
export interface QueryBuilderRule {
|
|
19
|
-
constraint: string
|
|
20
|
-
operator: ConstraintOperatorName
|
|
21
|
-
value?: unknown
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Top-level (or nested) shape of the QueryBuilder URL value. Each node
|
|
26
|
-
* carries a connector (`'and' | 'or'`) and a list of child rules — every
|
|
27
|
-
* child is either a single condition (`QueryBuilderRule`) or another
|
|
28
|
-
* sub-tree, so groups can nest arbitrarily deep.
|
|
29
|
-
*
|
|
30
|
-
* v2 dispatches the tree through the rudder ORM's `whereGroup` /
|
|
31
|
-
* `orWhereGroup` callbacks so nested OR / nested AND compose without
|
|
32
|
-
* leaking conditions into surrounding `where` clauses (search / tab
|
|
33
|
-
* predicate / sibling filters). When the underlying query lacks
|
|
34
|
-
* `whereGroup` (custom test stubs, exotic adapters), `applyTreeToQuery`
|
|
35
|
-
* falls back to flat AND chaining for compatibility.
|
|
36
|
-
*/
|
|
37
|
-
export interface QueryBuilderTree {
|
|
38
|
-
operator: 'and' | 'or'
|
|
39
|
-
rules: QueryBuilderTreeChild[]
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** A child node in a tree — either a single rule or another sub-tree. */
|
|
43
|
-
export type QueryBuilderTreeChild = QueryBuilderRule | QueryBuilderTree
|
|
44
|
-
|
|
45
|
-
/** Type predicate: is this node a sub-tree (vs. a leaf rule)? */
|
|
46
|
-
export function isQueryBuilderTree(node: QueryBuilderTreeChild): node is QueryBuilderTree {
|
|
47
|
-
if (!node || typeof node !== 'object') return false
|
|
48
|
-
const o = node as unknown as Record<string, unknown>
|
|
49
|
-
return Array.isArray(o['rules']) && (o['operator'] === 'and' || o['operator'] === 'or')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Empty tree — used when the URL key is absent or unparseable. */
|
|
53
|
-
export function emptyQueryBuilderTree(): QueryBuilderTree {
|
|
54
|
-
return { operator: 'and', rules: [] }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Parse the URL-encoded JSON payload back into a tree. Empty string,
|
|
59
|
-
* non-object, or malformed payloads yield the empty tree (silently —
|
|
60
|
-
* never throws so a tampered URL can't 500 the list page).
|
|
61
|
-
*
|
|
62
|
-
* Recurses into nested groups; child nodes that are neither well-formed
|
|
63
|
-
* rules nor well-formed sub-trees are dropped silently.
|
|
64
|
-
*/
|
|
65
|
-
export function parseQueryBuilderValue(value: string | undefined): QueryBuilderTree {
|
|
66
|
-
if (!value) return emptyQueryBuilderTree()
|
|
67
|
-
try {
|
|
68
|
-
const parsed = JSON.parse(value)
|
|
69
|
-
return parseTreeNode(parsed) ?? emptyQueryBuilderTree()
|
|
70
|
-
} catch {
|
|
71
|
-
return emptyQueryBuilderTree()
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function parseTreeNode(raw: unknown): QueryBuilderTree | undefined {
|
|
76
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return undefined
|
|
77
|
-
const op = (raw as { operator?: unknown }).operator
|
|
78
|
-
const rules = (raw as { rules?: unknown }).rules
|
|
79
|
-
if (!Array.isArray(rules)) return undefined
|
|
80
|
-
const children: QueryBuilderTreeChild[] = []
|
|
81
|
-
for (const r of rules) {
|
|
82
|
-
if (isRawTree(r)) {
|
|
83
|
-
const sub = parseTreeNode(r)
|
|
84
|
-
if (sub) children.push(sub)
|
|
85
|
-
} else if (isRule(r)) {
|
|
86
|
-
children.push(r)
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
operator: op === 'or' ? 'or' : 'and',
|
|
91
|
-
rules: children,
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function isRawTree(r: unknown): boolean {
|
|
96
|
-
if (!r || typeof r !== 'object') return false
|
|
97
|
-
const o = r as Record<string, unknown>
|
|
98
|
-
return Array.isArray(o['rules']) && (o['operator'] === 'and' || o['operator'] === 'or')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isRule(r: unknown): r is QueryBuilderRule {
|
|
102
|
-
if (!r || typeof r !== 'object') return false
|
|
103
|
-
const o = r as Record<string, unknown>
|
|
104
|
-
return typeof o['constraint'] === 'string' && typeof o['operator'] === 'string'
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Encode a tree back into the canonical URL payload. Empty trees (zero
|
|
109
|
-
* meaningful descendants) yield `''` so the caller can drop the URL key
|
|
110
|
-
* entirely rather than emitting a stub envelope.
|
|
111
|
-
*
|
|
112
|
-
* Rules with no `value` AND a value-bearing operator are dropped on
|
|
113
|
-
* encode so the URL doesn't bloat with half-typed in-progress rows.
|
|
114
|
-
* Sub-trees that collapse to zero meaningful children are dropped too.
|
|
115
|
-
*/
|
|
116
|
-
export function encodeQueryBuilderValue(tree: QueryBuilderTree): string {
|
|
117
|
-
const compacted = compactTree(tree)
|
|
118
|
-
if (!compacted) return ''
|
|
119
|
-
return JSON.stringify(compacted)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function compactTree(tree: QueryBuilderTree): QueryBuilderTree | undefined {
|
|
123
|
-
const out: QueryBuilderTreeChild[] = []
|
|
124
|
-
for (const child of tree.rules) {
|
|
125
|
-
if (isQueryBuilderTree(child)) {
|
|
126
|
-
const sub = compactTree(child)
|
|
127
|
-
if (sub) out.push(sub)
|
|
128
|
-
} else if (isMeaningfulRule(child)) {
|
|
129
|
-
out.push(child)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (out.length === 0) return undefined
|
|
133
|
-
return { operator: tree.operator, rules: out }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function isMeaningfulRule(r: QueryBuilderRule): boolean {
|
|
137
|
-
if (!r.constraint || !r.operator) return false
|
|
138
|
-
// value-less operators (isEmpty / isNotEmpty / isTrue / isFalse) are valid
|
|
139
|
-
// standalone — keep them even with no `value`.
|
|
140
|
-
if (isValuelessOperator(r.operator)) return true
|
|
141
|
-
if (r.value === undefined || r.value === null || r.value === '') return false
|
|
142
|
-
if (Array.isArray(r.value) && r.value.every(v => v === undefined || v === null || v === '')) return false
|
|
143
|
-
return true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function isValuelessOperator(op: ConstraintOperatorName): boolean {
|
|
147
|
-
return op === 'isEmpty' || op === 'isNotEmpty' || op === 'isTrue' || op === 'isFalse'
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Typed query callback — receives the parsed tree (vs the base
|
|
152
|
-
* `FilterQueryHandler`'s raw URL string). Override the default tree-walk
|
|
153
|
-
* via `QueryBuilderFilter.handle(fn)` when constraints can't model your
|
|
154
|
-
* query (e.g. cross-table joins).
|
|
155
|
-
*/
|
|
156
|
-
export type QueryBuilderQueryHandler = (
|
|
157
|
-
query: ModelQuery,
|
|
158
|
-
tree: QueryBuilderTree,
|
|
159
|
-
filter: QueryBuilderFilter,
|
|
160
|
-
) => ModelQuery
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Typed indicator-pill formatter — mirrors `FormFilter.formIndicator`.
|
|
164
|
-
* Default pill text is `"<Label>: N condition(s)"`.
|
|
165
|
-
*/
|
|
166
|
-
export type QueryBuilderIndicatorHandler = (
|
|
167
|
-
tree: QueryBuilderTree,
|
|
168
|
-
filter: QueryBuilderFilter,
|
|
169
|
-
) => string
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Composable advanced-filter — Filament-style. Lets end users compose
|
|
173
|
-
* multiple constraint rules at runtime against any pre-declared column,
|
|
174
|
-
* without requiring a developer to add a per-column filter.
|
|
175
|
-
*
|
|
176
|
-
* v1 = single root operator (AND), flat rule list. URL value is a single
|
|
177
|
-
* JSON-encoded payload `?advanced={"operator":"and","rules":[…]}`. The
|
|
178
|
-
* default `query()` callback walks the tree depth-first and dispatches
|
|
179
|
-
* each rule through its `Constraint.apply()` — every clause AND's into
|
|
180
|
-
* the running query.
|
|
181
|
-
*
|
|
182
|
-
* @example
|
|
183
|
-
* QueryBuilderFilter.make('advanced')
|
|
184
|
-
* .label('Advanced')
|
|
185
|
-
* .constraints([
|
|
186
|
-
* TextConstraint.make('title'),
|
|
187
|
-
* NumberConstraint.make('amount'),
|
|
188
|
-
* SelectConstraint.make('status').options([
|
|
189
|
-
* { value: 'draft', label: 'Draft' },
|
|
190
|
-
* { value: 'published', label: 'Published' },
|
|
191
|
-
* ]),
|
|
192
|
-
* ])
|
|
193
|
-
*/
|
|
194
|
-
export class QueryBuilderFilter extends Filter {
|
|
195
|
-
private _constraints: Constraint[] = []
|
|
196
|
-
|
|
197
|
-
static make(name: string): QueryBuilderFilter {
|
|
198
|
-
const f = new QueryBuilderFilter(name)
|
|
199
|
-
f.query((q, value) => {
|
|
200
|
-
const tree = parseQueryBuilderValue(value)
|
|
201
|
-
return applyTreeToQuery(q, tree, f._constraints)
|
|
202
|
-
})
|
|
203
|
-
return f
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
constraints(list: Constraint[]): this {
|
|
207
|
-
this._constraints = list
|
|
208
|
-
return this
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
getConstraints(): Constraint[] { return this._constraints }
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Override the default tree-walk. Receives the parsed tree (not the raw
|
|
215
|
-
* URL string) plus a back-reference to the filter so the handler can
|
|
216
|
-
* read `getConstraints()` if it wants to layer custom logic on top of
|
|
217
|
-
* the standard dispatch.
|
|
218
|
-
*/
|
|
219
|
-
handle(fn: QueryBuilderQueryHandler): this {
|
|
220
|
-
this.query((q, value) => fn(q, parseQueryBuilderValue(value), this))
|
|
221
|
-
return this
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Typed indicator override — receives the parsed tree (mirrors
|
|
226
|
-
* `FormFilter.formIndicator`).
|
|
227
|
-
*/
|
|
228
|
-
treeIndicator(fn: QueryBuilderIndicatorHandler): this {
|
|
229
|
-
this.indicator((value: string) => fn(parseQueryBuilderValue(value), this))
|
|
230
|
-
return this
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
override getKind(): FilterKind { return 'queryBuilder' }
|
|
234
|
-
|
|
235
|
-
protected override formatActiveValue(value: string): string {
|
|
236
|
-
const tree = parseQueryBuilderValue(value)
|
|
237
|
-
const n = countLeafRules(tree)
|
|
238
|
-
if (n === 0) return ''
|
|
239
|
-
return `${n} condition${n === 1 ? '' : 's'}`
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Override so that a stored value of `'{"operator":"and","rules":[]}'`
|
|
244
|
-
* (legitimately parsed JSON with zero rules — including trees where every
|
|
245
|
-
* rule was dropped as empty) doesn't paint an empty `"Label: "` pill.
|
|
246
|
-
* The base `getIndicator` only checks for raw empty-string / undefined.
|
|
247
|
-
*/
|
|
248
|
-
override getIndicator(): string | undefined {
|
|
249
|
-
const value = this.getValue()
|
|
250
|
-
if (value === undefined || value === '') return undefined
|
|
251
|
-
const tree = parseQueryBuilderValue(value)
|
|
252
|
-
if (countLeafRules(tree) === 0) return undefined
|
|
253
|
-
return super.getIndicator()
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
override toMeta(): FilterMeta {
|
|
257
|
-
const base = this.buildBaseMeta()
|
|
258
|
-
return {
|
|
259
|
-
...base,
|
|
260
|
-
placeholder: this.getPlaceholder() ?? 'Add filter…',
|
|
261
|
-
constraints: this._constraints.map(c => c.toMeta() as unknown as Record<string, unknown>),
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Walk a tree against an indexed constraint set and translate it into ORM
|
|
268
|
-
* `where` calls. v2 dispatches every sub-group through `whereGroup` /
|
|
269
|
-
* `orWhereGroup` so nested AND/OR composes without leaking into the query's
|
|
270
|
-
* surrounding `where` clauses (search / tab predicate / sibling filters).
|
|
271
|
-
*
|
|
272
|
-
* Falls back to flat AND chaining when the running query lacks
|
|
273
|
-
* `whereGroup` (custom test stubs or exotic adapters that haven't picked
|
|
274
|
-
* up the rudder ORM contract yet) — same behaviour as v1, but loses OR /
|
|
275
|
-
* nesting fidelity. Real rudder ORM queries always carry `whereGroup`.
|
|
276
|
-
*
|
|
277
|
-
* Exposed for tests + custom handlers that want the default behavior on
|
|
278
|
-
* top of their own pre-/post-processing.
|
|
279
|
-
*/
|
|
280
|
-
export function applyTreeToQuery(
|
|
281
|
-
query: ModelQuery,
|
|
282
|
-
tree: QueryBuilderTree,
|
|
283
|
-
constraints: Constraint[],
|
|
284
|
-
): ModelQuery {
|
|
285
|
-
if (tree.rules.length === 0) return query
|
|
286
|
-
|
|
287
|
-
const map = new Map<string, Constraint>()
|
|
288
|
-
for (const c of constraints) map.set(c.name, c)
|
|
289
|
-
|
|
290
|
-
// Without `whereGroup`, OR-root + nested groups can't be expressed safely
|
|
291
|
-
// — fall back to flat AND chaining (the v1 behaviour) so the page still
|
|
292
|
-
// renders, just with reduced fidelity. Leaves only; nested groups are
|
|
293
|
-
// walked and their leaves chained in.
|
|
294
|
-
if (typeof query.whereGroup !== 'function') {
|
|
295
|
-
return applyTreeFlat(query, tree, map)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return applyTreeGrouped(query, tree, map, /* atRoot */ true)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function applyTreeGrouped(
|
|
302
|
-
query: ModelQuery,
|
|
303
|
-
tree: QueryBuilderTree,
|
|
304
|
-
map: Map<string, Constraint>,
|
|
305
|
-
atRoot: boolean,
|
|
306
|
-
): ModelQuery {
|
|
307
|
-
// The tree's connector tells us how each child JOINS to its sibling. AND
|
|
308
|
-
// → child as `where(...)` / `whereGroup(...)`; OR → child as
|
|
309
|
-
// `orWhere(...)` / `orWhereGroup(...)`. The first child of an OR-rooted
|
|
310
|
-
// group still uses `whereGroup` at the parent boundary so the whole
|
|
311
|
-
// group is parenthesized correctly — the inner connector handles
|
|
312
|
-
// sibling-to-sibling joining.
|
|
313
|
-
let q = query
|
|
314
|
-
for (let i = 0; i < tree.rules.length; i++) {
|
|
315
|
-
const child = tree.rules[i]!
|
|
316
|
-
const useOr = tree.operator === 'or' && i > 0
|
|
317
|
-
if (isQueryBuilderTree(child)) {
|
|
318
|
-
// Nested group — recurse inside whereGroup / orWhereGroup so the
|
|
319
|
-
// sub-tree's clauses stay parenthesized.
|
|
320
|
-
q = useOr
|
|
321
|
-
? q.orWhereGroup!(g => { applyTreeGrouped(g, child, map, false) })
|
|
322
|
-
: q.whereGroup! (g => { applyTreeGrouped(g, child, map, false) })
|
|
323
|
-
} else {
|
|
324
|
-
const c = map.get(child.constraint)
|
|
325
|
-
if (!c) continue
|
|
326
|
-
try {
|
|
327
|
-
q = useOr
|
|
328
|
-
? applyAsOr(q, c, child)
|
|
329
|
-
: c.apply(q, child.operator, child.value)
|
|
330
|
-
} catch {
|
|
331
|
-
// Malformed values shouldn't 500 the page — skip the offending rule
|
|
332
|
-
// and continue with the rest. The list still loads.
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// Suppress unused-variable warning for atRoot — kept on the signature so
|
|
337
|
-
// future expansion (e.g. wrapping the root in its own group when mixing
|
|
338
|
-
// with other top-level wheres) has the discriminator handy.
|
|
339
|
-
void atRoot
|
|
340
|
-
return q
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Run a single rule against the query inside an `orWhereGroup` so its OR
|
|
345
|
-
* connector composes correctly. `Constraint.apply()` always emits AND
|
|
346
|
-
* (`.where()`); to flip the connector we wrap the leaf in a 1-element
|
|
347
|
-
* orWhereGroup. Multi-clause operators (e.g. `between` → two `.where()`
|
|
348
|
-
* calls) get AND'd inside the group, then the whole group OR's against
|
|
349
|
-
* the running query — which is exactly what the user means by
|
|
350
|
-
* "OR (amount BETWEEN 10 AND 50)".
|
|
351
|
-
*/
|
|
352
|
-
function applyAsOr(q: ModelQuery, c: Constraint, rule: QueryBuilderRule): ModelQuery {
|
|
353
|
-
if (typeof q.orWhereGroup !== 'function') {
|
|
354
|
-
// No grouping support — fall back to flat AND so the page still loads.
|
|
355
|
-
return c.apply(q, rule.operator, rule.value)
|
|
356
|
-
}
|
|
357
|
-
return q.orWhereGroup(g => { c.apply(g, rule.operator, rule.value) })
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Legacy fallback — chain every leaf through `.where()`, recurse into
|
|
362
|
-
* sub-trees as if they were AND-ed. Mirrors v1 behaviour for query
|
|
363
|
-
* builders that don't implement `whereGroup`.
|
|
364
|
-
*/
|
|
365
|
-
function applyTreeFlat(
|
|
366
|
-
query: ModelQuery,
|
|
367
|
-
tree: QueryBuilderTree,
|
|
368
|
-
map: Map<string, Constraint>,
|
|
369
|
-
): ModelQuery {
|
|
370
|
-
let q = query
|
|
371
|
-
for (const child of tree.rules) {
|
|
372
|
-
if (isQueryBuilderTree(child)) {
|
|
373
|
-
q = applyTreeFlat(q, child, map)
|
|
374
|
-
} else {
|
|
375
|
-
const c = map.get(child.constraint)
|
|
376
|
-
if (!c) continue
|
|
377
|
-
try {
|
|
378
|
-
q = c.apply(q, child.operator, child.value)
|
|
379
|
-
} catch {
|
|
380
|
-
// Same fail-soft posture as the grouped walker.
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return q
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Recursive count of leaf rules in a tree — used by the indicator pill so
|
|
389
|
-
* "5 conditions" reflects all leaves regardless of how they're grouped.
|
|
390
|
-
* Exposed for tests + custom indicator formatters.
|
|
391
|
-
*/
|
|
392
|
-
export function countLeafRules(tree: QueryBuilderTree): number {
|
|
393
|
-
let n = 0
|
|
394
|
-
for (const child of tree.rules) {
|
|
395
|
-
n += isQueryBuilderTree(child) ? countLeafRules(child) : 1
|
|
396
|
-
}
|
|
397
|
-
return n
|
|
398
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Filter, type FilterKind, type FilterMeta } from './Filter.js'
|
|
2
|
-
|
|
3
|
-
export interface SelectFilterOption {
|
|
4
|
-
value: string
|
|
5
|
-
label: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Single-value dropdown filter. Selecting a value adds a
|
|
10
|
-
* `where(name, value)` clause to the table's ORM query (or runs the
|
|
11
|
-
* user's `.query(fn)` hook when supplied).
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* SelectFilter.make('status').options([
|
|
15
|
-
* { value: 'draft', label: 'Draft' },
|
|
16
|
-
* { value: 'published', label: 'Published' },
|
|
17
|
-
* ])
|
|
18
|
-
*/
|
|
19
|
-
export class SelectFilter extends Filter {
|
|
20
|
-
private _options: SelectFilterOption[] = []
|
|
21
|
-
|
|
22
|
-
static make(name: string): SelectFilter {
|
|
23
|
-
return new SelectFilter(name)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
options(opts: SelectFilterOption[]): this {
|
|
27
|
-
this._options = opts
|
|
28
|
-
return this
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getOptions(): SelectFilterOption[] { return this._options }
|
|
32
|
-
|
|
33
|
-
override getKind(): FilterKind { return 'select' }
|
|
34
|
-
|
|
35
|
-
protected override formatActiveValue(value: string): string {
|
|
36
|
-
return this._options.find(o => o.value === value)?.label ?? value
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
override toMeta(): FilterMeta {
|
|
40
|
-
return {
|
|
41
|
-
...this.buildBaseMeta(),
|
|
42
|
-
options: this._options,
|
|
43
|
-
placeholder: this.getPlaceholder() ?? 'All',
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { Table } from '../elements/Table.js'
|
|
5
|
-
import { Column } from '../Column.js'
|
|
6
|
-
import { TernaryFilter } from './TernaryFilter.js'
|
|
7
|
-
import { modelTableRecords } from '../orm/modelDefaults.js'
|
|
8
|
-
import type { ModelLike, ModelQuery } from '../orm/modelDefaults.js'
|
|
9
|
-
|
|
10
|
-
interface FakeOp { op: string; args: unknown[] }
|
|
11
|
-
class FakeQueryWithNull implements ModelQuery {
|
|
12
|
-
ops: FakeOp[] = []
|
|
13
|
-
where(...args: unknown[]): ModelQuery { this.ops.push({ op: 'where', args }); return this }
|
|
14
|
-
orWhere(...args: unknown[]): ModelQuery { this.ops.push({ op: 'orWhere', args }); return this }
|
|
15
|
-
orderBy(c: string, d: 'ASC' | 'DESC' = 'ASC'): ModelQuery { this.ops.push({ op: 'orderBy', args: [c, d] }); return this }
|
|
16
|
-
whereNull(column: string): ModelQuery { this.ops.push({ op: 'whereNull', args: [column] }); return this }
|
|
17
|
-
async paginate() { this.ops.push({ op: 'paginate', args: [] }); return { data: [], total: 0 } }
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class FakeQueryNoNull implements ModelQuery {
|
|
21
|
-
ops: FakeOp[] = []
|
|
22
|
-
where(...args: unknown[]): ModelQuery { this.ops.push({ op: 'where', args }); return this }
|
|
23
|
-
orWhere(...args: unknown[]): ModelQuery { this.ops.push({ op: 'orWhere', args }); return this }
|
|
24
|
-
orderBy(c: string, d: 'ASC' | 'DESC' = 'ASC'): ModelQuery { this.ops.push({ op: 'orderBy', args: [c, d] }); return this }
|
|
25
|
-
// intentionally no whereNull — exercises the fallback path
|
|
26
|
-
async paginate() { this.ops.push({ op: 'paginate', args: [] }); return { data: [], total: 0 } }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
type FakeQuery = FakeQueryWithNull | FakeQueryNoNull
|
|
30
|
-
|
|
31
|
-
function fakeModel(opts: { whereNull?: boolean } = { whereNull: true }): ModelLike & { name: string; model: ModelLike; lastQuery: FakeQuery | null } {
|
|
32
|
-
let lastQuery: FakeQuery | null = null
|
|
33
|
-
const hasWhereNull = opts.whereNull ?? true
|
|
34
|
-
const M = {
|
|
35
|
-
name: 'TestResource',
|
|
36
|
-
async find() { return null },
|
|
37
|
-
async create(d: Record<string, unknown>) { return d },
|
|
38
|
-
async update(_id: string | number, d: Record<string, unknown>) { return d },
|
|
39
|
-
async delete() {},
|
|
40
|
-
query(): ModelQuery {
|
|
41
|
-
const q: FakeQuery = hasWhereNull ? new FakeQueryWithNull() : new FakeQueryNoNull()
|
|
42
|
-
lastQuery = q
|
|
43
|
-
return q
|
|
44
|
-
},
|
|
45
|
-
get lastQuery() { return lastQuery },
|
|
46
|
-
} as unknown as ModelLike & { name: string; model: ModelLike; lastQuery: FakeQuery | null }
|
|
47
|
-
M.model = M
|
|
48
|
-
return M
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe('TernaryFilter shape', () => {
|
|
52
|
-
it('toMeta emits kind:ternary + three options + Any placeholder', () => {
|
|
53
|
-
const meta = TernaryFilter.make('verified').toMeta()
|
|
54
|
-
assert.equal(meta.kind, 'ternary')
|
|
55
|
-
assert.equal(meta.placeholder, 'Any')
|
|
56
|
-
assert.deepEqual(meta.options, [
|
|
57
|
-
{ value: 'yes', label: 'Yes' },
|
|
58
|
-
{ value: 'no', label: 'No' },
|
|
59
|
-
{ value: 'blank', label: 'Blank' },
|
|
60
|
-
])
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('label overrides win', () => {
|
|
64
|
-
const meta = TernaryFilter.make('verified')
|
|
65
|
-
.trueLabel('Verified')
|
|
66
|
-
.falseLabel('Unverified')
|
|
67
|
-
.blankLabel('Pending')
|
|
68
|
-
.toMeta()
|
|
69
|
-
assert.deepEqual(meta.options, [
|
|
70
|
-
{ value: 'yes', label: 'Verified' },
|
|
71
|
-
{ value: 'no', label: 'Unverified' },
|
|
72
|
-
{ value: 'blank', label: 'Pending' },
|
|
73
|
-
])
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('nullable(false) drops the blank option', () => {
|
|
77
|
-
const meta = TernaryFilter.make('verified').nullable(false).toMeta()
|
|
78
|
-
assert.deepEqual(meta.options, [
|
|
79
|
-
{ value: 'yes', label: 'Yes' },
|
|
80
|
-
{ value: 'no', label: 'No' },
|
|
81
|
-
])
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('placeholder() override wins over Any default', () => {
|
|
85
|
-
const meta = TernaryFilter.make('verified').placeholder('Filter…').toMeta()
|
|
86
|
-
assert.equal(meta.placeholder, 'Filter…')
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('TernaryFilter default query handler', () => {
|
|
91
|
-
it('yes → where(name, true)', async () => {
|
|
92
|
-
const M = fakeModel()
|
|
93
|
-
const table = Table.make()
|
|
94
|
-
.columns([Column.make('title')])
|
|
95
|
-
.filters([TernaryFilter.make('verified')])
|
|
96
|
-
const handler = modelTableRecords(M, table)
|
|
97
|
-
await handler({ filters: { verified: 'yes' }, page: 1 })
|
|
98
|
-
assert.deepEqual(M.lastQuery!.ops[0], { op: 'where', args: ['verified', true] })
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('no → where(name, false)', async () => {
|
|
102
|
-
const M = fakeModel()
|
|
103
|
-
const table = Table.make()
|
|
104
|
-
.columns([Column.make('title')])
|
|
105
|
-
.filters([TernaryFilter.make('verified')])
|
|
106
|
-
const handler = modelTableRecords(M, table)
|
|
107
|
-
await handler({ filters: { verified: 'no' }, page: 1 })
|
|
108
|
-
assert.deepEqual(M.lastQuery!.ops[0], { op: 'where', args: ['verified', false] })
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('blank → whereNull(name) when the query exposes it', async () => {
|
|
112
|
-
const M = fakeModel()
|
|
113
|
-
const table = Table.make()
|
|
114
|
-
.columns([Column.make('title')])
|
|
115
|
-
.filters([TernaryFilter.make('verified')])
|
|
116
|
-
const handler = modelTableRecords(M, table)
|
|
117
|
-
await handler({ filters: { verified: 'blank' }, page: 1 })
|
|
118
|
-
assert.deepEqual(M.lastQuery!.ops[0], { op: 'whereNull', args: ['verified'] })
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
it('blank → where(name, null) when whereNull is missing', async () => {
|
|
122
|
-
const M = fakeModel({ whereNull: false })
|
|
123
|
-
const table = Table.make()
|
|
124
|
-
.columns([Column.make('title')])
|
|
125
|
-
.filters([TernaryFilter.make('verified')])
|
|
126
|
-
const handler = modelTableRecords(M, table)
|
|
127
|
-
await handler({ filters: { verified: 'blank' }, page: 1 })
|
|
128
|
-
assert.deepEqual(M.lastQuery!.ops[0], { op: 'where', args: ['verified', null] })
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('empty / unknown values produce no clause', async () => {
|
|
132
|
-
const M = fakeModel()
|
|
133
|
-
const table = Table.make()
|
|
134
|
-
.columns([Column.make('title')])
|
|
135
|
-
.filters([TernaryFilter.make('verified')])
|
|
136
|
-
const handler = modelTableRecords(M, table)
|
|
137
|
-
await handler({ filters: { verified: '' }, page: 1 })
|
|
138
|
-
// Only the paginate op should land — no where for the filter.
|
|
139
|
-
assert.equal(M.lastQuery!.ops.find(o => o.op === 'where'), undefined)
|
|
140
|
-
|
|
141
|
-
await handler({ filters: { verified: 'maybe' }, page: 1 })
|
|
142
|
-
// 'maybe' isn't yes/no/blank so the handler falls through to no-op.
|
|
143
|
-
const wheresAfter = M.lastQuery!.ops.filter(o => o.op === 'where')
|
|
144
|
-
assert.equal(wheresAfter.length, 0)
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('user .query(fn) override replaces the default handler', async () => {
|
|
148
|
-
const M = fakeModel()
|
|
149
|
-
const table = Table.make()
|
|
150
|
-
.columns([Column.make('title')])
|
|
151
|
-
.filters([
|
|
152
|
-
TernaryFilter.make('verified').query((q, value) =>
|
|
153
|
-
q.where('verified_state', value === 'yes' ? 1 : 0),
|
|
154
|
-
),
|
|
155
|
-
])
|
|
156
|
-
const handler = modelTableRecords(M, table)
|
|
157
|
-
await handler({ filters: { verified: 'yes' }, page: 1 })
|
|
158
|
-
assert.deepEqual(M.lastQuery!.ops[0], { op: 'where', args: ['verified_state', 1] })
|
|
159
|
-
})
|
|
160
|
-
})
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Filter, type FilterKind, type FilterMeta } from './Filter.js'
|
|
2
|
-
import type { ModelQuery } from '../orm/modelDefaults.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Three-state filter for nullable boolean columns where `NULL` is a
|
|
6
|
-
* meaningful third bucket. Distinct from `BooleanFilter` (yes/no/any)
|
|
7
|
-
* because the empty / unset URL value here means "any" while
|
|
8
|
-
* `'blank'` means "the column IS NULL".
|
|
9
|
-
*
|
|
10
|
-
* `''` — placeholder "Any" (no clause)
|
|
11
|
-
* `'yes'` — `where(name, true)`
|
|
12
|
-
* `'no'` — `where(name, false)`
|
|
13
|
-
* `'blank'`— `whereNull(name)` (or `where(name, null)` when the
|
|
14
|
-
* query builder doesn't expose `whereNull`)
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* TernaryFilter.make('verified')
|
|
18
|
-
* .label('Verification')
|
|
19
|
-
* .trueLabel('Verified').falseLabel('Unverified').blankLabel('Pending')
|
|
20
|
-
*/
|
|
21
|
-
export class TernaryFilter extends Filter {
|
|
22
|
-
private _trueLabel = 'Yes'
|
|
23
|
-
private _falseLabel = 'No'
|
|
24
|
-
private _blankLabel = 'Blank'
|
|
25
|
-
private _nullable = true
|
|
26
|
-
|
|
27
|
-
static make(name: string): TernaryFilter {
|
|
28
|
-
const f = new TernaryFilter(name)
|
|
29
|
-
f.query((q: ModelQuery, value: string) => {
|
|
30
|
-
if (value === 'yes') return q.where(f.name, true)
|
|
31
|
-
if (value === 'no') return q.where(f.name, false)
|
|
32
|
-
if (value === 'blank') {
|
|
33
|
-
if (typeof q.whereNull === 'function') return q.whereNull(f.name)
|
|
34
|
-
return q.where(f.name, null)
|
|
35
|
-
}
|
|
36
|
-
return q
|
|
37
|
-
})
|
|
38
|
-
return f
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
trueLabel(label: string): this { this._trueLabel = label; return this }
|
|
42
|
-
falseLabel(label: string): this { this._falseLabel = label; return this }
|
|
43
|
-
blankLabel(label: string): this { this._blankLabel = label; return this }
|
|
44
|
-
|
|
45
|
-
/** Drop the `'blank'` option — useful for non-null columns where the
|
|
46
|
-
* third bucket is meaningless. Default: `true`. */
|
|
47
|
-
nullable(value = true): this { this._nullable = value; return this }
|
|
48
|
-
|
|
49
|
-
override getKind(): FilterKind { return 'ternary' }
|
|
50
|
-
|
|
51
|
-
protected override formatActiveValue(value: string): string {
|
|
52
|
-
if (value === 'yes') return this._trueLabel
|
|
53
|
-
if (value === 'no') return this._falseLabel
|
|
54
|
-
if (value === 'blank') return this._blankLabel
|
|
55
|
-
return value
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
override toMeta(): FilterMeta {
|
|
59
|
-
const options = [
|
|
60
|
-
{ value: 'yes', label: this._trueLabel },
|
|
61
|
-
{ value: 'no', label: this._falseLabel },
|
|
62
|
-
...(this._nullable
|
|
63
|
-
? [{ value: 'blank', label: this._blankLabel }]
|
|
64
|
-
: []),
|
|
65
|
-
]
|
|
66
|
-
return {
|
|
67
|
-
...this.buildBaseMeta(),
|
|
68
|
-
options,
|
|
69
|
-
placeholder: this.getPlaceholder() ?? 'Any',
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|