@pilotiq/pilotiq 0.23.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (500) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/dist/actions/exportFactory.d.ts +10 -0
  15. package/dist/actions/exportFactory.d.ts.map +1 -1
  16. package/dist/actions/exportFactory.js +10 -0
  17. package/dist/actions/exportFactory.js.map +1 -1
  18. package/dist/react/CollabRoomContext.d.ts +5 -5
  19. package/dist/react/index.d.ts +0 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js +0 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/routes/helpers.d.ts.map +1 -1
  24. package/dist/routes/helpers.js +6 -2
  25. package/dist/routes/helpers.js.map +1 -1
  26. package/dist/routes/relations.d.ts.map +1 -1
  27. package/dist/routes/relations.js +12 -0
  28. package/dist/routes/relations.js.map +1 -1
  29. package/package.json +6 -1
  30. package/.turbo/turbo-build.log +0 -8
  31. package/CLAUDE.md +0 -265
  32. package/dist/react/useCollabSeed.d.ts +0 -23
  33. package/dist/react/useCollabSeed.d.ts.map +0 -1
  34. package/dist/react/useCollabSeed.js +0 -82
  35. package/dist/react/useCollabSeed.js.map +0 -1
  36. package/src/Cluster.test.ts +0 -283
  37. package/src/Cluster.ts +0 -83
  38. package/src/Column.test.ts +0 -199
  39. package/src/Column.ts +0 -710
  40. package/src/Global.test.ts +0 -367
  41. package/src/Global.ts +0 -169
  42. package/src/Page.test.ts +0 -114
  43. package/src/Page.ts +0 -208
  44. package/src/Pilotiq.perf.test.ts +0 -252
  45. package/src/Pilotiq.test.ts +0 -129
  46. package/src/Pilotiq.ts +0 -1158
  47. package/src/PilotiqRegistry.ts +0 -36
  48. package/src/PilotiqServiceProvider.ts +0 -121
  49. package/src/RelationManager.test.ts +0 -400
  50. package/src/RelationManager.ts +0 -527
  51. package/src/RenderHook.test.ts +0 -252
  52. package/src/RenderHook.ts +0 -242
  53. package/src/Resource.test.ts +0 -284
  54. package/src/Resource.ts +0 -526
  55. package/src/RightPanel.test.ts +0 -202
  56. package/src/RightPanel.ts +0 -132
  57. package/src/Tab.test.ts +0 -91
  58. package/src/Tab.ts +0 -156
  59. package/src/UserMenuItem.ts +0 -145
  60. package/src/actions/Action.test.ts +0 -2526
  61. package/src/actions/Action.ts +0 -1515
  62. package/src/actions/ActionGroup.test.ts +0 -112
  63. package/src/actions/ActionGroup.ts +0 -173
  64. package/src/actions/attachFactory.ts +0 -172
  65. package/src/actions/bulkFactories.ts +0 -168
  66. package/src/actions/crudFactories.ts +0 -220
  67. package/src/actions/exportFactory.ts +0 -215
  68. package/src/actions/factoryHelpers.ts +0 -177
  69. package/src/actions/importFactory.ts +0 -243
  70. package/src/actions/index.ts +0 -17
  71. package/src/actions/m2mFactories.ts +0 -193
  72. package/src/actions/relationFactories.ts +0 -372
  73. package/src/applyPageHooks.test.ts +0 -463
  74. package/src/applyPageHooks.ts +0 -330
  75. package/src/authorization.test.ts +0 -483
  76. package/src/breadcrumbs.test.ts +0 -238
  77. package/src/cells/coerce.test.ts +0 -85
  78. package/src/cells/coerce.ts +0 -84
  79. package/src/clusterPaths.ts +0 -35
  80. package/src/columns/BadgeColumn.test.ts +0 -54
  81. package/src/columns/BadgeColumn.ts +0 -32
  82. package/src/columns/BooleanColumn.test.ts +0 -41
  83. package/src/columns/BooleanColumn.ts +0 -18
  84. package/src/columns/ColorColumn.test.ts +0 -37
  85. package/src/columns/ColorColumn.ts +0 -38
  86. package/src/columns/IconColumn.test.ts +0 -54
  87. package/src/columns/IconColumn.ts +0 -37
  88. package/src/columns/ImageColumn.test.ts +0 -41
  89. package/src/columns/ImageColumn.ts +0 -28
  90. package/src/columns/SelectColumn.ts +0 -98
  91. package/src/columns/TextColumn.test.ts +0 -190
  92. package/src/columns/TextColumn.ts +0 -20
  93. package/src/columns/TextInputColumn.ts +0 -68
  94. package/src/columns/ToggleColumn.ts +0 -46
  95. package/src/columns/editableColumns.test.ts +0 -238
  96. package/src/columns/index.ts +0 -9
  97. package/src/defaultGlobalPages.ts +0 -95
  98. package/src/defaultPages.test.ts +0 -634
  99. package/src/defaultPages.ts +0 -617
  100. package/src/defaultViewPage.test.ts +0 -147
  101. package/src/elements/Form.test.ts +0 -223
  102. package/src/elements/Form.ts +0 -416
  103. package/src/elements/ListTabs.ts +0 -28
  104. package/src/elements/Table.test.ts +0 -422
  105. package/src/elements/Table.ts +0 -850
  106. package/src/elements/TableGroup.test.ts +0 -260
  107. package/src/elements/TableGroup.ts +0 -334
  108. package/src/elements/dispatchAction.test.ts +0 -463
  109. package/src/elements/dispatchAction.ts +0 -355
  110. package/src/elements/dispatchForm.test.ts +0 -477
  111. package/src/elements/dispatchForm.ts +0 -1993
  112. package/src/elements/dispatchTable.test.ts +0 -1514
  113. package/src/elements/dispatchTable.ts +0 -745
  114. package/src/elements/index.ts +0 -21
  115. package/src/entries/BadgeEntry.ts +0 -39
  116. package/src/entries/CodeEntry.test.ts +0 -40
  117. package/src/entries/CodeEntry.ts +0 -52
  118. package/src/entries/ColorEntry.ts +0 -63
  119. package/src/entries/ComponentEntry.test.ts +0 -173
  120. package/src/entries/ComponentEntry.ts +0 -95
  121. package/src/entries/Entry.ts +0 -304
  122. package/src/entries/IconEntry.ts +0 -49
  123. package/src/entries/ImageEntry.ts +0 -61
  124. package/src/entries/KeyValueEntry.ts +0 -47
  125. package/src/entries/RepeatableEntry.test.ts +0 -239
  126. package/src/entries/RepeatableEntry.ts +0 -173
  127. package/src/entries/TextEntry.test.ts +0 -394
  128. package/src/entries/TextEntry.ts +0 -60
  129. package/src/entries/index.ts +0 -12
  130. package/src/entries/leaves.test.ts +0 -306
  131. package/src/entries/registry.ts +0 -54
  132. package/src/fields/BuilderField.test.ts +0 -1188
  133. package/src/fields/BuilderField.ts +0 -605
  134. package/src/fields/BuilderRelationship.test.ts +0 -811
  135. package/src/fields/CheckboxField.test.ts +0 -44
  136. package/src/fields/CheckboxField.ts +0 -27
  137. package/src/fields/CheckboxListField.test.ts +0 -99
  138. package/src/fields/CheckboxListField.ts +0 -66
  139. package/src/fields/ColorPickerField.test.ts +0 -33
  140. package/src/fields/ColorPickerField.ts +0 -25
  141. package/src/fields/DateField.ts +0 -54
  142. package/src/fields/DateTimeField.test.ts +0 -55
  143. package/src/fields/EmailField.ts +0 -16
  144. package/src/fields/Field.test.ts +0 -654
  145. package/src/fields/Field.ts +0 -817
  146. package/src/fields/FileUploadField.test.ts +0 -143
  147. package/src/fields/FileUploadField.ts +0 -159
  148. package/src/fields/HiddenField.test.ts +0 -27
  149. package/src/fields/HiddenField.ts +0 -28
  150. package/src/fields/KeyValueField.test.ts +0 -105
  151. package/src/fields/KeyValueField.ts +0 -55
  152. package/src/fields/MarkdownField.test.ts +0 -167
  153. package/src/fields/MarkdownField.ts +0 -162
  154. package/src/fields/NumberField.ts +0 -33
  155. package/src/fields/RadioField.test.ts +0 -94
  156. package/src/fields/RadioField.ts +0 -67
  157. package/src/fields/RepeaterField.test.ts +0 -1806
  158. package/src/fields/RepeaterField.ts +0 -939
  159. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  160. package/src/fields/RepeaterSimple.test.ts +0 -248
  161. package/src/fields/RowButton.test.ts +0 -219
  162. package/src/fields/RowButton.ts +0 -135
  163. package/src/fields/SelectField.test.ts +0 -192
  164. package/src/fields/SelectField.ts +0 -235
  165. package/src/fields/SliderField.test.ts +0 -50
  166. package/src/fields/SliderField.ts +0 -53
  167. package/src/fields/SlugField.ts +0 -24
  168. package/src/fields/TagsInputField.test.ts +0 -154
  169. package/src/fields/TagsInputField.ts +0 -133
  170. package/src/fields/TextField.test.ts +0 -213
  171. package/src/fields/TextField.ts +0 -177
  172. package/src/fields/TextareaField.test.ts +0 -58
  173. package/src/fields/TextareaField.ts +0 -59
  174. package/src/fields/ToggleButtonsField.test.ts +0 -106
  175. package/src/fields/ToggleButtonsField.ts +0 -59
  176. package/src/fields/ToggleField.ts +0 -16
  177. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  178. package/src/fields/optionsResolver.ts +0 -95
  179. package/src/fields/resolveField.ts +0 -28
  180. package/src/filters/BooleanFilter.ts +0 -35
  181. package/src/filters/DateRangeFilter.test.ts +0 -194
  182. package/src/filters/DateRangeFilter.ts +0 -148
  183. package/src/filters/Filter.test.ts +0 -268
  184. package/src/filters/Filter.ts +0 -184
  185. package/src/filters/FormFilter.test.ts +0 -238
  186. package/src/filters/FormFilter.ts +0 -215
  187. package/src/filters/MultiSelectFilter.test.ts +0 -119
  188. package/src/filters/MultiSelectFilter.ts +0 -78
  189. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  190. package/src/filters/QueryBuilderFilter.ts +0 -398
  191. package/src/filters/SelectFilter.ts +0 -46
  192. package/src/filters/TernaryFilter.test.ts +0 -160
  193. package/src/filters/TernaryFilter.ts +0 -72
  194. package/src/filters/TrashedFilter.test.ts +0 -149
  195. package/src/filters/TrashedFilter.ts +0 -55
  196. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  197. package/src/filters/queryBuilder/Constraint.ts +0 -115
  198. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  199. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  200. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  201. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  202. package/src/filters/queryBuilder/index.ts +0 -12
  203. package/src/icons/index.ts +0 -2
  204. package/src/icons/lucide.ts +0 -204
  205. package/src/icons/registry.test.ts +0 -56
  206. package/src/icons/registry.ts +0 -41
  207. package/src/icons/types.ts +0 -47
  208. package/src/index.ts +0 -525
  209. package/src/io/csv.test.ts +0 -142
  210. package/src/io/csv.ts +0 -170
  211. package/src/nestedRelationManagerData.test.ts +0 -547
  212. package/src/notifications/Notification.test.ts +0 -210
  213. package/src/notifications/Notification.ts +0 -354
  214. package/src/notifications/broadcast.test.ts +0 -110
  215. package/src/notifications/broadcast.ts +0 -95
  216. package/src/notifications/database.test.ts +0 -383
  217. package/src/notifications/database.ts +0 -398
  218. package/src/notifications/databaseNotifications.test.ts +0 -187
  219. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  220. package/src/notifications/dispatchNotificationAction.ts +0 -142
  221. package/src/notifications/flash.test.ts +0 -89
  222. package/src/notifications/flash.ts +0 -71
  223. package/src/notifications/index.ts +0 -45
  224. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  225. package/src/notifications/registerBroadcastAuth.ts +0 -100
  226. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  227. package/src/notifications/resolveSavedNotification.ts +0 -59
  228. package/src/notifications/types.ts +0 -93
  229. package/src/orm/m2mAccessor.ts +0 -66
  230. package/src/orm/modelDefaults.test.ts +0 -633
  231. package/src/orm/modelDefaults.ts +0 -666
  232. package/src/pageData/breadcrumbs.ts +0 -288
  233. package/src/pageData/forms.ts +0 -578
  234. package/src/pageData/helpers.ts +0 -857
  235. package/src/pageData/misc.ts +0 -347
  236. package/src/pageData/navigation.ts +0 -842
  237. package/src/pageData/relationPages.ts +0 -1248
  238. package/src/pageData/relationTabs.ts +0 -286
  239. package/src/pageData/resourcePages.ts +0 -609
  240. package/src/pageData.test.ts +0 -1545
  241. package/src/pageData.ts +0 -341
  242. package/src/plugins/index.ts +0 -8
  243. package/src/plugins/themeEditor.test.ts +0 -36
  244. package/src/plugins/themeEditor.ts +0 -45
  245. package/src/react/AppShell.tsx +0 -251
  246. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  247. package/src/react/CollabRoomContext.ts +0 -98
  248. package/src/react/CollabTextRendererRegistry.ts +0 -102
  249. package/src/react/CommandPalette.tsx +0 -375
  250. package/src/react/CurrentUserContext.tsx +0 -50
  251. package/src/react/CustomPageWrapperGate.tsx +0 -69
  252. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  253. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  254. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  255. package/src/react/FieldPresenceRegistry.ts +0 -46
  256. package/src/react/FormCollabBindingRegistry.ts +0 -242
  257. package/src/react/FormStateContext.tsx +0 -591
  258. package/src/react/HeadHooks.tsx +0 -126
  259. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  260. package/src/react/MarkdownEditorRegistry.ts +0 -107
  261. package/src/react/NotificationActionStrip.tsx +0 -263
  262. package/src/react/NotificationBell.tsx +0 -426
  263. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  264. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  265. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  266. package/src/react/PendingSuggestionsContext.tsx +0 -172
  267. package/src/react/RecordWrapperGate.tsx +0 -58
  268. package/src/react/RecordWrapperRegistry.ts +0 -39
  269. package/src/react/RenderHookSlot.tsx +0 -32
  270. package/src/react/RightSidebar.tsx +0 -257
  271. package/src/react/RightSidebarContext.tsx +0 -234
  272. package/src/react/RightSidebarTrigger.tsx +0 -53
  273. package/src/react/RowCoordsContext.tsx +0 -23
  274. package/src/react/SchemaRenderer.tsx +0 -549
  275. package/src/react/SearchTrigger.tsx +0 -46
  276. package/src/react/ThemeProvider.tsx +0 -93
  277. package/src/react/ThemeSettingsPage.tsx +0 -579
  278. package/src/react/ThemeToggle.tsx +0 -20
  279. package/src/react/Toaster.tsx +0 -158
  280. package/src/react/UserMenu.tsx +0 -196
  281. package/src/react/WidgetDataContext.tsx +0 -157
  282. package/src/react/cells/EditableCell.tsx +0 -389
  283. package/src/react/component-slots.test.ts +0 -103
  284. package/src/react/component-slots.ts +0 -116
  285. package/src/react/fieldJsHandler.test.ts +0 -166
  286. package/src/react/fieldJsHandler.ts +0 -79
  287. package/src/react/fields/BuilderInput.tsx +0 -1078
  288. package/src/react/fields/CheckboxInput.tsx +0 -39
  289. package/src/react/fields/CheckboxListInput.tsx +0 -102
  290. package/src/react/fields/ColorInput.tsx +0 -71
  291. package/src/react/fields/DateFieldInput.tsx +0 -70
  292. package/src/react/fields/DateTimeInput.tsx +0 -62
  293. package/src/react/fields/FieldShell.tsx +0 -348
  294. package/src/react/fields/FileUploadInput.tsx +0 -639
  295. package/src/react/fields/HiddenInput.tsx +0 -17
  296. package/src/react/fields/KeyValueInput.tsx +0 -230
  297. package/src/react/fields/MarkdownInput.tsx +0 -560
  298. package/src/react/fields/RadioInput.tsx +0 -81
  299. package/src/react/fields/RepeaterInput.test.ts +0 -116
  300. package/src/react/fields/RepeaterInput.tsx +0 -1420
  301. package/src/react/fields/SelectFieldInput.tsx +0 -280
  302. package/src/react/fields/SliderInput.tsx +0 -81
  303. package/src/react/fields/TagsInput.tsx +0 -283
  304. package/src/react/fields/TextLikeInput.tsx +0 -256
  305. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  306. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  307. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  308. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  309. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  310. package/src/react/fields/repeaterReconcile.ts +0 -104
  311. package/src/react/fields/rowChromeButton.tsx +0 -336
  312. package/src/react/fields/rowState.ts +0 -106
  313. package/src/react/fields/syncRowGates.test.ts +0 -202
  314. package/src/react/fields/syncRowGates.ts +0 -66
  315. package/src/react/fields/textInputControls.tsx +0 -238
  316. package/src/react/fields/useRowReorderDnd.ts +0 -78
  317. package/src/react/formStateHelpers.test.ts +0 -508
  318. package/src/react/formStateHelpers.ts +0 -381
  319. package/src/react/hooks/use-mobile.ts +0 -19
  320. package/src/react/icon-context.tsx +0 -60
  321. package/src/react/index.ts +0 -195
  322. package/src/react/layouts/SidebarLayout.tsx +0 -250
  323. package/src/react/layouts/TopbarLayout.tsx +0 -258
  324. package/src/react/navigate.tsx +0 -37
  325. package/src/react/onProviderSynced.test.ts +0 -90
  326. package/src/react/parseRecordEditUrl.test.ts +0 -122
  327. package/src/react/parseRecordEditUrl.ts +0 -94
  328. package/src/react/persistedState.ts +0 -40
  329. package/src/react/registry.ts +0 -48
  330. package/src/react/right-panel-registry.tsx +0 -47
  331. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  332. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  333. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  334. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  335. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  336. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  337. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  338. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  339. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  340. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  341. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  342. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  343. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  344. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  345. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  346. package/src/react/schemaRenderer/constants.ts +0 -50
  347. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  348. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  349. package/src/react/schemaRenderer/helpers.tsx +0 -81
  350. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  351. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  352. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  353. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  354. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  355. package/src/react/schemaRenderer/table/links.tsx +0 -112
  356. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  357. package/src/react/schemaRenderer/table/url.tsx +0 -143
  358. package/src/react/theme-preview/apply.ts +0 -99
  359. package/src/react/theme-preview/build-html.ts +0 -436
  360. package/src/react/ui/button.tsx +0 -51
  361. package/src/react/ui/calendar.tsx +0 -67
  362. package/src/react/ui/checkbox.tsx +0 -29
  363. package/src/react/ui/dialog.tsx +0 -108
  364. package/src/react/ui/dropdown-menu.tsx +0 -97
  365. package/src/react/ui/input.tsx +0 -20
  366. package/src/react/ui/label.tsx +0 -21
  367. package/src/react/ui/popover.tsx +0 -50
  368. package/src/react/ui/select.tsx +0 -169
  369. package/src/react/ui/separator.tsx +0 -25
  370. package/src/react/ui/sheet.tsx +0 -136
  371. package/src/react/ui/sidebar.tsx +0 -723
  372. package/src/react/ui/skeleton.tsx +0 -13
  373. package/src/react/ui/slider.tsx +0 -34
  374. package/src/react/ui/switch.tsx +0 -28
  375. package/src/react/ui/table.tsx +0 -105
  376. package/src/react/ui/tabs.tsx +0 -63
  377. package/src/react/ui/textarea.tsx +0 -18
  378. package/src/react/ui/tooltip.tsx +0 -64
  379. package/src/react/useCollabSeed.ts +0 -86
  380. package/src/react/useResizableWidth.ts +0 -139
  381. package/src/react/utils.ts +0 -6
  382. package/src/react/widgetRegistry.test.ts +0 -43
  383. package/src/react/widgetRegistry.ts +0 -50
  384. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  385. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  386. package/src/react/widgets/ViewRenderer.tsx +0 -71
  387. package/src/relationManagerData.test.ts +0 -1595
  388. package/src/richtext/index.ts +0 -8
  389. package/src/richtext/registry.ts +0 -89
  390. package/src/routes/globals.ts +0 -148
  391. package/src/routes/guard.test.ts +0 -325
  392. package/src/routes/helpers.ts +0 -700
  393. package/src/routes/pages.ts +0 -175
  394. package/src/routes/panel.ts +0 -204
  395. package/src/routes/relations.ts +0 -1227
  396. package/src/routes/resources.ts +0 -781
  397. package/src/routes/theme.ts +0 -91
  398. package/src/routes-nested-relations.test.ts +0 -676
  399. package/src/routes-relations.test.ts +0 -972
  400. package/src/routes.test.ts +0 -2027
  401. package/src/routes.ts +0 -303
  402. package/src/schema/Alert.test.ts +0 -109
  403. package/src/schema/Alert.ts +0 -131
  404. package/src/schema/Block.ts +0 -169
  405. package/src/schema/Breadcrumbs.ts +0 -40
  406. package/src/schema/Card.ts +0 -35
  407. package/src/schema/Divider.ts +0 -20
  408. package/src/schema/Element.ts +0 -219
  409. package/src/schema/EmptyState.test.ts +0 -37
  410. package/src/schema/EmptyState.ts +0 -63
  411. package/src/schema/Fieldset.ts +0 -43
  412. package/src/schema/Grid.ts +0 -43
  413. package/src/schema/Group.ts +0 -30
  414. package/src/schema/Heading.ts +0 -39
  415. package/src/schema/Html.ts +0 -67
  416. package/src/schema/Icon.ts +0 -54
  417. package/src/schema/Image.ts +0 -57
  418. package/src/schema/LinkTag.ts +0 -41
  419. package/src/schema/Markdown.ts +0 -85
  420. package/src/schema/MetaTag.ts +0 -41
  421. package/src/schema/RelationTabs.ts +0 -71
  422. package/src/schema/ScriptTag.ts +0 -55
  423. package/src/schema/Section.ts +0 -160
  424. package/src/schema/ServerDataElement.test.ts +0 -140
  425. package/src/schema/ServerDataElement.ts +0 -156
  426. package/src/schema/SlotComponent.test.ts +0 -77
  427. package/src/schema/SlotComponent.ts +0 -71
  428. package/src/schema/Split.ts +0 -50
  429. package/src/schema/Stat.test.ts +0 -118
  430. package/src/schema/Stat.ts +0 -154
  431. package/src/schema/StatsOverview.test.ts +0 -141
  432. package/src/schema/StatsOverview.ts +0 -119
  433. package/src/schema/StyleTag.ts +0 -35
  434. package/src/schema/TableWidget.test.ts +0 -297
  435. package/src/schema/TableWidget.ts +0 -289
  436. package/src/schema/Tabs.ts +0 -79
  437. package/src/schema/Text.ts +0 -58
  438. package/src/schema/UnorderedList.ts +0 -49
  439. package/src/schema/View.test.ts +0 -111
  440. package/src/schema/View.ts +0 -127
  441. package/src/schema/Wizard.ts +0 -220
  442. package/src/schema/containers.test.ts +0 -564
  443. package/src/schema/headTags.test.ts +0 -134
  444. package/src/schema/index.ts +0 -40
  445. package/src/schema/primes.test.ts +0 -269
  446. package/src/schema/resolveSchema.test.ts +0 -379
  447. package/src/schema/resolveSchema.ts +0 -917
  448. package/src/schema/sanitize.ts +0 -58
  449. package/src/search.test.ts +0 -446
  450. package/src/search.ts +0 -178
  451. package/src/sessionFilters.test.ts +0 -375
  452. package/src/sessionFilters.ts +0 -143
  453. package/src/slot-components/index.ts +0 -10
  454. package/src/slot-components/registry.ts +0 -56
  455. package/src/styles/file-upload.css +0 -13
  456. package/src/summarizers/Summarizer.test.ts +0 -84
  457. package/src/summarizers/Summarizer.ts +0 -123
  458. package/src/summarizers/index.ts +0 -11
  459. package/src/theme/base-colors.ts +0 -68
  460. package/src/theme/chart-colors.ts +0 -50
  461. package/src/theme/colors.ts +0 -447
  462. package/src/theme/generate-css.test.ts +0 -139
  463. package/src/theme/generate-css.ts +0 -44
  464. package/src/theme/generate-scale.test.ts +0 -106
  465. package/src/theme/generate-scale.ts +0 -97
  466. package/src/theme/icon-map.ts +0 -42
  467. package/src/theme/index.ts +0 -34
  468. package/src/theme/migrate.test.ts +0 -178
  469. package/src/theme/migrate.ts +0 -81
  470. package/src/theme/presets.ts +0 -135
  471. package/src/theme/radius.ts +0 -18
  472. package/src/theme/resolve.test.ts +0 -238
  473. package/src/theme/resolve.ts +0 -96
  474. package/src/theme/spacing.ts +0 -18
  475. package/src/theme/storage.test.ts +0 -126
  476. package/src/theme/storage.ts +0 -106
  477. package/src/theme/theme-colors.ts +0 -88
  478. package/src/theme/types.ts +0 -125
  479. package/src/uploads/UploadAdapter.ts +0 -35
  480. package/src/uploads/index.ts +0 -2
  481. package/src/uploads/localUpload.test.ts +0 -70
  482. package/src/uploads/localUpload.ts +0 -84
  483. package/src/validation/Validator.ts +0 -49
  484. package/src/validation/index.ts +0 -28
  485. package/src/validation/rules.ts +0 -78
  486. package/src/validation/runValidators.ts +0 -435
  487. package/src/validation/uniqueValidator.test.ts +0 -196
  488. package/src/validation/uniqueValidator.ts +0 -133
  489. package/src/validation/validators.test.ts +0 -268
  490. package/src/vite.test.ts +0 -184
  491. package/src/vite.ts +0 -787
  492. package/src/widgets/index.ts +0 -10
  493. package/src/widgets/registry.ts +0 -45
  494. package/src/widgets.test.ts +0 -592
  495. package/tsconfig.build.json +0 -11
  496. package/tsconfig.json +0 -4
  497. package/tsconfig.test.json +0 -10
  498. package/views/react/Dashboard.tsx +0 -27
  499. package/views/react/Resources/Form.tsx +0 -102
  500. package/views/react/Resources/Index.tsx +0 -49
