@pilotiq/pilotiq 0.1.0

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 (1409) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +11 -0
  3. package/CLAUDE.md +207 -0
  4. package/LICENSE +21 -0
  5. package/dist/Cluster.d.ts +56 -0
  6. package/dist/Cluster.d.ts.map +1 -0
  7. package/dist/Cluster.js +62 -0
  8. package/dist/Cluster.js.map +1 -0
  9. package/dist/Column.d.ts +378 -0
  10. package/dist/Column.d.ts.map +1 -0
  11. package/dist/Column.js +434 -0
  12. package/dist/Column.js.map +1 -0
  13. package/dist/Global.d.ts +123 -0
  14. package/dist/Global.d.ts.map +1 -0
  15. package/dist/Global.js +124 -0
  16. package/dist/Global.js.map +1 -0
  17. package/dist/Page.d.ts +90 -0
  18. package/dist/Page.d.ts.map +1 -0
  19. package/dist/Page.js +107 -0
  20. package/dist/Page.js.map +1 -0
  21. package/dist/Pilotiq.d.ts +505 -0
  22. package/dist/Pilotiq.d.ts.map +1 -0
  23. package/dist/Pilotiq.js +463 -0
  24. package/dist/Pilotiq.js.map +1 -0
  25. package/dist/PilotiqRegistry.d.ts +10 -0
  26. package/dist/PilotiqRegistry.d.ts.map +1 -0
  27. package/dist/PilotiqRegistry.js +32 -0
  28. package/dist/PilotiqRegistry.js.map +1 -0
  29. package/dist/PilotiqServiceProvider.d.ts +16 -0
  30. package/dist/PilotiqServiceProvider.d.ts.map +1 -0
  31. package/dist/PilotiqServiceProvider.js +57 -0
  32. package/dist/PilotiqServiceProvider.js.map +1 -0
  33. package/dist/RelationManager.d.ts +372 -0
  34. package/dist/RelationManager.d.ts.map +1 -0
  35. package/dist/RelationManager.js +342 -0
  36. package/dist/RelationManager.js.map +1 -0
  37. package/dist/RenderHook.d.ts +86 -0
  38. package/dist/RenderHook.d.ts.map +1 -0
  39. package/dist/RenderHook.js +116 -0
  40. package/dist/RenderHook.js.map +1 -0
  41. package/dist/Resource.d.ts +290 -0
  42. package/dist/Resource.d.ts.map +1 -0
  43. package/dist/Resource.js +362 -0
  44. package/dist/Resource.js.map +1 -0
  45. package/dist/RightPanel.d.ts +92 -0
  46. package/dist/RightPanel.d.ts.map +1 -0
  47. package/dist/RightPanel.js +61 -0
  48. package/dist/RightPanel.js.map +1 -0
  49. package/dist/Tab.d.ts +92 -0
  50. package/dist/Tab.d.ts.map +1 -0
  51. package/dist/Tab.js +93 -0
  52. package/dist/Tab.js.map +1 -0
  53. package/dist/UserMenuItem.d.ts +76 -0
  54. package/dist/UserMenuItem.d.ts.map +1 -0
  55. package/dist/UserMenuItem.js +87 -0
  56. package/dist/UserMenuItem.js.map +1 -0
  57. package/dist/actions/Action.d.ts +888 -0
  58. package/dist/actions/Action.d.ts.map +1 -0
  59. package/dist/actions/Action.js +1652 -0
  60. package/dist/actions/Action.js.map +1 -0
  61. package/dist/actions/ActionGroup.d.ts +85 -0
  62. package/dist/actions/ActionGroup.d.ts.map +1 -0
  63. package/dist/actions/ActionGroup.js +132 -0
  64. package/dist/actions/ActionGroup.js.map +1 -0
  65. package/dist/actions/attachFactory.d.ts +67 -0
  66. package/dist/actions/attachFactory.d.ts.map +1 -0
  67. package/dist/actions/attachFactory.js +115 -0
  68. package/dist/actions/attachFactory.js.map +1 -0
  69. package/dist/actions/exportFactory.d.ts +88 -0
  70. package/dist/actions/exportFactory.d.ts.map +1 -0
  71. package/dist/actions/exportFactory.js +144 -0
  72. package/dist/actions/exportFactory.js.map +1 -0
  73. package/dist/actions/importFactory.d.ts +97 -0
  74. package/dist/actions/importFactory.d.ts.map +1 -0
  75. package/dist/actions/importFactory.js +143 -0
  76. package/dist/actions/importFactory.js.map +1 -0
  77. package/dist/actions/index.d.ts +3 -0
  78. package/dist/actions/index.d.ts.map +1 -0
  79. package/dist/actions/index.js +3 -0
  80. package/dist/actions/index.js.map +1 -0
  81. package/dist/applyPageHooks.d.ts +54 -0
  82. package/dist/applyPageHooks.d.ts.map +1 -0
  83. package/dist/applyPageHooks.js +149 -0
  84. package/dist/applyPageHooks.js.map +1 -0
  85. package/dist/cells/coerce.d.ts +25 -0
  86. package/dist/cells/coerce.d.ts.map +1 -0
  87. package/dist/cells/coerce.js +87 -0
  88. package/dist/cells/coerce.js.map +1 -0
  89. package/dist/clusterPaths.d.ts +9 -0
  90. package/dist/clusterPaths.d.ts.map +1 -0
  91. package/dist/clusterPaths.js +19 -0
  92. package/dist/clusterPaths.js.map +1 -0
  93. package/dist/columns/BadgeColumn.d.ts +16 -0
  94. package/dist/columns/BadgeColumn.d.ts.map +1 -0
  95. package/dist/columns/BadgeColumn.js +25 -0
  96. package/dist/columns/BadgeColumn.js.map +1 -0
  97. package/dist/columns/BooleanColumn.d.ts +10 -0
  98. package/dist/columns/BooleanColumn.d.ts.map +1 -0
  99. package/dist/columns/BooleanColumn.js +18 -0
  100. package/dist/columns/BooleanColumn.js.map +1 -0
  101. package/dist/columns/ColorColumn.d.ts +27 -0
  102. package/dist/columns/ColorColumn.d.ts.map +1 -0
  103. package/dist/columns/ColorColumn.js +35 -0
  104. package/dist/columns/ColorColumn.js.map +1 -0
  105. package/dist/columns/IconColumn.d.ts +22 -0
  106. package/dist/columns/IconColumn.d.ts.map +1 -0
  107. package/dist/columns/IconColumn.js +28 -0
  108. package/dist/columns/IconColumn.js.map +1 -0
  109. package/dist/columns/ImageColumn.d.ts +17 -0
  110. package/dist/columns/ImageColumn.d.ts.map +1 -0
  111. package/dist/columns/ImageColumn.js +24 -0
  112. package/dist/columns/ImageColumn.js.map +1 -0
  113. package/dist/columns/SelectColumn.d.ts +36 -0
  114. package/dist/columns/SelectColumn.d.ts.map +1 -0
  115. package/dist/columns/SelectColumn.js +52 -0
  116. package/dist/columns/SelectColumn.js.map +1 -0
  117. package/dist/columns/TextColumn.d.ts +18 -0
  118. package/dist/columns/TextColumn.d.ts.map +1 -0
  119. package/dist/columns/TextColumn.js +20 -0
  120. package/dist/columns/TextColumn.js.map +1 -0
  121. package/dist/columns/TextInputColumn.d.ts +47 -0
  122. package/dist/columns/TextInputColumn.d.ts.map +1 -0
  123. package/dist/columns/TextInputColumn.js +60 -0
  124. package/dist/columns/TextInputColumn.js.map +1 -0
  125. package/dist/columns/ToggleColumn.d.ts +32 -0
  126. package/dist/columns/ToggleColumn.d.ts.map +1 -0
  127. package/dist/columns/ToggleColumn.js +45 -0
  128. package/dist/columns/ToggleColumn.js.map +1 -0
  129. package/dist/columns/index.d.ts +10 -0
  130. package/dist/columns/index.d.ts.map +1 -0
  131. package/dist/columns/index.js +10 -0
  132. package/dist/columns/index.js.map +1 -0
  133. package/dist/defaultGlobalPages.d.ts +11 -0
  134. package/dist/defaultGlobalPages.d.ts.map +1 -0
  135. package/dist/defaultGlobalPages.js +87 -0
  136. package/dist/defaultGlobalPages.js.map +1 -0
  137. package/dist/defaultPages.d.ts +247 -0
  138. package/dist/defaultPages.d.ts.map +1 -0
  139. package/dist/defaultPages.js +558 -0
  140. package/dist/defaultPages.js.map +1 -0
  141. package/dist/elements/Form.d.ts +219 -0
  142. package/dist/elements/Form.d.ts.map +1 -0
  143. package/dist/elements/Form.js +259 -0
  144. package/dist/elements/Form.js.map +1 -0
  145. package/dist/elements/ListTabs.d.ts +17 -0
  146. package/dist/elements/ListTabs.d.ts.map +1 -0
  147. package/dist/elements/ListTabs.js +23 -0
  148. package/dist/elements/ListTabs.js.map +1 -0
  149. package/dist/elements/Table.d.ts +535 -0
  150. package/dist/elements/Table.d.ts.map +1 -0
  151. package/dist/elements/Table.js +481 -0
  152. package/dist/elements/Table.js.map +1 -0
  153. package/dist/elements/TableGroup.d.ts +121 -0
  154. package/dist/elements/TableGroup.d.ts.map +1 -0
  155. package/dist/elements/TableGroup.js +162 -0
  156. package/dist/elements/TableGroup.js.map +1 -0
  157. package/dist/elements/dispatchAction.d.ts +127 -0
  158. package/dist/elements/dispatchAction.d.ts.map +1 -0
  159. package/dist/elements/dispatchAction.js +254 -0
  160. package/dist/elements/dispatchAction.js.map +1 -0
  161. package/dist/elements/dispatchForm.d.ts +220 -0
  162. package/dist/elements/dispatchForm.d.ts.map +1 -0
  163. package/dist/elements/dispatchForm.js +1645 -0
  164. package/dist/elements/dispatchForm.js.map +1 -0
  165. package/dist/elements/dispatchTable.d.ts +69 -0
  166. package/dist/elements/dispatchTable.d.ts.map +1 -0
  167. package/dist/elements/dispatchTable.js +606 -0
  168. package/dist/elements/dispatchTable.js.map +1 -0
  169. package/dist/elements/index.d.ts +3 -0
  170. package/dist/elements/index.d.ts.map +1 -0
  171. package/dist/elements/index.js +3 -0
  172. package/dist/elements/index.js.map +1 -0
  173. package/dist/entries/BadgeEntry.d.ts +21 -0
  174. package/dist/entries/BadgeEntry.d.ts.map +1 -0
  175. package/dist/entries/BadgeEntry.js +32 -0
  176. package/dist/entries/BadgeEntry.js.map +1 -0
  177. package/dist/entries/CodeEntry.d.ts +38 -0
  178. package/dist/entries/CodeEntry.d.ts.map +1 -0
  179. package/dist/entries/CodeEntry.js +44 -0
  180. package/dist/entries/CodeEntry.js.map +1 -0
  181. package/dist/entries/ColorEntry.d.ts +32 -0
  182. package/dist/entries/ColorEntry.d.ts.map +1 -0
  183. package/dist/entries/ColorEntry.js +48 -0
  184. package/dist/entries/ColorEntry.js.map +1 -0
  185. package/dist/entries/ComponentEntry.d.ts +66 -0
  186. package/dist/entries/ComponentEntry.d.ts.map +1 -0
  187. package/dist/entries/ComponentEntry.js +86 -0
  188. package/dist/entries/ComponentEntry.js.map +1 -0
  189. package/dist/entries/Entry.d.ts +175 -0
  190. package/dist/entries/Entry.d.ts.map +1 -0
  191. package/dist/entries/Entry.js +233 -0
  192. package/dist/entries/Entry.js.map +1 -0
  193. package/dist/entries/IconEntry.d.ts +30 -0
  194. package/dist/entries/IconEntry.d.ts.map +1 -0
  195. package/dist/entries/IconEntry.js +34 -0
  196. package/dist/entries/IconEntry.js.map +1 -0
  197. package/dist/entries/ImageEntry.d.ts +33 -0
  198. package/dist/entries/ImageEntry.d.ts.map +1 -0
  199. package/dist/entries/ImageEntry.js +47 -0
  200. package/dist/entries/ImageEntry.js.map +1 -0
  201. package/dist/entries/KeyValueEntry.d.ts +30 -0
  202. package/dist/entries/KeyValueEntry.d.ts.map +1 -0
  203. package/dist/entries/KeyValueEntry.js +38 -0
  204. package/dist/entries/KeyValueEntry.js.map +1 -0
  205. package/dist/entries/RepeatableEntry.d.ts +122 -0
  206. package/dist/entries/RepeatableEntry.d.ts.map +1 -0
  207. package/dist/entries/RepeatableEntry.js +121 -0
  208. package/dist/entries/RepeatableEntry.js.map +1 -0
  209. package/dist/entries/TextEntry.d.ts +38 -0
  210. package/dist/entries/TextEntry.d.ts.map +1 -0
  211. package/dist/entries/TextEntry.js +49 -0
  212. package/dist/entries/TextEntry.js.map +1 -0
  213. package/dist/entries/index.d.ts +2 -0
  214. package/dist/entries/index.d.ts.map +1 -0
  215. package/dist/entries/index.js +8 -0
  216. package/dist/entries/index.js.map +1 -0
  217. package/dist/entries/registry.d.ts +41 -0
  218. package/dist/entries/registry.d.ts.map +1 -0
  219. package/dist/entries/registry.js +17 -0
  220. package/dist/entries/registry.js.map +1 -0
  221. package/dist/fields/BuilderField.d.ts +420 -0
  222. package/dist/fields/BuilderField.d.ts.map +1 -0
  223. package/dist/fields/BuilderField.js +359 -0
  224. package/dist/fields/BuilderField.js.map +1 -0
  225. package/dist/fields/CheckboxField.d.ts +18 -0
  226. package/dist/fields/CheckboxField.d.ts.map +1 -0
  227. package/dist/fields/CheckboxField.js +23 -0
  228. package/dist/fields/CheckboxField.js.map +1 -0
  229. package/dist/fields/CheckboxListField.d.ts +25 -0
  230. package/dist/fields/CheckboxListField.d.ts.map +1 -0
  231. package/dist/fields/CheckboxListField.js +46 -0
  232. package/dist/fields/CheckboxListField.js.map +1 -0
  233. package/dist/fields/ColorPickerField.d.ts +16 -0
  234. package/dist/fields/ColorPickerField.d.ts.map +1 -0
  235. package/dist/fields/ColorPickerField.js +21 -0
  236. package/dist/fields/ColorPickerField.js.map +1 -0
  237. package/dist/fields/DateField.d.ts +29 -0
  238. package/dist/fields/DateField.d.ts.map +1 -0
  239. package/dist/fields/DateField.js +45 -0
  240. package/dist/fields/DateField.js.map +1 -0
  241. package/dist/fields/EmailField.d.ts +8 -0
  242. package/dist/fields/EmailField.d.ts.map +1 -0
  243. package/dist/fields/EmailField.js +13 -0
  244. package/dist/fields/EmailField.js.map +1 -0
  245. package/dist/fields/Field.d.ts +485 -0
  246. package/dist/fields/Field.d.ts.map +1 -0
  247. package/dist/fields/Field.js +539 -0
  248. package/dist/fields/Field.js.map +1 -0
  249. package/dist/fields/FileUploadField.d.ts +43 -0
  250. package/dist/fields/FileUploadField.d.ts.map +1 -0
  251. package/dist/fields/FileUploadField.js +60 -0
  252. package/dist/fields/FileUploadField.js.map +1 -0
  253. package/dist/fields/HiddenField.d.ts +19 -0
  254. package/dist/fields/HiddenField.d.ts.map +1 -0
  255. package/dist/fields/HiddenField.js +24 -0
  256. package/dist/fields/HiddenField.js.map +1 -0
  257. package/dist/fields/KeyValueField.d.ts +36 -0
  258. package/dist/fields/KeyValueField.d.ts.map +1 -0
  259. package/dist/fields/KeyValueField.js +47 -0
  260. package/dist/fields/KeyValueField.js.map +1 -0
  261. package/dist/fields/MarkdownField.d.ts +79 -0
  262. package/dist/fields/MarkdownField.d.ts.map +1 -0
  263. package/dist/fields/MarkdownField.js +117 -0
  264. package/dist/fields/MarkdownField.js.map +1 -0
  265. package/dist/fields/NumberField.d.ts +17 -0
  266. package/dist/fields/NumberField.d.ts.map +1 -0
  267. package/dist/fields/NumberField.js +27 -0
  268. package/dist/fields/NumberField.js.map +1 -0
  269. package/dist/fields/RadioField.d.ts +26 -0
  270. package/dist/fields/RadioField.d.ts.map +1 -0
  271. package/dist/fields/RadioField.js +47 -0
  272. package/dist/fields/RadioField.js.map +1 -0
  273. package/dist/fields/RepeaterField.d.ts +594 -0
  274. package/dist/fields/RepeaterField.d.ts.map +1 -0
  275. package/dist/fields/RepeaterField.js +504 -0
  276. package/dist/fields/RepeaterField.js.map +1 -0
  277. package/dist/fields/RowButton.d.ts +86 -0
  278. package/dist/fields/RowButton.d.ts.map +1 -0
  279. package/dist/fields/RowButton.js +85 -0
  280. package/dist/fields/RowButton.js.map +1 -0
  281. package/dist/fields/SelectField.d.ts +127 -0
  282. package/dist/fields/SelectField.d.ts.map +1 -0
  283. package/dist/fields/SelectField.js +160 -0
  284. package/dist/fields/SelectField.js.map +1 -0
  285. package/dist/fields/SliderField.d.ts +31 -0
  286. package/dist/fields/SliderField.d.ts.map +1 -0
  287. package/dist/fields/SliderField.js +45 -0
  288. package/dist/fields/SliderField.js.map +1 -0
  289. package/dist/fields/SlugField.d.ts +11 -0
  290. package/dist/fields/SlugField.d.ts.map +1 -0
  291. package/dist/fields/SlugField.js +19 -0
  292. package/dist/fields/SlugField.js.map +1 -0
  293. package/dist/fields/TagsInputField.d.ts +65 -0
  294. package/dist/fields/TagsInputField.d.ts.map +1 -0
  295. package/dist/fields/TagsInputField.js +104 -0
  296. package/dist/fields/TagsInputField.js.map +1 -0
  297. package/dist/fields/TextField.d.ts +11 -0
  298. package/dist/fields/TextField.d.ts.map +1 -0
  299. package/dist/fields/TextField.js +19 -0
  300. package/dist/fields/TextField.js.map +1 -0
  301. package/dist/fields/TextareaField.d.ts +40 -0
  302. package/dist/fields/TextareaField.d.ts.map +1 -0
  303. package/dist/fields/TextareaField.js +51 -0
  304. package/dist/fields/TextareaField.js.map +1 -0
  305. package/dist/fields/ToggleButtonsField.d.ts +24 -0
  306. package/dist/fields/ToggleButtonsField.d.ts.map +1 -0
  307. package/dist/fields/ToggleButtonsField.js +41 -0
  308. package/dist/fields/ToggleButtonsField.js.map +1 -0
  309. package/dist/fields/ToggleField.d.ts +8 -0
  310. package/dist/fields/ToggleField.d.ts.map +1 -0
  311. package/dist/fields/ToggleField.js +13 -0
  312. package/dist/fields/ToggleField.js.map +1 -0
  313. package/dist/fields/optionsResolver.d.ts +54 -0
  314. package/dist/fields/optionsResolver.d.ts.map +1 -0
  315. package/dist/fields/optionsResolver.js +62 -0
  316. package/dist/fields/optionsResolver.js.map +1 -0
  317. package/dist/fields/resolveField.d.ts +21 -0
  318. package/dist/fields/resolveField.d.ts.map +1 -0
  319. package/dist/fields/resolveField.js +26 -0
  320. package/dist/fields/resolveField.js.map +1 -0
  321. package/dist/filters/BooleanFilter.d.ts +20 -0
  322. package/dist/filters/BooleanFilter.d.ts.map +1 -0
  323. package/dist/filters/BooleanFilter.js +31 -0
  324. package/dist/filters/BooleanFilter.js.map +1 -0
  325. package/dist/filters/DateRangeFilter.d.ts +68 -0
  326. package/dist/filters/DateRangeFilter.d.ts.map +1 -0
  327. package/dist/filters/DateRangeFilter.js +137 -0
  328. package/dist/filters/DateRangeFilter.js.map +1 -0
  329. package/dist/filters/Filter.d.ts +140 -0
  330. package/dist/filters/Filter.d.ts.map +1 -0
  331. package/dist/filters/Filter.js +99 -0
  332. package/dist/filters/Filter.js.map +1 -0
  333. package/dist/filters/FormFilter.d.ts +103 -0
  334. package/dist/filters/FormFilter.d.ts.map +1 -0
  335. package/dist/filters/FormFilter.js +180 -0
  336. package/dist/filters/FormFilter.js.map +1 -0
  337. package/dist/filters/MultiSelectFilter.d.ts +41 -0
  338. package/dist/filters/MultiSelectFilter.d.ts.map +1 -0
  339. package/dist/filters/MultiSelectFilter.js +67 -0
  340. package/dist/filters/MultiSelectFilter.js.map +1 -0
  341. package/dist/filters/QueryBuilderFilter.d.ts +145 -0
  342. package/dist/filters/QueryBuilderFilter.d.ts.map +1 -0
  343. package/dist/filters/QueryBuilderFilter.js +323 -0
  344. package/dist/filters/QueryBuilderFilter.js.map +1 -0
  345. package/dist/filters/SelectFilter.d.ts +26 -0
  346. package/dist/filters/SelectFilter.d.ts.map +1 -0
  347. package/dist/filters/SelectFilter.js +35 -0
  348. package/dist/filters/SelectFilter.js.map +1 -0
  349. package/dist/filters/TernaryFilter.d.ts +35 -0
  350. package/dist/filters/TernaryFilter.d.ts.map +1 -0
  351. package/dist/filters/TernaryFilter.js +71 -0
  352. package/dist/filters/TernaryFilter.js.map +1 -0
  353. package/dist/filters/TrashedFilter.d.ts +28 -0
  354. package/dist/filters/TrashedFilter.d.ts.map +1 -0
  355. package/dist/filters/TrashedFilter.js +52 -0
  356. package/dist/filters/TrashedFilter.js.map +1 -0
  357. package/dist/filters/queryBuilder/BooleanConstraint.d.ts +13 -0
  358. package/dist/filters/queryBuilder/BooleanConstraint.d.ts.map +1 -0
  359. package/dist/filters/queryBuilder/BooleanConstraint.js +27 -0
  360. package/dist/filters/queryBuilder/BooleanConstraint.js.map +1 -0
  361. package/dist/filters/queryBuilder/Constraint.d.ts +74 -0
  362. package/dist/filters/queryBuilder/Constraint.d.ts.map +1 -0
  363. package/dist/filters/queryBuilder/Constraint.js +45 -0
  364. package/dist/filters/queryBuilder/Constraint.js.map +1 -0
  365. package/dist/filters/queryBuilder/DateConstraint.d.ts +18 -0
  366. package/dist/filters/queryBuilder/DateConstraint.d.ts.map +1 -0
  367. package/dist/filters/queryBuilder/DateConstraint.js +63 -0
  368. package/dist/filters/queryBuilder/DateConstraint.js.map +1 -0
  369. package/dist/filters/queryBuilder/NumberConstraint.d.ts +12 -0
  370. package/dist/filters/queryBuilder/NumberConstraint.d.ts.map +1 -0
  371. package/dist/filters/queryBuilder/NumberConstraint.js +61 -0
  372. package/dist/filters/queryBuilder/NumberConstraint.js.map +1 -0
  373. package/dist/filters/queryBuilder/SelectConstraint.d.ts +22 -0
  374. package/dist/filters/queryBuilder/SelectConstraint.d.ts.map +1 -0
  375. package/dist/filters/queryBuilder/SelectConstraint.js +66 -0
  376. package/dist/filters/queryBuilder/SelectConstraint.js.map +1 -0
  377. package/dist/filters/queryBuilder/TextConstraint.d.ts +18 -0
  378. package/dist/filters/queryBuilder/TextConstraint.d.ts.map +1 -0
  379. package/dist/filters/queryBuilder/TextConstraint.js +58 -0
  380. package/dist/filters/queryBuilder/TextConstraint.js.map +1 -0
  381. package/dist/filters/queryBuilder/index.d.ts +7 -0
  382. package/dist/filters/queryBuilder/index.d.ts.map +1 -0
  383. package/dist/filters/queryBuilder/index.js +7 -0
  384. package/dist/filters/queryBuilder/index.js.map +1 -0
  385. package/dist/icons/index.d.ts +3 -0
  386. package/dist/icons/index.d.ts.map +1 -0
  387. package/dist/icons/index.js +3 -0
  388. package/dist/icons/index.js.map +1 -0
  389. package/dist/icons/lucide.d.ts +16 -0
  390. package/dist/icons/lucide.d.ts.map +1 -0
  391. package/dist/icons/lucide.js +173 -0
  392. package/dist/icons/lucide.js.map +1 -0
  393. package/dist/icons/registry.d.ts +27 -0
  394. package/dist/icons/registry.d.ts.map +1 -0
  395. package/dist/icons/registry.js +35 -0
  396. package/dist/icons/registry.js.map +1 -0
  397. package/dist/icons/types.d.ts +38 -0
  398. package/dist/icons/types.d.ts.map +1 -0
  399. package/dist/icons/types.js +23 -0
  400. package/dist/icons/types.js.map +1 -0
  401. package/dist/index.d.ts +118 -0
  402. package/dist/index.d.ts.map +1 -0
  403. package/dist/index.js +135 -0
  404. package/dist/index.js.map +1 -0
  405. package/dist/io/csv.d.ts +51 -0
  406. package/dist/io/csv.d.ts.map +1 -0
  407. package/dist/io/csv.js +168 -0
  408. package/dist/io/csv.js.map +1 -0
  409. package/dist/notifications/Notification.d.ts +181 -0
  410. package/dist/notifications/Notification.d.ts.map +1 -0
  411. package/dist/notifications/Notification.js +290 -0
  412. package/dist/notifications/Notification.js.map +1 -0
  413. package/dist/notifications/broadcast.d.ts +58 -0
  414. package/dist/notifications/broadcast.d.ts.map +1 -0
  415. package/dist/notifications/broadcast.js +72 -0
  416. package/dist/notifications/broadcast.js.map +1 -0
  417. package/dist/notifications/database.d.ts +164 -0
  418. package/dist/notifications/database.d.ts.map +1 -0
  419. package/dist/notifications/database.js +321 -0
  420. package/dist/notifications/database.js.map +1 -0
  421. package/dist/notifications/dispatchNotificationAction.d.ts +48 -0
  422. package/dist/notifications/dispatchNotificationAction.d.ts.map +1 -0
  423. package/dist/notifications/dispatchNotificationAction.js +100 -0
  424. package/dist/notifications/dispatchNotificationAction.js.map +1 -0
  425. package/dist/notifications/flash.d.ts +34 -0
  426. package/dist/notifications/flash.d.ts.map +1 -0
  427. package/dist/notifications/flash.js +51 -0
  428. package/dist/notifications/flash.js.map +1 -0
  429. package/dist/notifications/index.d.ts +8 -0
  430. package/dist/notifications/index.d.ts.map +1 -0
  431. package/dist/notifications/index.js +6 -0
  432. package/dist/notifications/index.js.map +1 -0
  433. package/dist/notifications/registerBroadcastAuth.d.ts +45 -0
  434. package/dist/notifications/registerBroadcastAuth.d.ts.map +1 -0
  435. package/dist/notifications/registerBroadcastAuth.js +86 -0
  436. package/dist/notifications/registerBroadcastAuth.js.map +1 -0
  437. package/dist/notifications/resolveSavedNotification.d.ts +21 -0
  438. package/dist/notifications/resolveSavedNotification.d.ts.map +1 -0
  439. package/dist/notifications/resolveSavedNotification.js +43 -0
  440. package/dist/notifications/resolveSavedNotification.js.map +1 -0
  441. package/dist/notifications/types.d.ts +87 -0
  442. package/dist/notifications/types.d.ts.map +1 -0
  443. package/dist/notifications/types.js +2 -0
  444. package/dist/notifications/types.js.map +1 -0
  445. package/dist/orm/m2mAccessor.d.ts +49 -0
  446. package/dist/orm/m2mAccessor.d.ts.map +1 -0
  447. package/dist/orm/m2mAccessor.js +45 -0
  448. package/dist/orm/m2mAccessor.js.map +1 -0
  449. package/dist/orm/modelDefaults.d.ts +347 -0
  450. package/dist/orm/modelDefaults.d.ts.map +1 -0
  451. package/dist/orm/modelDefaults.js +375 -0
  452. package/dist/orm/modelDefaults.js.map +1 -0
  453. package/dist/pageData.d.ts +778 -0
  454. package/dist/pageData.d.ts.map +1 -0
  455. package/dist/pageData.js +3725 -0
  456. package/dist/pageData.js.map +1 -0
  457. package/dist/plugins/index.d.ts +2 -0
  458. package/dist/plugins/index.d.ts.map +1 -0
  459. package/dist/plugins/index.js +2 -0
  460. package/dist/plugins/index.js.map +1 -0
  461. package/dist/plugins/themeEditor.d.ts +17 -0
  462. package/dist/plugins/themeEditor.d.ts.map +1 -0
  463. package/dist/plugins/themeEditor.js +23 -0
  464. package/dist/plugins/themeEditor.js.map +1 -0
  465. package/dist/react/AppShell.d.ts +58 -0
  466. package/dist/react/AppShell.d.ts.map +1 -0
  467. package/dist/react/AppShell.js +58 -0
  468. package/dist/react/AppShell.js.map +1 -0
  469. package/dist/react/CommandPalette.d.ts +21 -0
  470. package/dist/react/CommandPalette.d.ts.map +1 -0
  471. package/dist/react/CommandPalette.js +236 -0
  472. package/dist/react/CommandPalette.js.map +1 -0
  473. package/dist/react/FormStateContext.d.ts +83 -0
  474. package/dist/react/FormStateContext.d.ts.map +1 -0
  475. package/dist/react/FormStateContext.js +284 -0
  476. package/dist/react/FormStateContext.js.map +1 -0
  477. package/dist/react/HeadHooks.d.ts +26 -0
  478. package/dist/react/HeadHooks.d.ts.map +1 -0
  479. package/dist/react/HeadHooks.js +141 -0
  480. package/dist/react/HeadHooks.js.map +1 -0
  481. package/dist/react/NotificationActionStrip.d.ts +39 -0
  482. package/dist/react/NotificationActionStrip.d.ts.map +1 -0
  483. package/dist/react/NotificationActionStrip.js +129 -0
  484. package/dist/react/NotificationActionStrip.js.map +1 -0
  485. package/dist/react/NotificationBell.d.ts +20 -0
  486. package/dist/react/NotificationBell.d.ts.map +1 -0
  487. package/dist/react/NotificationBell.js +273 -0
  488. package/dist/react/NotificationBell.js.map +1 -0
  489. package/dist/react/RenderHookSlot.d.ts +20 -0
  490. package/dist/react/RenderHookSlot.d.ts.map +1 -0
  491. package/dist/react/RenderHookSlot.js +24 -0
  492. package/dist/react/RenderHookSlot.js.map +1 -0
  493. package/dist/react/RightSidebar.d.ts +33 -0
  494. package/dist/react/RightSidebar.d.ts.map +1 -0
  495. package/dist/react/RightSidebar.js +82 -0
  496. package/dist/react/RightSidebar.js.map +1 -0
  497. package/dist/react/RightSidebarContext.d.ts +62 -0
  498. package/dist/react/RightSidebarContext.d.ts.map +1 -0
  499. package/dist/react/RightSidebarContext.js +178 -0
  500. package/dist/react/RightSidebarContext.js.map +1 -0
  501. package/dist/react/RightSidebarTrigger.d.ts +16 -0
  502. package/dist/react/RightSidebarTrigger.d.ts.map +1 -0
  503. package/dist/react/RightSidebarTrigger.js +24 -0
  504. package/dist/react/RightSidebarTrigger.js.map +1 -0
  505. package/dist/react/SchemaRenderer.d.ts +63 -0
  506. package/dist/react/SchemaRenderer.d.ts.map +1 -0
  507. package/dist/react/SchemaRenderer.js +3458 -0
  508. package/dist/react/SchemaRenderer.js.map +1 -0
  509. package/dist/react/SearchTrigger.d.ts +13 -0
  510. package/dist/react/SearchTrigger.d.ts.map +1 -0
  511. package/dist/react/SearchTrigger.js +30 -0
  512. package/dist/react/SearchTrigger.js.map +1 -0
  513. package/dist/react/ThemeProvider.d.ts +18 -0
  514. package/dist/react/ThemeProvider.d.ts.map +1 -0
  515. package/dist/react/ThemeProvider.js +66 -0
  516. package/dist/react/ThemeProvider.js.map +1 -0
  517. package/dist/react/ThemeSettingsPage.d.ts +10 -0
  518. package/dist/react/ThemeSettingsPage.d.ts.map +1 -0
  519. package/dist/react/ThemeSettingsPage.js +293 -0
  520. package/dist/react/ThemeSettingsPage.js.map +1 -0
  521. package/dist/react/ThemeToggle.d.ts +2 -0
  522. package/dist/react/ThemeToggle.d.ts.map +1 -0
  523. package/dist/react/ThemeToggle.js +8 -0
  524. package/dist/react/ThemeToggle.js.map +1 -0
  525. package/dist/react/Toaster.d.ts +25 -0
  526. package/dist/react/Toaster.d.ts.map +1 -0
  527. package/dist/react/Toaster.js +89 -0
  528. package/dist/react/Toaster.js.map +1 -0
  529. package/dist/react/UserMenu.d.ts +23 -0
  530. package/dist/react/UserMenu.d.ts.map +1 -0
  531. package/dist/react/UserMenu.js +78 -0
  532. package/dist/react/UserMenu.js.map +1 -0
  533. package/dist/react/WidgetDataContext.d.ts +64 -0
  534. package/dist/react/WidgetDataContext.d.ts.map +1 -0
  535. package/dist/react/WidgetDataContext.js +89 -0
  536. package/dist/react/WidgetDataContext.js.map +1 -0
  537. package/dist/react/cells/EditableCell.d.ts +20 -0
  538. package/dist/react/cells/EditableCell.d.ts.map +1 -0
  539. package/dist/react/cells/EditableCell.js +251 -0
  540. package/dist/react/cells/EditableCell.js.map +1 -0
  541. package/dist/react/fieldJsHandler.d.ts +33 -0
  542. package/dist/react/fieldJsHandler.d.ts.map +1 -0
  543. package/dist/react/fieldJsHandler.js +61 -0
  544. package/dist/react/fieldJsHandler.js.map +1 -0
  545. package/dist/react/fields/BuilderInput.d.ts +21 -0
  546. package/dist/react/fields/BuilderInput.d.ts.map +1 -0
  547. package/dist/react/fields/BuilderInput.js +553 -0
  548. package/dist/react/fields/BuilderInput.js.map +1 -0
  549. package/dist/react/fields/CheckboxInput.d.ts +9 -0
  550. package/dist/react/fields/CheckboxInput.d.ts.map +1 -0
  551. package/dist/react/fields/CheckboxInput.js +23 -0
  552. package/dist/react/fields/CheckboxInput.js.map +1 -0
  553. package/dist/react/fields/CheckboxListInput.d.ts +19 -0
  554. package/dist/react/fields/CheckboxListInput.d.ts.map +1 -0
  555. package/dist/react/fields/CheckboxListInput.js +53 -0
  556. package/dist/react/fields/CheckboxListInput.js.map +1 -0
  557. package/dist/react/fields/ColorInput.d.ts +12 -0
  558. package/dist/react/fields/ColorInput.d.ts.map +1 -0
  559. package/dist/react/fields/ColorInput.js +29 -0
  560. package/dist/react/fields/ColorInput.js.map +1 -0
  561. package/dist/react/fields/DateFieldInput.d.ts +8 -0
  562. package/dist/react/fields/DateFieldInput.d.ts.map +1 -0
  563. package/dist/react/fields/DateFieldInput.js +39 -0
  564. package/dist/react/fields/DateFieldInput.js.map +1 -0
  565. package/dist/react/fields/DateTimeInput.d.ts +13 -0
  566. package/dist/react/fields/DateTimeInput.d.ts.map +1 -0
  567. package/dist/react/fields/DateTimeInput.js +29 -0
  568. package/dist/react/fields/DateTimeInput.js.map +1 -0
  569. package/dist/react/fields/FieldShell.d.ts +23 -0
  570. package/dist/react/fields/FieldShell.d.ts.map +1 -0
  571. package/dist/react/fields/FieldShell.js +46 -0
  572. package/dist/react/fields/FieldShell.js.map +1 -0
  573. package/dist/react/fields/FileUploadInput.d.ts +21 -0
  574. package/dist/react/fields/FileUploadInput.d.ts.map +1 -0
  575. package/dist/react/fields/FileUploadInput.js +120 -0
  576. package/dist/react/fields/FileUploadInput.js.map +1 -0
  577. package/dist/react/fields/HiddenInput.d.ts +11 -0
  578. package/dist/react/fields/HiddenInput.d.ts.map +1 -0
  579. package/dist/react/fields/HiddenInput.js +14 -0
  580. package/dist/react/fields/HiddenInput.js.map +1 -0
  581. package/dist/react/fields/KeyValueInput.d.ts +18 -0
  582. package/dist/react/fields/KeyValueInput.d.ts.map +1 -0
  583. package/dist/react/fields/KeyValueInput.js +122 -0
  584. package/dist/react/fields/KeyValueInput.js.map +1 -0
  585. package/dist/react/fields/MarkdownInput.d.ts +29 -0
  586. package/dist/react/fields/MarkdownInput.d.ts.map +1 -0
  587. package/dist/react/fields/MarkdownInput.js +250 -0
  588. package/dist/react/fields/MarkdownInput.js.map +1 -0
  589. package/dist/react/fields/RadioInput.d.ts +18 -0
  590. package/dist/react/fields/RadioInput.d.ts.map +1 -0
  591. package/dist/react/fields/RadioInput.js +34 -0
  592. package/dist/react/fields/RadioInput.js.map +1 -0
  593. package/dist/react/fields/RepeaterInput.d.ts +92 -0
  594. package/dist/react/fields/RepeaterInput.d.ts.map +1 -0
  595. package/dist/react/fields/RepeaterInput.js +705 -0
  596. package/dist/react/fields/RepeaterInput.js.map +1 -0
  597. package/dist/react/fields/SelectFieldInput.d.ts +23 -0
  598. package/dist/react/fields/SelectFieldInput.d.ts.map +1 -0
  599. package/dist/react/fields/SelectFieldInput.js +146 -0
  600. package/dist/react/fields/SelectFieldInput.js.map +1 -0
  601. package/dist/react/fields/SliderInput.d.ts +16 -0
  602. package/dist/react/fields/SliderInput.d.ts.map +1 -0
  603. package/dist/react/fields/SliderInput.js +37 -0
  604. package/dist/react/fields/SliderInput.js.map +1 -0
  605. package/dist/react/fields/TagsInput.d.ts +27 -0
  606. package/dist/react/fields/TagsInput.d.ts.map +1 -0
  607. package/dist/react/fields/TagsInput.js +189 -0
  608. package/dist/react/fields/TagsInput.js.map +1 -0
  609. package/dist/react/fields/TextLikeInput.d.ts +18 -0
  610. package/dist/react/fields/TextLikeInput.d.ts.map +1 -0
  611. package/dist/react/fields/TextLikeInput.js +46 -0
  612. package/dist/react/fields/TextLikeInput.js.map +1 -0
  613. package/dist/react/fields/ToggleButtonsInput.d.ts +20 -0
  614. package/dist/react/fields/ToggleButtonsInput.d.ts.map +1 -0
  615. package/dist/react/fields/ToggleButtonsInput.js +42 -0
  616. package/dist/react/fields/ToggleButtonsInput.js.map +1 -0
  617. package/dist/react/fields/ToggleFieldInput.d.ts +7 -0
  618. package/dist/react/fields/ToggleFieldInput.d.ts.map +1 -0
  619. package/dist/react/fields/ToggleFieldInput.js +30 -0
  620. package/dist/react/fields/ToggleFieldInput.js.map +1 -0
  621. package/dist/react/fields/rowChromeButton.d.ts +84 -0
  622. package/dist/react/fields/rowChromeButton.d.ts.map +1 -0
  623. package/dist/react/fields/rowChromeButton.js +111 -0
  624. package/dist/react/fields/rowChromeButton.js.map +1 -0
  625. package/dist/react/fields/syncRowGates.d.ts +11 -0
  626. package/dist/react/fields/syncRowGates.d.ts.map +1 -0
  627. package/dist/react/fields/syncRowGates.js +55 -0
  628. package/dist/react/fields/syncRowGates.js.map +1 -0
  629. package/dist/react/formStateHelpers.d.ts +44 -0
  630. package/dist/react/formStateHelpers.d.ts.map +1 -0
  631. package/dist/react/formStateHelpers.js +230 -0
  632. package/dist/react/formStateHelpers.js.map +1 -0
  633. package/dist/react/hooks/use-mobile.d.ts +2 -0
  634. package/dist/react/hooks/use-mobile.d.ts.map +1 -0
  635. package/dist/react/hooks/use-mobile.js +16 -0
  636. package/dist/react/hooks/use-mobile.js.map +1 -0
  637. package/dist/react/icon-context.d.ts +35 -0
  638. package/dist/react/icon-context.d.ts.map +1 -0
  639. package/dist/react/icon-context.js +45 -0
  640. package/dist/react/icon-context.js.map +1 -0
  641. package/dist/react/index.d.ts +26 -0
  642. package/dist/react/index.d.ts.map +1 -0
  643. package/dist/react/index.js +28 -0
  644. package/dist/react/index.js.map +1 -0
  645. package/dist/react/layouts/SidebarLayout.d.ts +3 -0
  646. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -0
  647. package/dist/react/layouts/SidebarLayout.js +85 -0
  648. package/dist/react/layouts/SidebarLayout.js.map +1 -0
  649. package/dist/react/layouts/TopbarLayout.d.ts +3 -0
  650. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -0
  651. package/dist/react/layouts/TopbarLayout.js +103 -0
  652. package/dist/react/layouts/TopbarLayout.js.map +1 -0
  653. package/dist/react/navigate.d.ts +22 -0
  654. package/dist/react/navigate.d.ts.map +1 -0
  655. package/dist/react/navigate.js +30 -0
  656. package/dist/react/navigate.js.map +1 -0
  657. package/dist/react/registry.d.ts +35 -0
  658. package/dist/react/registry.d.ts.map +1 -0
  659. package/dist/react/registry.js +22 -0
  660. package/dist/react/registry.js.map +1 -0
  661. package/dist/react/right-panel-registry.d.ts +32 -0
  662. package/dist/react/right-panel-registry.d.ts.map +1 -0
  663. package/dist/react/right-panel-registry.js +20 -0
  664. package/dist/react/right-panel-registry.js.map +1 -0
  665. package/dist/react/theme-preview/apply.d.ts +11 -0
  666. package/dist/react/theme-preview/apply.d.ts.map +1 -0
  667. package/dist/react/theme-preview/apply.js +93 -0
  668. package/dist/react/theme-preview/apply.js.map +1 -0
  669. package/dist/react/theme-preview/build-html.d.ts +3 -0
  670. package/dist/react/theme-preview/build-html.d.ts.map +1 -0
  671. package/dist/react/theme-preview/build-html.js +437 -0
  672. package/dist/react/theme-preview/build-html.js.map +1 -0
  673. package/dist/react/ui/button.d.ts +9 -0
  674. package/dist/react/ui/button.d.ts.map +1 -0
  675. package/dist/react/ui/button.js +35 -0
  676. package/dist/react/ui/button.js.map +1 -0
  677. package/dist/react/ui/calendar.d.ts +5 -0
  678. package/dist/react/ui/calendar.d.ts.map +1 -0
  679. package/dist/react/ui/calendar.js +34 -0
  680. package/dist/react/ui/calendar.js.map +1 -0
  681. package/dist/react/ui/checkbox.d.ts +4 -0
  682. package/dist/react/ui/checkbox.d.ts.map +1 -0
  683. package/dist/react/ui/checkbox.js +9 -0
  684. package/dist/react/ui/checkbox.js.map +1 -0
  685. package/dist/react/ui/dialog.d.ts +12 -0
  686. package/dist/react/ui/dialog.d.ts.map +1 -0
  687. package/dist/react/ui/dialog.js +34 -0
  688. package/dist/react/ui/dialog.js.map +1 -0
  689. package/dist/react/ui/dropdown-menu.d.ts +12 -0
  690. package/dist/react/ui/dropdown-menu.d.ts.map +1 -0
  691. package/dist/react/ui/dropdown-menu.js +23 -0
  692. package/dist/react/ui/dropdown-menu.js.map +1 -0
  693. package/dist/react/ui/input.d.ts +4 -0
  694. package/dist/react/ui/input.d.ts.map +1 -0
  695. package/dist/react/ui/input.js +8 -0
  696. package/dist/react/ui/input.js.map +1 -0
  697. package/dist/react/ui/label.d.ts +4 -0
  698. package/dist/react/ui/label.d.ts.map +1 -0
  699. package/dist/react/ui/label.js +7 -0
  700. package/dist/react/ui/label.js.map +1 -0
  701. package/dist/react/ui/popover.d.ts +6 -0
  702. package/dist/react/ui/popover.d.ts.map +1 -0
  703. package/dist/react/ui/popover.js +14 -0
  704. package/dist/react/ui/popover.js.map +1 -0
  705. package/dist/react/ui/select.d.ts +17 -0
  706. package/dist/react/ui/select.d.ts.map +1 -0
  707. package/dist/react/ui/select.js +39 -0
  708. package/dist/react/ui/select.js.map +1 -0
  709. package/dist/react/ui/separator.d.ts +4 -0
  710. package/dist/react/ui/separator.d.ts.map +1 -0
  711. package/dist/react/ui/separator.js +9 -0
  712. package/dist/react/ui/separator.js.map +1 -0
  713. package/dist/react/ui/sheet.d.ts +15 -0
  714. package/dist/react/ui/sheet.d.ts.map +1 -0
  715. package/dist/react/ui/sheet.js +37 -0
  716. package/dist/react/ui/sheet.js.map +1 -0
  717. package/dist/react/ui/sidebar.d.ts +64 -0
  718. package/dist/react/ui/sidebar.d.ts.map +1 -0
  719. package/dist/react/ui/sidebar.js +257 -0
  720. package/dist/react/ui/sidebar.js.map +1 -0
  721. package/dist/react/ui/skeleton.d.ts +3 -0
  722. package/dist/react/ui/skeleton.d.ts.map +1 -0
  723. package/dist/react/ui/skeleton.js +7 -0
  724. package/dist/react/ui/skeleton.js.map +1 -0
  725. package/dist/react/ui/slider.d.ts +4 -0
  726. package/dist/react/ui/slider.d.ts.map +1 -0
  727. package/dist/react/ui/slider.js +8 -0
  728. package/dist/react/ui/slider.js.map +1 -0
  729. package/dist/react/ui/switch.d.ts +4 -0
  730. package/dist/react/ui/switch.d.ts.map +1 -0
  731. package/dist/react/ui/switch.js +8 -0
  732. package/dist/react/ui/switch.js.map +1 -0
  733. package/dist/react/ui/table.d.ts +11 -0
  734. package/dist/react/ui/table.d.ts.map +1 -0
  735. package/dist/react/ui/table.js +28 -0
  736. package/dist/react/ui/table.js.map +1 -0
  737. package/dist/react/ui/tabs.d.ts +7 -0
  738. package/dist/react/ui/tabs.d.ts.map +1 -0
  739. package/dist/react/ui/tabs.js +17 -0
  740. package/dist/react/ui/tabs.js.map +1 -0
  741. package/dist/react/ui/textarea.d.ts +4 -0
  742. package/dist/react/ui/textarea.d.ts.map +1 -0
  743. package/dist/react/ui/textarea.js +7 -0
  744. package/dist/react/ui/textarea.js.map +1 -0
  745. package/dist/react/ui/tooltip.d.ts +7 -0
  746. package/dist/react/ui/tooltip.d.ts.map +1 -0
  747. package/dist/react/ui/tooltip.js +17 -0
  748. package/dist/react/ui/tooltip.js.map +1 -0
  749. package/dist/react/useResizableWidth.d.ts +47 -0
  750. package/dist/react/useResizableWidth.d.ts.map +1 -0
  751. package/dist/react/useResizableWidth.js +99 -0
  752. package/dist/react/useResizableWidth.js.map +1 -0
  753. package/dist/react/utils.d.ts +3 -0
  754. package/dist/react/utils.d.ts.map +1 -0
  755. package/dist/react/utils.js +6 -0
  756. package/dist/react/utils.js.map +1 -0
  757. package/dist/react/widgetRegistry.d.ts +33 -0
  758. package/dist/react/widgetRegistry.d.ts.map +1 -0
  759. package/dist/react/widgetRegistry.js +15 -0
  760. package/dist/react/widgetRegistry.js.map +1 -0
  761. package/dist/react/widgets/StatsOverviewRenderer.d.ts +6 -0
  762. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -0
  763. package/dist/react/widgets/StatsOverviewRenderer.js +124 -0
  764. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -0
  765. package/dist/react/widgets/TableWidgetRenderer.d.ts +6 -0
  766. package/dist/react/widgets/TableWidgetRenderer.d.ts.map +1 -0
  767. package/dist/react/widgets/TableWidgetRenderer.js +123 -0
  768. package/dist/react/widgets/TableWidgetRenderer.js.map +1 -0
  769. package/dist/react/widgets/ViewRenderer.d.ts +16 -0
  770. package/dist/react/widgets/ViewRenderer.d.ts.map +1 -0
  771. package/dist/react/widgets/ViewRenderer.js +26 -0
  772. package/dist/react/widgets/ViewRenderer.js.map +1 -0
  773. package/dist/richtext/index.d.ts +2 -0
  774. package/dist/richtext/index.d.ts.map +1 -0
  775. package/dist/richtext/index.js +2 -0
  776. package/dist/richtext/index.js.map +1 -0
  777. package/dist/richtext/registry.d.ts +55 -0
  778. package/dist/richtext/registry.d.ts.map +1 -0
  779. package/dist/richtext/registry.js +66 -0
  780. package/dist/richtext/registry.js.map +1 -0
  781. package/dist/routes.d.ts +9 -0
  782. package/dist/routes.d.ts.map +1 -0
  783. package/dist/routes.js +3116 -0
  784. package/dist/routes.js.map +1 -0
  785. package/dist/schema/Alert.d.ts +33 -0
  786. package/dist/schema/Alert.d.ts.map +1 -0
  787. package/dist/schema/Alert.js +41 -0
  788. package/dist/schema/Alert.js.map +1 -0
  789. package/dist/schema/Block.d.ts +112 -0
  790. package/dist/schema/Block.d.ts.map +1 -0
  791. package/dist/schema/Block.js +136 -0
  792. package/dist/schema/Block.js.map +1 -0
  793. package/dist/schema/Breadcrumbs.d.ts +31 -0
  794. package/dist/schema/Breadcrumbs.d.ts.map +1 -0
  795. package/dist/schema/Breadcrumbs.js +30 -0
  796. package/dist/schema/Breadcrumbs.js.map +1 -0
  797. package/dist/schema/Card.d.ts +17 -0
  798. package/dist/schema/Card.d.ts.map +1 -0
  799. package/dist/schema/Card.js +31 -0
  800. package/dist/schema/Card.js.map +1 -0
  801. package/dist/schema/Divider.d.ts +12 -0
  802. package/dist/schema/Divider.d.ts.map +1 -0
  803. package/dist/schema/Divider.js +19 -0
  804. package/dist/schema/Divider.js.map +1 -0
  805. package/dist/schema/Element.d.ts +150 -0
  806. package/dist/schema/Element.d.ts.map +1 -0
  807. package/dist/schema/Element.js +124 -0
  808. package/dist/schema/Element.js.map +1 -0
  809. package/dist/schema/EmptyState.d.ts +48 -0
  810. package/dist/schema/EmptyState.d.ts.map +1 -0
  811. package/dist/schema/EmptyState.js +57 -0
  812. package/dist/schema/EmptyState.js.map +1 -0
  813. package/dist/schema/Fieldset.d.ts +25 -0
  814. package/dist/schema/Fieldset.d.ts.map +1 -0
  815. package/dist/schema/Fieldset.js +39 -0
  816. package/dist/schema/Fieldset.js.map +1 -0
  817. package/dist/schema/Grid.d.ts +23 -0
  818. package/dist/schema/Grid.d.ts.map +1 -0
  819. package/dist/schema/Grid.js +36 -0
  820. package/dist/schema/Grid.js.map +1 -0
  821. package/dist/schema/Group.d.ts +19 -0
  822. package/dist/schema/Group.d.ts.map +1 -0
  823. package/dist/schema/Group.js +26 -0
  824. package/dist/schema/Group.js.map +1 -0
  825. package/dist/schema/Heading.d.ts +25 -0
  826. package/dist/schema/Heading.d.ts.map +1 -0
  827. package/dist/schema/Heading.js +34 -0
  828. package/dist/schema/Heading.js.map +1 -0
  829. package/dist/schema/Html.d.ts +48 -0
  830. package/dist/schema/Html.d.ts.map +1 -0
  831. package/dist/schema/Html.js +60 -0
  832. package/dist/schema/Html.js.map +1 -0
  833. package/dist/schema/Icon.d.ts +34 -0
  834. package/dist/schema/Icon.d.ts.map +1 -0
  835. package/dist/schema/Icon.js +40 -0
  836. package/dist/schema/Icon.js.map +1 -0
  837. package/dist/schema/Image.d.ts +38 -0
  838. package/dist/schema/Image.d.ts.map +1 -0
  839. package/dist/schema/Image.js +48 -0
  840. package/dist/schema/Image.js.map +1 -0
  841. package/dist/schema/LinkTag.d.ts +48 -0
  842. package/dist/schema/LinkTag.d.ts.map +1 -0
  843. package/dist/schema/LinkTag.js +16 -0
  844. package/dist/schema/LinkTag.js.map +1 -0
  845. package/dist/schema/Markdown.d.ts +57 -0
  846. package/dist/schema/Markdown.d.ts.map +1 -0
  847. package/dist/schema/Markdown.js +75 -0
  848. package/dist/schema/Markdown.js.map +1 -0
  849. package/dist/schema/MetaTag.d.ts +41 -0
  850. package/dist/schema/MetaTag.d.ts.map +1 -0
  851. package/dist/schema/MetaTag.js +16 -0
  852. package/dist/schema/MetaTag.js.map +1 -0
  853. package/dist/schema/RelationTabs.d.ts +50 -0
  854. package/dist/schema/RelationTabs.d.ts.map +1 -0
  855. package/dist/schema/RelationTabs.js +48 -0
  856. package/dist/schema/RelationTabs.js.map +1 -0
  857. package/dist/schema/ScriptTag.d.ts +63 -0
  858. package/dist/schema/ScriptTag.d.ts.map +1 -0
  859. package/dist/schema/ScriptTag.js +16 -0
  860. package/dist/schema/ScriptTag.js.map +1 -0
  861. package/dist/schema/Section.d.ts +93 -0
  862. package/dist/schema/Section.d.ts.map +1 -0
  863. package/dist/schema/Section.js +127 -0
  864. package/dist/schema/Section.js.map +1 -0
  865. package/dist/schema/ServerDataElement.d.ts +101 -0
  866. package/dist/schema/ServerDataElement.d.ts.map +1 -0
  867. package/dist/schema/ServerDataElement.js +135 -0
  868. package/dist/schema/ServerDataElement.js.map +1 -0
  869. package/dist/schema/Split.d.ts +31 -0
  870. package/dist/schema/Split.d.ts.map +1 -0
  871. package/dist/schema/Split.js +41 -0
  872. package/dist/schema/Split.js.map +1 -0
  873. package/dist/schema/Stat.d.ts +92 -0
  874. package/dist/schema/Stat.d.ts.map +1 -0
  875. package/dist/schema/Stat.js +116 -0
  876. package/dist/schema/Stat.js.map +1 -0
  877. package/dist/schema/StatsOverview.d.ts +76 -0
  878. package/dist/schema/StatsOverview.d.ts.map +1 -0
  879. package/dist/schema/StatsOverview.js +71 -0
  880. package/dist/schema/StatsOverview.js.map +1 -0
  881. package/dist/schema/StyleTag.d.ts +32 -0
  882. package/dist/schema/StyleTag.d.ts.map +1 -0
  883. package/dist/schema/StyleTag.js +38 -0
  884. package/dist/schema/StyleTag.js.map +1 -0
  885. package/dist/schema/TableWidget.d.ts +148 -0
  886. package/dist/schema/TableWidget.d.ts.map +1 -0
  887. package/dist/schema/TableWidget.js +190 -0
  888. package/dist/schema/TableWidget.js.map +1 -0
  889. package/dist/schema/Tabs.d.ts +40 -0
  890. package/dist/schema/Tabs.d.ts.map +1 -0
  891. package/dist/schema/Tabs.js +66 -0
  892. package/dist/schema/Tabs.js.map +1 -0
  893. package/dist/schema/Text.d.ts +33 -0
  894. package/dist/schema/Text.d.ts.map +1 -0
  895. package/dist/schema/Text.js +40 -0
  896. package/dist/schema/Text.js.map +1 -0
  897. package/dist/schema/UnorderedList.d.ts +36 -0
  898. package/dist/schema/UnorderedList.d.ts.map +1 -0
  899. package/dist/schema/UnorderedList.js +42 -0
  900. package/dist/schema/UnorderedList.js.map +1 -0
  901. package/dist/schema/View.d.ts +81 -0
  902. package/dist/schema/View.d.ts.map +1 -0
  903. package/dist/schema/View.js +81 -0
  904. package/dist/schema/View.js.map +1 -0
  905. package/dist/schema/Wizard.d.ts +67 -0
  906. package/dist/schema/Wizard.d.ts.map +1 -0
  907. package/dist/schema/Wizard.js +94 -0
  908. package/dist/schema/Wizard.js.map +1 -0
  909. package/dist/schema/index.d.ts +26 -0
  910. package/dist/schema/index.d.ts.map +1 -0
  911. package/dist/schema/index.js +26 -0
  912. package/dist/schema/index.js.map +1 -0
  913. package/dist/schema/resolveSchema.d.ts +122 -0
  914. package/dist/schema/resolveSchema.d.ts.map +1 -0
  915. package/dist/schema/resolveSchema.js +648 -0
  916. package/dist/schema/resolveSchema.js.map +1 -0
  917. package/dist/schema/sanitize.d.ts +21 -0
  918. package/dist/schema/sanitize.d.ts.map +1 -0
  919. package/dist/schema/sanitize.js +46 -0
  920. package/dist/schema/sanitize.js.map +1 -0
  921. package/dist/search.d.ts +53 -0
  922. package/dist/search.d.ts.map +1 -0
  923. package/dist/search.js +114 -0
  924. package/dist/search.js.map +1 -0
  925. package/dist/sessionFilters.d.ts +8 -0
  926. package/dist/sessionFilters.d.ts.map +1 -0
  927. package/dist/sessionFilters.js +115 -0
  928. package/dist/sessionFilters.js.map +1 -0
  929. package/dist/summarizers/Summarizer.d.ts +65 -0
  930. package/dist/summarizers/Summarizer.d.ts.map +1 -0
  931. package/dist/summarizers/Summarizer.js +98 -0
  932. package/dist/summarizers/Summarizer.js.map +1 -0
  933. package/dist/summarizers/index.d.ts +2 -0
  934. package/dist/summarizers/index.d.ts.map +1 -0
  935. package/dist/summarizers/index.js +2 -0
  936. package/dist/summarizers/index.js.map +1 -0
  937. package/dist/theme/base-colors.d.ts +3 -0
  938. package/dist/theme/base-colors.d.ts.map +1 -0
  939. package/dist/theme/base-colors.js +64 -0
  940. package/dist/theme/base-colors.js.map +1 -0
  941. package/dist/theme/chart-colors.d.ts +3 -0
  942. package/dist/theme/chart-colors.d.ts.map +1 -0
  943. package/dist/theme/chart-colors.js +46 -0
  944. package/dist/theme/chart-colors.js.map +1 -0
  945. package/dist/theme/colors.d.ts +56 -0
  946. package/dist/theme/colors.d.ts.map +1 -0
  947. package/dist/theme/colors.js +410 -0
  948. package/dist/theme/colors.js.map +1 -0
  949. package/dist/theme/generate-css.d.ts +9 -0
  950. package/dist/theme/generate-css.d.ts.map +1 -0
  951. package/dist/theme/generate-css.js +36 -0
  952. package/dist/theme/generate-css.js.map +1 -0
  953. package/dist/theme/generate-scale.d.ts +3 -0
  954. package/dist/theme/generate-scale.d.ts.map +1 -0
  955. package/dist/theme/generate-scale.js +89 -0
  956. package/dist/theme/generate-scale.js.map +1 -0
  957. package/dist/theme/icon-map.d.ts +9 -0
  958. package/dist/theme/icon-map.d.ts.map +1 -0
  959. package/dist/theme/icon-map.js +40 -0
  960. package/dist/theme/icon-map.js.map +1 -0
  961. package/dist/theme/index.d.ts +15 -0
  962. package/dist/theme/index.d.ts.map +1 -0
  963. package/dist/theme/index.js +13 -0
  964. package/dist/theme/index.js.map +1 -0
  965. package/dist/theme/migrate.d.ts +14 -0
  966. package/dist/theme/migrate.d.ts.map +1 -0
  967. package/dist/theme/migrate.js +79 -0
  968. package/dist/theme/migrate.js.map +1 -0
  969. package/dist/theme/presets.d.ts +30 -0
  970. package/dist/theme/presets.d.ts.map +1 -0
  971. package/dist/theme/presets.js +128 -0
  972. package/dist/theme/presets.js.map +1 -0
  973. package/dist/theme/radius.d.ts +11 -0
  974. package/dist/theme/radius.d.ts.map +1 -0
  975. package/dist/theme/radius.js +17 -0
  976. package/dist/theme/radius.js.map +1 -0
  977. package/dist/theme/resolve.d.ts +13 -0
  978. package/dist/theme/resolve.d.ts.map +1 -0
  979. package/dist/theme/resolve.js +91 -0
  980. package/dist/theme/resolve.js.map +1 -0
  981. package/dist/theme/spacing.d.ts +14 -0
  982. package/dist/theme/spacing.d.ts.map +1 -0
  983. package/dist/theme/spacing.js +17 -0
  984. package/dist/theme/spacing.js.map +1 -0
  985. package/dist/theme/theme-colors.d.ts +9 -0
  986. package/dist/theme/theme-colors.d.ts.map +1 -0
  987. package/dist/theme/theme-colors.js +84 -0
  988. package/dist/theme/theme-colors.js.map +1 -0
  989. package/dist/theme/types.d.ts +94 -0
  990. package/dist/theme/types.d.ts.map +1 -0
  991. package/dist/theme/types.js +2 -0
  992. package/dist/theme/types.js.map +1 -0
  993. package/dist/uploads/UploadAdapter.d.ts +34 -0
  994. package/dist/uploads/UploadAdapter.d.ts.map +1 -0
  995. package/dist/uploads/UploadAdapter.js +2 -0
  996. package/dist/uploads/UploadAdapter.js.map +1 -0
  997. package/dist/uploads/index.d.ts +3 -0
  998. package/dist/uploads/index.d.ts.map +1 -0
  999. package/dist/uploads/index.js +2 -0
  1000. package/dist/uploads/index.js.map +1 -0
  1001. package/dist/uploads/localUpload.d.ts +25 -0
  1002. package/dist/uploads/localUpload.d.ts.map +1 -0
  1003. package/dist/uploads/localUpload.js +65 -0
  1004. package/dist/uploads/localUpload.js.map +1 -0
  1005. package/dist/validation/Validator.d.ts +40 -0
  1006. package/dist/validation/Validator.d.ts.map +1 -0
  1007. package/dist/validation/Validator.js +25 -0
  1008. package/dist/validation/Validator.js.map +1 -0
  1009. package/dist/validation/index.d.ts +5 -0
  1010. package/dist/validation/index.d.ts.map +1 -0
  1011. package/dist/validation/index.js +5 -0
  1012. package/dist/validation/index.js.map +1 -0
  1013. package/dist/validation/rules.d.ts +9 -0
  1014. package/dist/validation/rules.d.ts.map +1 -0
  1015. package/dist/validation/rules.js +61 -0
  1016. package/dist/validation/rules.js.map +1 -0
  1017. package/dist/validation/runValidators.d.ts +30 -0
  1018. package/dist/validation/runValidators.d.ts.map +1 -0
  1019. package/dist/validation/runValidators.js +438 -0
  1020. package/dist/validation/runValidators.js.map +1 -0
  1021. package/dist/validation/uniqueValidator.d.ts +61 -0
  1022. package/dist/validation/uniqueValidator.d.ts.map +1 -0
  1023. package/dist/validation/uniqueValidator.js +80 -0
  1024. package/dist/validation/uniqueValidator.js.map +1 -0
  1025. package/dist/vite.d.ts +19 -0
  1026. package/dist/vite.d.ts.map +1 -0
  1027. package/dist/vite.js +696 -0
  1028. package/dist/vite.js.map +1 -0
  1029. package/dist/widgets/index.d.ts +2 -0
  1030. package/dist/widgets/index.d.ts.map +1 -0
  1031. package/dist/widgets/index.js +7 -0
  1032. package/dist/widgets/index.js.map +1 -0
  1033. package/dist/widgets/registry.d.ts +32 -0
  1034. package/dist/widgets/registry.d.ts.map +1 -0
  1035. package/dist/widgets/registry.js +17 -0
  1036. package/dist/widgets/registry.js.map +1 -0
  1037. package/package.json +101 -0
  1038. package/src/Cluster.test.ts +283 -0
  1039. package/src/Cluster.ts +83 -0
  1040. package/src/Column.test.ts +140 -0
  1041. package/src/Column.ts +612 -0
  1042. package/src/Global.test.ts +367 -0
  1043. package/src/Global.ts +169 -0
  1044. package/src/Page.test.ts +50 -0
  1045. package/src/Page.ts +139 -0
  1046. package/src/Pilotiq.test.ts +47 -0
  1047. package/src/Pilotiq.ts +705 -0
  1048. package/src/PilotiqRegistry.ts +36 -0
  1049. package/src/PilotiqServiceProvider.ts +69 -0
  1050. package/src/RelationManager.test.ts +400 -0
  1051. package/src/RelationManager.ts +527 -0
  1052. package/src/RenderHook.test.ts +252 -0
  1053. package/src/RenderHook.ts +226 -0
  1054. package/src/Resource.test.ts +240 -0
  1055. package/src/Resource.ts +439 -0
  1056. package/src/RightPanel.test.ts +202 -0
  1057. package/src/RightPanel.ts +132 -0
  1058. package/src/Tab.test.ts +91 -0
  1059. package/src/Tab.ts +156 -0
  1060. package/src/UserMenuItem.ts +145 -0
  1061. package/src/actions/Action.test.ts +2479 -0
  1062. package/src/actions/Action.ts +2124 -0
  1063. package/src/actions/ActionGroup.test.ts +112 -0
  1064. package/src/actions/ActionGroup.ts +173 -0
  1065. package/src/actions/attachFactory.ts +172 -0
  1066. package/src/actions/exportFactory.ts +215 -0
  1067. package/src/actions/importFactory.ts +222 -0
  1068. package/src/actions/index.ts +17 -0
  1069. package/src/applyPageHooks.test.ts +298 -0
  1070. package/src/applyPageHooks.ts +242 -0
  1071. package/src/authorization.test.ts +483 -0
  1072. package/src/breadcrumbs.test.ts +238 -0
  1073. package/src/cells/coerce.test.ts +85 -0
  1074. package/src/cells/coerce.ts +84 -0
  1075. package/src/clusterPaths.ts +35 -0
  1076. package/src/columns/BadgeColumn.test.ts +54 -0
  1077. package/src/columns/BadgeColumn.ts +32 -0
  1078. package/src/columns/BooleanColumn.test.ts +41 -0
  1079. package/src/columns/BooleanColumn.ts +18 -0
  1080. package/src/columns/ColorColumn.test.ts +37 -0
  1081. package/src/columns/ColorColumn.ts +38 -0
  1082. package/src/columns/IconColumn.test.ts +54 -0
  1083. package/src/columns/IconColumn.ts +37 -0
  1084. package/src/columns/ImageColumn.test.ts +41 -0
  1085. package/src/columns/ImageColumn.ts +28 -0
  1086. package/src/columns/SelectColumn.ts +60 -0
  1087. package/src/columns/TextColumn.test.ts +190 -0
  1088. package/src/columns/TextColumn.ts +20 -0
  1089. package/src/columns/TextInputColumn.ts +68 -0
  1090. package/src/columns/ToggleColumn.ts +46 -0
  1091. package/src/columns/editableColumns.test.ts +193 -0
  1092. package/src/columns/index.ts +9 -0
  1093. package/src/defaultGlobalPages.ts +95 -0
  1094. package/src/defaultPages.test.ts +634 -0
  1095. package/src/defaultPages.ts +614 -0
  1096. package/src/defaultViewPage.test.ts +147 -0
  1097. package/src/elements/Form.test.ts +223 -0
  1098. package/src/elements/Form.ts +397 -0
  1099. package/src/elements/ListTabs.ts +28 -0
  1100. package/src/elements/Table.test.ts +422 -0
  1101. package/src/elements/Table.ts +816 -0
  1102. package/src/elements/TableGroup.test.ts +149 -0
  1103. package/src/elements/TableGroup.ts +199 -0
  1104. package/src/elements/dispatchAction.test.ts +463 -0
  1105. package/src/elements/dispatchAction.ts +355 -0
  1106. package/src/elements/dispatchForm.test.ts +455 -0
  1107. package/src/elements/dispatchForm.ts +1855 -0
  1108. package/src/elements/dispatchTable.test.ts +1247 -0
  1109. package/src/elements/dispatchTable.ts +666 -0
  1110. package/src/elements/index.ts +21 -0
  1111. package/src/entries/BadgeEntry.ts +39 -0
  1112. package/src/entries/CodeEntry.test.ts +40 -0
  1113. package/src/entries/CodeEntry.ts +52 -0
  1114. package/src/entries/ColorEntry.ts +63 -0
  1115. package/src/entries/ComponentEntry.test.ts +173 -0
  1116. package/src/entries/ComponentEntry.ts +95 -0
  1117. package/src/entries/Entry.ts +304 -0
  1118. package/src/entries/IconEntry.ts +49 -0
  1119. package/src/entries/ImageEntry.ts +61 -0
  1120. package/src/entries/KeyValueEntry.ts +47 -0
  1121. package/src/entries/RepeatableEntry.test.ts +239 -0
  1122. package/src/entries/RepeatableEntry.ts +173 -0
  1123. package/src/entries/TextEntry.test.ts +394 -0
  1124. package/src/entries/TextEntry.ts +60 -0
  1125. package/src/entries/index.ts +12 -0
  1126. package/src/entries/leaves.test.ts +306 -0
  1127. package/src/entries/registry.ts +54 -0
  1128. package/src/fields/BuilderField.test.ts +1188 -0
  1129. package/src/fields/BuilderField.ts +568 -0
  1130. package/src/fields/BuilderRelationship.test.ts +811 -0
  1131. package/src/fields/CheckboxField.test.ts +44 -0
  1132. package/src/fields/CheckboxField.ts +27 -0
  1133. package/src/fields/CheckboxListField.test.ts +99 -0
  1134. package/src/fields/CheckboxListField.ts +66 -0
  1135. package/src/fields/ColorPickerField.test.ts +33 -0
  1136. package/src/fields/ColorPickerField.ts +25 -0
  1137. package/src/fields/DateField.ts +54 -0
  1138. package/src/fields/DateTimeField.test.ts +55 -0
  1139. package/src/fields/EmailField.ts +16 -0
  1140. package/src/fields/Field.test.ts +639 -0
  1141. package/src/fields/Field.ts +773 -0
  1142. package/src/fields/FileUploadField.test.ts +97 -0
  1143. package/src/fields/FileUploadField.ts +71 -0
  1144. package/src/fields/HiddenField.test.ts +27 -0
  1145. package/src/fields/HiddenField.ts +28 -0
  1146. package/src/fields/KeyValueField.test.ts +105 -0
  1147. package/src/fields/KeyValueField.ts +55 -0
  1148. package/src/fields/MarkdownField.test.ts +167 -0
  1149. package/src/fields/MarkdownField.ts +151 -0
  1150. package/src/fields/NumberField.ts +33 -0
  1151. package/src/fields/RadioField.test.ts +94 -0
  1152. package/src/fields/RadioField.ts +67 -0
  1153. package/src/fields/RepeaterField.test.ts +1806 -0
  1154. package/src/fields/RepeaterField.ts +791 -0
  1155. package/src/fields/RepeaterRelationship.test.ts +1630 -0
  1156. package/src/fields/RepeaterSimple.test.ts +248 -0
  1157. package/src/fields/RowButton.test.ts +149 -0
  1158. package/src/fields/RowButton.ts +125 -0
  1159. package/src/fields/SelectField.test.ts +192 -0
  1160. package/src/fields/SelectField.ts +235 -0
  1161. package/src/fields/SliderField.test.ts +50 -0
  1162. package/src/fields/SliderField.ts +53 -0
  1163. package/src/fields/SlugField.ts +24 -0
  1164. package/src/fields/TagsInputField.test.ts +154 -0
  1165. package/src/fields/TagsInputField.ts +133 -0
  1166. package/src/fields/TextField.ts +24 -0
  1167. package/src/fields/TextareaField.test.ts +58 -0
  1168. package/src/fields/TextareaField.ts +59 -0
  1169. package/src/fields/ToggleButtonsField.test.ts +106 -0
  1170. package/src/fields/ToggleButtonsField.ts +59 -0
  1171. package/src/fields/ToggleField.ts +16 -0
  1172. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +319 -0
  1173. package/src/fields/optionsResolver.ts +95 -0
  1174. package/src/fields/resolveField.ts +28 -0
  1175. package/src/filters/BooleanFilter.ts +35 -0
  1176. package/src/filters/DateRangeFilter.test.ts +194 -0
  1177. package/src/filters/DateRangeFilter.ts +148 -0
  1178. package/src/filters/Filter.test.ts +268 -0
  1179. package/src/filters/Filter.ts +184 -0
  1180. package/src/filters/FormFilter.test.ts +238 -0
  1181. package/src/filters/FormFilter.ts +215 -0
  1182. package/src/filters/MultiSelectFilter.test.ts +119 -0
  1183. package/src/filters/MultiSelectFilter.ts +78 -0
  1184. package/src/filters/QueryBuilderFilter.test.ts +644 -0
  1185. package/src/filters/QueryBuilderFilter.ts +398 -0
  1186. package/src/filters/SelectFilter.ts +46 -0
  1187. package/src/filters/TernaryFilter.test.ts +160 -0
  1188. package/src/filters/TernaryFilter.ts +72 -0
  1189. package/src/filters/TrashedFilter.test.ts +149 -0
  1190. package/src/filters/TrashedFilter.ts +55 -0
  1191. package/src/filters/queryBuilder/BooleanConstraint.ts +31 -0
  1192. package/src/filters/queryBuilder/Constraint.ts +115 -0
  1193. package/src/filters/queryBuilder/DateConstraint.ts +69 -0
  1194. package/src/filters/queryBuilder/NumberConstraint.ts +66 -0
  1195. package/src/filters/queryBuilder/SelectConstraint.ts +72 -0
  1196. package/src/filters/queryBuilder/TextConstraint.ts +65 -0
  1197. package/src/filters/queryBuilder/index.ts +12 -0
  1198. package/src/icons/index.ts +2 -0
  1199. package/src/icons/lucide.ts +204 -0
  1200. package/src/icons/registry.test.ts +56 -0
  1201. package/src/icons/registry.ts +41 -0
  1202. package/src/icons/types.ts +47 -0
  1203. package/src/index.ts +521 -0
  1204. package/src/io/csv.test.ts +142 -0
  1205. package/src/io/csv.ts +170 -0
  1206. package/src/nestedRelationManagerData.test.ts +526 -0
  1207. package/src/notifications/Notification.test.ts +210 -0
  1208. package/src/notifications/Notification.ts +354 -0
  1209. package/src/notifications/broadcast.test.ts +110 -0
  1210. package/src/notifications/broadcast.ts +95 -0
  1211. package/src/notifications/database.test.ts +383 -0
  1212. package/src/notifications/database.ts +398 -0
  1213. package/src/notifications/databaseNotifications.test.ts +187 -0
  1214. package/src/notifications/dispatchNotificationAction.test.ts +341 -0
  1215. package/src/notifications/dispatchNotificationAction.ts +142 -0
  1216. package/src/notifications/flash.test.ts +89 -0
  1217. package/src/notifications/flash.ts +71 -0
  1218. package/src/notifications/index.ts +45 -0
  1219. package/src/notifications/registerBroadcastAuth.test.ts +134 -0
  1220. package/src/notifications/registerBroadcastAuth.ts +100 -0
  1221. package/src/notifications/resolveSavedNotification.test.ts +82 -0
  1222. package/src/notifications/resolveSavedNotification.ts +59 -0
  1223. package/src/notifications/types.ts +93 -0
  1224. package/src/orm/m2mAccessor.ts +66 -0
  1225. package/src/orm/modelDefaults.test.ts +633 -0
  1226. package/src/orm/modelDefaults.ts +632 -0
  1227. package/src/pageData.test.ts +1121 -0
  1228. package/src/pageData.ts +4662 -0
  1229. package/src/plugins/index.ts +1 -0
  1230. package/src/plugins/themeEditor.ts +24 -0
  1231. package/src/react/AppShell.tsx +148 -0
  1232. package/src/react/CommandPalette.tsx +375 -0
  1233. package/src/react/FormStateContext.tsx +398 -0
  1234. package/src/react/HeadHooks.tsx +126 -0
  1235. package/src/react/NotificationActionStrip.tsx +263 -0
  1236. package/src/react/NotificationBell.tsx +426 -0
  1237. package/src/react/RenderHookSlot.tsx +32 -0
  1238. package/src/react/RightSidebar.tsx +257 -0
  1239. package/src/react/RightSidebarContext.tsx +211 -0
  1240. package/src/react/RightSidebarTrigger.tsx +53 -0
  1241. package/src/react/SchemaRenderer.tsx +6128 -0
  1242. package/src/react/SearchTrigger.tsx +46 -0
  1243. package/src/react/ThemeProvider.tsx +93 -0
  1244. package/src/react/ThemeSettingsPage.tsx +579 -0
  1245. package/src/react/ThemeToggle.tsx +20 -0
  1246. package/src/react/Toaster.tsx +158 -0
  1247. package/src/react/UserMenu.tsx +196 -0
  1248. package/src/react/WidgetDataContext.tsx +157 -0
  1249. package/src/react/cells/EditableCell.tsx +376 -0
  1250. package/src/react/fieldJsHandler.test.ts +166 -0
  1251. package/src/react/fieldJsHandler.ts +79 -0
  1252. package/src/react/fields/BuilderInput.tsx +995 -0
  1253. package/src/react/fields/CheckboxInput.tsx +39 -0
  1254. package/src/react/fields/CheckboxListInput.tsx +81 -0
  1255. package/src/react/fields/ColorInput.tsx +51 -0
  1256. package/src/react/fields/DateFieldInput.tsx +70 -0
  1257. package/src/react/fields/DateTimeInput.tsx +42 -0
  1258. package/src/react/fields/FieldShell.tsx +107 -0
  1259. package/src/react/fields/FileUploadInput.tsx +189 -0
  1260. package/src/react/fields/HiddenInput.tsx +17 -0
  1261. package/src/react/fields/KeyValueInput.tsx +200 -0
  1262. package/src/react/fields/MarkdownInput.tsx +333 -0
  1263. package/src/react/fields/RadioInput.tsx +60 -0
  1264. package/src/react/fields/RepeaterInput.test.ts +116 -0
  1265. package/src/react/fields/RepeaterInput.tsx +1313 -0
  1266. package/src/react/fields/SelectFieldInput.tsx +257 -0
  1267. package/src/react/fields/SliderInput.tsx +63 -0
  1268. package/src/react/fields/TagsInput.tsx +265 -0
  1269. package/src/react/fields/TextLikeInput.tsx +54 -0
  1270. package/src/react/fields/ToggleButtonsInput.tsx +60 -0
  1271. package/src/react/fields/ToggleFieldInput.tsx +35 -0
  1272. package/src/react/fields/rowChromeButton.tsx +225 -0
  1273. package/src/react/fields/syncRowGates.test.ts +202 -0
  1274. package/src/react/fields/syncRowGates.ts +66 -0
  1275. package/src/react/formStateHelpers.test.ts +295 -0
  1276. package/src/react/formStateHelpers.ts +218 -0
  1277. package/src/react/hooks/use-mobile.ts +19 -0
  1278. package/src/react/icon-context.tsx +60 -0
  1279. package/src/react/index.ts +85 -0
  1280. package/src/react/layouts/SidebarLayout.tsx +239 -0
  1281. package/src/react/layouts/TopbarLayout.tsx +245 -0
  1282. package/src/react/navigate.tsx +37 -0
  1283. package/src/react/registry.ts +48 -0
  1284. package/src/react/right-panel-registry.tsx +47 -0
  1285. package/src/react/theme-preview/apply.ts +99 -0
  1286. package/src/react/theme-preview/build-html.ts +436 -0
  1287. package/src/react/ui/button.tsx +51 -0
  1288. package/src/react/ui/calendar.tsx +67 -0
  1289. package/src/react/ui/checkbox.tsx +29 -0
  1290. package/src/react/ui/dialog.tsx +108 -0
  1291. package/src/react/ui/dropdown-menu.tsx +97 -0
  1292. package/src/react/ui/input.tsx +20 -0
  1293. package/src/react/ui/label.tsx +21 -0
  1294. package/src/react/ui/popover.tsx +50 -0
  1295. package/src/react/ui/select.tsx +169 -0
  1296. package/src/react/ui/separator.tsx +25 -0
  1297. package/src/react/ui/sheet.tsx +136 -0
  1298. package/src/react/ui/sidebar.tsx +723 -0
  1299. package/src/react/ui/skeleton.tsx +13 -0
  1300. package/src/react/ui/slider.tsx +34 -0
  1301. package/src/react/ui/switch.tsx +28 -0
  1302. package/src/react/ui/table.tsx +105 -0
  1303. package/src/react/ui/tabs.tsx +63 -0
  1304. package/src/react/ui/textarea.tsx +18 -0
  1305. package/src/react/ui/tooltip.tsx +64 -0
  1306. package/src/react/useResizableWidth.ts +139 -0
  1307. package/src/react/utils.ts +6 -0
  1308. package/src/react/widgetRegistry.test.ts +43 -0
  1309. package/src/react/widgetRegistry.ts +50 -0
  1310. package/src/react/widgets/StatsOverviewRenderer.tsx +232 -0
  1311. package/src/react/widgets/TableWidgetRenderer.tsx +231 -0
  1312. package/src/react/widgets/ViewRenderer.tsx +71 -0
  1313. package/src/relationManagerData.test.ts +1146 -0
  1314. package/src/richtext/index.ts +8 -0
  1315. package/src/richtext/registry.ts +89 -0
  1316. package/src/routes-nested-relations.test.ts +676 -0
  1317. package/src/routes-relations.test.ts +972 -0
  1318. package/src/routes.test.ts +1886 -0
  1319. package/src/routes.ts +3262 -0
  1320. package/src/schema/Alert.test.ts +63 -0
  1321. package/src/schema/Alert.ts +49 -0
  1322. package/src/schema/Block.ts +169 -0
  1323. package/src/schema/Breadcrumbs.ts +40 -0
  1324. package/src/schema/Card.ts +35 -0
  1325. package/src/schema/Divider.ts +20 -0
  1326. package/src/schema/Element.ts +219 -0
  1327. package/src/schema/EmptyState.test.ts +37 -0
  1328. package/src/schema/EmptyState.ts +63 -0
  1329. package/src/schema/Fieldset.ts +43 -0
  1330. package/src/schema/Grid.ts +43 -0
  1331. package/src/schema/Group.ts +30 -0
  1332. package/src/schema/Heading.ts +39 -0
  1333. package/src/schema/Html.ts +67 -0
  1334. package/src/schema/Icon.ts +54 -0
  1335. package/src/schema/Image.ts +57 -0
  1336. package/src/schema/LinkTag.ts +41 -0
  1337. package/src/schema/Markdown.ts +85 -0
  1338. package/src/schema/MetaTag.ts +41 -0
  1339. package/src/schema/RelationTabs.ts +71 -0
  1340. package/src/schema/ScriptTag.ts +55 -0
  1341. package/src/schema/Section.ts +143 -0
  1342. package/src/schema/ServerDataElement.test.ts +140 -0
  1343. package/src/schema/ServerDataElement.ts +156 -0
  1344. package/src/schema/Split.ts +50 -0
  1345. package/src/schema/Stat.test.ts +118 -0
  1346. package/src/schema/Stat.ts +154 -0
  1347. package/src/schema/StatsOverview.test.ts +141 -0
  1348. package/src/schema/StatsOverview.ts +119 -0
  1349. package/src/schema/StyleTag.ts +35 -0
  1350. package/src/schema/TableWidget.test.ts +297 -0
  1351. package/src/schema/TableWidget.ts +289 -0
  1352. package/src/schema/Tabs.ts +79 -0
  1353. package/src/schema/Text.ts +58 -0
  1354. package/src/schema/UnorderedList.ts +49 -0
  1355. package/src/schema/View.test.ts +111 -0
  1356. package/src/schema/View.ts +127 -0
  1357. package/src/schema/Wizard.ts +108 -0
  1358. package/src/schema/containers.test.ts +446 -0
  1359. package/src/schema/headTags.test.ts +134 -0
  1360. package/src/schema/index.ts +39 -0
  1361. package/src/schema/primes.test.ts +269 -0
  1362. package/src/schema/resolveSchema.test.ts +329 -0
  1363. package/src/schema/resolveSchema.ts +807 -0
  1364. package/src/schema/sanitize.ts +49 -0
  1365. package/src/search.test.ts +446 -0
  1366. package/src/search.ts +178 -0
  1367. package/src/sessionFilters.test.ts +352 -0
  1368. package/src/sessionFilters.ts +133 -0
  1369. package/src/summarizers/Summarizer.test.ts +84 -0
  1370. package/src/summarizers/Summarizer.ts +123 -0
  1371. package/src/summarizers/index.ts +11 -0
  1372. package/src/theme/base-colors.ts +68 -0
  1373. package/src/theme/chart-colors.ts +50 -0
  1374. package/src/theme/colors.ts +447 -0
  1375. package/src/theme/generate-css.test.ts +139 -0
  1376. package/src/theme/generate-css.ts +44 -0
  1377. package/src/theme/generate-scale.test.ts +106 -0
  1378. package/src/theme/generate-scale.ts +97 -0
  1379. package/src/theme/icon-map.ts +42 -0
  1380. package/src/theme/index.ts +28 -0
  1381. package/src/theme/migrate.ts +81 -0
  1382. package/src/theme/presets.ts +135 -0
  1383. package/src/theme/radius.ts +18 -0
  1384. package/src/theme/resolve.test.ts +238 -0
  1385. package/src/theme/resolve.ts +96 -0
  1386. package/src/theme/spacing.ts +18 -0
  1387. package/src/theme/theme-colors.ts +88 -0
  1388. package/src/theme/types.ts +125 -0
  1389. package/src/uploads/UploadAdapter.ts +35 -0
  1390. package/src/uploads/index.ts +2 -0
  1391. package/src/uploads/localUpload.test.ts +70 -0
  1392. package/src/uploads/localUpload.ts +84 -0
  1393. package/src/validation/Validator.ts +49 -0
  1394. package/src/validation/index.ts +28 -0
  1395. package/src/validation/rules.ts +78 -0
  1396. package/src/validation/runValidators.ts +435 -0
  1397. package/src/validation/uniqueValidator.test.ts +196 -0
  1398. package/src/validation/uniqueValidator.ts +133 -0
  1399. package/src/validation/validators.test.ts +268 -0
  1400. package/src/vite.ts +758 -0
  1401. package/src/widgets/index.ts +10 -0
  1402. package/src/widgets/registry.ts +45 -0
  1403. package/src/widgets.test.ts +592 -0
  1404. package/tsconfig.build.json +11 -0
  1405. package/tsconfig.json +4 -0
  1406. package/tsconfig.test.json +10 -0
  1407. package/views/react/Dashboard.tsx +27 -0
  1408. package/views/react/Resources/Form.tsx +102 -0
  1409. package/views/react/Resources/Index.tsx +49 -0
