@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,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
- })