@@ -1,341 +0,0 @@
1
- /**
2
- * Tests for the notification-action dispatcher. Exercises the auth /
3
- * lookup chain end-to-end against a fake orm adapter — the route
4
- * layer that mounts this is exercised separately via integration
5
- * tests against the rudder router.
6
- */
7
- import { describe, it, beforeEach, afterEach } from 'node:test'
8
- import assert from 'node:assert/strict'
9
-
10
- import { Pilotiq } from '../Pilotiq.js'
11
- import { Notification } from './Notification.js'
12
- import { Action } from '../actions/Action.js'
13
- import { _setTestAdapter, persist } from './database.js'
14
- import { dispatchNotificationAction } from './dispatchNotificationAction.js'
15
-
16
- // ─── Fake ORM adapter (mirror of database.test.ts) ─────────
17
-
18
- interface Row { [k: string]: unknown }
19
- interface FakeStore { rows: Row[] }
20
-
21
- function makeFakeAdapter() {
22
- const store: FakeStore = { rows: [] }
23
- const buildQB = (_table: string) => {
24
- const filters: Array<(r: Row) => boolean> = []
25
- let order: { column: string; dir: 'ASC' | 'DESC' } | null = null
26
- const apply = () => {
27
- let out = store.rows.slice()
28
- for (const f of filters) out = out.filter(f)
29
- if (order) {
30
- const { column, dir } = order
31
- out.sort((a, b) => {
32
- const av = (a[column] ?? '') as string
33
- const bv = (b[column] ?? '') as string
34
- const cmp = av < bv ? -1 : av > bv ? 1 : 0
35
- return dir === 'DESC' ? -cmp : cmp
36
- })
37
- }
38
- return out
39
- }
40
- const qb: any = {
41
- where: (col: string, value: unknown) => {
42
- filters.push(r => r[col] === value)
43
- return qb
44
- },
45
- orderBy: (col: string, dir: 'ASC' | 'DESC' = 'ASC') => {
46
- order = { column: col, dir }
47
- return qb
48
- },
49
- paginate: async (_p: number, perPage = 25) => {
50
- const out = apply()
51
- return { data: out.slice(0, perPage), total: out.length }
52
- },
53
- count: async () => apply().length,
54
- updateAll: async (data: Row) => {
55
- const matching = apply()
56
- for (const r of matching) for (const k of Object.keys(data)) r[k] = data[k]
57
- return matching.length
58
- },
59
- create: async (data: Row) => {
60
- store.rows.push({ ...data })
61
- return { ...data }
62
- },
63
- }
64
- return qb
65
- }
66
- return { adapter: { query: <T>(t: string) => buildQB(t) }, store }
67
- }
68
-
69
- let fake = makeFakeAdapter()
70
-
71
- beforeEach(() => {
72
- fake = makeFakeAdapter()
73
- _setTestAdapter(fake.adapter)
74
- })
75
-
76
- afterEach(() => {
77
- _setTestAdapter(undefined)
78
- })
79
-
80
- async function seedRowWithActions(opts: {
81
- notifiableId: string
82
- actions: Action[]
83
- }): Promise<string> {
84
- const n = Notification.make('Test').actions(opts.actions)
85
- const data = n.toDatabase() as Parameters<typeof persist>[0]['data']
86
- const { id } = await persist({
87
- notifiableType: 'users',
88
- notifiableId: opts.notifiableId,
89
- data,
90
- })
91
- return id
92
- }
93
-
94
- describe('dispatchNotificationAction — auth / lookup chain', () => {
95
- it('401 when no user resolves', async () => {
96
- const result = await dispatchNotificationAction(Pilotiq.make('admin'), {
97
- notificationId: 'whatever',
98
- actionName: 'view',
99
- notifiableType: 'users',
100
- notifiableId: '1',
101
- user: null,
102
- })
103
- assert.equal(result.ok, false)
104
- assert.equal(result.ok === false && result.status, 401)
105
- })
106
-
107
- it('404 when notification id does not exist', async () => {
108
- const result = await dispatchNotificationAction(Pilotiq.make('admin'), {
109
- notificationId: 'no-such-row',
110
- actionName: 'view',
111
- notifiableType: 'users',
112
- notifiableId: '1',
113
- user: { id: 1 },
114
- })
115
- assert.equal(result.ok, false)
116
- assert.equal(result.ok === false && result.status, 404)
117
- })
118
-
119
- it('404 when the action name is not on the row', async () => {
120
- const id = await seedRowWithActions({
121
- notifiableId: '1',
122
- actions: [Action.make('view').url('/p/123')],
123
- })
124
- const result = await dispatchNotificationAction(Pilotiq.make('admin'), {
125
- notificationId: id,
126
- actionName: 'nope',
127
- notifiableType: 'users',
128
- notifiableId: '1',
129
- user: { id: 1 },
130
- })
131
- assert.equal(result.ok, false)
132
- assert.equal(result.ok === false && result.status, 404)
133
- })
134
-
135
- it('404 when the matched action is not handler-mode', async () => {
136
- // url-mode actions don't dispatch through this route — clients
137
- // navigate the href directly. A request hitting the dispatch
138
- // endpoint for a url-mode action is malformed; 404 closes the door.
139
- const id = await seedRowWithActions({
140
- notifiableId: '1',
141
- actions: [Action.make('view').url('/p/123')],
142
- })
143
- const result = await dispatchNotificationAction(Pilotiq.make('admin'), {
144
- notificationId: id,
145
- actionName: 'view',
146
- notifiableType: 'users',
147
- notifiableId: '1',
148
- user: { id: 1 },
149
- })
150
- assert.equal(result.ok, false)
151
- assert.equal(result.ok === false && result.status, 404)
152
- })
153
-
154
- it('404 when the registry has no handler for the stored name', async () => {
155
- const id = await seedRowWithActions({
156
- notifiableId: '1',
157
- actions: [Action.make('archive').handler('archive-project').payload({ projectId: 1 })],
158
- })
159
- const panel = Pilotiq.make('admin') // no notificationHandlers registered
160
- const result = await dispatchNotificationAction(panel, {
161
- notificationId: id,
162
- actionName: 'archive',
163
- notifiableType: 'users',
164
- notifiableId: '1',
165
- user: { id: 1 },
166
- })
167
- assert.equal(result.ok, false)
168
- assert.equal(result.ok === false && result.status, 404)
169
- })
170
- })
171
-
172
- describe('dispatchNotificationAction — handler dispatch', () => {
173
- it('runs the registered handler with the stored payload', async () => {
174
- const id = await seedRowWithActions({
175
- notifiableId: '1',
176
- actions: [Action.make('archive').handler('archive-project').payload({ projectId: 42 })],
177
- })
178
- let seen: unknown = null
179
- const panel = Pilotiq.make('admin').notificationHandlers({
180
- 'archive-project': async (ctx) => {
181
- seen = { user: ctx.user, payload: ctx.payload, notificationId: ctx.notificationId }
182
- return { notify: { id: 'x', type: 'success', title: 'Archived' } }
183
- },
184
- })
185
- const result = await dispatchNotificationAction(panel, {
186
- notificationId: id,
187
- actionName: 'archive',
188
- notifiableType: 'users',
189
- notifiableId: '1',
190
- user: { id: 1 },
191
- })
192
- assert.equal(result.ok, true)
193
- assert.deepEqual(seen, {
194
- user: { id: 1 },
195
- payload: { projectId: 42 },
196
- notificationId: id,
197
- })
198
- assert.equal(result.ok === true && result.notifications?.[0]?.title, 'Archived')
199
- })
200
-
201
- it('flips read_at when the stored action carries markAsRead', async () => {
202
- const id = await seedRowWithActions({
203
- notifiableId: '1',
204
- actions: [Action.make('archive').handler('archive-project').markAsRead()],
205
- })
206
- const panel = Pilotiq.make('admin').notificationHandlers({
207
- 'archive-project': async () => undefined,
208
- })
209
- // Pre-condition: row is unread.
210
- assert.equal(fake.store.rows[0]?.['read_at'], null)
211
-
212
- const result = await dispatchNotificationAction(panel, {
213
- notificationId: id,
214
- actionName: 'archive',
215
- notifiableType: 'users',
216
- notifiableId: '1',
217
- user: { id: 1 },
218
- })
219
- assert.equal(result.ok, true)
220
- assert.equal(result.ok === true && result.markedAsRead, true)
221
- // Row was actually flipped — defensive in depth.
222
- assert.notEqual(fake.store.rows[0]?.['read_at'], null)
223
- })
224
-
225
- it('does not flip read_at when the stored action lacks markAsRead', async () => {
226
- const id = await seedRowWithActions({
227
- notifiableId: '1',
228
- actions: [Action.make('archive').handler('archive-project')],
229
- })
230
- const panel = Pilotiq.make('admin').notificationHandlers({
231
- 'archive-project': async () => undefined,
232
- })
233
- const result = await dispatchNotificationAction(panel, {
234
- notificationId: id,
235
- actionName: 'archive',
236
- notifiableType: 'users',
237
- notifiableId: '1',
238
- user: { id: 1 },
239
- })
240
- assert.equal(result.ok, true)
241
- assert.equal(result.ok === true && result.markedAsRead, undefined)
242
- assert.equal(fake.store.rows[0]?.['read_at'], null)
243
- })
244
-
245
- it('forwards a redirect from the handler', async () => {
246
- const id = await seedRowWithActions({
247
- notifiableId: '1',
248
- actions: [Action.make('go').handler('go')],
249
- })
250
- const panel = Pilotiq.make('admin').notificationHandlers({
251
- go: async () => ({ redirect: '/somewhere' }),
252
- })
253
- const result = await dispatchNotificationAction(panel, {
254
- notificationId: id,
255
- actionName: 'go',
256
- notifiableType: 'users',
257
- notifiableId: '1',
258
- user: { id: 1 },
259
- })
260
- assert.equal(result.ok === true && result.redirect, '/somewhere')
261
- })
262
-
263
- it('500 when the handler throws', async () => {
264
- const id = await seedRowWithActions({
265
- notifiableId: '1',
266
- actions: [Action.make('boom').handler('boom')],
267
- })
268
- const panel = Pilotiq.make('admin').notificationHandlers({
269
- boom: async () => { throw new Error('bang') },
270
- })
271
- const result = await dispatchNotificationAction(panel, {
272
- notificationId: id,
273
- actionName: 'boom',
274
- notifiableType: 'users',
275
- notifiableId: '1',
276
- user: { id: 1 },
277
- })
278
- assert.equal(result.ok, false)
279
- assert.equal(result.ok === false && result.status, 500)
280
- assert.match(result.ok === false ? result.error : '', /bang/)
281
- })
282
-
283
- it('rejects payload-injection — payload reads from the stored row only', async () => {
284
- // The route doesn't accept a request body in v1. The dispatcher
285
- // takes payload exclusively from `action.payload` on the stored row,
286
- // so even a tampered Pilotiq config can't sneak extra keys in.
287
- const id = await seedRowWithActions({
288
- notifiableId: '1',
289
- actions: [Action.make('check').handler('check').payload({ a: 1 })],
290
- })
291
- let received: Record<string, unknown> = {}
292
- const panel = Pilotiq.make('admin').notificationHandlers({
293
- check: async (ctx) => { received = ctx.payload; return undefined },
294
- })
295
- await dispatchNotificationAction(panel, {
296
- notificationId: id,
297
- actionName: 'check',
298
- notifiableType: 'users',
299
- notifiableId: '1',
300
- user: { id: 1 },
301
- })
302
- assert.deepEqual(received, { a: 1 })
303
- })
304
- })
305
-
306
- describe('Pilotiq.notificationHandlers registry', () => {
307
- it('rejects URL-unsafe handler names at registration', () => {
308
- assert.throws(
309
- () => Pilotiq.make('admin').notificationHandlers({ 'bad name with spaces': async () => undefined }),
310
- /URL-safe key/,
311
- )
312
- assert.throws(
313
- () => Pilotiq.make('admin').notificationHandlers({ '': async () => undefined }),
314
- /URL-safe key/,
315
- )
316
- assert.throws(
317
- () => Pilotiq.make('admin').notificationHandlers({ 'with/slash': async () => undefined }),
318
- /URL-safe key/,
319
- )
320
- })
321
-
322
- it('accepts alphanumeric + dash + underscore', () => {
323
- Pilotiq.make('admin').notificationHandlers({
324
- 'archive-project': async () => undefined,
325
- 'mark_done': async () => undefined,
326
- 'X1': async () => undefined,
327
- })
328
- })
329
-
330
- it('subsequent calls merge — later keys override earlier', () => {
331
- let which: string = ''
332
- const panel = Pilotiq.make('admin')
333
- .notificationHandlers({ 'a': async () => { which = 'first'; return undefined } })
334
- .notificationHandlers({ 'a': async () => { which = 'second'; return undefined } })
335
- const fn = panel.getNotificationHandler('a')
336
- assert.ok(fn)
337
- void fn!({ user: null, payload: {}, notificationId: 'x' })
338
- // synchronous resolution since the handler closures don't await anything
339
- assert.equal(which, 'second')
340
- })
341
- })
@@ -1,142 +0,0 @@
1
- /**
2
- * Server-side dispatcher for `Notification.actions([Action…])` slots
3
- * that target a registered handler by name. Mounted by `routes.ts` at
4
- * `POST {base}/_notifications/:id/_action/:actionName`.
5
- *
6
- * Auth/lookup chain (each step returns the next-status code on miss):
7
- * 401 → no resolved user
8
- * 404 → row doesn't exist OR `notifiable_id !== user.id`
9
- * 404 → stored row's `data.actions` doesn't contain `actionName`
10
- * 404 → stored action's `handler` field isn't a string (closures
11
- * should already be filtered at `sendToDatabase` — defend in
12
- * depth)
13
- * 404 → no registry entry under that handler name
14
- * 200 → handler ran cleanly; result echoes back as
15
- * `{ ok, redirect?, notifications?, download? }`
16
- * 500 → handler threw — caught and wrapped
17
- *
18
- * The handler reads `payload` exclusively from the stored row, never
19
- * from the request body, so a tampered client can't inject extra
20
- * payload keys.
21
- */
22
-
23
- import type { Pilotiq } from '../Pilotiq.js'
24
- import type {
25
- NotificationActionContext,
26
- NotificationActionResult,
27
- } from './types.js'
28
- import type { NotificationMeta } from './Notification.js'
29
- import { findOneForUser, markAsRead } from './database.js'
30
-
31
- export interface DispatchNotificationActionInput {
32
- notificationId: string
33
- actionName: string
34
- notifiableType: string
35
- notifiableId: string
36
- user: unknown
37
- request?: unknown
38
- }
39
-
40
- export interface DispatchNotificationActionSuccess {
41
- ok: true
42
- redirect?: string
43
- notifications?: NotificationMeta[]
44
- /** When the matched action carried `markAsRead: true`, the route
45
- * flipped `read_at` server-side. Mirrored back so the bell client
46
- * can update its optimistic state without an extra round-trip. */
47
- markedAsRead?: boolean
48
- }
49
-
50
- export interface DispatchNotificationActionFailure {
51
- ok: false
52
- status: 401 | 404 | 500
53
- error: string
54
- }
55
-
56
- export type DispatchNotificationActionResult =
57
- | DispatchNotificationActionSuccess
58
- | DispatchNotificationActionFailure
59
-
60
- export async function dispatchNotificationAction(
61
- pilotiq: Pilotiq,
62
- input: DispatchNotificationActionInput,
63
- ): Promise<DispatchNotificationActionResult> {
64
- if (input.user === null || input.user === undefined) {
65
- return { ok: false, status: 401, error: 'Not authenticated' }
66
- }
67
-
68
- const row = await findOneForUser(input.notificationId, {
69
- notifiableType: input.notifiableType,
70
- notifiableId: input.notifiableId,
71
- })
72
- if (!row) return { ok: false, status: 404, error: 'Notification not found' }
73
-
74
- const action = row.actions?.find(a => a.name === input.actionName)
75
- if (!action) return { ok: false, status: 404, error: 'Action not found on notification' }
76
-
77
- // Closures don't survive serialization — if a stored action carries
78
- // anything other than a string handler, treat it as a tampered row
79
- // (or one written by an older code path) and refuse to dispatch.
80
- if (typeof action.handler !== 'string') {
81
- return {
82
- ok: false,
83
- status: 404,
84
- error: 'Action does not target a registered handler (use Pilotiq.notificationHandlers).',
85
- }
86
- }
87
-
88
- const handler = pilotiq.getNotificationHandler(action.handler)
89
- if (!handler) {
90
- return {
91
- ok: false,
92
- status: 404,
93
- error: `No notification handler registered for "${action.handler}".`,
94
- }
95
- }
96
-
97
- const ctx: NotificationActionContext = {
98
- user: input.user,
99
- payload: action.payload ?? {},
100
- notificationId: input.notificationId,
101
- }
102
- if (input.request !== undefined) ctx.request = input.request
103
-
104
- let result: NotificationActionResult | undefined
105
- try {
106
- result = await handler(ctx)
107
- } catch (e) {
108
- return {
109
- ok: false,
110
- status: 500,
111
- error: e instanceof Error ? e.message : 'Notification handler threw',
112
- }
113
- }
114
-
115
- // Side-effect: flip `read_at` when the stored action opted in.
116
- // Server-side authoritative — handler dispatch is one round-trip,
117
- // not two, and tampered clients can't suppress the mark.
118
- let markedAsRead = false
119
- if (action.markAsRead) {
120
- markedAsRead = await markAsRead(input.notificationId, {
121
- notifiableType: input.notifiableType,
122
- notifiableId: input.notifiableId,
123
- })
124
- }
125
-
126
- const success: DispatchNotificationActionSuccess = { ok: true }
127
- if (result && typeof result === 'object') {
128
- if (typeof result.redirect === 'string') success.redirect = result.redirect
129
- if (result.notify !== undefined) {
130
- const notifs = Array.isArray(result.notify) ? result.notify : [result.notify]
131
- const valid = notifs.filter(
132
- (n): n is NotificationMeta => Boolean(n) && typeof n === 'object' && typeof (n as NotificationMeta).title === 'string',
133
- )
134
- if (valid.length > 0) success.notifications = valid
135
- }
136
- // download envelope (parallel to dispatchAction) — wired through
137
- // the route layer when present; v1 doesn't surface it here to keep
138
- // the wire shape narrow. Add when a consumer asks.
139
- }
140
- if (markedAsRead) success.markedAsRead = true
141
- return success
142
- }
@@ -1,89 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
-
4
- import { flashNotifications, consumeFlashedNotifications } from './flash.js'
5
- import type { NotificationMeta } from './Notification.js'
6
-
7
- /**
8
- * Minimal SessionInstance stand-in: just a key/value flash store with the
9
- * "current request reads previous request's stash" semantics. Mirrors the
10
- * `flash / getFlash` shape from `@rudderjs/session` without importing it
11
- * (pilotiq doesn't peer-depend on session — it's optional in the host).
12
- */
13
- function makeSession() {
14
- let prev: Record<string, unknown> = {}
15
- let next: Record<string, unknown> = {}
16
- return {
17
- flash(key: string, value: unknown) { next[key] = value },
18
- getFlash<T>(key: string, fallback?: T): T | undefined {
19
- return (key in prev ? prev[key] : fallback) as T | undefined
20
- },
21
- /** Test helper: simulate the redirect happening — current next becomes
22
- * next request's prev. */
23
- advance() { prev = next; next = {} },
24
- }
25
- }
26
-
27
- const meta = (title: string): NotificationMeta => ({
28
- id: `n-${title}`,
29
- type: 'success',
30
- title,
31
- })
32
-
33
- describe('flashNotifications / consumeFlashedNotifications', () => {
34
- it('roundtrip: flash on one request, consume on the next', () => {
35
- const session = makeSession()
36
- const req = { session }
37
- flashNotifications(req, [meta('Saved')])
38
- session.advance()
39
- const got = consumeFlashedNotifications(req)
40
- assert.equal(got.length, 1)
41
- assert.equal(got[0]!.title, 'Saved')
42
- })
43
-
44
- it('empty array no-ops (does not stash an empty key that overwrites a prior flash)', () => {
45
- const session = makeSession()
46
- const req = { session }
47
- flashNotifications(req, [meta('Saved')])
48
- flashNotifications(req, []) // should not clobber
49
- session.advance()
50
- const got = consumeFlashedNotifications(req)
51
- assert.equal(got.length, 1)
52
- assert.equal(got[0]!.title, 'Saved')
53
- })
54
-
55
- it('undefined notifications no-op', () => {
56
- const session = makeSession()
57
- const req = { session }
58
- flashNotifications(req, undefined)
59
- session.advance()
60
- assert.deepEqual(consumeFlashedNotifications(req), [])
61
- })
62
-
63
- it('missing session: writes are silently dropped, reads return []', () => {
64
- const req = {} // no session at all
65
- assert.doesNotThrow(() => flashNotifications(req, [meta('Saved')]))
66
- assert.deepEqual(consumeFlashedNotifications(req), [])
67
- })
68
-
69
- it('undefined req: both helpers no-op', () => {
70
- assert.doesNotThrow(() => flashNotifications(undefined, [meta('Saved')]))
71
- assert.deepEqual(consumeFlashedNotifications(undefined), [])
72
- })
73
-
74
- it('multiple notifications survive the roundtrip in order', () => {
75
- const session = makeSession()
76
- const req = { session }
77
- flashNotifications(req, [meta('first'), meta('second'), meta('third')])
78
- session.advance()
79
- const got = consumeFlashedNotifications(req)
80
- assert.deepEqual(got.map(n => n.title), ['first', 'second', 'third'])
81
- })
82
-
83
- it('consume after no flash returns []', () => {
84
- const session = makeSession()
85
- const req = { session }
86
- session.advance()
87
- assert.deepEqual(consumeFlashedNotifications(req), [])
88
- })
89
- })
@@ -1,71 +0,0 @@
1
- /**
2
- * Bridge between the form/action lifecycle (which produces
3
- * `NotificationMeta[]` on success) and the next request's render (which
4
- * needs them in `viewProps.notifications` so the `Toaster` can fire).
5
- *
6
- * Sits on top of `@rudderjs/session`'s built-in flash primitive — see
7
- * `SessionInstance.flash / getFlash`. We don't ship our own storage; we
8
- * only namespace the key (`pilotiq:notifications`) and shape the value.
9
- *
10
- * If the host app hasn't installed `@rudderjs/session`, `req.session` is
11
- * undefined; both helpers no-op silently rather than throwing — losing
12
- * notifications on the 303 path is the same status quo as before flash
13
- * landed, so an opt-out is the correct fallback.
14
- */
15
- import type { NotificationMeta } from './Notification.js'
16
-
17
- const FLASH_KEY = 'pilotiq:notifications'
18
-
19
- interface FlashCapableSession {
20
- flash(key: string, value: unknown): void
21
- getFlash<T>(key: string, fallback?: T): T | undefined
22
- }
23
-
24
- /**
25
- * Pull a `FlashCapableSession` off any request-like object. `AppRequest`
26
- * doesn't declare `session` in `@rudderjs/contracts` directly — the field
27
- * is added via module augmentation in `@rudderjs/session`, which pilotiq
28
- * doesn't peer-depend on (sessions are optional in the host). We duck-type
29
- * at runtime so the helpers compile against unaugmented `AppRequest` and
30
- * still find a real `SessionInstance` when one's mounted.
31
- */
32
- function getSession(req: unknown): FlashCapableSession | undefined {
33
- if (!req || typeof req !== 'object') return undefined
34
- const session = (req as { session?: unknown }).session
35
- if (!session || typeof session !== 'object') return undefined
36
- const s = session as Record<string, unknown>
37
- if (typeof s['flash'] !== 'function' || typeof s['getFlash'] !== 'function') return undefined
38
- return session as FlashCapableSession
39
- }
40
-
41
- /**
42
- * Stash notifications on the session for the next request to consume.
43
- * Called from POST handlers right before `res.redirect(..., 303)`.
44
- *
45
- * Invariant: call at most once per response. The underlying
46
- * `session.flash(key, value)` overwrites any previously flashed value
47
- * under the same key, so coalescing across multiple calls would silently
48
- * lose the earlier batch.
49
- *
50
- * No-ops silently when the host app hasn't installed `@rudderjs/session`.
51
- */
52
- export function flashNotifications(
53
- req: unknown,
54
- notifications: ReadonlyArray<NotificationMeta> | undefined,
55
- ): void {
56
- if (!notifications || notifications.length === 0) return
57
- const session = getSession(req)
58
- if (!session) return
59
- session.flash(FLASH_KEY, [...notifications])
60
- }
61
-
62
- /**
63
- * Read & clear the flashed notifications (consume-once semantics — the
64
- * session driver clears the previous-request flash slot on save). Returns
65
- * an empty array when there's nothing flashed or no session is installed.
66
- */
67
- export function consumeFlashedNotifications(req: unknown): NotificationMeta[] {
68
- const session = getSession(req)
69
- if (!session) return []
70
- return session.getFlash<NotificationMeta[]>(FLASH_KEY) ?? []
71
- }
@@ -1,45 +0,0 @@
1
- export {
2
- Notification,
3
- type NotificationType,
4
- type NotificationMeta,
5
- _resetNotificationIdSeq,
6
- } from './Notification.js'
7
-
8
- export {
9
- resolveSavedNotification,
10
- type SavedNotificationMode,
11
- } from './resolveSavedNotification.js'
12
-
13
- export {
14
- flashNotifications,
15
- consumeFlashedNotifications,
16
- } from './flash.js'
17
-
18
- export type { Notifiable } from './types.js'
19
-
20
- export {
21
- listForUser,
22
- unreadCount,
23
- findOneForUser as findDatabaseNotificationForUser,
24
- markAsRead,
25
- markAsUnread,
26
- markAllAsRead,
27
- persist as persistDatabaseNotification,
28
- type DatabaseNotificationMeta,
29
- type ListOptions as DatabaseNotificationListOptions,
30
- type ListResult as DatabaseNotificationListResult,
31
- } from './database.js'
32
-
33
- export type {
34
- NotificationActionMeta,
35
- NotificationActionHandler,
36
- NotificationActionContext,
37
- NotificationActionResult,
38
- } from './types.js'
39
-
40
- export {
41
- push as pushBroadcastNotification,
42
- notificationChannel,
43
- NOTIFICATION_CREATED_EVENT,
44
- type PushOptions as PushBroadcastOptions,
45
- } from './broadcast.js'