@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.
Files changed (518) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/boost/guidelines.md +571 -0
  3. package/boost/skills/pilotiq-actions/SKILL.md +49 -0
  4. package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
  5. package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
  6. package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -0
  7. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  9. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  10. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  11. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  12. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  13. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  14. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  15. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  16. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  17. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  18. package/dist/Pilotiq.d.ts +31 -0
  19. package/dist/Pilotiq.d.ts.map +1 -1
  20. package/dist/Pilotiq.js +3 -1
  21. package/dist/Pilotiq.js.map +1 -1
  22. package/dist/PilotiqRegistry.d.ts +13 -0
  23. package/dist/PilotiqRegistry.d.ts.map +1 -1
  24. package/dist/PilotiqRegistry.js +15 -0
  25. package/dist/PilotiqRegistry.js.map +1 -1
  26. package/dist/pageData/misc.d.ts.map +1 -1
  27. package/dist/pageData/misc.js +6 -0
  28. package/dist/pageData/misc.js.map +1 -1
  29. package/dist/pageData/navigation.d.ts +1 -0
  30. package/dist/pageData/navigation.d.ts.map +1 -1
  31. package/dist/pageData/navigation.js +3 -0
  32. package/dist/pageData/navigation.js.map +1 -1
  33. package/dist/pageData/relationPages.d.ts.map +1 -1
  34. package/dist/pageData/relationPages.js +3 -0
  35. package/dist/pageData/relationPages.js.map +1 -1
  36. package/dist/pageData/resourcePages.d.ts.map +1 -1
  37. package/dist/pageData/resourcePages.js +8 -0
  38. package/dist/pageData/resourcePages.js.map +1 -1
  39. package/dist/react/AppShell.d.ts +8 -0
  40. package/dist/react/AppShell.d.ts.map +1 -1
  41. package/dist/react/AppShell.js.map +1 -1
  42. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  43. package/dist/react/layouts/SidebarLayout.js +10 -2
  44. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  45. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
  46. package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
  47. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
  48. package/dist/routes/relations.d.ts.map +1 -1
  49. package/dist/routes/relations.js +25 -18
  50. package/dist/routes/relations.js.map +1 -1
  51. package/dist/routes/resources.js.map +1 -1
  52. package/package.json +10 -5
  53. package/.turbo/turbo-build.log +0 -8
  54. package/CLAUDE.md +0 -265
  55. package/src/Cluster.test.ts +0 -283
  56. package/src/Cluster.ts +0 -83
  57. package/src/Column.test.ts +0 -199
  58. package/src/Column.ts +0 -710
  59. package/src/Global.test.ts +0 -367
  60. package/src/Global.ts +0 -169
  61. package/src/Page.test.ts +0 -114
  62. package/src/Page.ts +0 -208
  63. package/src/Pilotiq.perf.test.ts +0 -252
  64. package/src/Pilotiq.test.ts +0 -129
  65. package/src/Pilotiq.ts +0 -1158
  66. package/src/PilotiqRegistry.ts +0 -36
  67. package/src/PilotiqServiceProvider.ts +0 -121
  68. package/src/RelationManager.test.ts +0 -400
  69. package/src/RelationManager.ts +0 -527
  70. package/src/RenderHook.test.ts +0 -252
  71. package/src/RenderHook.ts +0 -242
  72. package/src/Resource.test.ts +0 -284
  73. package/src/Resource.ts +0 -526
  74. package/src/RightPanel.test.ts +0 -202
  75. package/src/RightPanel.ts +0 -132
  76. package/src/Tab.test.ts +0 -91
  77. package/src/Tab.ts +0 -156
  78. package/src/UserMenuItem.ts +0 -145
  79. package/src/actions/Action.test.ts +0 -2526
  80. package/src/actions/Action.ts +0 -1515
  81. package/src/actions/ActionGroup.test.ts +0 -112
  82. package/src/actions/ActionGroup.ts +0 -173
  83. package/src/actions/attachFactory.ts +0 -172
  84. package/src/actions/bulkFactories.ts +0 -168
  85. package/src/actions/crudFactories.ts +0 -220
  86. package/src/actions/exportFactory.ts +0 -225
  87. package/src/actions/factoryHelpers.ts +0 -177
  88. package/src/actions/importFactory.ts +0 -243
  89. package/src/actions/index.ts +0 -17
  90. package/src/actions/m2mFactories.ts +0 -193
  91. package/src/actions/relationFactories.ts +0 -372
  92. package/src/applyPageHooks.test.ts +0 -463
  93. package/src/applyPageHooks.ts +0 -330
  94. package/src/authorization.test.ts +0 -483
  95. package/src/breadcrumbs.test.ts +0 -238
  96. package/src/cells/coerce.test.ts +0 -85
  97. package/src/cells/coerce.ts +0 -84
  98. package/src/clusterPaths.ts +0 -35
  99. package/src/columns/BadgeColumn.test.ts +0 -54
  100. package/src/columns/BadgeColumn.ts +0 -32
  101. package/src/columns/BooleanColumn.test.ts +0 -41
  102. package/src/columns/BooleanColumn.ts +0 -18
  103. package/src/columns/ColorColumn.test.ts +0 -37
  104. package/src/columns/ColorColumn.ts +0 -38
  105. package/src/columns/IconColumn.test.ts +0 -54
  106. package/src/columns/IconColumn.ts +0 -37
  107. package/src/columns/ImageColumn.test.ts +0 -41
  108. package/src/columns/ImageColumn.ts +0 -28
  109. package/src/columns/SelectColumn.ts +0 -98
  110. package/src/columns/TextColumn.test.ts +0 -190
  111. package/src/columns/TextColumn.ts +0 -20
  112. package/src/columns/TextInputColumn.ts +0 -68
  113. package/src/columns/ToggleColumn.ts +0 -46
  114. package/src/columns/editableColumns.test.ts +0 -238
  115. package/src/columns/index.ts +0 -9
  116. package/src/defaultGlobalPages.ts +0 -95
  117. package/src/defaultPages.test.ts +0 -634
  118. package/src/defaultPages.ts +0 -617
  119. package/src/defaultViewPage.test.ts +0 -147
  120. package/src/elements/Form.test.ts +0 -223
  121. package/src/elements/Form.ts +0 -416
  122. package/src/elements/ListTabs.ts +0 -28
  123. package/src/elements/Table.test.ts +0 -422
  124. package/src/elements/Table.ts +0 -850
  125. package/src/elements/TableGroup.test.ts +0 -260
  126. package/src/elements/TableGroup.ts +0 -334
  127. package/src/elements/dispatchAction.test.ts +0 -463
  128. package/src/elements/dispatchAction.ts +0 -355
  129. package/src/elements/dispatchForm.test.ts +0 -477
  130. package/src/elements/dispatchForm.ts +0 -1993
  131. package/src/elements/dispatchTable.test.ts +0 -1514
  132. package/src/elements/dispatchTable.ts +0 -745
  133. package/src/elements/index.ts +0 -21
  134. package/src/entries/BadgeEntry.ts +0 -39
  135. package/src/entries/CodeEntry.test.ts +0 -40
  136. package/src/entries/CodeEntry.ts +0 -52
  137. package/src/entries/ColorEntry.ts +0 -63
  138. package/src/entries/ComponentEntry.test.ts +0 -173
  139. package/src/entries/ComponentEntry.ts +0 -95
  140. package/src/entries/Entry.ts +0 -304
  141. package/src/entries/IconEntry.ts +0 -49
  142. package/src/entries/ImageEntry.ts +0 -61
  143. package/src/entries/KeyValueEntry.ts +0 -47
  144. package/src/entries/RepeatableEntry.test.ts +0 -239
  145. package/src/entries/RepeatableEntry.ts +0 -173
  146. package/src/entries/TextEntry.test.ts +0 -394
  147. package/src/entries/TextEntry.ts +0 -60
  148. package/src/entries/index.ts +0 -12
  149. package/src/entries/leaves.test.ts +0 -306
  150. package/src/entries/registry.ts +0 -54
  151. package/src/fields/BuilderField.test.ts +0 -1188
  152. package/src/fields/BuilderField.ts +0 -605
  153. package/src/fields/BuilderRelationship.test.ts +0 -811
  154. package/src/fields/CheckboxField.test.ts +0 -44
  155. package/src/fields/CheckboxField.ts +0 -27
  156. package/src/fields/CheckboxListField.test.ts +0 -99
  157. package/src/fields/CheckboxListField.ts +0 -66
  158. package/src/fields/ColorPickerField.test.ts +0 -33
  159. package/src/fields/ColorPickerField.ts +0 -25
  160. package/src/fields/DateField.ts +0 -54
  161. package/src/fields/DateTimeField.test.ts +0 -55
  162. package/src/fields/EmailField.ts +0 -16
  163. package/src/fields/Field.test.ts +0 -654
  164. package/src/fields/Field.ts +0 -817
  165. package/src/fields/FileUploadField.test.ts +0 -143
  166. package/src/fields/FileUploadField.ts +0 -159
  167. package/src/fields/HiddenField.test.ts +0 -27
  168. package/src/fields/HiddenField.ts +0 -28
  169. package/src/fields/KeyValueField.test.ts +0 -105
  170. package/src/fields/KeyValueField.ts +0 -55
  171. package/src/fields/MarkdownField.test.ts +0 -167
  172. package/src/fields/MarkdownField.ts +0 -162
  173. package/src/fields/NumberField.ts +0 -33
  174. package/src/fields/RadioField.test.ts +0 -94
  175. package/src/fields/RadioField.ts +0 -67
  176. package/src/fields/RepeaterField.test.ts +0 -1806
  177. package/src/fields/RepeaterField.ts +0 -939
  178. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  179. package/src/fields/RepeaterSimple.test.ts +0 -248
  180. package/src/fields/RowButton.test.ts +0 -219
  181. package/src/fields/RowButton.ts +0 -135
  182. package/src/fields/SelectField.test.ts +0 -192
  183. package/src/fields/SelectField.ts +0 -235
  184. package/src/fields/SliderField.test.ts +0 -50
  185. package/src/fields/SliderField.ts +0 -53
  186. package/src/fields/SlugField.ts +0 -24
  187. package/src/fields/TagsInputField.test.ts +0 -154
  188. package/src/fields/TagsInputField.ts +0 -133
  189. package/src/fields/TextField.test.ts +0 -213
  190. package/src/fields/TextField.ts +0 -177
  191. package/src/fields/TextareaField.test.ts +0 -58
  192. package/src/fields/TextareaField.ts +0 -59
  193. package/src/fields/ToggleButtonsField.test.ts +0 -106
  194. package/src/fields/ToggleButtonsField.ts +0 -59
  195. package/src/fields/ToggleField.ts +0 -16
  196. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  197. package/src/fields/optionsResolver.ts +0 -95
  198. package/src/fields/resolveField.ts +0 -28
  199. package/src/filters/BooleanFilter.ts +0 -35
  200. package/src/filters/DateRangeFilter.test.ts +0 -194
  201. package/src/filters/DateRangeFilter.ts +0 -148
  202. package/src/filters/Filter.test.ts +0 -268
  203. package/src/filters/Filter.ts +0 -184
  204. package/src/filters/FormFilter.test.ts +0 -238
  205. package/src/filters/FormFilter.ts +0 -215
  206. package/src/filters/MultiSelectFilter.test.ts +0 -119
  207. package/src/filters/MultiSelectFilter.ts +0 -78
  208. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  209. package/src/filters/QueryBuilderFilter.ts +0 -398
  210. package/src/filters/SelectFilter.ts +0 -46
  211. package/src/filters/TernaryFilter.test.ts +0 -160
  212. package/src/filters/TernaryFilter.ts +0 -72
  213. package/src/filters/TrashedFilter.test.ts +0 -149
  214. package/src/filters/TrashedFilter.ts +0 -55
  215. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  216. package/src/filters/queryBuilder/Constraint.ts +0 -115
  217. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  218. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  219. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  220. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  221. package/src/filters/queryBuilder/index.ts +0 -12
  222. package/src/icons/index.ts +0 -2
  223. package/src/icons/lucide.ts +0 -204
  224. package/src/icons/registry.test.ts +0 -56
  225. package/src/icons/registry.ts +0 -41
  226. package/src/icons/types.ts +0 -47
  227. package/src/index.ts +0 -525
  228. package/src/io/csv.test.ts +0 -142
  229. package/src/io/csv.ts +0 -170
  230. package/src/nestedRelationManagerData.test.ts +0 -547
  231. package/src/notifications/Notification.test.ts +0 -210
  232. package/src/notifications/Notification.ts +0 -354
  233. package/src/notifications/broadcast.test.ts +0 -110
  234. package/src/notifications/broadcast.ts +0 -95
  235. package/src/notifications/database.test.ts +0 -383
  236. package/src/notifications/database.ts +0 -398
  237. package/src/notifications/databaseNotifications.test.ts +0 -187
  238. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  239. package/src/notifications/dispatchNotificationAction.ts +0 -142
  240. package/src/notifications/flash.test.ts +0 -89
  241. package/src/notifications/flash.ts +0 -71
  242. package/src/notifications/index.ts +0 -45
  243. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  244. package/src/notifications/registerBroadcastAuth.ts +0 -100
  245. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  246. package/src/notifications/resolveSavedNotification.ts +0 -59
  247. package/src/notifications/types.ts +0 -93
  248. package/src/orm/m2mAccessor.ts +0 -66
  249. package/src/orm/modelDefaults.test.ts +0 -633
  250. package/src/orm/modelDefaults.ts +0 -666
  251. package/src/pageData/breadcrumbs.ts +0 -288
  252. package/src/pageData/forms.ts +0 -578
  253. package/src/pageData/helpers.ts +0 -857
  254. package/src/pageData/misc.ts +0 -347
  255. package/src/pageData/navigation.ts +0 -842
  256. package/src/pageData/relationPages.ts +0 -1248
  257. package/src/pageData/relationTabs.ts +0 -286
  258. package/src/pageData/resourcePages.ts +0 -609
  259. package/src/pageData.test.ts +0 -1545
  260. package/src/pageData.ts +0 -341
  261. package/src/plugins/index.ts +0 -8
  262. package/src/plugins/themeEditor.test.ts +0 -36
  263. package/src/plugins/themeEditor.ts +0 -45
  264. package/src/react/AppShell.tsx +0 -251
  265. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  266. package/src/react/CollabRoomContext.ts +0 -98
  267. package/src/react/CollabTextRendererRegistry.ts +0 -102
  268. package/src/react/CommandPalette.tsx +0 -375
  269. package/src/react/CurrentUserContext.tsx +0 -50
  270. package/src/react/CustomPageWrapperGate.tsx +0 -69
  271. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  272. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  273. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  274. package/src/react/FieldPresenceRegistry.ts +0 -46
  275. package/src/react/FormCollabBindingRegistry.ts +0 -242
  276. package/src/react/FormStateContext.tsx +0 -591
  277. package/src/react/HeadHooks.tsx +0 -126
  278. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  279. package/src/react/MarkdownEditorRegistry.ts +0 -107
  280. package/src/react/NotificationActionStrip.tsx +0 -263
  281. package/src/react/NotificationBell.tsx +0 -426
  282. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  283. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  284. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  285. package/src/react/PendingSuggestionsContext.tsx +0 -172
  286. package/src/react/RecordWrapperGate.tsx +0 -58
  287. package/src/react/RecordWrapperRegistry.ts +0 -39
  288. package/src/react/RenderHookSlot.tsx +0 -32
  289. package/src/react/RightSidebar.tsx +0 -257
  290. package/src/react/RightSidebarContext.tsx +0 -234
  291. package/src/react/RightSidebarTrigger.tsx +0 -53
  292. package/src/react/RowCoordsContext.tsx +0 -23
  293. package/src/react/SchemaRenderer.tsx +0 -549
  294. package/src/react/SearchTrigger.tsx +0 -46
  295. package/src/react/ThemeProvider.tsx +0 -93
  296. package/src/react/ThemeSettingsPage.tsx +0 -579
  297. package/src/react/ThemeToggle.tsx +0 -20
  298. package/src/react/Toaster.tsx +0 -158
  299. package/src/react/UserMenu.tsx +0 -196
  300. package/src/react/WidgetDataContext.tsx +0 -157
  301. package/src/react/cells/EditableCell.tsx +0 -389
  302. package/src/react/component-slots.test.ts +0 -103
  303. package/src/react/component-slots.ts +0 -116
  304. package/src/react/fieldJsHandler.test.ts +0 -166
  305. package/src/react/fieldJsHandler.ts +0 -79
  306. package/src/react/fields/BuilderInput.tsx +0 -1078
  307. package/src/react/fields/CheckboxInput.tsx +0 -39
  308. package/src/react/fields/CheckboxListInput.tsx +0 -102
  309. package/src/react/fields/ColorInput.tsx +0 -71
  310. package/src/react/fields/DateFieldInput.tsx +0 -70
  311. package/src/react/fields/DateTimeInput.tsx +0 -62
  312. package/src/react/fields/FieldShell.tsx +0 -348
  313. package/src/react/fields/FileUploadInput.tsx +0 -639
  314. package/src/react/fields/HiddenInput.tsx +0 -17
  315. package/src/react/fields/KeyValueInput.tsx +0 -230
  316. package/src/react/fields/MarkdownInput.tsx +0 -560
  317. package/src/react/fields/RadioInput.tsx +0 -81
  318. package/src/react/fields/RepeaterInput.test.ts +0 -116
  319. package/src/react/fields/RepeaterInput.tsx +0 -1420
  320. package/src/react/fields/SelectFieldInput.tsx +0 -280
  321. package/src/react/fields/SliderInput.tsx +0 -81
  322. package/src/react/fields/TagsInput.tsx +0 -283
  323. package/src/react/fields/TextLikeInput.tsx +0 -256
  324. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  325. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  326. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  327. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  328. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  329. package/src/react/fields/repeaterReconcile.ts +0 -104
  330. package/src/react/fields/rowChromeButton.tsx +0 -336
  331. package/src/react/fields/rowState.ts +0 -106
  332. package/src/react/fields/syncRowGates.test.ts +0 -202
  333. package/src/react/fields/syncRowGates.ts +0 -66
  334. package/src/react/fields/textInputControls.tsx +0 -238
  335. package/src/react/fields/useRowReorderDnd.ts +0 -78
  336. package/src/react/formStateHelpers.test.ts +0 -508
  337. package/src/react/formStateHelpers.ts +0 -381
  338. package/src/react/hooks/use-mobile.ts +0 -19
  339. package/src/react/icon-context.tsx +0 -60
  340. package/src/react/index.ts +0 -194
  341. package/src/react/layouts/SidebarLayout.tsx +0 -250
  342. package/src/react/layouts/TopbarLayout.tsx +0 -258
  343. package/src/react/navigate.tsx +0 -37
  344. package/src/react/onProviderSynced.test.ts +0 -90
  345. package/src/react/parseRecordEditUrl.test.ts +0 -122
  346. package/src/react/parseRecordEditUrl.ts +0 -94
  347. package/src/react/persistedState.ts +0 -40
  348. package/src/react/registry.ts +0 -48
  349. package/src/react/right-panel-registry.tsx +0 -47
  350. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  351. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  352. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  353. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  354. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  355. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  356. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  357. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  358. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  359. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  360. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  361. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  362. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  363. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  364. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  365. package/src/react/schemaRenderer/constants.ts +0 -50
  366. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  367. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  368. package/src/react/schemaRenderer/helpers.tsx +0 -81
  369. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  370. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  371. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  372. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  373. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  374. package/src/react/schemaRenderer/table/links.tsx +0 -112
  375. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  376. package/src/react/schemaRenderer/table/url.tsx +0 -143
  377. package/src/react/theme-preview/apply.ts +0 -99
  378. package/src/react/theme-preview/build-html.ts +0 -436
  379. package/src/react/ui/button.tsx +0 -51
  380. package/src/react/ui/calendar.tsx +0 -67
  381. package/src/react/ui/checkbox.tsx +0 -29
  382. package/src/react/ui/dialog.tsx +0 -108
  383. package/src/react/ui/dropdown-menu.tsx +0 -97
  384. package/src/react/ui/input.tsx +0 -20
  385. package/src/react/ui/label.tsx +0 -21
  386. package/src/react/ui/popover.tsx +0 -50
  387. package/src/react/ui/select.tsx +0 -169
  388. package/src/react/ui/separator.tsx +0 -25
  389. package/src/react/ui/sheet.tsx +0 -136
  390. package/src/react/ui/sidebar.tsx +0 -723
  391. package/src/react/ui/skeleton.tsx +0 -13
  392. package/src/react/ui/slider.tsx +0 -34
  393. package/src/react/ui/switch.tsx +0 -28
  394. package/src/react/ui/table.tsx +0 -105
  395. package/src/react/ui/tabs.tsx +0 -63
  396. package/src/react/ui/textarea.tsx +0 -18
  397. package/src/react/ui/tooltip.tsx +0 -64
  398. package/src/react/useResizableWidth.ts +0 -139
  399. package/src/react/utils.ts +0 -6
  400. package/src/react/widgetRegistry.test.ts +0 -43
  401. package/src/react/widgetRegistry.ts +0 -50
  402. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  403. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  404. package/src/react/widgets/ViewRenderer.tsx +0 -71
  405. package/src/relationManagerData.test.ts +0 -1595
  406. package/src/richtext/index.ts +0 -8
  407. package/src/richtext/registry.ts +0 -89
  408. package/src/routes/globals.ts +0 -148
  409. package/src/routes/guard.test.ts +0 -325
  410. package/src/routes/helpers.ts +0 -704
  411. package/src/routes/pages.ts +0 -175
  412. package/src/routes/panel.ts +0 -204
  413. package/src/routes/relations.ts +0 -1243
  414. package/src/routes/resources.ts +0 -781
  415. package/src/routes/theme.ts +0 -91
  416. package/src/routes-nested-relations.test.ts +0 -676
  417. package/src/routes-relations.test.ts +0 -972
  418. package/src/routes.test.ts +0 -2027
  419. package/src/routes.ts +0 -303
  420. package/src/schema/Alert.test.ts +0 -109
  421. package/src/schema/Alert.ts +0 -131
  422. package/src/schema/Block.ts +0 -169
  423. package/src/schema/Breadcrumbs.ts +0 -40
  424. package/src/schema/Card.ts +0 -35
  425. package/src/schema/Divider.ts +0 -20
  426. package/src/schema/Element.ts +0 -219
  427. package/src/schema/EmptyState.test.ts +0 -37
  428. package/src/schema/EmptyState.ts +0 -63
  429. package/src/schema/Fieldset.ts +0 -43
  430. package/src/schema/Grid.ts +0 -43
  431. package/src/schema/Group.ts +0 -30
  432. package/src/schema/Heading.ts +0 -39
  433. package/src/schema/Html.ts +0 -67
  434. package/src/schema/Icon.ts +0 -54
  435. package/src/schema/Image.ts +0 -57
  436. package/src/schema/LinkTag.ts +0 -41
  437. package/src/schema/Markdown.ts +0 -85
  438. package/src/schema/MetaTag.ts +0 -41
  439. package/src/schema/RelationTabs.ts +0 -71
  440. package/src/schema/ScriptTag.ts +0 -55
  441. package/src/schema/Section.ts +0 -160
  442. package/src/schema/ServerDataElement.test.ts +0 -140
  443. package/src/schema/ServerDataElement.ts +0 -156
  444. package/src/schema/SlotComponent.test.ts +0 -77
  445. package/src/schema/SlotComponent.ts +0 -71
  446. package/src/schema/Split.ts +0 -50
  447. package/src/schema/Stat.test.ts +0 -118
  448. package/src/schema/Stat.ts +0 -154
  449. package/src/schema/StatsOverview.test.ts +0 -141
  450. package/src/schema/StatsOverview.ts +0 -119
  451. package/src/schema/StyleTag.ts +0 -35
  452. package/src/schema/TableWidget.test.ts +0 -297
  453. package/src/schema/TableWidget.ts +0 -289
  454. package/src/schema/Tabs.ts +0 -79
  455. package/src/schema/Text.ts +0 -58
  456. package/src/schema/UnorderedList.ts +0 -49
  457. package/src/schema/View.test.ts +0 -111
  458. package/src/schema/View.ts +0 -127
  459. package/src/schema/Wizard.ts +0 -220
  460. package/src/schema/containers.test.ts +0 -564
  461. package/src/schema/headTags.test.ts +0 -134
  462. package/src/schema/index.ts +0 -40
  463. package/src/schema/primes.test.ts +0 -269
  464. package/src/schema/resolveSchema.test.ts +0 -379
  465. package/src/schema/resolveSchema.ts +0 -917
  466. package/src/schema/sanitize.ts +0 -58
  467. package/src/search.test.ts +0 -446
  468. package/src/search.ts +0 -178
  469. package/src/sessionFilters.test.ts +0 -375
  470. package/src/sessionFilters.ts +0 -143
  471. package/src/slot-components/index.ts +0 -10
  472. package/src/slot-components/registry.ts +0 -56
  473. package/src/styles/file-upload.css +0 -13
  474. package/src/summarizers/Summarizer.test.ts +0 -84
  475. package/src/summarizers/Summarizer.ts +0 -123
  476. package/src/summarizers/index.ts +0 -11
  477. package/src/theme/base-colors.ts +0 -68
  478. package/src/theme/chart-colors.ts +0 -50
  479. package/src/theme/colors.ts +0 -447
  480. package/src/theme/generate-css.test.ts +0 -139
  481. package/src/theme/generate-css.ts +0 -44
  482. package/src/theme/generate-scale.test.ts +0 -106
  483. package/src/theme/generate-scale.ts +0 -97
  484. package/src/theme/icon-map.ts +0 -42
  485. package/src/theme/index.ts +0 -34
  486. package/src/theme/migrate.test.ts +0 -178
  487. package/src/theme/migrate.ts +0 -81
  488. package/src/theme/presets.ts +0 -135
  489. package/src/theme/radius.ts +0 -18
  490. package/src/theme/resolve.test.ts +0 -238
  491. package/src/theme/resolve.ts +0 -96
  492. package/src/theme/spacing.ts +0 -18
  493. package/src/theme/storage.test.ts +0 -126
  494. package/src/theme/storage.ts +0 -106
  495. package/src/theme/theme-colors.ts +0 -88
  496. package/src/theme/types.ts +0 -125
  497. package/src/uploads/UploadAdapter.ts +0 -35
  498. package/src/uploads/index.ts +0 -2
  499. package/src/uploads/localUpload.test.ts +0 -70
  500. package/src/uploads/localUpload.ts +0 -84
  501. package/src/validation/Validator.ts +0 -49
  502. package/src/validation/index.ts +0 -28
  503. package/src/validation/rules.ts +0 -78
  504. package/src/validation/runValidators.ts +0 -435
  505. package/src/validation/uniqueValidator.test.ts +0 -196
  506. package/src/validation/uniqueValidator.ts +0 -133
  507. package/src/validation/validators.test.ts +0 -268
  508. package/src/vite.test.ts +0 -184
  509. package/src/vite.ts +0 -787
  510. package/src/widgets/index.ts +0 -10
  511. package/src/widgets/registry.ts +0 -45
  512. package/src/widgets.test.ts +0 -592
  513. package/tsconfig.build.json +0 -11
  514. package/tsconfig.json +0 -4
  515. package/tsconfig.test.json +0 -10
  516. package/views/react/Dashboard.tsx +0 -27
  517. package/views/react/Resources/Form.tsx +0 -102
  518. package/views/react/Resources/Index.tsx +0 -49