@@ -0,0 +1,1855 @@
1
+ import { Element } from '../schema/Element.js'
2
+ import { Field, type AfterStateUpdatedContext } from '../fields/Field.js'
3
+ import { RepeaterField, isRepeaterField } from '../fields/RepeaterField.js'
4
+ import type { RepeaterRelationshipConfig } from '../fields/RepeaterField.js'
5
+ import { BuilderField, isBuilderField } from '../fields/BuilderField.js'
6
+ import type { BuilderRelationshipConfig } from '../fields/BuilderField.js'
7
+ import { Form, type FormContext } from './Form.js'
8
+ import { validateSchema, type ValidationErrors } from '../validation/index.js'
9
+ import { resolveSavedNotification, type NotificationMeta } from '../notifications/index.js'
10
+ import {
11
+ getParentRelationDescriptor,
12
+ getMorphRelationDescriptor,
13
+ getM2MRelationDescriptor,
14
+ computeMorphPayload,
15
+ getPrimaryKey,
16
+ resolveRelatedQuery,
17
+ type ModelLike,
18
+ type MorphRelationDescriptor,
19
+ } from '../orm/modelDefaults.js'
20
+ import { resolveM2MAccessor } from '../orm/m2mAccessor.js'
21
+
22
+ export interface DispatchSuccess<R> {
23
+ ok: true
24
+ record: R
25
+ redirect: string | undefined
26
+ /**
27
+ * Resolved success notifications to flash to the client. Empty when the
28
+ * form has `disableSavedNotification()` or no spec configured. Currently
29
+ * only delivered through the JSON action-modal path; the form-post 303
30
+ * path drops them until a flash mechanism lands.
31
+ */
32
+ notifications: NotificationMeta[]
33
+ }
34
+
35
+ export interface DispatchFailure {
36
+ ok: false
37
+ errors: ValidationErrors
38
+ }
39
+
40
+ export type DispatchResult<R> = DispatchSuccess<R> | DispatchFailure
41
+
42
+ /**
43
+ * Run the full form submit lifecycle on a `Form` element. Mode is inferred
44
+ * from `ctx.record`: undefined → create, set → update. Mode-specific hooks
45
+ * fire after their generic counterparts so cross-cutting logic (auth
46
+ * stamping, audit fields) lives above mode-specific business rules.
47
+ *
48
+ * Order:
49
+ *
50
+ * validateSchema
51
+ * → form-level validators
52
+ * → mutateData (both modes)
53
+ * → mutateDataBeforeCreate / mutateDataBeforeUpdate
54
+ * → beforeSave (both modes)
55
+ * → beforeCreate / beforeUpdate
56
+ * → handleCreate || handleUpdate || save ← persistence
57
+ * → afterCreate / afterUpdate
58
+ * → afterSave (both modes)
59
+ * → redirectAfterSave
60
+ *
61
+ * Validation failures short-circuit and return `{ ok: false, errors }`. On
62
+ * success the result includes the saved record and the resolved redirect URL
63
+ * (when `redirectAfterSave` is configured).
64
+ *
65
+ * Form-level validator errors are keyed under `_form` so the renderer can
66
+ * surface them as a top-of-form banner without colliding with field names.
67
+ */
68
+ export async function dispatchFormSubmit<R = unknown>(
69
+ form: Form<R>,
70
+ body: Record<string, unknown>,
71
+ ctx: FormContext<R>,
72
+ ): Promise<DispatchResult<R>> {
73
+ const children = form.getChildren() ?? []
74
+ const isCreate = ctx.record === undefined
75
+
76
+ const fieldErrors = await validateSchema(children as Element[], body, ctx.record)
77
+
78
+ const formValidatorErrors: string[] = []
79
+ for (const v of form.getFormValidators()) {
80
+ const msg = await v(body, { values: body, ...(ctx.record !== undefined ? { record: ctx.record } : {}) })
81
+ if (msg) formValidatorErrors.push(msg)
82
+ }
83
+
84
+ const errors: ValidationErrors = { ...fieldErrors }
85
+ if (formValidatorErrors.length > 0) {
86
+ errors['_form'] = formValidatorErrors
87
+ }
88
+
89
+ if (Object.keys(errors).length > 0) {
90
+ return { ok: false, errors }
91
+ }
92
+
93
+ let data: Record<string, unknown> = coerceFormValues(children as Element[], body)
94
+ // Flatten `simple()` Repeaters from the wrapped `[{name: v}]` pipeline
95
+ // shape to the user-declared `[v]` storage shape before any user-side
96
+ // transform runs. Non-simple repeaters are untouched.
97
+ data = unwrapSimpleRepeaters(children as Element[], data)
98
+
99
+ // Pull relationship-backed Repeater values OUT of `data` so the
100
+ // parent's save handler doesn't try to write them as a JSON column.
101
+ // The deferral list holds the rows + the field reference; we run
102
+ // the create/update/delete diff against the relation AFTER the
103
+ // parent save returns (so we have a parent PK in create mode).
104
+ const relationshipDeferrals = extractRelationshipRepeaters(children as Element[], data)
105
+ // Same trick for Builders. Heterogeneous-row sibling — each row is a
106
+ // `{ __id?, type, data }` envelope persisted as a child carrying a
107
+ // discriminator column + a JSON payload column.
108
+ const builderRelationshipDeferrals = extractRelationshipBuilders(children as Element[], data)
109
+
110
+ const mutate = form.getMutateData()
111
+ if (mutate) data = await mutate(data, { ...ctx, values: data })
112
+
113
+ const modeMutate = isCreate ? form.getMutateDataBeforeCreate() : form.getMutateDataBeforeUpdate()
114
+ if (modeMutate) data = await modeMutate(data, { ...ctx, values: data })
115
+
116
+ const before = form.getBeforeSave()
117
+ if (before) await before(data, { ...ctx, values: data })
118
+
119
+ const modeBefore = isCreate ? form.getBeforeCreate() : form.getBeforeUpdate()
120
+ if (modeBefore) await modeBefore(data, { ...ctx, values: data })
121
+
122
+ const persist = (isCreate ? form.getHandleCreate() : form.getHandleUpdate()) ?? form.getSave()
123
+ if (!persist) {
124
+ throw new Error(
125
+ '[Pilotiq] Form has no save() handler. Configure Form.save() (or handleCreate/handleUpdate) on the page schema, or override Resource.pages() with a Page that supplies one.',
126
+ )
127
+ }
128
+ const record = await persist(data, { ...ctx, values: data })
129
+
130
+ // Persist the relationship-backed Repeater diffs against the saved
131
+ // parent. Runs BEFORE `afterCreate / afterUpdate` so user hooks can
132
+ // observe the fully-saved tree (parent + children).
133
+ if (relationshipDeferrals.length > 0 || builderRelationshipDeferrals.length > 0) {
134
+ const parentModel = (ctx as { parentModel?: ModelLike }).parentModel
135
+ if (!parentModel) {
136
+ throw new Error(
137
+ '[Pilotiq] Repeater/Builder.relationship: form has relationship-backed rows but no parentModel on the FormContext. ' +
138
+ 'Routes that submit forms with relationship-backed Repeaters/Builders must set ctx.parentModel = R.model.',
139
+ )
140
+ }
141
+ for (const deferral of relationshipDeferrals) {
142
+ await persistRelationshipRows(record, deferral, parentModel)
143
+ }
144
+ for (const deferral of builderRelationshipDeferrals) {
145
+ await persistRelationshipBuilderRows(record, deferral, parentModel)
146
+ }
147
+ }
148
+
149
+ const modeAfter = isCreate ? form.getAfterCreate() : form.getAfterUpdate()
150
+ if (modeAfter) await modeAfter(record, { ...ctx, record, values: data })
151
+
152
+ const after = form.getAfterSave()
153
+ if (after) await after(record, { ...ctx, record, values: data })
154
+
155
+ const redirectFn = form.getRedirectAfterSave()
156
+ const redirect = redirectFn ? redirectFn(record, { ...ctx, record, values: data }) : undefined
157
+
158
+ const notification = resolveSavedNotification(
159
+ form,
160
+ isCreate ? 'create' : 'update',
161
+ record,
162
+ { ...ctx, record, values: data },
163
+ )
164
+ const notifications = notification ? [notification] : []
165
+
166
+ return { ok: true, record, redirect, notifications }
167
+ }
168
+
169
+ /**
170
+ * Coerce raw form-body strings into the runtime types each field expects:
171
+ * booleans for toggles, numbers for number inputs, Dates for dates. The
172
+ * browser submits everything as a string by default, but ORM layers (Prisma,
173
+ * etc.) expect actual booleans/numbers/Dates. Runs after validation so
174
+ * validators still see the raw submitted text.
175
+ *
176
+ * Empty / missing values are normalized:
177
+ * - `toggle` → `false` when missing or 'false'/empty; `true` otherwise.
178
+ * - `number` → `null` when empty; otherwise `Number(v)` (NaN passes through).
179
+ * - `date` → `null` when empty; otherwise a `Date` parsed from the string.
180
+ *
181
+ * Other field types are passed through untouched.
182
+ */
183
+ export function coerceFormValues(
184
+ elements: Element[],
185
+ body: Record<string, unknown>,
186
+ ): Record<string, unknown> {
187
+ const out: Record<string, unknown> = { ...body }
188
+
189
+ // Plan #14 — Repeater pass. Run BEFORE the regular field coercion so
190
+ // each row's body is coerced against the inner schema (recursive
191
+ // `coerceFormValues` call), not against the parent form. Two body
192
+ // shapes supported: array-valued JSON (`out[name]` already an array)
193
+ // and flat-keyed form bodies (`name.0.childName=…`). Flat-shape keys
194
+ // are removed from `out` after the Repeater value is composed so they
195
+ // don't leak into the persisted record.
196
+ walkRepeatersTopLevel(elements, repeater => {
197
+ if (repeater.isDehydrated() === false) {
198
+ delete out[repeater.name]
199
+ return
200
+ }
201
+ out[repeater.name] = coerceRepeaterValue(repeater, out)
202
+ const prefix = `${repeater.name}.`
203
+ for (const key of Object.keys(out)) {
204
+ if (key.startsWith(prefix)) delete out[key]
205
+ }
206
+ })
207
+
208
+ // Plan #14 follow-up — Builder pass. Same disposition as Repeater
209
+ // (run BEFORE the generic field walker so per-row inner-schema
210
+ // coercion uses the row's own body, not the parent form's), but each
211
+ // row's coercion is dispatched against the block matching the row's
212
+ // `type` discriminator. Rows whose `type` doesn't match a registered
213
+ // block have their `data` body passed through verbatim — better to
214
+ // round-trip than to silently drop unknown content.
215
+ walkBuildersTopLevel(elements, builder => {
216
+ if (builder.isDehydrated() === false) {
217
+ delete out[builder.name]
218
+ return
219
+ }
220
+ out[builder.name] = coerceBuilderValue(builder, out)
221
+ const prefix = `${builder.name}.`
222
+ for (const key of Object.keys(out)) {
223
+ if (key.startsWith(prefix)) delete out[key]
224
+ }
225
+ })
226
+
227
+ walkFields(elements, field => {
228
+ const name = field.name
229
+
230
+ // Plan #6 — `dehydrated(false)` fields are decorative / computed;
231
+ // their value never enters the persisted record. Drop the body key
232
+ // before any coercion or validation runs so downstream code can't
233
+ // see it.
234
+ if (field.isDehydrated() === false) {
235
+ delete out[name]
236
+ return
237
+ }
238
+
239
+ const raw = out[name]
240
+ switch (field.fieldType) {
241
+ case 'toggle':
242
+ case 'checkbox': {
243
+ if (raw === undefined || raw === null || raw === '' || raw === 'false' || raw === '0' || raw === false) {
244
+ out[name] = false
245
+ } else {
246
+ out[name] = true
247
+ }
248
+ break
249
+ }
250
+ case 'number':
251
+ case 'slider': {
252
+ if (raw === undefined || raw === null || raw === '') {
253
+ out[name] = null
254
+ } else if (typeof raw === 'string') {
255
+ out[name] = Number(raw)
256
+ }
257
+ break
258
+ }
259
+ case 'date':
260
+ case 'dateTime': {
261
+ // Both 'date' and 'dateTime' accept ISO strings and
262
+ // YYYY-MM-DD(THH:mm) shapes — `new Date()` handles both.
263
+ if (raw === undefined || raw === null || raw === '') {
264
+ out[name] = null
265
+ } else if (typeof raw === 'string') {
266
+ out[name] = new Date(raw)
267
+ }
268
+ break
269
+ }
270
+ case 'checkboxList': {
271
+ // HTML form bodies post checkbox-lists as either an array (when
272
+ // multiple boxes are checked) or a single string (one checked) or
273
+ // undefined (none). Normalize all three to `string[]`.
274
+ if (raw === undefined || raw === null) {
275
+ out[name] = []
276
+ } else if (Array.isArray(raw)) {
277
+ out[name] = raw.map(v => String(v))
278
+ } else {
279
+ out[name] = [String(raw)]
280
+ }
281
+ break
282
+ }
283
+ case 'tagsInput': {
284
+ // Client serializes the chip set as a JSON-encoded string in a
285
+ // single hidden input. Parse back into `string[]`. Already-array
286
+ // values pass through (e.g. when a `live()` partial-resolve has
287
+ // already shipped structured data, or when a server-side default
288
+ // landed pre-coerce). Empty / null / unparseable → `[]`.
289
+ if (raw === undefined || raw === null || raw === '') {
290
+ out[name] = []
291
+ } else if (Array.isArray(raw)) {
292
+ out[name] = raw.map(v => String(v))
293
+ } else if (typeof raw === 'string') {
294
+ try {
295
+ const parsed = JSON.parse(raw)
296
+ if (Array.isArray(parsed)) {
297
+ out[name] = parsed.map(v => String(v))
298
+ } else {
299
+ out[name] = []
300
+ }
301
+ } catch {
302
+ out[name] = []
303
+ }
304
+ } else {
305
+ out[name] = []
306
+ }
307
+ break
308
+ }
309
+ case 'color': {
310
+ // Empty string → null so DB nullable columns accept it. Otherwise
311
+ // pass the hex string through verbatim.
312
+ if (raw === undefined || raw === null || raw === '') {
313
+ out[name] = null
314
+ }
315
+ break
316
+ }
317
+ case 'fileUpload': {
318
+ // The browser already turned uploaded files into URLs via the
319
+ // `_uploads` route; what arrives here is either a string, a
320
+ // string[] (multi-mode), or a JSON-encoded array (when the
321
+ // client serialized through a hidden input). Normalize to the
322
+ // declared shape: array bodies → string[], string body → string.
323
+ if (raw === undefined || raw === null || raw === '') {
324
+ out[name] = null
325
+ } else if (Array.isArray(raw)) {
326
+ out[name] = raw.map(v => String(v))
327
+ } else if (typeof raw === 'string') {
328
+ // Try JSON-decode for multi-file fields encoded as JSON; otherwise pass through.
329
+ if (raw.startsWith('[')) {
330
+ try {
331
+ const parsed = JSON.parse(raw)
332
+ if (Array.isArray(parsed)) { out[name] = parsed.map(v => String(v)); break }
333
+ } catch { /* fall through */ }
334
+ }
335
+ out[name] = raw
336
+ }
337
+ break
338
+ }
339
+ case 'keyValue': {
340
+ // Client serializes the row map as a JSON string in a hidden
341
+ // input. Parse back into a Record<string,string>; filter empty
342
+ // rows (`{ "": "" }`) before yielding so the persisted record
343
+ // doesn't carry placeholder noise. Already-object values pass
344
+ // through (e.g. when the `live()` partial-resolve already shipped
345
+ // structured data).
346
+ let parsed: Record<string, string> = {}
347
+ if (raw === undefined || raw === null || raw === '') {
348
+ parsed = {}
349
+ } else if (typeof raw === 'string') {
350
+ try {
351
+ const obj = JSON.parse(raw)
352
+ if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
353
+ for (const [k, v] of Object.entries(obj)) {
354
+ parsed[String(k)] = v == null ? '' : String(v)
355
+ }
356
+ }
357
+ } catch { parsed = {} }
358
+ } else if (typeof raw === 'object' && !Array.isArray(raw)) {
359
+ for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {
360
+ parsed[String(k)] = v == null ? '' : String(v)
361
+ }
362
+ }
363
+ const filtered: Record<string, string> = {}
364
+ for (const [k, v] of Object.entries(parsed)) {
365
+ if (k === '' && v === '') continue
366
+ filtered[k] = v
367
+ }
368
+ out[name] = filtered
369
+ break
370
+ }
371
+ case 'richtext': {
372
+ // Editor posts the document as a JSON-encoded string via a hidden
373
+ // input. Prisma's Json column wants a real object, so parse here.
374
+ // Empty / unparseable → null so the column accepts it.
375
+ if (raw === undefined || raw === null || raw === '') {
376
+ out[name] = null
377
+ } else if (typeof raw === 'string') {
378
+ try { out[name] = JSON.parse(raw) }
379
+ catch { out[name] = null }
380
+ }
381
+ break
382
+ }
383
+ default:
384
+ // text/textarea/email/select/slug — leave as string.
385
+ break
386
+ }
387
+ })
388
+ return out
389
+ }
390
+
391
+ function walkFields(elements: Element[], visit: (f: Field) => void): void {
392
+ for (const el of elements) {
393
+ if (el instanceof Field) {
394
+ visit(el)
395
+ // Plan #14 — don't recurse into Repeater / Builder children. Their
396
+ // inner schemas belong to row bodies, not the parent form's body,
397
+ // so the parent walker would coerce siblings against the wrong
398
+ // values map. The dedicated Repeater + Builder passes in
399
+ // `coerceFormValues` recurse into rows with the proper per-row body.
400
+ if (el instanceof RepeaterField) continue
401
+ if (el instanceof BuilderField) continue
402
+ }
403
+ const children = el.getChildren()
404
+ if (children && children.length > 0) walkFields(children as Element[], visit)
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Walk an element tree and visit every top-level Repeater — i.e., every
410
+ * `RepeaterField` that isn't itself nested inside another Repeater. Inner
411
+ * Repeaters are handled recursively when the outer Repeater coerces its
412
+ * row bodies against the inner schema (which then enters this walker
413
+ * again from `coerceFormValues`).
414
+ */
415
+ function walkRepeatersTopLevel(
416
+ elements: Element[],
417
+ visit: (f: RepeaterField) => void,
418
+ ): void {
419
+ for (const el of elements) {
420
+ if (el instanceof RepeaterField) {
421
+ visit(el)
422
+ continue
423
+ }
424
+ // Builder boundaries are also opaque — its inner schemas live per-row
425
+ // and never need to be visited by the Repeater pass.
426
+ if (el instanceof BuilderField) continue
427
+ const children = el.getChildren()
428
+ if (children && children.length > 0) walkRepeatersTopLevel(children as Element[], visit)
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Walk an Element tree and visit every top-level Builder — i.e., every
434
+ * `BuilderField` that isn't itself nested inside a Repeater or another
435
+ * Builder. Inner Builders are reached recursively when the outer
436
+ * array-row field coerces its row bodies (which then re-enters this
437
+ * walker via `coerceFormValues`).
438
+ */
439
+ function walkBuildersTopLevel(
440
+ elements: Element[],
441
+ visit: (f: BuilderField) => void,
442
+ ): void {
443
+ for (const el of elements) {
444
+ if (el instanceof BuilderField) {
445
+ visit(el)
446
+ continue
447
+ }
448
+ if (el instanceof RepeaterField) continue
449
+ const children = el.getChildren()
450
+ if (children && children.length > 0) walkBuildersTopLevel(children as Element[], visit)
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Build the coerced array value for a single Builder field from the
456
+ * parent form body. Two body shapes are supported:
457
+ *
458
+ * 1. **JSON-shape** — `body[name]` is `unknown[]`. Each entry should be
459
+ * an object with shape `{ __id?, type, data?: {…} }`. Non-object
460
+ * entries coerce to a sentinel empty row; missing / non-string `type`
461
+ * rounds to `''` (resolver flags as `unknownType`).
462
+ * 2. **Flat-shape** — body has keys like `${name}.${i}.type`,
463
+ * `${name}.${i}.__id`, `${name}.${i}.data.${childName}`. Indices are
464
+ * grouped, gaps filled with empty rows, and the per-block schema
465
+ * drives coercion of `data.*` keys.
466
+ *
467
+ * Trailing rows whose `data` body is empty are trimmed (matching
468
+ * Repeater's posture). The row's `__id` and `type` alone don't keep a
469
+ * row alive — same trim semantics.
470
+ */
471
+ function coerceBuilderValue(
472
+ field: BuilderField,
473
+ body: Record<string, unknown>,
474
+ ): Array<Record<string, unknown>> {
475
+ const fieldName = field.name
476
+ const raw = body[fieldName]
477
+
478
+ type RawRow = { __id?: string; type: string; data: Record<string, unknown> }
479
+ let rows: RawRow[] = []
480
+
481
+ if (Array.isArray(raw)) {
482
+ rows = raw.map(coerceBuilderRowEntry)
483
+ } else {
484
+ const prefix = `${fieldName}.`
485
+ const grouped = new Map<number, RawRow>()
486
+ let maxIdx = -1
487
+ for (const key of Object.keys(body)) {
488
+ if (!key.startsWith(prefix)) continue
489
+ const rest = key.slice(prefix.length)
490
+ const dot = rest.indexOf('.')
491
+ if (dot < 0) continue
492
+ const idxStr = rest.slice(0, dot)
493
+ const tail = rest.slice(dot + 1)
494
+ const idx = Number(idxStr)
495
+ if (!Number.isInteger(idx) || idx < 0) continue
496
+ if (idx > maxIdx) maxIdx = idx
497
+ let row = grouped.get(idx)
498
+ if (!row) { row = { type: '', data: {} }; grouped.set(idx, row) }
499
+ const value = body[key]
500
+ if (tail === '__id') {
501
+ if (typeof value === 'string') row.__id = value
502
+ } else if (tail === 'type') {
503
+ if (typeof value === 'string') row.type = value
504
+ } else if (tail.startsWith('data.')) {
505
+ row.data[tail.slice('data.'.length)] = value
506
+ } else if (tail === 'data') {
507
+ // Whole `data` body posted as a single value (rare — typically
508
+ // a stringified JSON blob from a hidden input). Best-effort parse.
509
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
510
+ row.data = { ...(value as Record<string, unknown>) }
511
+ } else if (typeof value === 'string' && value !== '') {
512
+ try {
513
+ const parsed = JSON.parse(value)
514
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
515
+ row.data = parsed as Record<string, unknown>
516
+ }
517
+ } catch { /* leave row.data alone */ }
518
+ }
519
+ }
520
+ }
521
+ if (maxIdx >= 0) {
522
+ rows = Array.from({ length: maxIdx + 1 }, (_, i) => grouped.get(i) ?? { type: '', data: {} })
523
+ }
524
+ }
525
+
526
+ // Trim trailing empty rows (matches Repeater). A row counts as empty
527
+ // when its `data` body has no values beyond round-tripped sentinels.
528
+ // Note we don't gate on `type` — a freshly-picked block with no fields
529
+ // typed in is still "untouched" for the purposes of submit-trim.
530
+ while (rows.length > 0 && isBuilderRowEmpty(rows[rows.length - 1]!)) {
531
+ rows.pop()
532
+ }
533
+
534
+ return rows.map(row => {
535
+ const block = field.getBlock(row.type)
536
+ let coercedData: Record<string, unknown>
537
+ if (block) {
538
+ coercedData = coerceFormValues(block.getSchema(), row.data)
539
+ } else {
540
+ // Unknown block type — pass `data` through verbatim so a stale
541
+ // record with a since-removed block type doesn't lose its
542
+ // contents on the next save. Validation will surface the issue.
543
+ coercedData = { ...row.data }
544
+ }
545
+ const out: Record<string, unknown> = { type: row.type, data: coercedData }
546
+ if (typeof row.__id === 'string') out['__id'] = row.__id
547
+ return out
548
+ })
549
+ }
550
+
551
+ function coerceBuilderRowEntry(raw: unknown): { __id?: string; type: string; data: Record<string, unknown> } {
552
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
553
+ return { type: '', data: {} }
554
+ }
555
+ const r = raw as Record<string, unknown>
556
+ const type = typeof r['type'] === 'string' ? (r['type'] as string) : ''
557
+ const dataRaw = r['data']
558
+ const data: Record<string, unknown> = (dataRaw && typeof dataRaw === 'object' && !Array.isArray(dataRaw))
559
+ ? { ...(dataRaw as Record<string, unknown>) }
560
+ : {}
561
+ const out: { __id?: string; type: string; data: Record<string, unknown> } = { type, data }
562
+ if (typeof r['__id'] === 'string') out.__id = r['__id'] as string
563
+ return out
564
+ }
565
+
566
+ function isBuilderRowEmpty(row: { type: string; data: Record<string, unknown> }): boolean {
567
+ for (const [k, v] of Object.entries(row.data)) {
568
+ if (v === undefined || v === null || v === '') continue
569
+ void k
570
+ return false
571
+ }
572
+ return true
573
+ }
574
+
575
+ /**
576
+ * Build the coerced array value for a single Repeater field from the
577
+ * parent form body. Two body shapes are supported:
578
+ *
579
+ * 1. **JSON-shape** — `body[name]` is an `unknown[]`. Each element should
580
+ * be an object; non-object entries coerce to `{}`. This is the SPA
581
+ * `fetch+JSON` path (the default since `feedback_action_dispatch_fetch_vs_303.md`).
582
+ * 2. **Flat-shape** — body has keys like `${name}.${i}.${childName}`.
583
+ * The browser submits these for `application/x-www-form-urlencoded`
584
+ * bodies when the form-post 303 fallback path is used. Indices are
585
+ * grouped, gaps are filled with `{}`, and the resulting per-row
586
+ * bodies feed into the recursive coercion call.
587
+ *
588
+ * Empty trailing rows (no entered values, only `__id` carrying through
589
+ * from the previous render) are trimmed before the coerced array is
590
+ * returned.
591
+ */
592
+ function coerceRepeaterValue(
593
+ field: RepeaterField,
594
+ body: Record<string, unknown>,
595
+ ): Array<Record<string, unknown>> {
596
+ const inner = field.getInnerSchema()
597
+ const fieldName = field.name
598
+ const raw = body[fieldName]
599
+ const simpleInner = field.getSimpleInnerField()
600
+
601
+ let rowBodies: Array<Record<string, unknown>> = []
602
+ if (Array.isArray(raw)) {
603
+ rowBodies = raw.map(r => simpleInner ? coerceSimpleEntry(r, simpleInner.name) : coerceRowEntry(r))
604
+ } else {
605
+ const prefix = `${fieldName}.`
606
+ const grouped = new Map<number, Record<string, unknown>>()
607
+ let maxIdx = -1
608
+ for (const key of Object.keys(body)) {
609
+ if (!key.startsWith(prefix)) continue
610
+ const rest = key.slice(prefix.length)
611
+ const dot = rest.indexOf('.')
612
+ if (dot < 0) continue
613
+ const idxStr = rest.slice(0, dot)
614
+ const childKey = rest.slice(dot + 1)
615
+ const idx = Number(idxStr)
616
+ if (!Number.isInteger(idx) || idx < 0) continue
617
+ if (idx > maxIdx) maxIdx = idx
618
+ let row = grouped.get(idx)
619
+ if (!row) { row = {}; grouped.set(idx, row) }
620
+ row[childKey] = body[key]
621
+ }
622
+ if (maxIdx >= 0) {
623
+ rowBodies = Array.from({ length: maxIdx + 1 }, (_, i) => grouped.get(i) ?? {})
624
+ }
625
+ }
626
+
627
+ // Trim trailing rows where the user didn't enter anything beyond the
628
+ // round-tripped `__id`. We trim BEFORE coercion so default fills (e.g.
629
+ // toggle → false, number → null) don't disguise an untouched row as a
630
+ // touched one. Only trailing emptiness — gaps in the middle survive.
631
+ while (rowBodies.length > 0 && isRawRowEmpty(rowBodies[rowBodies.length - 1]!)) {
632
+ rowBodies.pop()
633
+ }
634
+
635
+ return rowBodies.map(rowBody => {
636
+ const coerced = coerceFormValues(inner, rowBody)
637
+ if (typeof rowBody['__id'] === 'string') coerced['__id'] = rowBody['__id']
638
+ return coerced
639
+ })
640
+ }
641
+
642
+ function coerceRowEntry(raw: unknown): Record<string, unknown> {
643
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
644
+ return { ...(raw as Record<string, unknown>) }
645
+ }
646
+ return {}
647
+ }
648
+
649
+ /**
650
+ * Variant of `coerceRowEntry` for `Repeater.simple(field)`. Wraps a
651
+ * primitive entry under the inner field's name so the rest of the
652
+ * coerce pipeline keeps using `{ <innerName>: v }` row shape. Object
653
+ * entries pass through. The unwrap (back to `[v]`) happens once at the
654
+ * top of `dispatchFormSubmit` via `unwrapSimpleRepeaters`.
655
+ */
656
+ function coerceSimpleEntry(raw: unknown, innerName: string): Record<string, unknown> {
657
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
658
+ return { ...(raw as Record<string, unknown>) }
659
+ }
660
+ if (raw === undefined) return {}
661
+ return { [innerName]: raw }
662
+ }
663
+
664
+ /**
665
+ * After `coerceFormValues` has produced wrapped `[{<innerName>: v}]`
666
+ * rows for every Repeater in the schema, flatten the `simple()` ones
667
+ * back to `[v, v, …]` for storage. Non-simple repeaters are left alone.
668
+ *
669
+ * Runs before `mutateData` / `save` so user-facing data already uses
670
+ * the storage shape they declared via `.simple(field)` — they don't
671
+ * have to remember the internal wrapping at the save site.
672
+ *
673
+ * Tolerates already-flat input (e.g. when a `dehydrated(false)` upstream
674
+ * has dropped wrapping, or when the user manually fed a flat array
675
+ * through `withValues`) by re-emitting verbatim.
676
+ */
677
+ export function unwrapSimpleRepeaters(
678
+ elements: Element[],
679
+ values: Record<string, unknown>,
680
+ ): Record<string, unknown> {
681
+ const out: Record<string, unknown> = { ...values }
682
+ walkRepeatersTopLevel(elements, repeater => {
683
+ const innerName = repeater.getSimpleInnerField()?.name
684
+ if (!innerName) return
685
+ const rows = out[repeater.name]
686
+ if (!Array.isArray(rows)) return
687
+ out[repeater.name] = rows.map(row => {
688
+ if (row && typeof row === 'object' && !Array.isArray(row)) {
689
+ return (row as Record<string, unknown>)[innerName]
690
+ }
691
+ return row
692
+ })
693
+ })
694
+ return out
695
+ }
696
+
697
+ function isRawRowEmpty(rowBody: Record<string, unknown>): boolean {
698
+ for (const [k, v] of Object.entries(rowBody)) {
699
+ if (k === '__id') continue
700
+ if (v === undefined || v === null || v === '') continue
701
+ return false
702
+ }
703
+ return true
704
+ }
705
+
706
+ /**
707
+ * Walk an Element tree and return every `Form` instance, in document order.
708
+ * Used by route handlers to locate the form being submitted on a page that
709
+ * may declare more than one.
710
+ *
711
+ * Uses a structural `getType() === 'form'` check rather than `instanceof
712
+ * Form`. Vite's SSR module cache can load the package through two
713
+ * different module paths during a single dev session — the path used by
714
+ * the rudder SSR route and the path used by Vike's `+data` hook for SPA
715
+ * navigations end up importing different `Form` classes, so `instanceof`
716
+ * silently returns false and the form goes "missing" on SPA nav while
717
+ * SSR keeps working. The structural check is robust to that and matches
718
+ * the convention used elsewhere in the codebase (see Filter, Column,
719
+ * Action — all keyed on the serialized type, not class identity).
720
+ */
721
+ export function findForms(elements: ReadonlyArray<Element>): Form[] {
722
+ const forms: Form[] = []
723
+ const walk = (els: ReadonlyArray<Element>): void => {
724
+ for (const el of els) {
725
+ if (el.getType() === 'form') forms.push(el as Form)
726
+ // Plan #14 — don't dive into Repeater / Builder children. Forms
727
+ // inside an array-row field don't have row context for dispatch,
728
+ // so finding them at the parent level would mis-route submissions.
729
+ // Use structural checks (not `instanceof`) per the Vite SSR module
730
+ // duplication note above.
731
+ if (isRepeaterField(el)) continue
732
+ if (isBuilderField(el)) continue
733
+ const children = el.getChildren()
734
+ if (children && children.length > 0) walk(children)
735
+ }
736
+ }
737
+ walk(elements)
738
+ return forms
739
+ }
740
+
741
+ /**
742
+ * Plan #8 — locate the children of a Wizard step at the given index inside
743
+ * the form's tree. Returns `undefined` when the form has no Wizard
744
+ * descendant or the step index is out of range. Step children are returned
745
+ * as a fresh Element[] so callers can pass them straight to
746
+ * `validateSchema`. Walks structurally (`getType() === 'wizard'/'step'`)
747
+ * to stay robust to Vite SSR module-cache duplication.
748
+ */
749
+ export function findWizardStepFields(
750
+ formChildren: ReadonlyArray<Element>,
751
+ stepIndex: number,
752
+ ): Element[] | undefined {
753
+ let wizard: Element | undefined
754
+ const walk = (els: ReadonlyArray<Element>): void => {
755
+ for (const el of els) {
756
+ if (el.getType() === 'wizard') { wizard = el; return }
757
+ const children = el.getChildren()
758
+ if (children && children.length > 0) walk(children)
759
+ if (wizard) return
760
+ }
761
+ }
762
+ walk(formChildren)
763
+ if (!wizard) return undefined
764
+ const steps = (wizard.getChildren() ?? []).filter(c => c.getType() === 'step')
765
+ const step = steps[stepIndex]
766
+ if (!step) return undefined
767
+ return step.getChildren() ?? []
768
+ }
769
+
770
+ /**
771
+ * Pick the `Form` matching the submitted `_formId`, or fall back to the
772
+ * first form on the page when no id was sent OR the submitted id misses.
773
+ *
774
+ * Use this on **legacy form-submit paths** (POST create / edit / global-edit
775
+ * / custom-page) where a single page may host multiple forms and the
776
+ * fallback to "first form" is a back-compat affordance for submissions that
777
+ * predate the `_formId` hidden input.
778
+ *
779
+ * Do NOT use this on partial-resolve paths (Plan #5 form-state, Plan #8
780
+ * wizard step-validate) — those must hard-fail on a mismatched id so the
781
+ * client gets a 404 instead of silently writing the wrong form's state.
782
+ * Use `selectFormById` there.
783
+ */
784
+ export function selectForm(forms: ReadonlyArray<Form>, submittedId: unknown): Form | undefined {
785
+ if (typeof submittedId === 'string') {
786
+ const match = forms.find(f => f.getFormId() === submittedId)
787
+ if (match) return match
788
+ }
789
+ return forms[0]
790
+ }
791
+
792
+ /**
793
+ * ID-match counterpart to `selectForm`, used by partial-resolve endpoints
794
+ * (Plan #5 form-state, Plan #8 wizard step-validate).
795
+ *
796
+ * - If `id` matches a form, return it.
797
+ * - If there's no match AND the page has exactly one form, return that
798
+ * form. This is safe — there's no ambiguity about which form the POST
799
+ * meant — and it removes the auto-counter desync footgun: the GET
800
+ * render and the partial-resolve POST run through `Form.make()` in
801
+ * different requests, so the process-global formId counter ticks
802
+ * forward and a strict match would 404. See
803
+ * `feedback_pilotiq_live_forms_pin_formid.md`.
804
+ * - Otherwise return `undefined`. Multi-form pages with a missing/wrong
805
+ * id must hard-fail so the client surfaces a 404 instead of writing
806
+ * the wrong form's state.
807
+ *
808
+ * Pages with multiple reactive forms still need to pin a stable
809
+ * `Form.make().formId(...)` to disambiguate.
810
+ */
811
+ export function selectFormById(forms: ReadonlyArray<Form>, id: string): Form | undefined {
812
+ const match = forms.find(f => f.getFormId() === id)
813
+ if (match) return match
814
+ if (forms.length === 1) return forms[0]
815
+ return undefined
816
+ }
817
+
818
+ // ─── Plan #5: applyStateUpdate ────────────────────────────
819
+
820
+ export interface StateUpdateContext<R = unknown> {
821
+ record?: R
822
+ user?: unknown
823
+ request?: unknown
824
+ }
825
+
826
+ export interface StateUpdateResult {
827
+ /**
828
+ * Updated values map after coercing the changed field and running
829
+ * its `afterStateUpdated` hook. The same object the client should
830
+ * rebind to its inputs on the next render.
831
+ */
832
+ values: Record<string, unknown>
833
+ /**
834
+ * Field names whose value was written via `$set` during this resolve.
835
+ * Includes the changed field itself. The client uses this to decide
836
+ * which inputs to update without disrupting focus on others.
837
+ */
838
+ dirty: string[]
839
+ }
840
+
841
+ /**
842
+ * Apply a partial-resolve update from the client. Coerces the changed
843
+ * field's value (other fields keep whatever the client sent), runs the
844
+ * field's `afterStateUpdated` hook with bound `$get / $set` helpers,
845
+ * and returns the updated values + names of fields whose values were
846
+ * mutated. The caller (the partial-resolve route handler) feeds the
847
+ * resulting values into `resolveSchema` to produce a fresh form meta.
848
+ *
849
+ * Returns `null` when the changed field name doesn't correspond to a
850
+ * field on the form — the route handler turns this into a 404.
851
+ *
852
+ * Plan #14 — `changed` may be a dotted path into a Repeater row
853
+ * (`items.2.quantity` or, for nested Repeaters, `items.0.modifiers.1.name`).
854
+ * The dotted form routes through `applyRepeaterStateUpdate` which scopes
855
+ * `$get / $set` to the innermost row by default; cross-row reads / writes
856
+ * go through the parent `$get / $set` using a full dotted path.
857
+ */
858
+ export async function applyStateUpdate<R = unknown>(
859
+ form: Form<R>,
860
+ values: Record<string, unknown>,
861
+ changed: string,
862
+ ctx: StateUpdateContext<R> = {},
863
+ ): Promise<StateUpdateResult | null> {
864
+ const children = (form.getChildren() ?? []) as Element[]
865
+
866
+ if (changed.includes('.')) {
867
+ // Plan #14 — dotted paths route to the array-row field that owns
868
+ // the path's first segment. Builder paths look like `name.<i>.data.<leaf>`
869
+ // (the literal `data` segment is the giveaway); Repeater paths look
870
+ // like `name.<i>.<leaf>`. Inspect the first segment's field on the
871
+ // schema to dispatch.
872
+ const head = changed.split('.', 1)[0]!
873
+ const headField = findFieldDirect(children, head)
874
+ if (headField instanceof BuilderField) {
875
+ return applyBuilderStateUpdate(headField, values, changed, ctx)
876
+ }
877
+ return applyRepeaterStateUpdate(children, values, changed, ctx)
878
+ }
879
+
880
+ const target = findFieldByName(children, changed)
881
+ if (!target) return null
882
+
883
+ // Coerce the changed field only — other fields may have been mid-edit
884
+ // on the client and we don't want to clobber their in-flight state.
885
+ const coerced = { ...values }
886
+ const subset: Record<string, unknown> = { [changed]: values[changed] }
887
+ const after = coerceFormValues([target], subset)
888
+ coerced[changed] = after[changed]
889
+
890
+ const dirty = new Set<string>([changed])
891
+
892
+ const hook = target.getAfterStateUpdated()
893
+ if (hook) {
894
+ const $get = (name: string): unknown => coerced[name]
895
+ const $set = (name: string, v: unknown): void => {
896
+ coerced[name] = v
897
+ dirty.add(name)
898
+ }
899
+ const hookCtx: AfterStateUpdatedContext = {
900
+ $get,
901
+ $set,
902
+ values: coerced,
903
+ ...(ctx.record !== undefined ? { record: ctx.record } : {}),
904
+ ...(ctx.user !== undefined ? { user: ctx.user } : {}),
905
+ ...(ctx.request !== undefined ? { request: ctx.request } : {}),
906
+ }
907
+ await hook(coerced[changed], hookCtx)
908
+ }
909
+
910
+ return { values: coerced, dirty: Array.from(dirty) }
911
+ }
912
+
913
+ function findFieldByName(elements: Element[], name: string): Field | undefined {
914
+ for (const el of elements) {
915
+ if (el instanceof Field && el.name === name) return el
916
+ // Plan #14 — don't dive into Repeater / Builder inner schemas when
917
+ // looking for a top-level field; row-local fields are addressed via
918
+ // dotted paths through `applyRepeaterStateUpdate` /
919
+ // `applyBuilderStateUpdate`.
920
+ if (el instanceof RepeaterField) continue
921
+ if (el instanceof BuilderField) continue
922
+ const children = el.getChildren()
923
+ if (children && children.length > 0) {
924
+ const hit = findFieldByName(children as Element[], name)
925
+ if (hit) return hit
926
+ }
927
+ }
928
+ return undefined
929
+ }
930
+
931
+ /**
932
+ * Plan #14 — resolve a dotted-path live-update into a Repeater row.
933
+ *
934
+ * `changed` looks like `items.2.quantity` (one level) or
935
+ * `items.0.modifiers.1.name` (nested). Segments alternate field-name and
936
+ * row-index. The leaf must be a real Field inside the innermost
937
+ * Repeater's inner schema. Returns `null` (→ 404) when the path doesn't
938
+ * resolve.
939
+ *
940
+ * Mutates a shallow-cloned `values` so the caller gets a fresh map and
941
+ * the input isn't aliased. Row arrays + row maps along the path are
942
+ * cloned to avoid mutating shared state in the input.
943
+ */
944
+ async function applyRepeaterStateUpdate<R>(
945
+ children: Element[],
946
+ values: Record<string, unknown>,
947
+ changed: string,
948
+ ctx: StateUpdateContext<R>,
949
+ ): Promise<StateUpdateResult | null> {
950
+ const resolved = resolveRepeaterPath(children, changed)
951
+ if (!resolved) return null
952
+ const { field, rowPath } = resolved
953
+
954
+ const coerced = { ...values }
955
+
956
+ // Clone path-traversed arrays + row maps so we can mutate them without
957
+ // touching the caller's input. Final row map is the innermost row.
958
+ let rowMap = ensureRowAtPath(coerced, rowPath)
959
+
960
+ // Coerce only the leaf field's value — read raw value from the existing
961
+ // row map, then run it through `coerceFormValues` against the leaf field
962
+ // alone, and write the coerced value back.
963
+ const rawAtPath = rowMap[field.name]
964
+ const coercedSubset = coerceFormValues([field], { [field.name]: rawAtPath })
965
+ rowMap[field.name] = coercedSubset[field.name]
966
+
967
+ const dirty = new Set<string>([changed])
968
+
969
+ const hook = field.getAfterStateUpdated()
970
+ if (hook) {
971
+ const innermost = rowPath[rowPath.length - 1]!
972
+ const rowPrefix = rowPath.map(r => `${r.repeater.name}.${r.index}`).join('.')
973
+
974
+ const $get = (name: string): unknown => {
975
+ if (name.includes('.')) return readDottedPath(coerced, name)
976
+ return rowMap[name]
977
+ }
978
+ const $set = (name: string, v: unknown): void => {
979
+ if (name.includes('.')) {
980
+ writeDottedPath(coerced, name, v)
981
+ dirty.add(name)
982
+ return
983
+ }
984
+ rowMap[name] = v
985
+ dirty.add(`${rowPrefix}.${name}`)
986
+ }
987
+
988
+ const row = {
989
+ index: innermost.index,
990
+ $get: (name: string): unknown => rowMap[name],
991
+ $set: (name: string, v: unknown): void => {
992
+ rowMap[name] = v
993
+ dirty.add(`${rowPrefix}.${name}`)
994
+ },
995
+ }
996
+
997
+ const hookCtx: AfterStateUpdatedContext = {
998
+ $get,
999
+ $set,
1000
+ values: coerced,
1001
+ row,
1002
+ ...(ctx.record !== undefined ? { record: ctx.record } : {}),
1003
+ ...(ctx.user !== undefined ? { user: ctx.user } : {}),
1004
+ ...(ctx.request !== undefined ? { request: ctx.request } : {}),
1005
+ }
1006
+
1007
+ await hook(rowMap[field.name], hookCtx)
1008
+ }
1009
+
1010
+ return { values: coerced, dirty: Array.from(dirty) }
1011
+ }
1012
+
1013
+ interface ResolvedPath {
1014
+ field: Field
1015
+ rowPath: Array<{ repeater: RepeaterField; index: number }>
1016
+ }
1017
+
1018
+ /**
1019
+ * Walk a dotted path against an Element tree. Segments alternate
1020
+ * field-name and row-index. Returns the leaf Field plus the chain of
1021
+ * (Repeater, index) hops needed to reach it.
1022
+ */
1023
+ function resolveRepeaterPath(elements: Element[], path: string): ResolvedPath | null {
1024
+ const segments = path.split('.')
1025
+ const rowPath: Array<{ repeater: RepeaterField; index: number }> = []
1026
+
1027
+ let currentElements = elements
1028
+ let i = 0
1029
+ while (i < segments.length) {
1030
+ const seg = segments[i]!
1031
+ const field = findFieldDirect(currentElements, seg)
1032
+ if (!field) return null
1033
+
1034
+ if (i === segments.length - 1) {
1035
+ return { field, rowPath }
1036
+ }
1037
+
1038
+ if (!(field instanceof RepeaterField)) return null
1039
+ const idxStr = segments[i + 1]
1040
+ if (idxStr === undefined) return null
1041
+ const idx = Number(idxStr)
1042
+ if (!Number.isInteger(idx) || idx < 0) return null
1043
+
1044
+ rowPath.push({ repeater: field, index: idx })
1045
+ currentElements = field.getInnerSchema()
1046
+ i += 2
1047
+ }
1048
+
1049
+ return null
1050
+ }
1051
+
1052
+ /**
1053
+ * Find a top-level field by name inside an element tree, walking through
1054
+ * non-Repeater containers but stopping at Repeater boundaries (those need
1055
+ * a dotted path to address inner fields).
1056
+ */
1057
+ function findFieldDirect(elements: Element[], name: string): Field | undefined {
1058
+ for (const el of elements) {
1059
+ if (el instanceof Field && el.name === name) return el
1060
+ if (el instanceof RepeaterField) continue
1061
+ const children = el.getChildren()
1062
+ if (children && children.length > 0) {
1063
+ const hit = findFieldDirect(children as Element[], name)
1064
+ if (hit) return hit
1065
+ }
1066
+ }
1067
+ return undefined
1068
+ }
1069
+
1070
+ /**
1071
+ * Walk + clone the row arrays/maps along `rowPath`, ensuring each row
1072
+ * exists, then return the innermost row map. Mutations on the returned
1073
+ * object propagate up to `coerced` because we replace each step's
1074
+ * container with a fresh clone in the parent.
1075
+ */
1076
+ function ensureRowAtPath(
1077
+ coerced: Record<string, unknown>,
1078
+ rowPath: Array<{ repeater: RepeaterField; index: number }>,
1079
+ ): Record<string, unknown> {
1080
+ let parent: Record<string, unknown> | unknown[] = coerced
1081
+ for (const { repeater, index } of rowPath) {
1082
+ const arrName = repeater.name
1083
+ let arr: unknown[]
1084
+ if (Array.isArray(parent)) {
1085
+ // Should never happen at the outer iteration (parent starts as
1086
+ // `coerced`, an object); guard anyway.
1087
+ arr = (parent as unknown[]).slice()
1088
+ } else {
1089
+ const existing = (parent as Record<string, unknown>)[arrName]
1090
+ arr = Array.isArray(existing) ? existing.slice() : []
1091
+ ;(parent as Record<string, unknown>)[arrName] = arr
1092
+ }
1093
+ while (arr.length <= index) arr.push({})
1094
+ const existingRow = arr[index]
1095
+ const row: Record<string, unknown> = (existingRow && typeof existingRow === 'object' && !Array.isArray(existingRow))
1096
+ ? { ...(existingRow as Record<string, unknown>) }
1097
+ : {}
1098
+ arr[index] = row
1099
+ parent = row
1100
+ }
1101
+ return parent as Record<string, unknown>
1102
+ }
1103
+
1104
+ function readDottedPath(values: Record<string, unknown>, path: string): unknown {
1105
+ const segments = path.split('.')
1106
+ let cur: unknown = values
1107
+ for (const seg of segments) {
1108
+ if (cur === null || cur === undefined) return undefined
1109
+ if (Array.isArray(cur)) {
1110
+ const idx = Number(seg)
1111
+ if (!Number.isInteger(idx)) return undefined
1112
+ cur = cur[idx]
1113
+ } else if (typeof cur === 'object') {
1114
+ cur = (cur as Record<string, unknown>)[seg]
1115
+ } else {
1116
+ return undefined
1117
+ }
1118
+ }
1119
+ return cur
1120
+ }
1121
+
1122
+ function writeDottedPath(values: Record<string, unknown>, path: string, value: unknown): void {
1123
+ const segments = path.split('.')
1124
+ let cur: Record<string, unknown> | unknown[] = values
1125
+ for (let i = 0; i < segments.length - 1; i++) {
1126
+ const seg = segments[i]!
1127
+ const nextSeg = segments[i + 1]!
1128
+ const childIsIndex = /^\d+$/.test(nextSeg)
1129
+ if (Array.isArray(cur)) {
1130
+ const idx = Number(seg)
1131
+ if (!Number.isInteger(idx)) return
1132
+ while (cur.length <= idx) cur.push(childIsIndex ? [] : {})
1133
+ let next = cur[idx]
1134
+ if (next === undefined || next === null) {
1135
+ next = childIsIndex ? [] : {}
1136
+ cur[idx] = next
1137
+ }
1138
+ cur = next as Record<string, unknown> | unknown[]
1139
+ } else {
1140
+ let next = (cur as Record<string, unknown>)[seg]
1141
+ if (next === undefined || next === null) {
1142
+ next = childIsIndex ? [] : {}
1143
+ ;(cur as Record<string, unknown>)[seg] = next
1144
+ }
1145
+ cur = next as Record<string, unknown> | unknown[]
1146
+ }
1147
+ }
1148
+ const last = segments[segments.length - 1]!
1149
+ if (Array.isArray(cur)) {
1150
+ const idx = Number(last)
1151
+ if (!Number.isInteger(idx)) return
1152
+ cur[idx] = value
1153
+ } else {
1154
+ (cur as Record<string, unknown>)[last] = value
1155
+ }
1156
+ }
1157
+
1158
+ // ─── Plan #14 follow-up: Builder partial-resolve ─────────
1159
+
1160
+ /**
1161
+ * Resolve a dotted-path live-update into a Builder row.
1162
+ *
1163
+ * Path shape: `<name>.<i>.data.<leaf>`. The literal `data` segment
1164
+ * separates the row's envelope (`__id`, `type`) from the block-scoped
1165
+ * inner field. The row's block schema is selected from the values map
1166
+ * via `values[name][i].type` — Builder rows are heterogeneous, so the
1167
+ * schema can't be derived from the field alone.
1168
+ *
1169
+ * Nested array-row fields inside a block (Repeater-in-Builder, etc.)
1170
+ * aren't supported in v1 — same posture as nested Repeater leaf depth
1171
+ * past one level. Returns `null` (→ 404) on any unsupported shape.
1172
+ *
1173
+ * Mutates a shallow-cloned `values` so the caller gets a fresh map; the
1174
+ * row array + row map + `data` map along the path are cloned to avoid
1175
+ * aliasing the input.
1176
+ */
1177
+ async function applyBuilderStateUpdate<R>(
1178
+ field: BuilderField,
1179
+ values: Record<string, unknown>,
1180
+ changed: string,
1181
+ ctx: StateUpdateContext<R>,
1182
+ ): Promise<StateUpdateResult | null> {
1183
+ const segments = changed.split('.')
1184
+ // Expected: name (already matched by caller), <i>, 'data', <leaf>...
1185
+ if (segments.length < 4) return null
1186
+ const name = segments[0]!
1187
+ if (name !== field.name) return null
1188
+ const idxStr = segments[1]!
1189
+ const idx = Number(idxStr)
1190
+ if (!Number.isInteger(idx) || idx < 0) return null
1191
+ if (segments[2] !== 'data') return null
1192
+ const leafName = segments[3]!
1193
+ // Nested-array path past `data.<leaf>` not supported in v1.
1194
+ if (segments.length > 4) return null
1195
+
1196
+ // Look up the row's block from the submitted values.
1197
+ const arrRaw = values[name]
1198
+ if (!Array.isArray(arrRaw)) return null
1199
+ const rowRaw = arrRaw[idx]
1200
+ if (!rowRaw || typeof rowRaw !== 'object' || Array.isArray(rowRaw)) return null
1201
+ const blockName = (rowRaw as Record<string, unknown>)['type']
1202
+ if (typeof blockName !== 'string' || blockName === '') return null
1203
+ const block = field.getBlock(blockName)
1204
+ if (!block) return null
1205
+
1206
+ // Locate the leaf field inside the block's schema.
1207
+ const leafField = findFieldDirect(block.getSchema(), leafName)
1208
+ if (!leafField) return null
1209
+
1210
+ // Clone path-traversed containers.
1211
+ const coerced = { ...values }
1212
+ const arrClone = (coerced[name] as unknown[]).slice()
1213
+ coerced[name] = arrClone
1214
+ const rowSrc = arrClone[idx] as Record<string, unknown>
1215
+ const rowClone: Record<string, unknown> = { ...rowSrc }
1216
+ arrClone[idx] = rowClone
1217
+ const dataSrc = rowClone['data']
1218
+ const dataClone: Record<string, unknown> = (dataSrc && typeof dataSrc === 'object' && !Array.isArray(dataSrc))
1219
+ ? { ...(dataSrc as Record<string, unknown>) }
1220
+ : {}
1221
+ rowClone['data'] = dataClone
1222
+
1223
+ // Coerce the leaf field's value only.
1224
+ const rawLeaf = dataClone[leafName]
1225
+ const coercedSubset = coerceFormValues([leafField], { [leafName]: rawLeaf })
1226
+ dataClone[leafName] = coercedSubset[leafName]
1227
+
1228
+ const dirty = new Set<string>([changed])
1229
+
1230
+ const hook = leafField.getAfterStateUpdated()
1231
+ if (hook) {
1232
+ const rowPrefix = `${name}.${idx}.data`
1233
+
1234
+ const $get = (n: string): unknown => {
1235
+ if (n.includes('.')) return readDottedPath(coerced, n)
1236
+ return dataClone[n]
1237
+ }
1238
+ const $set = (n: string, v: unknown): void => {
1239
+ if (n.includes('.')) {
1240
+ writeDottedPath(coerced, n, v)
1241
+ dirty.add(n)
1242
+ return
1243
+ }
1244
+ dataClone[n] = v
1245
+ dirty.add(`${rowPrefix}.${n}`)
1246
+ }
1247
+
1248
+ const row = {
1249
+ index: idx,
1250
+ blockType: block.name,
1251
+ $get: (n: string): unknown => dataClone[n],
1252
+ $set: (n: string, v: unknown): void => {
1253
+ dataClone[n] = v
1254
+ dirty.add(`${rowPrefix}.${n}`)
1255
+ },
1256
+ }
1257
+
1258
+ const hookCtx: AfterStateUpdatedContext = {
1259
+ $get,
1260
+ $set,
1261
+ values: coerced,
1262
+ row,
1263
+ ...(ctx.record !== undefined ? { record: ctx.record } : {}),
1264
+ ...(ctx.user !== undefined ? { user: ctx.user } : {}),
1265
+ ...(ctx.request !== undefined ? { request: ctx.request } : {}),
1266
+ }
1267
+
1268
+ await hook(dataClone[leafName], hookCtx)
1269
+ }
1270
+
1271
+ return { values: coerced, dirty: Array.from(dirty) }
1272
+ }
1273
+
1274
+ // ─── Repeater.relationship — extraction + persistence ────────
1275
+
1276
+ interface RelationshipDeferral {
1277
+ field: RepeaterField
1278
+ rows: Array<Record<string, unknown>>
1279
+ cfg: RepeaterRelationshipConfig
1280
+ }
1281
+
1282
+ /**
1283
+ * Walk the form's top-level Repeaters and extract values for any that
1284
+ * have a `relationship(...)` config. Returns the deferral list and
1285
+ * mutates `data` in place by deleting each extracted key — the parent's
1286
+ * save handler doesn't need to see those values (they aren't real
1287
+ * columns on the parent).
1288
+ *
1289
+ * Inner / nested Repeaters aren't supported in v1; we only walk the top
1290
+ * level (consistent with the existing `walkRepeatersTopLevel` helper)
1291
+ * so a relationship-backed Repeater nested inside a JSON-backed
1292
+ * Repeater silently falls back to JSON storage. Documented as a
1293
+ * v1 limitation in `docs/plans/repeater-relationship.md`.
1294
+ */
1295
+ export function extractRelationshipRepeaters(
1296
+ elements: Element[],
1297
+ data: Record<string, unknown>,
1298
+ ): RelationshipDeferral[] {
1299
+ const out: RelationshipDeferral[] = []
1300
+ walkRepeatersTopLevel(elements, repeater => {
1301
+ const cfg = repeater.getRelationship()
1302
+ if (!cfg) return
1303
+ const value = data[repeater.name]
1304
+ delete data[repeater.name]
1305
+ if (!Array.isArray(value)) return
1306
+ out.push({
1307
+ field: repeater,
1308
+ rows: value as Array<Record<string, unknown>>,
1309
+ cfg,
1310
+ })
1311
+ })
1312
+ return out
1313
+ }
1314
+
1315
+ /**
1316
+ * Resolved attachment shape for a relationship-backed Repeater. Five
1317
+ * variants reflect the persisted-relation kinds we know how to write
1318
+ * back from a Repeater submit:
1319
+ *
1320
+ * - `hasMany` — single FK column on the child.
1321
+ * - `morphMany` — polymorphic owner side; `<morphName>Id` +
1322
+ * `<morphName>Type` stamped on the child.
1323
+ * `morphOne` collapses into this kind (storage
1324
+ * shape is identical; "one row" is enforced
1325
+ * upstream).
1326
+ * - `belongsToMany` — pivot-table M2M; the child has no parent
1327
+ * attachment column, so create + attach goes
1328
+ * through `parent[rel]().attach([childPk])` and
1329
+ * delete-from-row goes through `.detach([pk])`.
1330
+ * - `morphToMany` — polymorphic pivot M2M; pivot row carries
1331
+ * `<morphName>Type` + the parent's PK, written
1332
+ * transparently by the accessor.
1333
+ * - `morphedByMany` — inverse polymorphic pivot. Same accessor
1334
+ * surface.
1335
+ *
1336
+ * The three M2M variants carry only the relation name — the persist
1337
+ * pipeline reaches the accessor via `resolveM2MAccessor(parent, relation)`.
1338
+ */
1339
+ type RepeaterChildAttachment =
1340
+ | { kind: 'hasMany'; model: ModelLike; foreignKey: string }
1341
+ | { kind: 'morphMany'; model: ModelLike; morph: MorphRelationDescriptor }
1342
+ | { kind: 'belongsToMany'; model: ModelLike; relation: string }
1343
+ | { kind: 'morphToMany'; model: ModelLike; relation: string }
1344
+ | { kind: 'morphedByMany'; model: ModelLike; relation: string }
1345
+
1346
+ /**
1347
+ * Resolve the child model + parent-attachment shape for a
1348
+ * relationship-backed Repeater. Five supported modes:
1349
+ *
1350
+ * - `hasMany` — single foreign key on the child.
1351
+ * - `morphMany` / `morphOne` — polymorphic owner side.
1352
+ * - `belongsToMany` — pivot-table M2M.
1353
+ * - `morphToMany` / `morphedByMany` — polymorphic pivot M2M.
1354
+ *
1355
+ * Detection order: M2M descriptor (covers all three M2M variants) →
1356
+ * morph descriptor (morphMany / morphOne) → hasMany. The order matters
1357
+ * because `getParentRelationDescriptor` accepts entries with
1358
+ * `foreignKey: string` even if the type is M2M, so checking M2M first
1359
+ * keeps mis-shaped entries from falling through to the hasMany branch.
1360
+ *
1361
+ * `cfg.orderColumn` is rejected under M2M because pivot-side ordering
1362
+ * needs ORM `orderByPivot` which v1 doesn't expose. Throwing here
1363
+ * beats silently writing into a non-existent column on the related
1364
+ * model.
1365
+ *
1366
+ * Throws a clear configuration error when the relation type isn't one
1367
+ * of the five, or when descriptor lookup fails entirely.
1368
+ */
1369
+ function resolveChildAndAttachment(
1370
+ parentModel: ModelLike,
1371
+ cfg: RepeaterRelationshipConfig,
1372
+ ): RepeaterChildAttachment {
1373
+ const m2mDescriptor = getM2MRelationDescriptor(parentModel, cfg.name)
1374
+ if (m2mDescriptor) {
1375
+ if (cfg.orderColumn !== undefined) {
1376
+ throw new Error(
1377
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): orderColumn() is not supported under ` +
1378
+ `'${m2mDescriptor.type}' v1. Pivot-side ordering needs ORM \`orderByPivot\` which is deferred.`,
1379
+ )
1380
+ }
1381
+ const model = cfg.model ?? m2mDescriptor.model()
1382
+ if (!model) {
1383
+ throw new Error(
1384
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): could not resolve the related model. ` +
1385
+ `Pass it explicitly via .relationship({ name, model: RelatedModel }) or declare ` +
1386
+ `the relation's \`model\` thunk on the parent model's static relations map.`,
1387
+ )
1388
+ }
1389
+ return { kind: m2mDescriptor.type, model, relation: cfg.name }
1390
+ }
1391
+
1392
+ const parentDescriptor = getParentRelationDescriptor(parentModel, cfg.name)
1393
+ const morphDescriptor = getMorphRelationDescriptor(parentModel, cfg.name)
1394
+ const type = parentDescriptor?.type
1395
+ ?? (morphDescriptor ? 'morphMany' : 'hasMany')
1396
+
1397
+ if (type === 'morphMany' || type === 'morphOne') {
1398
+ const model = cfg.model ?? morphDescriptor?.model?.()
1399
+ if (!model) {
1400
+ throw new Error(
1401
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): could not resolve the child model. ` +
1402
+ `Pass it explicitly via .relationship({ name, model: ChildModel }) or declare ` +
1403
+ `the relation's \`model\` thunk on the parent model's static relations map.`,
1404
+ )
1405
+ }
1406
+ if (!morphDescriptor) {
1407
+ throw new Error(
1408
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): polymorphic relation entry is missing \`morphName\`. ` +
1409
+ `Set \`relations.${cfg.name} = { type: 'morphMany', morphName: '<name>', model: () => ChildModel }\` on the parent.`,
1410
+ )
1411
+ }
1412
+ return { kind: 'morphMany', model, morph: morphDescriptor }
1413
+ }
1414
+
1415
+ const model = cfg.model ?? parentDescriptor?.model()
1416
+ const foreignKey = cfg.foreignKey ?? parentDescriptor?.foreignKey
1417
+
1418
+ if (!model) {
1419
+ throw new Error(
1420
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): could not resolve the child model. ` +
1421
+ `Pass it explicitly via .relationship({ name, model: ChildModel }) or declare ` +
1422
+ `the relation on the parent model's static relations map.`,
1423
+ )
1424
+ }
1425
+ if (!foreignKey) {
1426
+ throw new Error(
1427
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): could not resolve the foreign-key column. ` +
1428
+ `Pass it explicitly via .relationship({ name, foreignKey: 'parentId' }) or declare ` +
1429
+ `it on the parent model's static relations map.`,
1430
+ )
1431
+ }
1432
+ if (type !== 'hasMany') {
1433
+ throw new Error(
1434
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): unsupported relation type '${type}'. ` +
1435
+ `Supported: hasMany, morphMany, morphOne, belongsToMany, morphToMany, morphedByMany.`,
1436
+ )
1437
+ }
1438
+
1439
+ return { kind: 'hasMany', model, foreignKey }
1440
+ }
1441
+
1442
+ /**
1443
+ * Diff submitted rows against the existing related rows and apply
1444
+ * create / update / delete operations through the child model.
1445
+ *
1446
+ * Identity:
1447
+ * - Submitted row with `__id` matching an existing PK → update.
1448
+ * - Submitted row without `__id` (or with one not in the existing
1449
+ * set) → create. The FK is stamped onto the create payload.
1450
+ * - Existing PK not present in any submitted `__id` → delete.
1451
+ *
1452
+ * Order:
1453
+ * - When `cfg.orderColumn` is set, every create / update payload
1454
+ * stamps it with the row's 0-based index.
1455
+ *
1456
+ * Errors propagate. v1 isn't transactional — partial failure leaves
1457
+ * the parent saved and some children unchanged. See plan doc for the
1458
+ * follow-up.
1459
+ */
1460
+ async function persistRelationshipRows(
1461
+ parent: unknown,
1462
+ deferral: RelationshipDeferral,
1463
+ parentModel: ModelLike,
1464
+ ): Promise<void> {
1465
+ const { rows, cfg, field } = deferral
1466
+ const attachment = resolveChildAndAttachment(parentModel, cfg)
1467
+ const { model } = attachment
1468
+ const pk = getPrimaryKey(model)
1469
+ const orderColumn = cfg.orderColumn
1470
+ const parentPk = (parent as Record<string, unknown> | undefined)?.[getPrimaryKey(parentModel)]
1471
+ if (parentPk === undefined || parentPk === null) {
1472
+ throw new Error(
1473
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): parent record has no primary key after save. ` +
1474
+ `Form.save() / handleCreate() must return a record with a primary key set.`,
1475
+ )
1476
+ }
1477
+
1478
+ // Compute the morph stamp once — `computeMorphPayload` is pure.
1479
+ const morphStamp = attachment.kind === 'morphMany'
1480
+ ? computeMorphPayload(parent, attachment.morph)
1481
+ : undefined
1482
+
1483
+ // Resolve the M2M pivot-mutation accessor once — fails closed with a
1484
+ // clear error if the parent doesn't expose `parent[rel]()` or a
1485
+ // legacy `parent.related(rel)` shape returning attach/detach.
1486
+ const isM2M = attachment.kind === 'belongsToMany'
1487
+ || attachment.kind === 'morphToMany'
1488
+ || attachment.kind === 'morphedByMany'
1489
+ const m2mAccessor = isM2M
1490
+ ? resolveM2MAccessor(parent, (attachment as { relation: string }).relation)
1491
+ : undefined
1492
+ if (isM2M && !m2mAccessor) {
1493
+ throw new Error(
1494
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): could not resolve the pivot-mutation accessor on the parent record. ` +
1495
+ `Expected \`parent.${cfg.name}()\` to return \`{ attach, detach, sync }\` (rudder ORM convention). ` +
1496
+ `Make sure the parent model declares the relation under \`static relations\` and that the prototype method is installed.`,
1497
+ )
1498
+ }
1499
+
1500
+ const existing = await loadRelationRows(parentModel, parent, cfg.name)
1501
+ const existingByPk = new Map<string, Record<string, unknown>>()
1502
+ for (const row of existing) {
1503
+ const key = String((row as Record<string, unknown>)[pk])
1504
+ existingByPk.set(key, row as Record<string, unknown>)
1505
+ }
1506
+
1507
+ const keptPks = new Set<string>()
1508
+
1509
+ // M2M-only: the user may have declared `pivotColumns([…])`. Those
1510
+ // names live on the pivot table, NOT the child model — split them
1511
+ // out before each create / update so the child writes never see
1512
+ // them and the pivot writes only see them.
1513
+ const pivotColumnSet = (isM2M && cfg.pivotColumns && cfg.pivotColumns.length > 0)
1514
+ ? new Set(cfg.pivotColumns)
1515
+ : undefined
1516
+
1517
+ for (let idx = 0; idx < rows.length; idx++) {
1518
+ const submitted = rows[idx] ?? {}
1519
+ const submittedId = typeof submitted['__id'] === 'string' ? submitted['__id'] : undefined
1520
+ const isUpdate = submittedId !== undefined && existingByPk.has(submittedId)
1521
+
1522
+ // Strip framework keys before constructing the payload — the
1523
+ // child model never sees `__id`, and the parent attachment cols
1524
+ // are stamped explicitly below so user-supplied values are
1525
+ // ignored (FK / morph cols can't be retargeted; order is
1526
+ // canonical from row index).
1527
+ const payload: Record<string, unknown> = {}
1528
+ const pivotPayload: Record<string, unknown> = {}
1529
+ for (const [k, v] of Object.entries(submitted)) {
1530
+ if (k === '__id') continue
1531
+ if (pivotColumnSet?.has(k)) {
1532
+ pivotPayload[k] = v
1533
+ } else {
1534
+ payload[k] = v
1535
+ }
1536
+ }
1537
+ if (orderColumn !== undefined) payload[orderColumn] = idx
1538
+ const hasPivotPayload = pivotColumnSet !== undefined
1539
+ && Object.keys(pivotPayload).length > 0
1540
+
1541
+ if (isUpdate) {
1542
+ // Don't overwrite the parent attachment on update — for hasMany
1543
+ // the FK is already correct; for morphMany the `<morphName>Id`
1544
+ // + `<morphName>Type` cols are too. Defense against a tampered
1545
+ // client trying to re-link the child to a different (poly)
1546
+ // parent. M2M variants have no parent-attachment column on the
1547
+ // child to strip — pivot lives on its own table.
1548
+ if (attachment.kind === 'hasMany') {
1549
+ delete payload[attachment.foreignKey]
1550
+ } else if (attachment.kind === 'morphMany') {
1551
+ for (const k of Object.keys(morphStamp!)) delete payload[k]
1552
+ }
1553
+ // For M2M without pivot extras the row still benefits from a
1554
+ // child-row update (user may have edited the child's own
1555
+ // columns through the Repeater). Skip the child write only
1556
+ // when the payload would be empty (M2M + pivot-only edits).
1557
+ if (Object.keys(payload).length > 0) {
1558
+ await model.update(submittedId!, payload)
1559
+ }
1560
+ if (hasPivotPayload) {
1561
+ if (typeof m2mAccessor!.updatePivot !== 'function') {
1562
+ throw new Error(
1563
+ `[Pilotiq] Repeater.relationship("${cfg.name}").pivotColumns(...) requires a rudder ORM with \`updatePivot\` ` +
1564
+ `on the M2M accessor (shipped via \`feat(orm): pivot-extras read/update\`). ` +
1565
+ `Upgrade @rudderjs/orm or drop the pivotColumns call.`,
1566
+ )
1567
+ }
1568
+ await m2mAccessor!.updatePivot(submittedId!, pivotPayload)
1569
+ }
1570
+ keptPks.add(submittedId!)
1571
+ } else {
1572
+ if (attachment.kind === 'hasMany') {
1573
+ payload[attachment.foreignKey] = parentPk
1574
+ await model.create(payload)
1575
+ } else if (attachment.kind === 'morphMany') {
1576
+ Object.assign(payload, morphStamp)
1577
+ await model.create(payload)
1578
+ } else {
1579
+ // M2M: create the related record first, then attach via the
1580
+ // pivot accessor. The accessor handles polymorphic stamping
1581
+ // (`<morphName>Type`) transparently for morphToMany /
1582
+ // morphedByMany. When `pivotColumns` is set the per-id
1583
+ // attach map ferries pivot extras into the new pivot row.
1584
+ const created = await model.create(payload)
1585
+ const newPk = (created as Record<string, unknown> | null | undefined)?.[pk]
1586
+ if (newPk === undefined || newPk === null) {
1587
+ throw new Error(
1588
+ `[Pilotiq] Repeater.relationship("${cfg.name}"): newly created related record has no primary key — ` +
1589
+ `cannot attach pivot row. Check that \`${(model as { name?: string }).name ?? 'related model'}.create()\` ` +
1590
+ `returns a record with the primary key set.`,
1591
+ )
1592
+ }
1593
+ if (hasPivotPayload) {
1594
+ await m2mAccessor!.attach!({ [String(newPk)]: pivotPayload })
1595
+ } else {
1596
+ await m2mAccessor!.attach!([newPk as string | number])
1597
+ }
1598
+ }
1599
+ }
1600
+ void field
1601
+ }
1602
+
1603
+ for (const [pkVal, _row] of existingByPk) {
1604
+ if (keptPks.has(pkVal)) continue
1605
+ if (isM2M) {
1606
+ // Detach the pivot link only — the related record may still be
1607
+ // attached to other parents. `cascadeDelete` opt-in is a Tier-2
1608
+ // follow-up.
1609
+ await m2mAccessor!.detach!([pkVal])
1610
+ } else {
1611
+ await model.delete(pkVal)
1612
+ }
1613
+ void _row
1614
+ }
1615
+ }
1616
+
1617
+ /**
1618
+ * Read all rows from `parent.related(name)`. Used both by the load-
1619
+ * side fill (in pageData) and the save-side diff (above). Caps at
1620
+ * 10k — admin Repeaters should never get that large; if they do we'll
1621
+ * add explicit pagination.
1622
+ */
1623
+ export async function loadRelationRows(
1624
+ parentModel: ModelLike,
1625
+ parent: unknown,
1626
+ name: string,
1627
+ pivotColumns?: readonly string[],
1628
+ ): Promise<unknown[]> {
1629
+ let q = resolveRelatedQuery(parentModel, parent, name)
1630
+ if (pivotColumns && pivotColumns.length > 0 && typeof q.withPivot === 'function') {
1631
+ q = q.withPivot(...pivotColumns)
1632
+ }
1633
+ const result = await q.paginate(1, 10000)
1634
+ return result.data
1635
+ }
1636
+
1637
+ // ─── Builder.relationship — extraction + persistence ─────────
1638
+
1639
+ interface BuilderRelationshipDeferral {
1640
+ field: BuilderField
1641
+ rows: Array<Record<string, unknown>>
1642
+ cfg: BuilderRelationshipConfig
1643
+ }
1644
+
1645
+ /**
1646
+ * Walk the form's top-level Builders and extract values for any that have
1647
+ * a `relationship(...)` config. Same shape + posture as
1648
+ * `extractRelationshipRepeaters`; mutates `data` in place by deleting each
1649
+ * extracted key. Heterogeneous-row sibling — each row is a
1650
+ * `{ __id?, type, data: {…} }` envelope after `coerceBuilderValue`.
1651
+ */
1652
+ export function extractRelationshipBuilders(
1653
+ elements: Element[],
1654
+ data: Record<string, unknown>,
1655
+ ): BuilderRelationshipDeferral[] {
1656
+ const out: BuilderRelationshipDeferral[] = []
1657
+ walkBuildersTopLevel(elements, builder => {
1658
+ const cfg = builder.getRelationship()
1659
+ if (!cfg) return
1660
+ const value = data[builder.name]
1661
+ delete data[builder.name]
1662
+ if (!Array.isArray(value)) return
1663
+ out.push({
1664
+ field: builder,
1665
+ rows: value as Array<Record<string, unknown>>,
1666
+ cfg,
1667
+ })
1668
+ })
1669
+ return out
1670
+ }
1671
+
1672
+ /**
1673
+ * Resolved attachment shape for a relationship-backed Builder. v1 of
1674
+ * Builder.relationship handled `hasMany` only; the morphMany variant
1675
+ * stamps `<morphName>Id` + `<morphName>Type` on every create instead of
1676
+ * a single FK column. The two branches share the load path
1677
+ * (`parent.related(name)` already filters morph cols) but differ in the
1678
+ * persist payload.
1679
+ */
1680
+ type BuilderChildAttachment =
1681
+ | { kind: 'hasMany'; model: ModelLike; foreignKey: string }
1682
+ | { kind: 'morphMany'; model: ModelLike; morph: MorphRelationDescriptor }
1683
+
1684
+ /**
1685
+ * Resolve the child model + parent-attachment shape for a
1686
+ * relationship-backed Builder. Two supported modes:
1687
+ *
1688
+ * - `hasMany` — single foreign key on the child. Falls back to
1689
+ * `cfg.model` / `cfg.foreignKey` overrides when the
1690
+ * parent's `static relations[name]` doesn't expose them.
1691
+ * - `morphMany` — polymorphic owner side. Reads the morph descriptor
1692
+ * off the parent's `static relations[name]` (no
1693
+ * override path — the discriminator + id columns are
1694
+ * driven entirely by `morphName`). `morphOne` collapses
1695
+ * into the same branch (the storage shape is identical;
1696
+ * "one row" is enforced upstream by the schema).
1697
+ *
1698
+ * Throws a clear configuration error when the relation type isn't one of
1699
+ * those two, or when the descriptor lookup fails entirely.
1700
+ */
1701
+ function resolveBuilderChildAndAttachment(
1702
+ parentModel: ModelLike,
1703
+ cfg: BuilderRelationshipConfig,
1704
+ ): BuilderChildAttachment {
1705
+ // Detect M2M first — a `belongsToMany` / `morphToMany` /
1706
+ // `morphedByMany` entry has no `foreignKey`, so it would silently
1707
+ // fall through to the hasMany branch below and surface a less-useful
1708
+ // "could not resolve foreign-key" error. Builder rows
1709
+ // (`{ type, data }`) don't compose with M2M pivot semantics, so this
1710
+ // is the surface where we point users at Repeater.relationship.
1711
+ const m2mDescriptor = getM2MRelationDescriptor(parentModel, cfg.name)
1712
+ if (m2mDescriptor) {
1713
+ throw new Error(
1714
+ `[Pilotiq] Builder.relationship("${cfg.name}"): unsupported relation type '${m2mDescriptor.type}'. ` +
1715
+ `Only 'hasMany' and 'morphMany' / 'morphOne' are supported on Builder.relationship in v1. ` +
1716
+ `belongsToMany / morphToMany / morphedByMany are not supported — the heterogeneous {type, data} ` +
1717
+ `envelope doesn't compose cleanly with M2M pivot semantics. Use a hasMany or morphMany relation, ` +
1718
+ `or use Repeater.relationship if your rows are homogeneous.`,
1719
+ )
1720
+ }
1721
+
1722
+ const parentDescriptor = getParentRelationDescriptor(parentModel, cfg.name)
1723
+ const morphDescriptor = getMorphRelationDescriptor(parentModel, cfg.name)
1724
+ const type = parentDescriptor?.type
1725
+ ?? (morphDescriptor ? 'morphMany' : 'hasMany')
1726
+
1727
+ if (type === 'morphMany' || type === 'morphOne') {
1728
+ const model = cfg.model ?? morphDescriptor?.model?.()
1729
+ if (!model) {
1730
+ throw new Error(
1731
+ `[Pilotiq] Builder.relationship("${cfg.name}"): could not resolve the child model. ` +
1732
+ `Pass it explicitly via .relationship({ name, model: ChildModel }) or declare ` +
1733
+ `the relation's \`model\` thunk on the parent model's static relations map.`,
1734
+ )
1735
+ }
1736
+ if (!morphDescriptor) {
1737
+ throw new Error(
1738
+ `[Pilotiq] Builder.relationship("${cfg.name}"): polymorphic relation entry is missing \`morphName\`. ` +
1739
+ `Set \`relations.${cfg.name} = { type: 'morphMany', morphName: '<name>', model: () => ChildModel }\` on the parent.`,
1740
+ )
1741
+ }
1742
+ return { kind: 'morphMany', model, morph: morphDescriptor }
1743
+ }
1744
+
1745
+ const model = cfg.model ?? parentDescriptor?.model()
1746
+ const foreignKey = cfg.foreignKey ?? parentDescriptor?.foreignKey
1747
+
1748
+ if (!model) {
1749
+ throw new Error(
1750
+ `[Pilotiq] Builder.relationship("${cfg.name}"): could not resolve the child model. ` +
1751
+ `Pass it explicitly via .relationship({ name, model: ChildModel }) or declare ` +
1752
+ `the relation on the parent model's static relations map.`,
1753
+ )
1754
+ }
1755
+ if (!foreignKey) {
1756
+ throw new Error(
1757
+ `[Pilotiq] Builder.relationship("${cfg.name}"): could not resolve the foreign-key column. ` +
1758
+ `Pass it explicitly via .relationship({ name, foreignKey: 'parentId' }) or declare ` +
1759
+ `it on the parent model's static relations map.`,
1760
+ )
1761
+ }
1762
+ if (type !== 'hasMany') {
1763
+ throw new Error(
1764
+ `[Pilotiq] Builder.relationship("${cfg.name}"): unsupported relation type '${type}'. ` +
1765
+ `Only 'hasMany' and 'morphMany' / 'morphOne' are supported on Builder.relationship in v1. ` +
1766
+ `belongsToMany / morphToMany / morphedByMany are not supported — the heterogeneous {type, data} ` +
1767
+ `envelope doesn't compose cleanly with M2M pivot semantics. Use a hasMany or morphMany relation, ` +
1768
+ `or use Repeater.relationship if your rows are homogeneous.`,
1769
+ )
1770
+ }
1771
+
1772
+ return { kind: 'hasMany', model, foreignKey }
1773
+ }
1774
+
1775
+ /**
1776
+ * Diff submitted Builder rows against the existing related rows and apply
1777
+ * create / update / delete operations through the child model. Same
1778
+ * identity rules as the Repeater pair — `__id` matches an existing PK →
1779
+ * update, missing → create, existing PK absent from submitted set →
1780
+ * delete. Each row writes its `type` discriminator + JSON `data` payload
1781
+ * to the configured columns.
1782
+ */
1783
+ async function persistRelationshipBuilderRows(
1784
+ parent: unknown,
1785
+ deferral: BuilderRelationshipDeferral,
1786
+ parentModel: ModelLike,
1787
+ ): Promise<void> {
1788
+ const { rows, cfg } = deferral
1789
+ const attachment = resolveBuilderChildAndAttachment(parentModel, cfg)
1790
+ const { model } = attachment
1791
+ const pk = getPrimaryKey(model)
1792
+ const typeColumn = cfg.typeColumn ?? 'type'
1793
+ const dataColumn = cfg.dataColumn ?? 'data'
1794
+ const orderColumn = cfg.orderColumn
1795
+ const parentPk = (parent as Record<string, unknown> | undefined)?.[getPrimaryKey(parentModel)]
1796
+ if (parentPk === undefined || parentPk === null) {
1797
+ throw new Error(
1798
+ `[Pilotiq] Builder.relationship("${cfg.name}"): parent record has no primary key after save. ` +
1799
+ `Form.save() / handleCreate() must return a record with a primary key set.`,
1800
+ )
1801
+ }
1802
+
1803
+ // Compute the morph stamp once — `computeMorphPayload` is pure.
1804
+ const morphStamp = attachment.kind === 'morphMany'
1805
+ ? computeMorphPayload(parent, attachment.morph)
1806
+ : undefined
1807
+
1808
+ const existing = await loadRelationRows(parentModel, parent, cfg.name)
1809
+ const existingByPk = new Map<string, Record<string, unknown>>()
1810
+ for (const row of existing) {
1811
+ const key = String((row as Record<string, unknown>)[pk])
1812
+ existingByPk.set(key, row as Record<string, unknown>)
1813
+ }
1814
+
1815
+ const keptPks = new Set<string>()
1816
+
1817
+ for (let idx = 0; idx < rows.length; idx++) {
1818
+ const submitted = rows[idx] ?? {}
1819
+ const submittedId = typeof submitted['__id'] === 'string' ? submitted['__id'] : undefined
1820
+ const isUpdate = submittedId !== undefined && existingByPk.has(submittedId)
1821
+
1822
+ const blockType = typeof submitted['type'] === 'string' ? submitted['type'] : ''
1823
+ const blockData = (submitted['data'] && typeof submitted['data'] === 'object')
1824
+ ? submitted['data']
1825
+ : {}
1826
+
1827
+ const payload: Record<string, unknown> = {
1828
+ [typeColumn]: blockType,
1829
+ [dataColumn]: blockData,
1830
+ }
1831
+ if (orderColumn !== undefined) payload[orderColumn] = idx
1832
+
1833
+ if (isUpdate) {
1834
+ // Don't overwrite the parent attachment on update — for hasMany the
1835
+ // FK is already correct; for morphMany the `<morphName>Id` +
1836
+ // `<morphName>Type` cols are too. Defense against a tampered
1837
+ // client trying to re-link the child to a different polymorphic
1838
+ // parent.
1839
+ await model.update(submittedId!, payload)
1840
+ keptPks.add(submittedId!)
1841
+ } else {
1842
+ if (attachment.kind === 'hasMany') {
1843
+ payload[attachment.foreignKey] = parentPk
1844
+ } else {
1845
+ Object.assign(payload, morphStamp)
1846
+ }
1847
+ await model.create(payload)
1848
+ }
1849
+ }
1850
+
1851
+ for (const [pkVal] of existingByPk) {
1852
+ if (keptPks.has(pkVal)) continue
1853
+ await model.delete(pkVal)
1854
+ }
1855
+ }