@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,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { Action } from './Action.js'
|
|
5
|
-
import { ActionGroup } from './ActionGroup.js'
|
|
6
|
-
import { resolveSchema, _resetResolverRegistry } from '../schema/resolveSchema.js'
|
|
7
|
-
|
|
8
|
-
beforeEach(() => _resetResolverRegistry())
|
|
9
|
-
|
|
10
|
-
describe('ActionGroup builder', () => {
|
|
11
|
-
it('emits type=actionGroup with sensible defaults', () => {
|
|
12
|
-
const meta = ActionGroup.make('manage').toMeta()
|
|
13
|
-
assert.equal(meta.type, 'actionGroup')
|
|
14
|
-
assert.equal(meta.name, 'manage')
|
|
15
|
-
assert.equal(meta.label, 'Manage') // auto-derived
|
|
16
|
-
assert.equal(meta.placement, 'inline')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('label() / icon() / tooltip() round-trip', () => {
|
|
20
|
-
const meta = ActionGroup.make('m').label('More').icon('more-horizontal').tooltip('More actions').toMeta()
|
|
21
|
-
assert.equal(meta.label, 'More')
|
|
22
|
-
assert.equal(meta.icon, 'more-horizontal')
|
|
23
|
-
assert.equal(meta.tooltip, 'More actions')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('placement helpers', () => {
|
|
27
|
-
assert.equal(ActionGroup.make('a').header().toMeta().placement, 'header')
|
|
28
|
-
assert.equal(ActionGroup.make('a').row().toMeta().placement, 'row')
|
|
29
|
-
assert.equal(ActionGroup.make('a').bulk().toMeta().placement, 'bulk')
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('cosmetic builders round-trip', () => {
|
|
33
|
-
const meta = ActionGroup.make('a').color('success').size('lg').outlined().iconButton().toMeta()
|
|
34
|
-
assert.equal(meta.color, 'success')
|
|
35
|
-
assert.equal(meta.size, 'lg')
|
|
36
|
-
assert.equal(meta.outlined, true)
|
|
37
|
-
assert.equal(meta.iconOnly, true)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('actions() stores the children', () => {
|
|
41
|
-
const g = ActionGroup.make('m').actions([
|
|
42
|
-
Action.make('export'),
|
|
43
|
-
Action.make('archive'),
|
|
44
|
-
])
|
|
45
|
-
const children = g.getActions()
|
|
46
|
-
assert.deepEqual(children.map(a => a.name), ['export', 'archive'])
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('actions() flattens nested ActionGroups', () => {
|
|
50
|
-
const inner = ActionGroup.make('inner').actions([Action.make('a'), Action.make('b')])
|
|
51
|
-
const outer = ActionGroup.make('outer').actions([Action.make('c'), inner])
|
|
52
|
-
assert.deepEqual(outer.getActions().map(a => a.name), ['c', 'a', 'b'])
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('hasVisibilityRules detects any rule', () => {
|
|
56
|
-
assert.equal(ActionGroup.make('a').hasVisibilityRules(), false)
|
|
57
|
-
assert.equal(ActionGroup.make('a').visible(true).hasVisibilityRules(), true)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('evaluate combines visible and hidden via AND', async () => {
|
|
61
|
-
const g = ActionGroup.make('a').visible(true).hidden(({ user }) => (user as { admin?: boolean })?.admin === false)
|
|
62
|
-
assert.equal((await g.evaluate({ user: { admin: true } })).visible, true)
|
|
63
|
-
assert.equal((await g.evaluate({ user: { admin: false } })).visible, false)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('toMeta emits conditional:true when rules exist', () => {
|
|
67
|
-
assert.equal(ActionGroup.make('a').toMeta().conditional, undefined)
|
|
68
|
-
assert.equal(ActionGroup.make('a').visible(true).toMeta().conditional, true)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
describe('ActionGroup through resolveSchema', () => {
|
|
73
|
-
it('drops the group when its rule resolves to !visible', async () => {
|
|
74
|
-
const tree = [ActionGroup.make('hidden').visible(false).actions([Action.make('a')])]
|
|
75
|
-
const result = await resolveSchema(tree)
|
|
76
|
-
assert.equal(result.length, 0)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it('serializes children as meta.children', async () => {
|
|
80
|
-
const tree = [
|
|
81
|
-
ActionGroup.make('m').actions([
|
|
82
|
-
Action.make('export'),
|
|
83
|
-
Action.make('delete').destructive(),
|
|
84
|
-
]),
|
|
85
|
-
]
|
|
86
|
-
const result = await resolveSchema(tree)
|
|
87
|
-
assert.equal(result[0]!.type, 'actionGroup')
|
|
88
|
-
assert.equal(result[0]!.children?.length, 2)
|
|
89
|
-
assert.equal(result[0]!.children![0]!['name'], 'export')
|
|
90
|
-
assert.equal(result[0]!.children![1]!['destructive'], true)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
it('child Action visibility rules still apply', async () => {
|
|
94
|
-
const tree = [
|
|
95
|
-
ActionGroup.make('m').actions([
|
|
96
|
-
Action.make('shown'),
|
|
97
|
-
Action.make('hidden').visible(false),
|
|
98
|
-
]),
|
|
99
|
-
]
|
|
100
|
-
const result = await resolveSchema(tree)
|
|
101
|
-
assert.equal(result[0]!.children?.length, 1)
|
|
102
|
-
assert.equal(result[0]!.children![0]!['name'], 'shown')
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it('stamps disabled:true on the group when its disabled rule matches', async () => {
|
|
106
|
-
const tree = [
|
|
107
|
-
ActionGroup.make('m').disabled(true).actions([Action.make('a')]),
|
|
108
|
-
]
|
|
109
|
-
const result = await resolveSchema(tree)
|
|
110
|
-
assert.equal(result[0]!['disabled'], true)
|
|
111
|
-
})
|
|
112
|
-
})
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { Element, type ElementMeta } from '../schema/Element.js'
|
|
2
|
-
import {
|
|
3
|
-
Action,
|
|
4
|
-
type ActionColor,
|
|
5
|
-
type ActionSize,
|
|
6
|
-
type ActionPlacement,
|
|
7
|
-
type ActionVisibilityContext,
|
|
8
|
-
type VisibilityRule,
|
|
9
|
-
} from './Action.js'
|
|
10
|
-
|
|
11
|
-
export interface ActionGroupMeta extends ElementMeta {
|
|
12
|
-
type: 'actionGroup'
|
|
13
|
-
name: string
|
|
14
|
-
label: string
|
|
15
|
-
placement: ActionPlacement
|
|
16
|
-
icon?: string
|
|
17
|
-
tooltip?: string
|
|
18
|
-
color?: ActionColor
|
|
19
|
-
size?: ActionSize
|
|
20
|
-
outlined?: boolean
|
|
21
|
-
iconOnly?: boolean
|
|
22
|
-
/** True when the group itself has visibility rules. The renderer checks
|
|
23
|
-
* `disabled` separately. */
|
|
24
|
-
conditional?: boolean
|
|
25
|
-
disabled?: boolean
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* `ActionGroup` — a labelled trigger that opens a dropdown of related
|
|
30
|
-
* actions. Lives wherever a single `Action` can live (header slot,
|
|
31
|
-
* inline in a Card, etc). Children are `Action[]` (nesting groups inside
|
|
32
|
-
* groups is supported but renders flat — the inner group's actions are
|
|
33
|
-
* folded into the parent dropdown).
|
|
34
|
-
*
|
|
35
|
-
* Visibility evaluation: each child Action's own `.visible()`/`.hidden()`
|
|
36
|
-
* rules still apply at render time. The group itself can also opt-in to
|
|
37
|
-
* group-level visibility via the same builders. When ALL children would
|
|
38
|
-
* be hidden the group falls back to the disabled-ghost state so the
|
|
39
|
-
* trigger doesn't disappear unexpectedly — the renderer handles that.
|
|
40
|
-
*/
|
|
41
|
-
export class ActionGroup extends Element {
|
|
42
|
-
readonly name: string
|
|
43
|
-
|
|
44
|
-
protected _label: string
|
|
45
|
-
protected _icon?: string
|
|
46
|
-
protected _tooltip?: string
|
|
47
|
-
protected _placement: ActionPlacement = 'inline'
|
|
48
|
-
|
|
49
|
-
// Trigger styling
|
|
50
|
-
protected _color?: ActionColor
|
|
51
|
-
protected _size?: ActionSize
|
|
52
|
-
protected _outlined = false
|
|
53
|
-
protected _iconOnly = false
|
|
54
|
-
|
|
55
|
-
// Visibility
|
|
56
|
-
protected _visible?: VisibilityRule
|
|
57
|
-
protected _hidden?: VisibilityRule
|
|
58
|
-
protected _isDisabled?: VisibilityRule
|
|
59
|
-
|
|
60
|
-
private constructor(name: string) {
|
|
61
|
-
super()
|
|
62
|
-
this.name = name
|
|
63
|
-
this._label = name.charAt(0).toUpperCase() + name.slice(1)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static make(name: string): ActionGroup {
|
|
67
|
-
return new ActionGroup(name)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Children ─────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/** Set the actions inside this group. Accepts plain `Action`s; nested
|
|
73
|
-
* `ActionGroup`s are accepted but flatten in v1 (their children get
|
|
74
|
-
* pulled up into this group's dropdown). */
|
|
75
|
-
actions(items: Array<Action | ActionGroup>): this {
|
|
76
|
-
const flat: Action[] = []
|
|
77
|
-
for (const item of items) {
|
|
78
|
-
if (item instanceof ActionGroup) {
|
|
79
|
-
flat.push(...item.getActions())
|
|
80
|
-
} else {
|
|
81
|
-
flat.push(item)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
this._children = flat
|
|
85
|
-
return this
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** Convenience: the `Action` children only. */
|
|
89
|
-
getActions(): Action[] {
|
|
90
|
-
return (this._children ?? []).filter((c): c is Action => c instanceof Action)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ─── Trigger config ───────────────────────────────────
|
|
94
|
-
|
|
95
|
-
label(l: string): this { this._label = l; return this }
|
|
96
|
-
icon(i: string): this { this._icon = i; return this }
|
|
97
|
-
tooltip(t: string): this { this._tooltip = t; return this }
|
|
98
|
-
color(c: ActionColor): this { this._color = c; return this }
|
|
99
|
-
size(s: ActionSize): this { this._size = s; return this }
|
|
100
|
-
outlined(v = true): this { this._outlined = v; return this }
|
|
101
|
-
iconButton(v = true): this { this._iconOnly = v; return this }
|
|
102
|
-
|
|
103
|
-
// ─── Placement ────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
placement(p: ActionPlacement): this { this._placement = p; return this }
|
|
106
|
-
inline(): this { return this.placement('inline') }
|
|
107
|
-
row(): this { return this.placement('row') }
|
|
108
|
-
bulk(): this { return this.placement('bulk') }
|
|
109
|
-
header(): this { return this.placement('header') }
|
|
110
|
-
|
|
111
|
-
// ─── Visibility ───────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
visible(rule: VisibilityRule): this { this._visible = rule; return this }
|
|
114
|
-
hidden(rule: VisibilityRule): this { this._hidden = rule; return this }
|
|
115
|
-
disabled(rule: VisibilityRule): this { this._isDisabled = rule; return this }
|
|
116
|
-
authorize(rule: VisibilityRule): this { return this.visible(rule) }
|
|
117
|
-
|
|
118
|
-
async evaluate(ctx: ActionVisibilityContext = {}): Promise<{ visible: boolean; disabled: boolean }> {
|
|
119
|
-
const evalRule = async (rule: VisibilityRule | undefined, fallback: boolean): Promise<boolean> => {
|
|
120
|
-
if (rule === undefined) return fallback
|
|
121
|
-
if (typeof rule !== 'function') return rule
|
|
122
|
-
try {
|
|
123
|
-
return await rule(ctx)
|
|
124
|
-
} catch {
|
|
125
|
-
return !fallback
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
const [visibleRaw, hiddenRaw, disabledRaw] = await Promise.all([
|
|
129
|
-
evalRule(this._visible, true),
|
|
130
|
-
evalRule(this._hidden, false),
|
|
131
|
-
evalRule(this._isDisabled, false),
|
|
132
|
-
])
|
|
133
|
-
return {
|
|
134
|
-
visible: visibleRaw && !hiddenRaw,
|
|
135
|
-
disabled: disabledRaw,
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
hasVisibilityRules(): boolean {
|
|
140
|
-
return this._visible !== undefined || this._hidden !== undefined || this._isDisabled !== undefined
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ─── Getters ──────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
getLabel(): string { return this._label }
|
|
146
|
-
getIcon(): string | undefined { return this._icon }
|
|
147
|
-
getTooltip(): string | undefined { return this._tooltip }
|
|
148
|
-
getPlacement(): ActionPlacement { return this._placement }
|
|
149
|
-
getColor(): ActionColor | undefined { return this._color }
|
|
150
|
-
getSize(): ActionSize | undefined { return this._size }
|
|
151
|
-
isOutlined(): boolean { return this._outlined }
|
|
152
|
-
isIconOnly(): boolean { return this._iconOnly }
|
|
153
|
-
|
|
154
|
-
// ─── Element contract ────────────────────────────────
|
|
155
|
-
|
|
156
|
-
override getType(): string { return 'actionGroup' }
|
|
157
|
-
|
|
158
|
-
override toMeta(): ActionGroupMeta {
|
|
159
|
-
return {
|
|
160
|
-
type: 'actionGroup',
|
|
161
|
-
name: this.name,
|
|
162
|
-
label: this._label,
|
|
163
|
-
placement: this._placement,
|
|
164
|
-
...(this._icon ? { icon: this._icon } : {}),
|
|
165
|
-
...(this._tooltip ? { tooltip: this._tooltip } : {}),
|
|
166
|
-
...(this._color ? { color: this._color } : {}),
|
|
167
|
-
...(this._size ? { size: this._size } : {}),
|
|
168
|
-
...(this._outlined ? { outlined: true } : {}),
|
|
169
|
-
...(this._iconOnly ? { iconOnly: true } : {}),
|
|
170
|
-
...(this.hasVisibilityRules() ? { conditional: true } : {}),
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import type { Element } from '../schema/Element.js'
|
|
2
|
-
import type { ResourceClass } from '../Resource.js'
|
|
3
|
-
import type { ModelLike, ModelQuery } from '../orm/modelDefaults.js'
|
|
4
|
-
import { getPrimaryKey } from '../orm/modelDefaults.js'
|
|
5
|
-
import { SelectField } from '../fields/SelectField.js'
|
|
6
|
-
|
|
7
|
-
/** Minimum shape required on the parent record to read related rows
|
|
8
|
-
* via the rudder ORM convention. The candidate-picker side uses this
|
|
9
|
-
* directly instead of `resolveRelatedQuery` because it doesn't need
|
|
10
|
-
* the `ModelLike.relatedQuery` override slot — the override only
|
|
11
|
-
* matters at table render time, not for "what's already attached?"
|
|
12
|
-
* filtering. */
|
|
13
|
-
interface ParentWithRelated {
|
|
14
|
-
related(name: string): { paginate(page: number, perPage: number): Promise<{ data: unknown[]; total: number }> }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Internals for `Action.relationAttach / relationDetach /
|
|
19
|
-
* relationBulkDetach`. Lives in its own module so the relation-manager
|
|
20
|
-
* factory side of `Action.ts` can call into the M2M-specific helpers
|
|
21
|
-
* (candidate picker query, attach modal schema) without dragging
|
|
22
|
-
* `SelectField` / model-defaults imports into the main file. Mirrors
|
|
23
|
-
* the `importFactory.ts` / `exportFactory.ts` split.
|
|
24
|
-
*
|
|
25
|
-
* The picker is intentionally simple in v1 — fetch up to 50 candidates
|
|
26
|
-
* client-resolved at page render time, filter out already-attached ids
|
|
27
|
-
* server-side. Document the cap; users with high-cardinality relations
|
|
28
|
-
* reach for `Resource.globalSearch` semantics or hand-roll an
|
|
29
|
-
* `Action.handler` with a custom picker UI.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
export interface AttachableRow {
|
|
33
|
-
/** Stringified primary key of the candidate record. */
|
|
34
|
-
value: string
|
|
35
|
-
/** Title text rendered in the picker. Resolved via the related
|
|
36
|
-
* Resource's `recordTitleAttribute` chain. */
|
|
37
|
-
label: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Read the parent's already-attached ids under `relationship`. Used by
|
|
42
|
-
* the candidate picker to filter them out (so a user can't accidentally
|
|
43
|
-
* re-attach the same row). Reads through the parent's `related()`
|
|
44
|
-
* accessor — not through the related model's `query()` — because the
|
|
45
|
-
* accessor automatically scopes to the pivot rows for this parent.
|
|
46
|
-
*
|
|
47
|
-
* Returns `Set<string>` of stringified primary keys for fast lookup.
|
|
48
|
-
* Quietly returns an empty set on failure (the picker still works,
|
|
49
|
-
* just shows everything — the attach route's own duplicate handling
|
|
50
|
-
* would pick up the dupe).
|
|
51
|
-
*/
|
|
52
|
-
export async function loadAttachedIds(
|
|
53
|
-
parent: unknown,
|
|
54
|
-
relationship: string,
|
|
55
|
-
relatedModel: ModelLike,
|
|
56
|
-
): Promise<Set<string>> {
|
|
57
|
-
const p = parent as Partial<ParentWithRelated>
|
|
58
|
-
if (typeof p?.related !== 'function') return new Set()
|
|
59
|
-
try {
|
|
60
|
-
const q = (p as ParentWithRelated).related(relationship)
|
|
61
|
-
const pk = getPrimaryKey(relatedModel)
|
|
62
|
-
const out = await q.paginate(1, 1000)
|
|
63
|
-
const ids = new Set<string>()
|
|
64
|
-
for (const row of out.data) {
|
|
65
|
-
const id = (row as Record<string, unknown>)[pk]
|
|
66
|
-
if (id !== undefined && id !== null) ids.add(String(id))
|
|
67
|
-
}
|
|
68
|
-
return ids
|
|
69
|
-
} catch {
|
|
70
|
-
return new Set()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Build the candidate list for the Attach modal's SelectField. Loads
|
|
76
|
-
* up to `limit` rows from the related model (default 50), filters out
|
|
77
|
-
* any that are already attached to the parent, and maps each to
|
|
78
|
-
* `{ value, label }` using the related Resource's record-title chain.
|
|
79
|
-
*
|
|
80
|
-
* `recordTitleAttr` is resolved from the related Resource (or falls
|
|
81
|
-
* back through the standard `name → title → id` chain). The PK comes
|
|
82
|
-
* from `relatedModel`'s introspection.
|
|
83
|
-
*/
|
|
84
|
-
export async function loadAttachableCandidates(
|
|
85
|
-
parent: unknown,
|
|
86
|
-
relationship: string,
|
|
87
|
-
relatedModel: ModelLike,
|
|
88
|
-
recordTitleAttr: string | undefined,
|
|
89
|
-
limit: number = 50,
|
|
90
|
-
): Promise<AttachableRow[]> {
|
|
91
|
-
const attached = await loadAttachedIds(parent, relationship, relatedModel)
|
|
92
|
-
|
|
93
|
-
// Use the related model's own query (not the relation accessor) so
|
|
94
|
-
// we see all attachable rows, then subtract attached ids in memory.
|
|
95
|
-
// For implicit Prisma pivots the accessor doesn't expose a
|
|
96
|
-
// whereNotIn-on-pivot-keys helper, and even when it does the cost
|
|
97
|
-
// of fetching ≤ 50 rows + a Set lookup is negligible.
|
|
98
|
-
const q = (relatedModel as { query?: () => ModelQuery }).query?.()
|
|
99
|
-
if (!q) return []
|
|
100
|
-
|
|
101
|
-
const result = await q.paginate(1, limit + attached.size).catch(() => null)
|
|
102
|
-
if (!result) return []
|
|
103
|
-
|
|
104
|
-
const pk = getPrimaryKey(relatedModel)
|
|
105
|
-
|
|
106
|
-
const rows: AttachableRow[] = []
|
|
107
|
-
for (const row of result.data) {
|
|
108
|
-
const r = row as Record<string, unknown>
|
|
109
|
-
const id = r[pk]
|
|
110
|
-
if (id === undefined || id === null) continue
|
|
111
|
-
const idStr = String(id)
|
|
112
|
-
if (attached.has(idStr)) continue
|
|
113
|
-
rows.push({ value: idStr, label: deriveLabel(r, recordTitleAttr, idStr) })
|
|
114
|
-
if (rows.length >= limit) break
|
|
115
|
-
}
|
|
116
|
-
return rows
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function deriveLabel(
|
|
120
|
-
record: Record<string, unknown>,
|
|
121
|
-
attr: string | undefined,
|
|
122
|
-
fallback: string,
|
|
123
|
-
): string {
|
|
124
|
-
const explicit = attr && typeof record[attr] === 'string' ? (record[attr] as string) : undefined
|
|
125
|
-
if (explicit && explicit.length > 0) return explicit
|
|
126
|
-
for (const key of ['name', 'title', 'label']) {
|
|
127
|
-
const v = record[key]
|
|
128
|
-
if (typeof v === 'string' && v.length > 0) return v
|
|
129
|
-
}
|
|
130
|
-
return fallback
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Build the modal-form schema for `Action.relationAttach`. A single
|
|
135
|
-
* `SelectField` named `_attachId` populated by an async resolver that
|
|
136
|
-
* defers to `loadAttachableCandidates` at render time. Server-side the
|
|
137
|
-
* resolver runs while the table is being resolved (the parent record
|
|
138
|
-
* lives on `RenderContext.record` because the manager's list-page mode
|
|
139
|
-
* stamps it there).
|
|
140
|
-
*
|
|
141
|
-
* `recordTitleAttr` defaults to `'name'` when neither the manager nor
|
|
142
|
-
* the related Resource has set it explicitly. The candidate query
|
|
143
|
-
* limit is fixed at 50 in v1 — see plan doc for rationale.
|
|
144
|
-
*/
|
|
145
|
-
export function buildAttachModalSchema(deps: {
|
|
146
|
-
Related: ResourceClass
|
|
147
|
-
relationship: string
|
|
148
|
-
recordTitleAttr: string | undefined
|
|
149
|
-
labelSingular: string
|
|
150
|
-
}): Element[] {
|
|
151
|
-
const { Related, relationship, recordTitleAttr, labelSingular } = deps
|
|
152
|
-
|
|
153
|
-
const select = SelectField.make('_attachId')
|
|
154
|
-
.label(labelSingular)
|
|
155
|
-
.required()
|
|
156
|
-
.options(async (ctx) => {
|
|
157
|
-
// The resolver runs at schema-resolve time. For a header action
|
|
158
|
-
// on a manager list page the parent record is at `ctx.record`.
|
|
159
|
-
const parent = ctx.record
|
|
160
|
-
if (!parent) return []
|
|
161
|
-
if (!Related.model) return []
|
|
162
|
-
const rows = await loadAttachableCandidates(
|
|
163
|
-
parent,
|
|
164
|
-
relationship,
|
|
165
|
-
Related.model,
|
|
166
|
-
recordTitleAttr,
|
|
167
|
-
)
|
|
168
|
-
return rows
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
return [select]
|
|
172
|
-
}
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bulk-placement Action factories — `bulkDelete / bulkRestore /
|
|
3
|
-
* bulkForceDelete / bulkReplicate`. Handler-style: iterate
|
|
4
|
-
* `ctx.records`, run policy per-row (parallelized via
|
|
5
|
-
* `forEachAllowed`), call the matching Resource / Model method. No new
|
|
6
|
-
* routes — the existing `/_action/:actionName` dispatcher already
|
|
7
|
-
* handles bulk via `ctx.records`.
|
|
8
|
-
*
|
|
9
|
-
* Drop into `bulkActions([...])` from inside `Resource.table()`. Each
|
|
10
|
-
* returns a notification with the count succeeded; rows whose policy
|
|
11
|
-
* denied (or whose call threw) are silently skipped — surface them
|
|
12
|
-
* via your own logging if needed. When no rows succeed (empty
|
|
13
|
-
* selection, all denied, all threw) the handler emits a `'warning'`
|
|
14
|
-
* toast instead of misleading "0 X deleted" success.
|
|
15
|
-
*
|
|
16
|
-
* See `docs/plans/action-split.md` for the split plan.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { Action, type ReplicateOptions, type ResourceLike } from './Action.js'
|
|
20
|
-
import { buildReplica, callPredicate, forEachAllowed } from './factoryHelpers.js'
|
|
21
|
-
|
|
22
|
-
/** Pick the right label form for a count — `labelSingular` for 1,
|
|
23
|
-
* `label` (plural, lowercased) for any other count. Fall back to a
|
|
24
|
-
* naive `${labelSingular}s` when no plural label is set. Used by bulk
|
|
25
|
-
* notification copy so we don't ship "1 posts moved to trash". */
|
|
26
|
-
function labelForCount(R: ResourceLike, n: number): string {
|
|
27
|
-
if (n === 1) return R.labelSingular.toLowerCase()
|
|
28
|
-
const plural = R.label?.toLowerCase()
|
|
29
|
-
return plural ?? `${R.labelSingular.toLowerCase()}s`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Bulk delete — calls `R.deleteRecord(id)` per row. On a
|
|
33
|
-
* soft-delete resource that hits `Model.delete()` which writes
|
|
34
|
-
* `deletedAt`. Notification: "N posts moved to trash" / "N posts
|
|
35
|
-
* deleted" depending on `R.softDeletes`. */
|
|
36
|
-
export function bulkDeleteAction(R: ResourceLike, _basePath: string): Action {
|
|
37
|
-
return Action.make('bulkDelete')
|
|
38
|
-
.label('Delete selected')
|
|
39
|
-
.destructive()
|
|
40
|
-
.bulk()
|
|
41
|
-
.confirm(`Delete the selected ${labelForCount(R, 0)}?`)
|
|
42
|
-
.handler(async (ctx) => {
|
|
43
|
-
const records = ctx.records ?? []
|
|
44
|
-
const Rfull = R as ResourceLike & { deleteRecord(id: string): Promise<void> }
|
|
45
|
-
const verb = R.softDeletes ? 'moved to trash' : 'deleted'
|
|
46
|
-
const n = await forEachAllowed(
|
|
47
|
-
records,
|
|
48
|
-
(record) => callPredicate(R.canDelete, ctx.user, record),
|
|
49
|
-
async (id) => { await Rfull.deleteRecord(id) },
|
|
50
|
-
)
|
|
51
|
-
if (n === 0) {
|
|
52
|
-
return { notify: { title: `Nothing to delete (no permitted rows)`, type: 'warning' } as never }
|
|
53
|
-
}
|
|
54
|
-
return { notify: { title: `${n} ${labelForCount(R, n)} ${verb}`, type: 'success' } as never }
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Bulk restore — calls `R.model.restore(id)` per row. Visible only
|
|
59
|
-
* on soft-delete resources (the entire bulk-restore concept is
|
|
60
|
-
* specific to them). */
|
|
61
|
-
export function bulkRestoreAction(R: ResourceLike, _basePath: string): Action {
|
|
62
|
-
return Action.make('bulkRestore')
|
|
63
|
-
.label('Restore selected')
|
|
64
|
-
.color('success')
|
|
65
|
-
.bulk()
|
|
66
|
-
.confirm(`Restore the selected ${labelForCount(R, 0)}?`)
|
|
67
|
-
.handler(async (ctx) => {
|
|
68
|
-
const Rfull = R as ResourceLike & { model?: { restore?(id: string | number): Promise<unknown> } }
|
|
69
|
-
const restore = Rfull.model?.restore
|
|
70
|
-
if (!restore) {
|
|
71
|
-
return { notify: { title: 'Restore not configured', type: 'error' } as never }
|
|
72
|
-
}
|
|
73
|
-
const records = ctx.records ?? []
|
|
74
|
-
const n = await forEachAllowed(
|
|
75
|
-
records,
|
|
76
|
-
(record) => callPredicate(R.canRestore, ctx.user, record),
|
|
77
|
-
async (id) => { await restore(id) },
|
|
78
|
-
)
|
|
79
|
-
if (n === 0) {
|
|
80
|
-
return { notify: { title: `Nothing to restore (no permitted rows)`, type: 'warning' } as never }
|
|
81
|
-
}
|
|
82
|
-
return { notify: { title: `${n} ${labelForCount(R, n)} restored`, type: 'success' } as never }
|
|
83
|
-
})
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Bulk force-delete — calls `R.model.forceDelete(id)` per row. Same
|
|
87
|
-
* destructive confirm as the per-row variant. Visible only on
|
|
88
|
-
* soft-delete resources. */
|
|
89
|
-
export function bulkForceDeleteAction(R: ResourceLike, _basePath: string): Action {
|
|
90
|
-
return Action.make('bulkForceDelete')
|
|
91
|
-
.label('Delete forever')
|
|
92
|
-
.destructive()
|
|
93
|
-
.bulk()
|
|
94
|
-
.confirm(`Permanently delete the selected ${labelForCount(R, 0)}? This cannot be undone.`)
|
|
95
|
-
.handler(async (ctx) => {
|
|
96
|
-
const Rfull = R as ResourceLike & { model?: { forceDelete?(id: string | number): Promise<void> } }
|
|
97
|
-
const forceDelete = Rfull.model?.forceDelete
|
|
98
|
-
if (!forceDelete) {
|
|
99
|
-
return { notify: { title: 'Force-delete not configured', type: 'error' } as never }
|
|
100
|
-
}
|
|
101
|
-
const records = ctx.records ?? []
|
|
102
|
-
const n = await forEachAllowed(
|
|
103
|
-
records,
|
|
104
|
-
(record) => callPredicate(R.canForceDelete, ctx.user, record),
|
|
105
|
-
async (id) => { await forceDelete(id) },
|
|
106
|
-
)
|
|
107
|
-
if (n === 0) {
|
|
108
|
-
return { notify: { title: `Nothing to delete (no permitted rows)`, type: 'warning' } as never }
|
|
109
|
-
}
|
|
110
|
-
return { notify: { title: `${n} ${labelForCount(R, n)} permanently deleted`, type: 'success' } as never }
|
|
111
|
-
})
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Bulk replicate — calls `R.model.create(...)` once per selected row
|
|
116
|
-
* with the source row's attributes minus PK / soft-delete column /
|
|
117
|
-
* `opts.excludeAttributes`. Optional `opts.beforeReplicaSaved(replica,
|
|
118
|
-
* source)` runs per-row. Rows that throw during create are skipped
|
|
119
|
-
* silently so a single bad row doesn't abort the batch (the user sees
|
|
120
|
-
* the success count on the toast). Visibility delegates to
|
|
121
|
-
* `R.canCreate(user)`.
|
|
122
|
-
*
|
|
123
|
-
* Sibling of `replicateAction` — same options bag, same strip set,
|
|
124
|
-
* same authorization gate. Stays on the list page (no per-row
|
|
125
|
-
* redirect possible for N rows).
|
|
126
|
-
*/
|
|
127
|
-
export function bulkReplicateAction(
|
|
128
|
-
R: ResourceLike,
|
|
129
|
-
_basePath: string,
|
|
130
|
-
opts: ReplicateOptions = {},
|
|
131
|
-
): Action {
|
|
132
|
-
return Action.make('bulkReplicate')
|
|
133
|
-
.label('Replicate selected')
|
|
134
|
-
.bulk()
|
|
135
|
-
.confirm(`Replicate the selected ${labelForCount(R, 0)}?`)
|
|
136
|
-
.handler(async (ctx) => {
|
|
137
|
-
const M = R.model
|
|
138
|
-
if (!M || typeof M.create !== 'function') {
|
|
139
|
-
return { notify: { title: 'Replicate not configured (resource has no model.create)', type: 'error' } as never }
|
|
140
|
-
}
|
|
141
|
-
const records = ctx.records ?? []
|
|
142
|
-
// Per-row predicate eval preserved — `canCreate` ignores the record
|
|
143
|
-
// in the common case, but users may write stateful predicates that
|
|
144
|
-
// gate per-attempt.
|
|
145
|
-
const n = await forEachAllowed(
|
|
146
|
-
records,
|
|
147
|
-
() => callPredicate(R.canCreate, ctx.user),
|
|
148
|
-
async (_id, source) => {
|
|
149
|
-
const { replica } = await buildReplica(source, M, {
|
|
150
|
-
excludeAttributes: opts.excludeAttributes,
|
|
151
|
-
deletedAtColumn: R.deletedAtColumn,
|
|
152
|
-
beforeReplicaSaved: opts.beforeReplicaSaved,
|
|
153
|
-
})
|
|
154
|
-
await M.create(replica)
|
|
155
|
-
},
|
|
156
|
-
)
|
|
157
|
-
if (n === 0) {
|
|
158
|
-
return { notify: { title: `Nothing to replicate (no permitted rows)`, type: 'warning' } as never }
|
|
159
|
-
}
|
|
160
|
-
const defaultTitle = `${n} ${labelForCount(R, n)} replicated`
|
|
161
|
-
const overrideTitle = opts.getCreatedNotificationTitle
|
|
162
|
-
? await opts.getCreatedNotificationTitle({ count: n, records })
|
|
163
|
-
: undefined
|
|
164
|
-
const title = overrideTitle !== undefined ? overrideTitle : defaultTitle
|
|
165
|
-
return { notify: { title, type: 'success' } as never }
|
|
166
|
-
})
|
|
167
|
-
.visible(({ user }) => callPredicate(R.canCreate, user))
|
|
168
|
-
}
|