@@ -1,58 +0,0 @@
1
- import type sanitizeHtmlNs from 'sanitize-html'
2
-
3
- export type SanitizeConfig = sanitizeHtmlNs.IOptions
4
-
5
- /**
6
- * Default-secure allowlist for `Markdown` and `Html` primes — covers prose
7
- * content (headings, lists, code, links, images, tables) while blocking
8
- * `<script>` / `<iframe>`, inline event handlers, `javascript:` URIs, and
9
- * `style` attribute overrides that could break the surrounding admin chrome.
10
- *
11
- * Pass a custom config to `Markdown.sanitize(...)` / `Html.sanitize(...)`
12
- * when an admin-trusted source needs a wider allowlist (e.g. legacy CMS
13
- * HTML with embedded media, or a custom block highlighter that emits
14
- * `style="color:..."` on inline spans).
15
- */
16
- export const DEFAULT_SANITIZE_CONFIG: SanitizeConfig = {
17
- allowedTags: [
18
- 'p', 'br', 'hr', 'span', 'div',
19
- 'strong', 'em', 'b', 'i', 'u', 's', 'del', 'ins', 'mark',
20
- 'sub', 'sup', 'small', 'abbr', 'kbd',
21
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
22
- 'ul', 'ol', 'li',
23
- 'blockquote', 'pre', 'code',
24
- 'a', 'img',
25
- 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
26
- ],
27
- allowedAttributes: {
28
- a: ['href', 'title', 'rel', 'target', 'name'],
29
- img: ['src', 'alt', 'title', 'width', 'height'],
30
- abbr: ['title'],
31
- th: ['colspan', 'rowspan', 'scope'],
32
- td: ['colspan', 'rowspan'],
33
- code: ['class'],
34
- pre: ['class'],
35
- },
36
- allowedSchemes: ['http', 'https', 'mailto', 'tel'],
37
- allowedSchemesByTag: { img: ['http', 'https', 'data'] },
38
- allowProtocolRelative: false,
39
- disallowedTagsMode: 'discard',
40
- }
41
-
42
- // Lazy import — keeps `sanitize-html` (and its transitive `postcss` chain that
43
- // reaches into Node built-ins) out of the client bundle. Cached after first
44
- // resolve so per-call overhead is just the `sanitize-html` invocation itself.
45
- let sanitizerPromise: Promise<typeof sanitizeHtmlNs> | null = null
46
- function loadSanitizer(): Promise<typeof sanitizeHtmlNs> {
47
- return sanitizerPromise ??= import('sanitize-html').then(m => m.default ?? m)
48
- }
49
-
50
- /**
51
- * Sanitizes an HTML string against `DEFAULT_SANITIZE_CONFIG` (or a
52
- * caller-supplied config). Server-side only — never call from a renderer,
53
- * since the wire shape ships pre-rendered HTML.
54
- */
55
- export async function sanitizeHtml(html: string, config?: SanitizeConfig): Promise<string> {
56
- const sanitizer = await loadSanitizer()
57
- return sanitizer(html, config ?? DEFAULT_SANITIZE_CONFIG)
58
- }
@@ -1,446 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import { Pilotiq } from './Pilotiq.js'
5
- import { Resource } from './Resource.js'
6
- import { Column } from './Column.js'
7
- import { Table } from './elements/Table.js'
8
- import type { ModelLike, ModelQuery, ModelWhereOperator } from './orm/modelDefaults.js'
9
- import { searchAllResources } from './search.js'
10
- import { searchData } from './pageData.js'
11
- import { Heading } from './schema/Heading.js'
12
- import { Text } from './schema/Text.js'
13
-
14
- // ─── Fake ModelQuery / ModelLike ────────────────────────────
15
-
16
- interface FakeRow { id: string | number; [key: string]: unknown }
17
-
18
- function fakeModel(rows: FakeRow[]): ModelLike & {
19
- recordedQueries: Array<{ wheres: Array<[string, ModelWhereOperator | unknown, unknown?]>; limit?: number }>
20
- } {
21
- const recordedQueries: Array<{ wheres: Array<[string, ModelWhereOperator | unknown, unknown?]>; limit?: number }> = []
22
-
23
- const M: ModelLike & { recordedQueries: typeof recordedQueries } = {
24
- primaryKey: 'id',
25
- async find(id) { return rows.find(r => String(r['id']) === String(id)) ?? null },
26
- async create(data) { return data },
27
- async update(_id, data) { return data },
28
- async delete(_id) {},
29
- query(): ModelQuery {
30
- const wheres: Array<[string, ModelWhereOperator | unknown, unknown?]> = []
31
- const matches = (row: FakeRow): boolean => {
32
- // OR-of-LIKE only for our tests; column LIKE '%needle%'
33
- if (wheres.length === 0) return true
34
- return wheres.some(([col, op, val]) => {
35
- const haystack = String(row[col as string] ?? '').toLowerCase()
36
- if (op === 'LIKE') {
37
- const needle = String(val).replace(/^%|%$/g, '').toLowerCase()
38
- return haystack.includes(needle)
39
- }
40
- if (op === undefined) {
41
- return String(row[col as string] ?? '') === String(val)
42
- }
43
- return false
44
- })
45
- }
46
- const q: ModelQuery = {
47
- where(col: string, opOrVal: unknown, val?: unknown): ModelQuery {
48
- wheres.push([col, val !== undefined ? opOrVal : undefined, val !== undefined ? val : opOrVal])
49
- return q
50
- },
51
- orWhere(col: string, opOrVal: unknown, val?: unknown): ModelQuery {
52
- wheres.push([col, val !== undefined ? opOrVal : undefined, val !== undefined ? val : opOrVal])
53
- return q
54
- },
55
- orderBy(_col: string): ModelQuery { return q },
56
- async paginate(page: number, perPage = 10) {
57
- recordedQueries.push({ wheres: [...wheres], limit: perPage })
58
- const matched = rows.filter(matches)
59
- const start = (page - 1) * perPage
60
- return { data: matched.slice(start, start + perPage), total: matched.length }
61
- },
62
- }
63
- return q
64
- },
65
- recordedQueries,
66
- }
67
- return M
68
- }
69
-
70
- // ─── Tests ──────────────────────────────────────────────────
71
-
72
- describe('searchAllResources (Plan #12)', () => {
73
- it('returns [] for queries shorter than 2 chars', async () => {
74
- const M = fakeModel([{ id: '1', title: 'Hello' }])
75
- class A extends Resource {
76
- static override label = 'Articles'
77
- static override slug = 'articles'
78
- static override globalSearch = true
79
- static override recordTitleAttribute = 'title'
80
- static override model = M
81
- static override table(table: Table): Table {
82
- return table.columns([Column.make('title').searchable()])
83
- }
84
- }
85
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
86
- assert.deepEqual(await searchAllResources(pilotiq, '', null), [])
87
- assert.deepEqual(await searchAllResources(pilotiq, 'a', null), [])
88
- })
89
-
90
- it('skips resources with globalSearch=false', async () => {
91
- const M = fakeModel([{ id: '1', title: 'Hello world' }])
92
- class A extends Resource {
93
- static override label = 'Articles'
94
- static override slug = 'articles'
95
- // globalSearch defaults to false
96
- static override recordTitleAttribute = 'title'
97
- static override model = M
98
- static override table(table: Table): Table {
99
- return table.columns([Column.make('title').searchable()])
100
- }
101
- }
102
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
103
- assert.deepEqual(await searchAllResources(pilotiq, 'hello', null), [])
104
- })
105
-
106
- it('returns matching rows from opted-in resources', async () => {
107
- const M = fakeModel([
108
- { id: '1', title: 'Hello world' },
109
- { id: '2', title: 'Goodbye' },
110
- { id: '3', title: 'Hello again' },
111
- ])
112
- class A extends Resource {
113
- static override label = 'Articles'
114
- static override slug = 'articles'
115
- static override globalSearch = true
116
- static override recordTitleAttribute = 'title'
117
- static override model = M
118
- static override table(table: Table): Table {
119
- return table.columns([Column.make('title').searchable()])
120
- }
121
- }
122
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
123
- const results = await searchAllResources(pilotiq, 'hello', null)
124
- assert.equal(results.length, 2)
125
- assert.equal(results[0]!.resource, 'articles')
126
- assert.equal(results[0]!.resourceLabel, 'Articles')
127
- assert.equal(results[0]!.title, 'Hello world')
128
- assert.equal(results[0]!.url, '/admin/articles/1')
129
- })
130
-
131
- it('applies subtitle override when set', async () => {
132
- const M = fakeModel([{ id: '1', title: 'Post', publishedAt: '2026-04-01' }])
133
- class A extends Resource {
134
- static override label = 'Articles'
135
- static override slug = 'articles'
136
- static override globalSearch = true
137
- static override recordTitleAttribute = 'title'
138
- static override model = M
139
- static override table(table: Table): Table {
140
- return table.columns([Column.make('title').searchable()])
141
- }
142
- static override getGlobalSearchResultSubtitle(record: unknown): string | undefined {
143
- const r = record as { publishedAt?: string }
144
- return r.publishedAt ? `Published ${r.publishedAt}` : undefined
145
- }
146
- }
147
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
148
- const results = await searchAllResources(pilotiq, 'post', null)
149
- assert.equal(results[0]!.subtitle, 'Published 2026-04-01')
150
- })
151
-
152
- it('honors getGlobalSearchResultUrl override', async () => {
153
- const M = fakeModel([{ id: '99', title: 'Edit me' }])
154
- class A extends Resource {
155
- static override label = 'Articles'
156
- static override slug = 'articles'
157
- static override globalSearch = true
158
- static override recordTitleAttribute = 'title'
159
- static override model = M
160
- static override table(table: Table): Table {
161
- return table.columns([Column.make('title').searchable()])
162
- }
163
- static override getGlobalSearchResultUrl(record: unknown, base: string): string {
164
- return `${base}/articles/${(record as { id: string }).id}/edit`
165
- }
166
- }
167
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
168
- const results = await searchAllResources(pilotiq, 'edit', null)
169
- assert.equal(results[0]!.url, '/admin/articles/99/edit')
170
- })
171
-
172
- it('drops resources where canAccess returns false', async () => {
173
- const M1 = fakeModel([{ id: '1', title: 'A1' }])
174
- const M2 = fakeModel([{ id: '2', title: 'A2' }])
175
- class Visible extends Resource {
176
- static override label = 'Visible'
177
- static override slug = 'visible'
178
- static override globalSearch = true
179
- static override recordTitleAttribute = 'title'
180
- static override model = M1
181
- static override table(table: Table): Table {
182
- return table.columns([Column.make('title').searchable()])
183
- }
184
- }
185
- class Hidden extends Resource {
186
- static override label = 'Hidden'
187
- static override slug = 'hidden'
188
- static override globalSearch = true
189
- static override recordTitleAttribute = 'title'
190
- static override model = M2
191
- static override table(table: Table): Table {
192
- return table.columns([Column.make('title').searchable()])
193
- }
194
- static override async canAccess(_user: unknown): Promise<boolean> { return false }
195
- }
196
- const pilotiq = Pilotiq.make('test').path('/admin').resources([Visible, Hidden])
197
- const results = await searchAllResources(pilotiq, 'A1', null)
198
- assert.equal(results.length, 1)
199
- assert.equal(results[0]!.resource, 'visible')
200
- })
201
-
202
- it('drops resources where canViewAny returns false', async () => {
203
- const M = fakeModel([{ id: '1', title: 'Hidden' }])
204
- class A extends Resource {
205
- static override label = 'Articles'
206
- static override slug = 'articles'
207
- static override globalSearch = true
208
- static override recordTitleAttribute = 'title'
209
- static override model = M
210
- static override table(table: Table): Table {
211
- return table.columns([Column.make('title').searchable()])
212
- }
213
- static override async canViewAny(_user: unknown): Promise<boolean> { return false }
214
- }
215
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
216
- const results = await searchAllResources(pilotiq, 'hidden', null)
217
- assert.equal(results.length, 0)
218
- })
219
-
220
- it('continues other resources when one throws on getGlobalSearchQuery', async () => {
221
- const M1 = fakeModel([{ id: '1', title: 'works' }])
222
- class Broken extends Resource {
223
- static override label = 'Broken'
224
- static override slug = 'broken'
225
- static override globalSearch = true
226
- static override getGlobalSearchQuery(_needle: string): ModelQuery | undefined {
227
- throw new Error('intentional')
228
- }
229
- }
230
- class OK extends Resource {
231
- static override label = 'OK'
232
- static override slug = 'ok'
233
- static override globalSearch = true
234
- static override recordTitleAttribute = 'title'
235
- static override model = M1
236
- static override table(table: Table): Table {
237
- return table.columns([Column.make('title').searchable()])
238
- }
239
- }
240
- const pilotiq = Pilotiq.make('test').path('/admin').resources([Broken, OK])
241
- // Silence the warn during the test.
242
- const originalWarn = console.warn
243
- console.warn = () => {}
244
- try {
245
- const results = await searchAllResources(pilotiq, 'works', null)
246
- assert.equal(results.length, 1)
247
- assert.equal(results[0]!.resource, 'ok')
248
- } finally {
249
- console.warn = originalWarn
250
- }
251
- })
252
-
253
- it('returns [] when a resource has no model AND no override', async () => {
254
- class A extends Resource {
255
- static override label = 'Modeless'
256
- static override slug = 'modeless'
257
- static override globalSearch = true
258
- // No model. No getGlobalSearchQuery override. No data source → skip.
259
- }
260
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
261
- const results = await searchAllResources(pilotiq, 'anything', null)
262
- assert.equal(results.length, 0)
263
- })
264
-
265
- it('caps results per resource via opts.limitPerResource', async () => {
266
- const M = fakeModel(Array.from({ length: 20 }, (_, i) => ({ id: String(i), title: `row ${i}` })))
267
- class A extends Resource {
268
- static override label = 'Many'
269
- static override slug = 'many'
270
- static override globalSearch = true
271
- static override recordTitleAttribute = 'title'
272
- static override model = M
273
- static override table(table: Table): Table {
274
- return table.columns([Column.make('title').searchable()])
275
- }
276
- }
277
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
278
- const results = await searchAllResources(pilotiq, 'row', null, { limitPerResource: 3 })
279
- assert.equal(results.length, 3)
280
- })
281
-
282
- it('caps total results across all resources', async () => {
283
- const rows = Array.from({ length: 10 }, (_, i) => ({ id: String(i), title: `row ${i}` }))
284
- const Ma = fakeModel(rows.map(r => ({ ...r, id: `a-${r.id}` })))
285
- const Mb = fakeModel(rows.map(r => ({ ...r, id: `b-${r.id}` })))
286
- class A extends Resource {
287
- static override label = 'A'
288
- static override slug = 'a'
289
- static override globalSearch = true
290
- static override recordTitleAttribute = 'title'
291
- static override model = Ma
292
- static override table(table: Table): Table {
293
- return table.columns([Column.make('title').searchable()])
294
- }
295
- }
296
- class B extends Resource {
297
- static override label = 'B'
298
- static override slug = 'b'
299
- static override globalSearch = true
300
- static override recordTitleAttribute = 'title'
301
- static override model = Mb
302
- static override table(table: Table): Table {
303
- return table.columns([Column.make('title').searchable()])
304
- }
305
- }
306
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A, B])
307
- const results = await searchAllResources(pilotiq, 'row', null, {
308
- limitPerResource: 10,
309
- limitTotal: 7,
310
- })
311
- assert.equal(results.length, 7)
312
- })
313
-
314
- it('uses getGlobalSearchQuery override when present', async () => {
315
- const M = fakeModel([{ id: '1', title: 'pre-override' }])
316
- let queryArg = ''
317
- class A extends Resource {
318
- static override label = 'Override'
319
- static override slug = 'override'
320
- static override globalSearch = true
321
- static override recordTitleAttribute = 'title'
322
- static override model = M
323
- static override table(table: Table): Table {
324
- return table.columns([Column.make('title').searchable()])
325
- }
326
- static override getGlobalSearchQuery(needle: string): ModelQuery | undefined {
327
- queryArg = needle
328
- // Just return the unmodified query so paginate still produces the row.
329
- return M.query()
330
- }
331
- }
332
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
333
- const results = await searchAllResources(pilotiq, 'anything', null)
334
- assert.equal(queryArg, 'anything')
335
- assert.equal(results.length, 1)
336
- })
337
-
338
- it('searchData wraps searchAllResources with resolveUser', async () => {
339
- const M = fakeModel([{ id: '1', title: 'searchable' }])
340
- let userSeen: unknown = 'untouched'
341
- class A extends Resource {
342
- static override label = 'Articles'
343
- static override slug = 'articles'
344
- static override globalSearch = true
345
- static override recordTitleAttribute = 'title'
346
- static override model = M
347
- static override table(table: Table): Table {
348
- return table.columns([Column.make('title').searchable()])
349
- }
350
- static override async canAccess(user: unknown): Promise<boolean> {
351
- userSeen = user
352
- return true
353
- }
354
- }
355
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A]).user(() => ({ role: 'admin' }))
356
- const result = await searchData(pilotiq, 'searchable', { fakeReq: true })
357
- assert.equal(result.ok, true)
358
- assert.equal(result.results.length, 1)
359
- assert.deepEqual(userSeen, { role: 'admin' })
360
- })
361
-
362
- it('serializes the resource icon onto each result', async () => {
363
- const M = fakeModel([{ id: '1', title: 'AB' }])
364
- class A extends Resource {
365
- static override label = 'Articles'
366
- static override slug = 'articles'
367
- static override icon = 'newspaper'
368
- static override globalSearch = true
369
- static override recordTitleAttribute = 'title'
370
- static override model = M
371
- static override table(table: Table): Table {
372
- return table.columns([Column.make('title').searchable()])
373
- }
374
- }
375
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
376
- const results = await searchAllResources(pilotiq, 'AB', null)
377
- assert.equal(results[0]!.icon, 'newspaper')
378
- })
379
-
380
- it('omits renderHooks key when no global-search hooks registered', async () => {
381
- const M = fakeModel([{ id: '1', title: 'searchable' }])
382
- class A extends Resource {
383
- static override label = 'Articles'
384
- static override slug = 'articles'
385
- static override globalSearch = true
386
- static override recordTitleAttribute = 'title'
387
- static override model = M
388
- static override table(table: Table): Table {
389
- return table.columns([Column.make('title').searchable()])
390
- }
391
- }
392
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
393
- const result = await searchData(pilotiq, 'searchable')
394
- assert.equal('renderHooks' in result, false)
395
- })
396
-
397
- it('resolves panels::global-search.results.before/.after when registered', async () => {
398
- const M = fakeModel([{ id: '1', title: 'searchable' }])
399
- class A extends Resource {
400
- static override label = 'Articles'
401
- static override slug = 'articles'
402
- static override globalSearch = true
403
- static override recordTitleAttribute = 'title'
404
- static override model = M
405
- static override table(table: Table): Table {
406
- return table.columns([Column.make('title').searchable()])
407
- }
408
- }
409
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
410
- .renderHook('panels::global-search.results.before', () => [Heading.make('Before')])
411
- .renderHook('panels::global-search.results.after', () => [Text.make('After')])
412
- const result = await searchData(pilotiq, 'searchable')
413
- assert.equal(result.ok, true)
414
- const hooks = result.renderHooks
415
- assert.ok(hooks, 'renderHooks should be present')
416
- const before = hooks!['panels::global-search.results.before']
417
- const after = hooks!['panels::global-search.results.after']
418
- assert.equal(before?.length, 1)
419
- assert.equal(after?.length, 1)
420
- assert.equal(before![0]!['type'], 'heading')
421
- assert.equal(after![0]!['type'], 'text')
422
- })
423
-
424
- it('passes resolved user into the global-search hook context', async () => {
425
- const M = fakeModel([{ id: '1', title: 'searchable' }])
426
- class A extends Resource {
427
- static override label = 'Articles'
428
- static override slug = 'articles'
429
- static override globalSearch = true
430
- static override recordTitleAttribute = 'title'
431
- static override model = M
432
- static override table(table: Table): Table {
433
- return table.columns([Column.make('title').searchable()])
434
- }
435
- }
436
- let userSeen: unknown = 'untouched'
437
- const pilotiq = Pilotiq.make('test').path('/admin').resources([A])
438
- .user(() => ({ role: 'editor' }))
439
- .renderHook('panels::global-search.results.before', (ctx) => {
440
- userSeen = ctx.user
441
- return [Heading.make('Hi')]
442
- })
443
- await searchData(pilotiq, 'searchable', { fakeReq: true })
444
- assert.deepEqual(userSeen, { role: 'editor' })
445
- })
446
- })
package/src/search.ts DELETED
@@ -1,178 +0,0 @@
1
- /**
2
- * Plan #12 — global search.
3
- *
4
- * Walks every opted-in resource on a panel, runs a per-resource search
5
- * query in parallel, and returns a flat list of results suitable for
6
- * rendering inside a Cmd+K palette.
7
- *
8
- * What "opted in" means:
9
- * 1. `Resource.globalSearch === true` — the static toggle.
10
- * 2. `R.canAccess(user)` AND `R.canViewAny(user)` (Plan #10) both
11
- * resolve true. Failures fail closed (resource silently dropped).
12
- * 3. Either `R.getGlobalSearchQuery(needle)` returns a query, OR the
13
- * resource has `static model = …` AND a non-empty
14
- * `globallySearchableAttributes()` array.
15
- *
16
- * Each surviving resource hits `query.paginate(1, limitPerResource)`
17
- * (the same method `modelTableRecords` uses, no new ORM contract).
18
- * Results map through `getGlobalSearchResultTitle / Subtitle / Url`
19
- * and `serializeIcon(R.icon, R.name)` for transport. The caller
20
- * receives a flat array sorted by registration order — the palette
21
- * handles visual grouping.
22
- */
23
- import type { Pilotiq } from './Pilotiq.js'
24
- import type { ResourceClass } from './Resource.js'
25
- import type { ModelQuery } from './orm/modelDefaults.js'
26
- import { serializeIcon, type SerializedIcon } from './icons/types.js'
27
-
28
- export interface GlobalSearchResult {
29
- /** URL slug of the resource, e.g. `'articles'`. Useful for grouping. */
30
- resource: string
31
- /** Plural label for headings. */
32
- resourceLabel: string
33
- /** Pre-serialized icon — string registry key or `{ class }` manifest ref. */
34
- icon?: SerializedIcon
35
- /** Stringified primary key. */
36
- id: string
37
- /** Big text in the palette row. */
38
- title: string
39
- /** Optional second line. */
40
- subtitle?: string
41
- /** URL the palette navigates to on Enter. */
42
- url: string
43
- }
44
-
45
- export interface GlobalSearchOptions {
46
- /** Cap results PER RESOURCE before merge. Default 5. */
47
- limitPerResource?: number
48
- /** Cap total results returned. Default 25. */
49
- limitTotal?: number
50
- }
51
-
52
- const DEFAULT_PER_RESOURCE = 5
53
- const DEFAULT_TOTAL = 25
54
- const MIN_QUERY_LENGTH = 2
55
-
56
- /**
57
- * Search all opted-in resources for `query`. Returns up to
58
- * `opts.limitTotal` (default 25) results. Empty/short queries return
59
- * `[]` cheaply without touching the DB.
60
- */
61
- export async function searchAllResources(
62
- pilotiq: Pilotiq,
63
- query: string,
64
- user: unknown,
65
- opts: GlobalSearchOptions = {},
66
- ): Promise<GlobalSearchResult[]> {
67
- const needle = query.trim()
68
- if (needle.length < MIN_QUERY_LENGTH) return []
69
-
70
- const limitPerResource = opts.limitPerResource ?? DEFAULT_PER_RESOURCE
71
- const limitTotal = opts.limitTotal ?? DEFAULT_TOTAL
72
-
73
- const cfg = pilotiq.getConfig()
74
- const candidates = cfg.resources.filter(R => R.globalSearch === true)
75
- if (candidates.length === 0) return []
76
-
77
- // Authorization gate. `canAccess` AND `canViewAny` must both pass —
78
- // we don't surface records the user can't list. Throwing predicates
79
- // fail closed (drop the resource).
80
- const accessChecks = await Promise.all(
81
- candidates.map(async (R) => {
82
- try {
83
- const ok = (await R.canAccess(user)) && (await R.canViewAny(user))
84
- return ok ? R : null
85
- } catch {
86
- return null
87
- }
88
- }),
89
- )
90
- const allowed = accessChecks.filter((R): R is ResourceClass => R !== null)
91
- if (allowed.length === 0) return []
92
-
93
- const perResource = await Promise.all(
94
- allowed.map(R => searchResource(R, needle, limitPerResource, cfg.path, user)),
95
- )
96
-
97
- // Flatten + cap. Registration order preserved.
98
- const flat: GlobalSearchResult[] = []
99
- for (const chunk of perResource) {
100
- for (const r of chunk) {
101
- flat.push(r)
102
- if (flat.length >= limitTotal) return flat
103
- }
104
- }
105
- return flat
106
- }
107
-
108
- async function searchResource(
109
- R: ResourceClass,
110
- needle: string,
111
- limit: number,
112
- base: string,
113
- user: unknown,
114
- ): Promise<GlobalSearchResult[]> {
115
- try {
116
- const q = buildSearchQuery(R, needle, user)
117
- if (!q) return []
118
-
119
- const result = await q.paginate(1, limit)
120
- const rows = (result?.data ?? []) as unknown[]
121
- const slug = R.getSlug()
122
- const icon = serializeIcon(R.icon, R.name)
123
-
124
- return rows.map((record) => {
125
- const r = record as Record<string, unknown>
126
- const id = r['id']
127
- const title = R.getGlobalSearchResultTitle(record)
128
- const subtitle = R.getGlobalSearchResultSubtitle(record)
129
- const url = R.getGlobalSearchResultUrl(record, base)
130
- const out: GlobalSearchResult = {
131
- resource: slug,
132
- resourceLabel: R.label,
133
- id: id !== undefined && id !== null ? String(id) : '',
134
- title,
135
- url,
136
- }
137
- if (icon !== undefined) out.icon = icon
138
- if (subtitle !== undefined) out.subtitle = subtitle
139
- return out
140
- })
141
- } catch (err) {
142
- // One resource throwing must not blank the palette — log and drop
143
- // its results, the rest still resolve via Promise.all.
144
- console.warn(`[pilotiq] global search failed on ${R.name}:`, err)
145
- return []
146
- }
147
- }
148
-
149
- /**
150
- * Build the `ModelQuery` we run for a resource. Picks in this order:
151
- * 1. User override `R.getGlobalSearchQuery(needle)`.
152
- * 2. Default chain: `R.query({ user })` chained with LIKE on each
153
- * attribute returned from `R.globallySearchableAttributes()`.
154
- *
155
- * Routes through `R.query(ctx)` (instead of `R.model.query()` directly)
156
- * so user-installed scopes — tenant filters, default ordering, etc. —
157
- * apply to global-search hits the same way they apply to list pages.
158
- *
159
- * Returns `undefined` when the resource has no model AND no override —
160
- * pilotiq won't load every record into memory just to LIKE-match.
161
- */
162
- function buildSearchQuery(R: ResourceClass, needle: string, user: unknown): ModelQuery | undefined {
163
- const override = R.getGlobalSearchQuery(needle)
164
- if (override) return override
165
- if (!R.model) return undefined
166
-
167
- const attrs = R.globallySearchableAttributes()
168
- if (attrs.length === 0) return undefined
169
-
170
- const wildcarded = `%${needle}%`
171
- let q = R.query(user != null ? { user } : undefined)
172
- attrs.forEach((col, i) => {
173
- q = i === 0
174
- ? q.where(col, 'LIKE', wildcarded)
175
- : q.orWhere(col, 'LIKE', wildcarded)
176
- })
177
- return q
178
- }