@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,633 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import { Resource } from '../Resource.js'
|
|
5
|
-
import { Form } from '../elements/Form.js'
|
|
6
|
-
import { Table } from '../elements/Table.js'
|
|
7
|
-
import { Column } from '../Column.js'
|
|
8
|
-
import { TextField } from '../fields/TextField.js'
|
|
9
|
-
import { defaultListPage, defaultCreatePage, defaultEditPage } from '../defaultPages.js'
|
|
10
|
-
import type { ModelLike, ModelQuery } from './modelDefaults.js'
|
|
11
|
-
import type { TableContext } from '../elements/Table.js'
|
|
12
|
-
import {
|
|
13
|
-
defaultRelatedQuery,
|
|
14
|
-
resolveRelatedQuery,
|
|
15
|
-
modelRelationTableRecords,
|
|
16
|
-
getRelationType,
|
|
17
|
-
findRecord,
|
|
18
|
-
} from './modelDefaults.js'
|
|
19
|
-
|
|
20
|
-
// ── Fake ModelLike that records every call so tests can assert on it ──
|
|
21
|
-
|
|
22
|
-
interface FakeCall { kind: string; args: unknown[] }
|
|
23
|
-
|
|
24
|
-
function makeFakeModel(opts: {
|
|
25
|
-
primaryKey?: string
|
|
26
|
-
paginateResult?: { data: unknown[]; total: number }
|
|
27
|
-
findResult?: unknown
|
|
28
|
-
createResult?: unknown
|
|
29
|
-
updateResult?: unknown
|
|
30
|
-
} = {}): ModelLike & { calls: FakeCall[]; lastQuery: FakeQuery | null } {
|
|
31
|
-
const calls: FakeCall[] = []
|
|
32
|
-
let lastQuery: FakeQuery | null = null
|
|
33
|
-
|
|
34
|
-
const model = {
|
|
35
|
-
primaryKey: opts.primaryKey,
|
|
36
|
-
async find(id: string | number) {
|
|
37
|
-
calls.push({ kind: 'find', args: [id] })
|
|
38
|
-
return opts.findResult ?? null
|
|
39
|
-
},
|
|
40
|
-
async create(data: Record<string, unknown>) {
|
|
41
|
-
calls.push({ kind: 'create', args: [data] })
|
|
42
|
-
return opts.createResult ?? { id: 'new', ...data }
|
|
43
|
-
},
|
|
44
|
-
async update(id: string | number, data: Record<string, unknown>) {
|
|
45
|
-
calls.push({ kind: 'update', args: [id, data] })
|
|
46
|
-
return opts.updateResult ?? { id, ...data }
|
|
47
|
-
},
|
|
48
|
-
async delete(id: string | number) {
|
|
49
|
-
calls.push({ kind: 'delete', args: [id] })
|
|
50
|
-
},
|
|
51
|
-
query(): ModelQuery {
|
|
52
|
-
const q = new FakeQuery(opts.paginateResult ?? { data: [], total: 0 })
|
|
53
|
-
lastQuery = q
|
|
54
|
-
return q
|
|
55
|
-
},
|
|
56
|
-
calls,
|
|
57
|
-
get lastQuery() { return lastQuery },
|
|
58
|
-
} as unknown as ModelLike & { calls: FakeCall[]; lastQuery: FakeQuery | null }
|
|
59
|
-
|
|
60
|
-
return model
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
class FakeQuery implements ModelQuery {
|
|
64
|
-
ops: Array<{ op: string; args: unknown[] }> = []
|
|
65
|
-
constructor(private readonly _paginateResult: { data: unknown[]; total: number }) {}
|
|
66
|
-
|
|
67
|
-
where(...args: unknown[]): ModelQuery { this.ops.push({ op: 'where', args }); return this }
|
|
68
|
-
orWhere(...args: unknown[]): ModelQuery { this.ops.push({ op: 'orWhere', args }); return this }
|
|
69
|
-
orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): ModelQuery {
|
|
70
|
-
this.ops.push({ op: 'orderBy', args: [column, direction] })
|
|
71
|
-
return this
|
|
72
|
-
}
|
|
73
|
-
async paginate(page: number, perPage?: number): Promise<{ data: unknown[]; total: number }> {
|
|
74
|
-
this.ops.push({ op: 'paginate', args: [page, perPage] })
|
|
75
|
-
return this._paginateResult
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Tests ────────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
describe('Model-driven defaults — list page Table.records', () => {
|
|
82
|
-
let model: ReturnType<typeof makeFakeModel>
|
|
83
|
-
|
|
84
|
-
class ArticleResource extends Resource {
|
|
85
|
-
static override label = 'Articles'
|
|
86
|
-
static override labelSingular = 'Article'
|
|
87
|
-
static override slug = 'articles'
|
|
88
|
-
static override get model() { return model }
|
|
89
|
-
static override table(table: Table): Table {
|
|
90
|
-
return table.columns([
|
|
91
|
-
Column.make('title').sortable().searchable(),
|
|
92
|
-
Column.make('slug').searchable(),
|
|
93
|
-
Column.make('createdAt').sortable(),
|
|
94
|
-
]).defaultSort('createdAt', 'desc').paginate(20)
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
beforeEach(() => {
|
|
99
|
-
model = makeFakeModel({ paginateResult: { data: [{ id: '1' }], total: 7 } })
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('paginates with default sort + perPage when ctx is empty', async () => {
|
|
103
|
-
const List = defaultListPage(ArticleResource)
|
|
104
|
-
const schema = await List.schema() as Array<{ getType(): string }>
|
|
105
|
-
const table = schema[1] as Table
|
|
106
|
-
const handler = table.getRecords()
|
|
107
|
-
assert.ok(handler, 'records handler should be installed')
|
|
108
|
-
|
|
109
|
-
const result = await handler({ page: 1 } as TableContext)
|
|
110
|
-
assert.deepEqual(result, { rows: [{ id: '1' }], total: 7 })
|
|
111
|
-
|
|
112
|
-
const ops = model.lastQuery!.ops
|
|
113
|
-
// No search / no sort applied here — defaults to paginate(1, undefined).
|
|
114
|
-
// The defaultSort + perPage are honored by loadTableRecords (the URL
|
|
115
|
-
// query parser), not the records handler — verified separately below.
|
|
116
|
-
assert.equal(ops.length, 1)
|
|
117
|
-
assert.deepEqual(ops[0], { op: 'paginate', args: [1, 20] })
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('applies search across every searchable column with LIKE/orWhere', async () => {
|
|
121
|
-
const List = defaultListPage(ArticleResource)
|
|
122
|
-
const table = (await List.schema())[1] as unknown as Table
|
|
123
|
-
const handler = table.getRecords()!
|
|
124
|
-
|
|
125
|
-
await handler({ search: 'foo', page: 1 } as TableContext)
|
|
126
|
-
const ops = model.lastQuery!.ops
|
|
127
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['title', 'LIKE', '%foo%'] })
|
|
128
|
-
assert.deepEqual(ops[1], { op: 'orWhere', args: ['slug', 'LIKE', '%foo%'] })
|
|
129
|
-
assert.deepEqual(ops[2], { op: 'paginate', args: [1, 20] })
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('translates ctx.sort to orderBy with uppercased direction', async () => {
|
|
133
|
-
const List = defaultListPage(ArticleResource)
|
|
134
|
-
const table = (await List.schema())[1] as unknown as Table
|
|
135
|
-
const handler = table.getRecords()!
|
|
136
|
-
|
|
137
|
-
await handler({ sort: { column: 'createdAt', direction: 'desc' }, page: 2, perPage: 5 } as TableContext)
|
|
138
|
-
const ops = model.lastQuery!.ops
|
|
139
|
-
assert.deepEqual(ops[0], { op: 'orderBy', args: ['createdAt', 'DESC'] })
|
|
140
|
-
assert.deepEqual(ops[1], { op: 'paginate', args: [2, 5] })
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('user-supplied .records() wins over the model default', async () => {
|
|
144
|
-
class CustomResource extends ArticleResource {
|
|
145
|
-
static override table(table: Table): Table {
|
|
146
|
-
return table.columns([Column.make('title')])
|
|
147
|
-
.records(async () => ({ rows: [{ id: 'custom' }], total: 1 }))
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const table = (await defaultListPage(CustomResource).schema())[1] as unknown as Table
|
|
151
|
-
// The user's records handler stays — running it should NOT touch the model.
|
|
152
|
-
const handler = table.getRecords()!
|
|
153
|
-
const result = await handler({} as TableContext)
|
|
154
|
-
assert.deepEqual(result, { rows: [{ id: 'custom' }], total: 1 })
|
|
155
|
-
assert.equal(model.calls.length, 0)
|
|
156
|
-
})
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
describe('Model-driven defaults — Form.save', () => {
|
|
160
|
-
let model: ReturnType<typeof makeFakeModel>
|
|
161
|
-
|
|
162
|
-
class ArticleResource extends Resource {
|
|
163
|
-
static override label = 'Articles'
|
|
164
|
-
static override labelSingular = 'Article'
|
|
165
|
-
static override slug = 'articles'
|
|
166
|
-
static override get model() { return model }
|
|
167
|
-
static override form(form: Form): Form {
|
|
168
|
-
return form.schema([TextField.make('title').required()])
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
beforeEach(() => {
|
|
173
|
-
model = makeFakeModel()
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('create page calls model.create when ctx has no record', async () => {
|
|
177
|
-
const Create = defaultCreatePage(ArticleResource)
|
|
178
|
-
const form = (Create.schema() as Array<unknown>)[1] as Form
|
|
179
|
-
const save = form.getSave()!
|
|
180
|
-
|
|
181
|
-
await save({ title: 'Hello' }, { values: { title: 'Hello' } })
|
|
182
|
-
assert.deepEqual(model.calls, [{ kind: 'create', args: [{ title: 'Hello' }] }])
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
it('edit page calls model.update with the record id when ctx.record carries one', async () => {
|
|
186
|
-
const Edit = defaultEditPage(ArticleResource)
|
|
187
|
-
const form = (Edit.schema() as Array<unknown>)[1] as Form
|
|
188
|
-
const save = form.getSave()!
|
|
189
|
-
|
|
190
|
-
await save({ title: 'Updated' }, { values: { title: 'Updated' }, record: { id: 42, title: 'Old' } })
|
|
191
|
-
assert.deepEqual(model.calls, [{ kind: 'update', args: [42, { title: 'Updated' }] }])
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('honors ModelLike.primaryKey when discriminating create vs update', async () => {
|
|
195
|
-
model = makeFakeModel({ primaryKey: 'uuid' })
|
|
196
|
-
const Edit = defaultEditPage(ArticleResource)
|
|
197
|
-
const form = (Edit.schema() as Array<unknown>)[1] as Form
|
|
198
|
-
const save = form.getSave()!
|
|
199
|
-
|
|
200
|
-
await save({ title: 'X' }, { values: { title: 'X' }, record: { uuid: 'abc-123', title: 'Old' } })
|
|
201
|
-
assert.deepEqual(model.calls, [{ kind: 'update', args: ['abc-123', { title: 'X' }] }])
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
it('user-supplied Form.save() wins over the model default', async () => {
|
|
205
|
-
let calledWith: unknown
|
|
206
|
-
class CustomResource extends ArticleResource {
|
|
207
|
-
static override form(form: Form): Form {
|
|
208
|
-
return form.schema([TextField.make('title')]).save(async (data) => {
|
|
209
|
-
calledWith = data
|
|
210
|
-
return { id: 'custom' }
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const Create = defaultCreatePage(CustomResource)
|
|
215
|
-
const form = (Create.schema() as Array<unknown>)[1] as Form
|
|
216
|
-
const save = form.getSave()!
|
|
217
|
-
|
|
218
|
-
await save({ title: 'Hi' }, { values: { title: 'Hi' } })
|
|
219
|
-
assert.deepEqual(calledWith, { title: 'Hi' })
|
|
220
|
-
assert.equal(model.calls.length, 0)
|
|
221
|
-
})
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
describe('Model-driven defaults — Form.loadRecord + Resource.deleteRecord', () => {
|
|
225
|
-
let model: ReturnType<typeof makeFakeModel>
|
|
226
|
-
|
|
227
|
-
class ArticleResource extends Resource {
|
|
228
|
-
static override label = 'Articles'
|
|
229
|
-
static override labelSingular = 'Article'
|
|
230
|
-
static override slug = 'articles'
|
|
231
|
-
static override get model() { return model }
|
|
232
|
-
static override form(form: Form): Form {
|
|
233
|
-
return form.schema([TextField.make('title')])
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
beforeEach(() => {
|
|
238
|
-
// loadRecord routes through `R.query(ctx).where(pk, id).paginate(1, 1)`
|
|
239
|
-
// (so a `Resource.query()` override scopes per-record loads too).
|
|
240
|
-
// Tests therefore configure paginateResult, not findResult — `find()`
|
|
241
|
-
// is no longer on the load path.
|
|
242
|
-
model = makeFakeModel({ paginateResult: { data: [{ id: '7', title: 'Loaded' }], total: 1 } })
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('edit page loadRecord routes through Resource.query → where(pk, id) → paginate(1, 1)', async () => {
|
|
246
|
-
const Edit = defaultEditPage(ArticleResource)
|
|
247
|
-
const form = (Edit.schema() as Array<unknown>)[1] as Form
|
|
248
|
-
const load = form.getLoadRecord()!
|
|
249
|
-
|
|
250
|
-
const record = await load('7', { values: {} })
|
|
251
|
-
assert.deepEqual(record, { id: '7', title: 'Loaded' })
|
|
252
|
-
// No bare `find(id)` call — the load goes through the query builder
|
|
253
|
-
// so user-installed `Resource.query` scopes apply.
|
|
254
|
-
assert.equal(model.calls.find(c => c.kind === 'find'), undefined)
|
|
255
|
-
const ops = model.lastQuery!.ops
|
|
256
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['id', '=', '7'] })
|
|
257
|
-
assert.deepEqual(ops[1], { op: 'paginate', args: [1, 1] })
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('edit page loadRecord returns null when the scoped query yields no rows', async () => {
|
|
261
|
-
model = makeFakeModel({ paginateResult: { data: [], total: 0 } })
|
|
262
|
-
const Edit = defaultEditPage(ArticleResource)
|
|
263
|
-
const form = (Edit.schema() as Array<unknown>)[1] as Form
|
|
264
|
-
const record = await form.getLoadRecord()!('7', { values: {} })
|
|
265
|
-
assert.equal(record, null)
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
it('deleteRecord proxies to model.delete', async () => {
|
|
269
|
-
await ArticleResource.deleteRecord('7')
|
|
270
|
-
assert.deepEqual(model.calls, [{ kind: 'delete', args: ['7'] }])
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('deleteRecord still throws when no model is set and the user did not override', async () => {
|
|
274
|
-
class NoModelResource extends Resource {
|
|
275
|
-
static override label = 'Bare'
|
|
276
|
-
}
|
|
277
|
-
await assert.rejects(
|
|
278
|
-
() => NoModelResource.deleteRecord('1'),
|
|
279
|
-
/no deleteRecord/,
|
|
280
|
-
)
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('user-supplied Resource.deleteRecord wins over the model default', async () => {
|
|
284
|
-
let called = false
|
|
285
|
-
class CustomResource extends ArticleResource {
|
|
286
|
-
static override async deleteRecord(_id: string): Promise<void> {
|
|
287
|
-
called = true
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
await CustomResource.deleteRecord('7')
|
|
291
|
-
assert.equal(called, true)
|
|
292
|
-
assert.equal(model.calls.length, 0)
|
|
293
|
-
})
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
describe('Model-driven defaults — sentinel errors when no model + no handler', () => {
|
|
297
|
-
class BareResource extends Resource {
|
|
298
|
-
static override label = 'Bare'
|
|
299
|
-
static override labelSingular = 'Bare'
|
|
300
|
-
static override slug = 'bare'
|
|
301
|
-
static override form(form: Form): Form {
|
|
302
|
-
return form.schema([TextField.make('title')])
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
it('create page save still throws the sentinel error when neither model nor save is set', () => {
|
|
307
|
-
const Create = defaultCreatePage(BareResource)
|
|
308
|
-
const form = (Create.schema() as Array<unknown>)[1] as Form
|
|
309
|
-
const save = form.getSave()!
|
|
310
|
-
assert.throws(() => (save as () => unknown)(), /no save handler/)
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it('edit page loadRecord still throws the sentinel error when neither model nor loadRecord is set', () => {
|
|
314
|
-
const Edit = defaultEditPage(BareResource)
|
|
315
|
-
const form = (Edit.schema() as Array<unknown>)[1] as Form
|
|
316
|
-
const load = form.getLoadRecord()!
|
|
317
|
-
assert.throws(() => (load as () => unknown)(), /no loadRecord handler/)
|
|
318
|
-
})
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
// ── Plan #11 relation helpers ─────────────────────────────────────────
|
|
322
|
-
|
|
323
|
-
describe('defaultRelatedQuery (Plan #11)', () => {
|
|
324
|
-
it('delegates to parent.related(name) and returns the ModelQuery', () => {
|
|
325
|
-
const q = new FakeQuery({ data: [], total: 0 })
|
|
326
|
-
const parent = {
|
|
327
|
-
relatedCalls: [] as string[],
|
|
328
|
-
related(name: string): ModelQuery {
|
|
329
|
-
this.relatedCalls.push(name)
|
|
330
|
-
return q
|
|
331
|
-
},
|
|
332
|
-
}
|
|
333
|
-
const out = defaultRelatedQuery(parent, 'posts')
|
|
334
|
-
assert.equal(out, q)
|
|
335
|
-
assert.deepEqual(parent.relatedCalls, ['posts'])
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
it('throws a clear error when the parent has no .related() method', () => {
|
|
339
|
-
const parent = { id: 1, name: 'no-related-here' }
|
|
340
|
-
assert.throws(
|
|
341
|
-
() => defaultRelatedQuery(parent, 'posts'),
|
|
342
|
-
/Cannot resolve relation "posts" — parent record has no \.related\(\) method/,
|
|
343
|
-
)
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
it('throws when parent is null/undefined', () => {
|
|
347
|
-
assert.throws(() => defaultRelatedQuery(null, 'posts'), /Cannot resolve relation/)
|
|
348
|
-
assert.throws(() => defaultRelatedQuery(undefined, 'posts'), /Cannot resolve relation/)
|
|
349
|
-
})
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
describe('resolveRelatedQuery (Plan #11)', () => {
|
|
353
|
-
it('prefers ModelLike.relatedQuery override when present', () => {
|
|
354
|
-
const customQ = new FakeQuery({ data: [], total: 0 })
|
|
355
|
-
const calls: Array<{ parent: unknown; name: string }> = []
|
|
356
|
-
const M: ModelLike = {
|
|
357
|
-
...makeFakeModel(),
|
|
358
|
-
relatedQuery(parent, name) {
|
|
359
|
-
calls.push({ parent, name })
|
|
360
|
-
return customQ
|
|
361
|
-
},
|
|
362
|
-
}
|
|
363
|
-
const parent = { id: 1, related(_n: string): ModelQuery { throw new Error('should not be called') } }
|
|
364
|
-
const out = resolveRelatedQuery(M, parent, 'posts')
|
|
365
|
-
assert.equal(out, customQ)
|
|
366
|
-
assert.deepEqual(calls, [{ parent, name: 'posts' }])
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
it('falls back to parent.related(name) when no override is set', () => {
|
|
370
|
-
const M: ModelLike = makeFakeModel()
|
|
371
|
-
const q = new FakeQuery({ data: [], total: 0 })
|
|
372
|
-
const parent = { related(_n: string): ModelQuery { return q } }
|
|
373
|
-
const out = resolveRelatedQuery(M, parent, 'posts')
|
|
374
|
-
assert.equal(out, q)
|
|
375
|
-
})
|
|
376
|
-
})
|
|
377
|
-
|
|
378
|
-
describe('modelRelationTableRecords (Plan #11)', () => {
|
|
379
|
-
it('drives pagination through parent.related(name) with sort/search/perPage', async () => {
|
|
380
|
-
const q = new FakeQuery({ data: [{ id: 1 }, { id: 2 }], total: 17 })
|
|
381
|
-
const relatedCalls: string[] = []
|
|
382
|
-
const parent = {
|
|
383
|
-
related(name: string): ModelQuery {
|
|
384
|
-
relatedCalls.push(name)
|
|
385
|
-
return q
|
|
386
|
-
},
|
|
387
|
-
}
|
|
388
|
-
const parentModel: ModelLike = makeFakeModel()
|
|
389
|
-
const table = Table.make()
|
|
390
|
-
.columns([
|
|
391
|
-
Column.make('title').sortable().searchable(),
|
|
392
|
-
Column.make('body').searchable(),
|
|
393
|
-
])
|
|
394
|
-
.paginate(10)
|
|
395
|
-
const handler = modelRelationTableRecords(parentModel, parent, 'posts', table)
|
|
396
|
-
|
|
397
|
-
const ctx: TableContext = { search: 'hello', page: 2, perPage: 10, sort: { column: 'title', direction: 'asc' } }
|
|
398
|
-
const result = await handler(ctx)
|
|
399
|
-
|
|
400
|
-
assert.deepEqual(relatedCalls, ['posts'])
|
|
401
|
-
assert.deepEqual(result, { rows: [{ id: 1 }, { id: 2 }], total: 17 })
|
|
402
|
-
|
|
403
|
-
// Search → where + orWhere across the two searchable columns,
|
|
404
|
-
// then orderBy + paginate.
|
|
405
|
-
assert.deepEqual(q.ops[0], { op: 'where', args: ['title', 'LIKE', '%hello%'] })
|
|
406
|
-
assert.deepEqual(q.ops[1], { op: 'orWhere', args: ['body', 'LIKE', '%hello%'] })
|
|
407
|
-
assert.deepEqual(q.ops[2], { op: 'orderBy', args: ['title', 'ASC'] })
|
|
408
|
-
assert.deepEqual(q.ops[3], { op: 'paginate', args: [2, 10] })
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
it('honors the parent ModelLike.relatedQuery override', async () => {
|
|
412
|
-
const q = new FakeQuery({ data: [], total: 0 })
|
|
413
|
-
const overrideCalls: Array<{ parent: unknown; name: string }> = []
|
|
414
|
-
const parentModel: ModelLike = {
|
|
415
|
-
...makeFakeModel(),
|
|
416
|
-
relatedQuery(parent, name) {
|
|
417
|
-
overrideCalls.push({ parent, name })
|
|
418
|
-
return q
|
|
419
|
-
},
|
|
420
|
-
}
|
|
421
|
-
// This parent has NO `.related()` method — so falling back to
|
|
422
|
-
// defaultRelatedQuery would throw. The override must win.
|
|
423
|
-
const parent = { id: 99 }
|
|
424
|
-
|
|
425
|
-
const table = Table.make().columns([Column.make('title')])
|
|
426
|
-
const handler = modelRelationTableRecords(parentModel, parent, 'children', table)
|
|
427
|
-
const result = await handler({})
|
|
428
|
-
|
|
429
|
-
assert.deepEqual(result, { rows: [], total: 0 })
|
|
430
|
-
assert.deepEqual(overrideCalls, [{ parent, name: 'children' }])
|
|
431
|
-
})
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
describe('getRelationType (M2M follow-up)', () => {
|
|
435
|
-
it('reads the type field off a parent model relations map', () => {
|
|
436
|
-
const M: ModelLike = {
|
|
437
|
-
async find() { return null },
|
|
438
|
-
async create() { return null },
|
|
439
|
-
async update() { return null },
|
|
440
|
-
async delete() { /* no-op */ },
|
|
441
|
-
query() { return null as never },
|
|
442
|
-
}
|
|
443
|
-
Object.assign(M as object, {
|
|
444
|
-
relations: {
|
|
445
|
-
tags: { type: 'belongsToMany', model: () => null },
|
|
446
|
-
posts: { type: 'hasMany', model: () => null },
|
|
447
|
-
author: { type: 'belongsTo', model: () => null },
|
|
448
|
-
},
|
|
449
|
-
})
|
|
450
|
-
assert.equal(getRelationType(M, 'tags'), 'belongsToMany')
|
|
451
|
-
assert.equal(getRelationType(M, 'posts'), 'hasMany')
|
|
452
|
-
assert.equal(getRelationType(M, 'author'), 'belongsTo')
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
it('defaults to hasMany when the relations map is missing', () => {
|
|
456
|
-
const M: ModelLike = {
|
|
457
|
-
async find() { return null },
|
|
458
|
-
async create() { return null },
|
|
459
|
-
async update() { return null },
|
|
460
|
-
async delete() { /* no-op */ },
|
|
461
|
-
query() { return null as never },
|
|
462
|
-
}
|
|
463
|
-
assert.equal(getRelationType(M, 'whatever'), 'hasMany')
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
it('defaults to hasMany when the relation entry has no type field', () => {
|
|
467
|
-
const M: ModelLike = {
|
|
468
|
-
async find() { return null },
|
|
469
|
-
async create() { return null },
|
|
470
|
-
async update() { return null },
|
|
471
|
-
async delete() { /* no-op */ },
|
|
472
|
-
query() { return null as never },
|
|
473
|
-
}
|
|
474
|
-
Object.assign(M as object, {
|
|
475
|
-
// Some users author light test stubs without `type` — keep tolerating.
|
|
476
|
-
relations: { posts: { model: () => null, foreignKey: 'parentId' } },
|
|
477
|
-
})
|
|
478
|
-
assert.equal(getRelationType(M, 'posts'), 'hasMany')
|
|
479
|
-
})
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
// ── Resource.query() override + findRecord helper ────────────────────
|
|
483
|
-
|
|
484
|
-
describe('Resource.query() override', () => {
|
|
485
|
-
let model: ReturnType<typeof makeFakeModel>
|
|
486
|
-
|
|
487
|
-
class ArticleResource extends Resource {
|
|
488
|
-
static override label = 'Articles'
|
|
489
|
-
static override slug = 'articles'
|
|
490
|
-
static override get model() { return model }
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
beforeEach(() => {
|
|
494
|
-
model = makeFakeModel({ paginateResult: { data: [], total: 0 } })
|
|
495
|
-
})
|
|
496
|
-
|
|
497
|
-
it('default returns this.model.query()', () => {
|
|
498
|
-
const q = ArticleResource.query()
|
|
499
|
-
assert.equal(q, model.lastQuery)
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
it('throws a clear error when called on a Resource without a model', () => {
|
|
503
|
-
class NoModelResource extends Resource {
|
|
504
|
-
static override label = 'Bare'
|
|
505
|
-
}
|
|
506
|
-
assert.throws(
|
|
507
|
-
() => NoModelResource.query(),
|
|
508
|
-
/requires `static model = …` to be set/,
|
|
509
|
-
)
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
it('subclass override receives ctx and can splice in a where-clause', async () => {
|
|
513
|
-
class TenantResource extends ArticleResource {
|
|
514
|
-
static override query(ctx?: { user?: unknown }) {
|
|
515
|
-
const tenantId = (ctx?.user as { tenantId?: string } | undefined)?.tenantId
|
|
516
|
-
return super.query(ctx).where('tenantId', tenantId)
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
const q = TenantResource.query({ user: { tenantId: 't42' } })
|
|
520
|
-
const ops = (q as unknown as { ops: Array<{ op: string; args: unknown[] }> }).ops
|
|
521
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['tenantId', 't42'] })
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
it('list-page Table.records routes through R.query(ctx) — override scopes flow through', async () => {
|
|
525
|
-
let capturedUser: unknown
|
|
526
|
-
class TenantResource extends ArticleResource {
|
|
527
|
-
static override table(table: Table): Table {
|
|
528
|
-
return table.columns([Column.make('title')])
|
|
529
|
-
}
|
|
530
|
-
static override query(ctx?: { user?: unknown }) {
|
|
531
|
-
capturedUser = ctx?.user
|
|
532
|
-
return super.query(ctx)
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
const table = (await defaultListPage(TenantResource).schema())[1] as unknown as Table
|
|
536
|
-
const handler = table.getRecords()!
|
|
537
|
-
await handler({ page: 1, user: { tenantId: 't42' } } as unknown as TableContext)
|
|
538
|
-
assert.deepEqual(capturedUser, { tenantId: 't42' })
|
|
539
|
-
})
|
|
540
|
-
|
|
541
|
-
it('list-page Table.records does NOT pass user when none is set on ctx', async () => {
|
|
542
|
-
let capturedCtx: unknown
|
|
543
|
-
class WatchResource extends ArticleResource {
|
|
544
|
-
static override table(table: Table): Table { return table.columns([Column.make('title')]) }
|
|
545
|
-
static override query(ctx?: { user?: unknown }) {
|
|
546
|
-
capturedCtx = ctx
|
|
547
|
-
return super.query(ctx)
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
const table = (await defaultListPage(WatchResource).schema())[1] as unknown as Table
|
|
551
|
-
await table.getRecords()!({ page: 1 } as TableContext)
|
|
552
|
-
assert.equal(capturedCtx, undefined)
|
|
553
|
-
})
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
describe('findRecord helper', () => {
|
|
557
|
-
let model: ReturnType<typeof makeFakeModel>
|
|
558
|
-
|
|
559
|
-
class ArticleResource extends Resource {
|
|
560
|
-
static override label = 'Articles'
|
|
561
|
-
static override slug = 'articles'
|
|
562
|
-
static override get model() { return model }
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
beforeEach(() => {
|
|
566
|
-
model = makeFakeModel({ paginateResult: { data: [{ id: '7', title: 'Loaded' }], total: 1 } })
|
|
567
|
-
})
|
|
568
|
-
|
|
569
|
-
it('returns the first row from R.query(ctx).where(pk, id).paginate(1, 1)', async () => {
|
|
570
|
-
const out = await findRecord(ArticleResource, '7')
|
|
571
|
-
assert.deepEqual(out, { id: '7', title: 'Loaded' })
|
|
572
|
-
const ops = model.lastQuery!.ops
|
|
573
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['id', '=', '7'] })
|
|
574
|
-
assert.deepEqual(ops[1], { op: 'paginate', args: [1, 1] })
|
|
575
|
-
})
|
|
576
|
-
|
|
577
|
-
it('returns undefined when the scoped query yields no rows', async () => {
|
|
578
|
-
model = makeFakeModel({ paginateResult: { data: [], total: 0 } })
|
|
579
|
-
const out = await findRecord(ArticleResource, '7')
|
|
580
|
-
assert.equal(out, undefined)
|
|
581
|
-
})
|
|
582
|
-
|
|
583
|
-
it('honours a custom `primaryKey` on the model', async () => {
|
|
584
|
-
model = makeFakeModel({
|
|
585
|
-
primaryKey: 'uuid',
|
|
586
|
-
paginateResult: { data: [{ uuid: 'abc' }], total: 1 },
|
|
587
|
-
})
|
|
588
|
-
await findRecord(ArticleResource, 'abc')
|
|
589
|
-
const ops = model.lastQuery!.ops
|
|
590
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['uuid', '=', 'abc'] })
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
it('threads ctx.user through to R.query', async () => {
|
|
594
|
-
let capturedUser: unknown
|
|
595
|
-
class TenantResource extends ArticleResource {
|
|
596
|
-
static override query(ctx?: { user?: unknown }) {
|
|
597
|
-
capturedUser = ctx?.user
|
|
598
|
-
return super.query(ctx)
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
await findRecord(TenantResource, '7', { user: { tenantId: 't42' } })
|
|
602
|
-
assert.deepEqual(capturedUser, { tenantId: 't42' })
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
it('returns undefined when the Resource has no model', async () => {
|
|
606
|
-
class NoModelResource extends Resource {
|
|
607
|
-
static override label = 'Bare'
|
|
608
|
-
}
|
|
609
|
-
const out = await findRecord(NoModelResource, '7')
|
|
610
|
-
assert.equal(out, undefined)
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
it('records loaded through findRecord are filterable via override — record outside scope is invisible', async () => {
|
|
614
|
-
// Simulate a tenant scope: the override emits a where-clause that
|
|
615
|
-
// narrows results to a specific tenant. The fake query records the
|
|
616
|
-
// clauses; if we paginate on a non-matching tenant, the fake returns
|
|
617
|
-
// empty data. Pilotiq treats the missed lookup as "not found", so the
|
|
618
|
-
// override's scope acts as an authorization fence.
|
|
619
|
-
model = makeFakeModel({ paginateResult: { data: [], total: 0 } })
|
|
620
|
-
class TenantResource extends ArticleResource {
|
|
621
|
-
static override query(ctx?: { user?: unknown }) {
|
|
622
|
-
const tenantId = (ctx?.user as { tenantId?: string } | undefined)?.tenantId
|
|
623
|
-
return super.query(ctx).where('tenantId', tenantId ?? null)
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
const out = await findRecord(TenantResource, '7', { user: { tenantId: 'other-tenant' } })
|
|
627
|
-
assert.equal(out, undefined)
|
|
628
|
-
const ops = model.lastQuery!.ops
|
|
629
|
-
assert.deepEqual(ops[0], { op: 'where', args: ['tenantId', 'other-tenant'] })
|
|
630
|
-
assert.deepEqual(ops[1], { op: 'where', args: ['id', '=', '7'] })
|
|
631
|
-
assert.deepEqual(ops[2], { op: 'paginate', args: [1, 1] })
|
|
632
|
-
})
|
|
633
|
-
})
|