spine-framework 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 (385) hide show
  1. package/.framework/README.md +129 -0
  2. package/.framework/cli/bin.cjs +14 -0
  3. package/.framework/cli/commands/agents.ts +153 -0
  4. package/.framework/cli/commands/auth.ts +94 -0
  5. package/.framework/cli/commands/create-app.ts +185 -0
  6. package/.framework/cli/commands/dev.ts +295 -0
  7. package/.framework/cli/commands/doctor.ts +442 -0
  8. package/.framework/cli/commands/generate.ts +332 -0
  9. package/.framework/cli/commands/init.ts +272 -0
  10. package/.framework/cli/commands/install-app.ts +391 -0
  11. package/.framework/cli/commands/items.ts +253 -0
  12. package/.framework/cli/commands/migrations.ts +141 -0
  13. package/.framework/cli/commands/pipelines.ts +166 -0
  14. package/.framework/cli/commands/status.ts +197 -0
  15. package/.framework/cli/commands/system.ts +184 -0
  16. package/.framework/cli/commands/test.ts +227 -0
  17. package/.framework/cli/commands/uninstall-app.ts +166 -0
  18. package/.framework/cli/context.ts +268 -0
  19. package/.framework/cli/env-loader.ts +36 -0
  20. package/.framework/cli/index.ts +106 -0
  21. package/.framework/cli/welcome.cjs +45 -0
  22. package/.framework/docs/API.md +384 -0
  23. package/.framework/docs/STABILITY.md +52 -0
  24. package/.framework/docs/admin-routes.md +76 -0
  25. package/.framework/docs/api-docs-progress.md +38 -0
  26. package/.framework/docs/api-governance.md +146 -0
  27. package/.framework/docs/api-testing-results.md +212 -0
  28. package/.framework/docs/apis/admin-configs.md +567 -0
  29. package/.framework/docs/apis/admin-data.md +272 -0
  30. package/.framework/docs/apis/index.md +231 -0
  31. package/.framework/docs/apis/internal.md +295 -0
  32. package/.framework/docs/apis/runtime.md +537 -0
  33. package/.framework/docs/assembly-launch-guide.md +138 -0
  34. package/.framework/docs/audit-results.md +590 -0
  35. package/.framework/docs/authorization-model.md +170 -0
  36. package/.framework/docs/db-api-inventory.md +95 -0
  37. package/.framework/docs/examples/custom-app/README.md +77 -0
  38. package/.framework/docs/examples/custom-function/README.md +27 -0
  39. package/.framework/docs/examples/custom-function/handler.ts +48 -0
  40. package/.framework/docs/examples/custom-webhook/README.md +68 -0
  41. package/.framework/docs/gap-remediation-backlog.md +103 -0
  42. package/.framework/docs/guides/cli-guide.md +224 -0
  43. package/.framework/docs/guides/getting-started.md +103 -0
  44. package/.framework/docs/guides/import-guide.md +193 -0
  45. package/.framework/docs/guides/testing-guide.md +229 -0
  46. package/.framework/docs/permission-examples.md +326 -0
  47. package/.framework/docs/ui-adoption-verification.md +111 -0
  48. package/.framework/docs/ui-api-coverage.md +84 -0
  49. package/.framework/docs/v2-compatibility-audit.md +228 -0
  50. package/.framework/functions/.gitkeep +1 -0
  51. package/.framework/functions/_shared/agent-runner.ts +1097 -0
  52. package/.framework/functions/_shared/app-manifest.ts +184 -0
  53. package/.framework/functions/_shared/audit.ts +150 -0
  54. package/.framework/functions/_shared/db.ts +174 -0
  55. package/.framework/functions/_shared/index.ts +382 -0
  56. package/.framework/functions/_shared/middleware.ts +490 -0
  57. package/.framework/functions/_shared/permissions.ts +1325 -0
  58. package/.framework/functions/_shared/pipeline-runner.ts +731 -0
  59. package/.framework/functions/_shared/principal.ts +760 -0
  60. package/.framework/functions/_shared/schema-utils.ts +967 -0
  61. package/.framework/functions/_shared/testing.ts +258 -0
  62. package/.framework/functions/_shared/trigger-engine.ts +425 -0
  63. package/.framework/functions/_shared/webhook-registration.ts +168 -0
  64. package/.framework/functions/_shared/webhook-registry.ts +129 -0
  65. package/.framework/functions/account-nodes.ts +111 -0
  66. package/.framework/functions/admin-data.ts +606 -0
  67. package/.framework/functions/ai-agents.ts +323 -0
  68. package/.framework/functions/api-keys.ts +376 -0
  69. package/.framework/functions/apps.ts +483 -0
  70. package/.framework/functions/auth.ts +196 -0
  71. package/.framework/functions/debug-auth.ts +107 -0
  72. package/.framework/functions/embeddings.ts +556 -0
  73. package/.framework/functions/integration-routes.ts +523 -0
  74. package/.framework/functions/integrations.ts +319 -0
  75. package/.framework/functions/item-progress.ts +272 -0
  76. package/.framework/functions/logs.ts +438 -0
  77. package/.framework/functions/observability.ts +275 -0
  78. package/.framework/functions/pipeline-executions.ts +494 -0
  79. package/.framework/functions/pipelines.ts +485 -0
  80. package/.framework/functions/prompt-configs.ts +339 -0
  81. package/.framework/functions/roles.ts +387 -0
  82. package/.framework/functions/system-cron.ts +742 -0
  83. package/.framework/functions/system.ts +323 -0
  84. package/.framework/functions/tests.ts +119 -0
  85. package/.framework/functions/timers.ts +357 -0
  86. package/.framework/functions/triggers.ts +563 -0
  87. package/.framework/functions/types.ts +604 -0
  88. package/.framework/migrations/000_foundation.sql +1256 -0
  89. package/.framework/migrations/001_seed.sql +92 -0
  90. package/.framework/migrations/002_seed_constraints.sql +13 -0
  91. package/.framework/migrations/003_auth_user_trigger.sql +59 -0
  92. package/.framework/src/App.tsx +126 -0
  93. package/.framework/src/apps/admin/index.tsx +173 -0
  94. package/.framework/src/components/AppWrapper.tsx +56 -0
  95. package/.framework/src/components/CustomAppLoader.tsx +116 -0
  96. package/.framework/src/components/admin/AdminListPage.tsx +151 -0
  97. package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
  98. package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
  99. package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
  100. package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
  101. package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
  102. package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
  103. package/.framework/src/components/app-sidebar.tsx +228 -0
  104. package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
  105. package/.framework/src/components/layout/AppShell.tsx +91 -0
  106. package/.framework/src/components/layout/Header.tsx +88 -0
  107. package/.framework/src/components/layout/Layout.tsx +95 -0
  108. package/.framework/src/components/layout/Sidebar.tsx +329 -0
  109. package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
  110. package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
  111. package/.framework/src/components/runtime/DataFilters.tsx +91 -0
  112. package/.framework/src/components/runtime/DataHeader.tsx +68 -0
  113. package/.framework/src/components/runtime/DataListPage.tsx +124 -0
  114. package/.framework/src/components/runtime/DataStats.tsx +70 -0
  115. package/.framework/src/components/runtime/DataTable.tsx +174 -0
  116. package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
  117. package/.framework/src/components/runtime/index.ts +18 -0
  118. package/.framework/src/components/search-form.tsx +29 -0
  119. package/.framework/src/components/shared/AgentView.tsx +213 -0
  120. package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
  121. package/.framework/src/components/shared/SchemaFields.tsx +226 -0
  122. package/.framework/src/components/ui/DataTable.tsx +343 -0
  123. package/.framework/src/components/ui/Form.tsx +281 -0
  124. package/.framework/src/components/ui/ItemCard.tsx +296 -0
  125. package/.framework/src/components/ui/ItemListView.tsx +308 -0
  126. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
  127. package/.framework/src/components/ui/Modal.tsx +61 -0
  128. package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
  129. package/.framework/src/components/ui/accordion.tsx +82 -0
  130. package/.framework/src/components/ui/alert-dialog.tsx +197 -0
  131. package/.framework/src/components/ui/alert.tsx +76 -0
  132. package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
  133. package/.framework/src/components/ui/avatar.tsx +110 -0
  134. package/.framework/src/components/ui/badge.tsx +49 -0
  135. package/.framework/src/components/ui/breadcrumb.tsx +122 -0
  136. package/.framework/src/components/ui/button-group.tsx +83 -0
  137. package/.framework/src/components/ui/button.tsx +65 -0
  138. package/.framework/src/components/ui/calendar.tsx +222 -0
  139. package/.framework/src/components/ui/card.tsx +100 -0
  140. package/.framework/src/components/ui/carousel.tsx +240 -0
  141. package/.framework/src/components/ui/chart.tsx +373 -0
  142. package/.framework/src/components/ui/checkbox.tsx +31 -0
  143. package/.framework/src/components/ui/collapsible.tsx +33 -0
  144. package/.framework/src/components/ui/combobox.tsx +299 -0
  145. package/.framework/src/components/ui/command.tsx +193 -0
  146. package/.framework/src/components/ui/context-menu.tsx +261 -0
  147. package/.framework/src/components/ui/dialog.tsx +165 -0
  148. package/.framework/src/components/ui/direction.tsx +22 -0
  149. package/.framework/src/components/ui/drawer.tsx +132 -0
  150. package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
  151. package/.framework/src/components/ui/empty.tsx +104 -0
  152. package/.framework/src/components/ui/field.tsx +238 -0
  153. package/.framework/src/components/ui/hover-card.tsx +42 -0
  154. package/.framework/src/components/ui/input-group.tsx +153 -0
  155. package/.framework/src/components/ui/input-otp.tsx +87 -0
  156. package/.framework/src/components/ui/input.tsx +19 -0
  157. package/.framework/src/components/ui/item.tsx +196 -0
  158. package/.framework/src/components/ui/kbd.tsx +26 -0
  159. package/.framework/src/components/ui/label.tsx +22 -0
  160. package/.framework/src/components/ui/menubar.tsx +277 -0
  161. package/.framework/src/components/ui/native-select.tsx +61 -0
  162. package/.framework/src/components/ui/navigation-menu.tsx +164 -0
  163. package/.framework/src/components/ui/pagination.tsx +129 -0
  164. package/.framework/src/components/ui/popover.tsx +87 -0
  165. package/.framework/src/components/ui/progress.tsx +31 -0
  166. package/.framework/src/components/ui/radio-group.tsx +42 -0
  167. package/.framework/src/components/ui/resizable.tsx +50 -0
  168. package/.framework/src/components/ui/scroll-area.tsx +53 -0
  169. package/.framework/src/components/ui/select.tsx +195 -0
  170. package/.framework/src/components/ui/separator.tsx +26 -0
  171. package/.framework/src/components/ui/sheet.tsx +145 -0
  172. package/.framework/src/components/ui/sidebar.tsx +706 -0
  173. package/.framework/src/components/ui/skeleton.tsx +13 -0
  174. package/.framework/src/components/ui/slider.tsx +59 -0
  175. package/.framework/src/components/ui/sonner.tsx +47 -0
  176. package/.framework/src/components/ui/spinner.tsx +10 -0
  177. package/.framework/src/components/ui/switch.tsx +33 -0
  178. package/.framework/src/components/ui/table-primitives.tsx +141 -0
  179. package/.framework/src/components/ui/table.tsx +114 -0
  180. package/.framework/src/components/ui/tabs.tsx +90 -0
  181. package/.framework/src/components/ui/textarea.tsx +18 -0
  182. package/.framework/src/components/ui/toggle-group.tsx +89 -0
  183. package/.framework/src/components/ui/toggle.tsx +45 -0
  184. package/.framework/src/components/ui/tooltip.tsx +57 -0
  185. package/.framework/src/contexts/AppContext.tsx +133 -0
  186. package/.framework/src/contexts/AuthContext.tsx +371 -0
  187. package/.framework/src/hooks/use-mobile.ts +19 -0
  188. package/.framework/src/hooks/useApi.ts +526 -0
  189. package/.framework/src/hooks/useApps.ts +114 -0
  190. package/.framework/src/hooks/useEntityList.ts +190 -0
  191. package/.framework/src/hooks/useEntityRecord.ts +308 -0
  192. package/.framework/src/hooks/useForm.ts +307 -0
  193. package/.framework/src/hooks/useListSchema.ts +264 -0
  194. package/.framework/src/hooks/useSchemaRecord.ts +223 -0
  195. package/.framework/src/index.css +128 -0
  196. package/.framework/src/lib/api.ts +156 -0
  197. package/.framework/src/lib/supabase.ts +94 -0
  198. package/.framework/src/lib/utils.ts +317 -0
  199. package/.framework/src/main.tsx +27 -0
  200. package/.framework/src/pages/DashboardPage.tsx +181 -0
  201. package/.framework/src/pages/NotFoundPage.tsx +39 -0
  202. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
  203. package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
  204. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
  205. package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
  206. package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
  207. package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
  208. package/.framework/src/pages/admin/AppsPage.tsx +355 -0
  209. package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
  210. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
  211. package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
  212. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
  213. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
  214. package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
  215. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
  216. package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
  217. package/.framework/src/pages/admin/LogsPage.tsx +283 -0
  218. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
  219. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
  220. package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
  221. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
  222. package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
  223. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
  224. package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
  225. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
  226. package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
  227. package/.framework/src/pages/admin/RolesPage.tsx +292 -0
  228. package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
  229. package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
  230. package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
  231. package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
  232. package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
  233. package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
  234. package/.framework/src/pages/admin/TimersPage.tsx +376 -0
  235. package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
  236. package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
  237. package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
  238. package/.framework/src/pages/admin/TypesPage.tsx +295 -0
  239. package/.framework/src/pages/auth/LoginPage.tsx +188 -0
  240. package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
  241. package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
  242. package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
  243. package/.framework/src/types/auth.ts +125 -0
  244. package/.framework/src/types/types.ts +407 -0
  245. package/STRUCTURE.md +150 -0
  246. package/config/components.json +25 -0
  247. package/config/deno.lock +108 -0
  248. package/config/package-lock.json +17183 -0
  249. package/config/postcss.config.cjs +10 -0
  250. package/config/tailwind.config.cjs +78 -0
  251. package/config/tsconfig.build.json +32 -0
  252. package/config/tsconfig.cli.json +18 -0
  253. package/config/tsconfig.json +41 -0
  254. package/config/tsconfig.node.json +17 -0
  255. package/config/tsconfig.node.tsbuildinfo +1 -0
  256. package/config/tsconfig.tsbuildinfo +1 -0
  257. package/config/typedoc.json +16 -0
  258. package/config/vite.config.d.ts +2 -0
  259. package/config/vite.config.ts +72 -0
  260. package/dist/cli/commands/agents.d.ts +39 -0
  261. package/dist/cli/commands/agents.d.ts.map +1 -0
  262. package/dist/cli/commands/auth.d.ts +36 -0
  263. package/dist/cli/commands/auth.d.ts.map +1 -0
  264. package/dist/cli/commands/create-app.d.ts +23 -0
  265. package/dist/cli/commands/create-app.d.ts.map +1 -0
  266. package/dist/cli/commands/dev.d.ts +39 -0
  267. package/dist/cli/commands/dev.d.ts.map +1 -0
  268. package/dist/cli/commands/doctor.d.ts +42 -0
  269. package/dist/cli/commands/doctor.d.ts.map +1 -0
  270. package/dist/cli/commands/generate.d.ts +36 -0
  271. package/dist/cli/commands/generate.d.ts.map +1 -0
  272. package/dist/cli/commands/init.d.ts +30 -0
  273. package/dist/cli/commands/init.d.ts.map +1 -0
  274. package/dist/cli/commands/install-app.d.ts +30 -0
  275. package/dist/cli/commands/install-app.d.ts.map +1 -0
  276. package/dist/cli/commands/items.d.ts +45 -0
  277. package/dist/cli/commands/items.d.ts.map +1 -0
  278. package/dist/cli/commands/migrations.d.ts +41 -0
  279. package/dist/cli/commands/migrations.d.ts.map +1 -0
  280. package/dist/cli/commands/pipelines.d.ts +40 -0
  281. package/dist/cli/commands/pipelines.d.ts.map +1 -0
  282. package/dist/cli/commands/status.d.ts +23 -0
  283. package/dist/cli/commands/status.d.ts.map +1 -0
  284. package/dist/cli/commands/system.d.ts +29 -0
  285. package/dist/cli/commands/system.d.ts.map +1 -0
  286. package/dist/cli/commands/test.d.ts +46 -0
  287. package/dist/cli/commands/test.d.ts.map +1 -0
  288. package/dist/cli/commands/uninstall-app.d.ts +23 -0
  289. package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
  290. package/dist/cli/context.d.ts +88 -0
  291. package/dist/cli/context.d.ts.map +1 -0
  292. package/dist/cli/env-loader.d.ts +14 -0
  293. package/dist/cli/env-loader.d.ts.map +1 -0
  294. package/dist/cli/index.d.ts +41 -0
  295. package/dist/cli/index.d.ts.map +1 -0
  296. package/dist/functions/_shared/agent-runner.d.ts +156 -0
  297. package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
  298. package/dist/functions/_shared/app-manifest.d.ts +68 -0
  299. package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
  300. package/dist/functions/_shared/audit.d.ts +91 -0
  301. package/dist/functions/_shared/audit.d.ts.map +1 -0
  302. package/dist/functions/_shared/db.d.ts +125 -0
  303. package/dist/functions/_shared/db.d.ts.map +1 -0
  304. package/dist/functions/_shared/index.d.ts +298 -0
  305. package/dist/functions/_shared/index.d.ts.map +1 -0
  306. package/dist/functions/_shared/middleware.d.ts +315 -0
  307. package/dist/functions/_shared/middleware.d.ts.map +1 -0
  308. package/dist/functions/_shared/permissions.d.ts +626 -0
  309. package/dist/functions/_shared/permissions.d.ts.map +1 -0
  310. package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
  311. package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
  312. package/dist/functions/_shared/principal.d.ts +284 -0
  313. package/dist/functions/_shared/principal.d.ts.map +1 -0
  314. package/dist/functions/_shared/schema-utils.d.ts +181 -0
  315. package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
  316. package/dist/functions/_shared/testing.d.ts +172 -0
  317. package/dist/functions/_shared/testing.d.ts.map +1 -0
  318. package/dist/functions/_shared/trigger-engine.d.ts +140 -0
  319. package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
  320. package/dist/functions/_shared/webhook-registration.d.ts +81 -0
  321. package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
  322. package/dist/functions/_shared/webhook-registry.d.ts +57 -0
  323. package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
  324. package/dist/functions/account-nodes.d.ts +48 -0
  325. package/dist/functions/account-nodes.d.ts.map +1 -0
  326. package/dist/functions/admin-data.d.ts +178 -0
  327. package/dist/functions/admin-data.d.ts.map +1 -0
  328. package/dist/functions/ai-agents.d.ts +125 -0
  329. package/dist/functions/ai-agents.d.ts.map +1 -0
  330. package/dist/functions/api-keys.d.ts +140 -0
  331. package/dist/functions/api-keys.d.ts.map +1 -0
  332. package/dist/functions/apps.d.ts +163 -0
  333. package/dist/functions/apps.d.ts.map +1 -0
  334. package/dist/functions/auth.d.ts +74 -0
  335. package/dist/functions/auth.d.ts.map +1 -0
  336. package/dist/functions/debug-auth.d.ts +33 -0
  337. package/dist/functions/debug-auth.d.ts.map +1 -0
  338. package/dist/functions/embeddings.d.ts +205 -0
  339. package/dist/functions/embeddings.d.ts.map +1 -0
  340. package/dist/functions/integration-routes.d.ts +45 -0
  341. package/dist/functions/integration-routes.d.ts.map +1 -0
  342. package/dist/functions/integrations.d.ts +124 -0
  343. package/dist/functions/integrations.d.ts.map +1 -0
  344. package/dist/functions/item-progress.d.ts +41 -0
  345. package/dist/functions/item-progress.d.ts.map +1 -0
  346. package/dist/functions/logs.d.ts +162 -0
  347. package/dist/functions/logs.d.ts.map +1 -0
  348. package/dist/functions/observability.d.ts +123 -0
  349. package/dist/functions/observability.d.ts.map +1 -0
  350. package/dist/functions/pipeline-executions.d.ts +190 -0
  351. package/dist/functions/pipeline-executions.d.ts.map +1 -0
  352. package/dist/functions/pipelines.d.ts +171 -0
  353. package/dist/functions/pipelines.d.ts.map +1 -0
  354. package/dist/functions/prompt-configs.d.ts +125 -0
  355. package/dist/functions/prompt-configs.d.ts.map +1 -0
  356. package/dist/functions/roles.d.ts +118 -0
  357. package/dist/functions/roles.d.ts.map +1 -0
  358. package/dist/functions/system-cron.d.ts +65 -0
  359. package/dist/functions/system-cron.d.ts.map +1 -0
  360. package/dist/functions/system.d.ts +29 -0
  361. package/dist/functions/system.d.ts.map +1 -0
  362. package/dist/functions/tests.d.ts +28 -0
  363. package/dist/functions/tests.d.ts.map +1 -0
  364. package/dist/functions/timers.d.ts +139 -0
  365. package/dist/functions/timers.d.ts.map +1 -0
  366. package/dist/functions/triggers.d.ts +203 -0
  367. package/dist/functions/triggers.d.ts.map +1 -0
  368. package/dist/functions/types.d.ts +151 -0
  369. package/dist/functions/types.d.ts.map +1 -0
  370. package/dist/src/types/types.d.ts +364 -0
  371. package/dist/src/types/types.d.ts.map +1 -0
  372. package/package.json +192 -0
  373. package/scripts/app-install-cli.ts +286 -0
  374. package/scripts/assemble-frontend.sh +79 -0
  375. package/scripts/assemble-functions.sh +62 -0
  376. package/scripts/assemble.sh +35 -0
  377. package/scripts/boundary-check.sh +106 -0
  378. package/scripts/build-manifest.sh +80 -0
  379. package/scripts/check-core-integrity.sh +82 -0
  380. package/scripts/ingest-chunks.cjs +202 -0
  381. package/scripts/kb-chunk-parser.cjs +312 -0
  382. package/scripts/kb-chunk-parser.ts +330 -0
  383. package/scripts/load-test-app-install.ts +484 -0
  384. package/scripts/netlify-dev-wrapper.sh +22 -0
  385. package/scripts/verify-integrity.sh +69 -0
@@ -0,0 +1,967 @@
1
+ /**
2
+ * @module schema-utils
3
+ * @audience both
4
+ * @layer shared-core
5
+ * @stability stable
6
+ *
7
+ * Schema generation and field-level data transformation for all Spine types.
8
+ *
9
+ * Three public functions form the core contract:
10
+ * - `generateValidationSchema` — derives a runtime validation schema from a
11
+ * `design_schema`, stripping display/permission info and keeping only
12
+ * structural constraints.
13
+ * - `sanitizeFieldData` — coerces and validates a single field value for
14
+ * write (create/update) operations. Throws on invalid data.
15
+ * - `formatFieldData` — converts a stored field value to a human-readable
16
+ * display string for read operations. Never throws.
17
+ * - `transformRecordData` — applies sanitize or format to all fields in a
18
+ * record using a pre-generated ValidationSchema.
19
+ *
20
+ * These are called by `permissions.ts` (`validateFirstSurfaceUpdatePermissions`
21
+ * and `sanitizeFirstSurfaceRecordData`) — do not call them directly from API
22
+ * handlers. Use `PermissionEngine.sanitizeRecordData` / `validateUpdatePermissions`.
23
+ *
24
+ * INVARIANT: all sanitize functions throw on invalid data. Callers must catch
25
+ * errors and convert them to field-level validation error messages.
26
+ * INVARIANT: all format functions return the raw data unchanged if formatting
27
+ * is not applicable (never throw, never return null for non-null input).
28
+ *
29
+ * @seeAlso permissions.ts (primary caller of sanitizeFieldData, formatFieldData)
30
+ * @seeAlso src/types/types.ts (FieldDefinition interface)
31
+ * @seeAlso index.ts (not re-exported — internal to core; use PermissionEngine)
32
+ */
33
+
34
+ import { FieldDefinition } from '../../src/types/types'
35
+
36
+ // ─── TYPES ───────────────────────────────────────────────────────────────
37
+
38
+ /**
39
+ * Structural-only validation schema derived from a `design_schema`.
40
+ *
41
+ * Contains one entry per field with the field's `data_type` and any explicit
42
+ * `validation` constraints. Display properties (`display_type`, views, sections)
43
+ * and permission properties are stripped. Used as input to `sanitizeFieldData`
44
+ * and `formatFieldData`.
45
+ *
46
+ * @inputSpec none — output type of generateValidationSchema
47
+ * @outputSpec fields: Record<fieldName, { data_type, required?, ...constraints }>
48
+ * @calledBy generateValidationSchema (producer), transformRecordData,
49
+ * permissions.ts validateFirstSurfaceUpdatePermissions (consumer)
50
+ */
51
+ export interface ValidationSchema {
52
+ fields: Record<string, {
53
+ data_type: string
54
+ required?: boolean
55
+ [key: string]: any // Type-specific validation properties
56
+ }>
57
+ }
58
+
59
+ // ─── PUBLIC API ──────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Derives a `ValidationSchema` from a `design_schema` by extracting only
63
+ * structural constraints (data_type, required, validation.*) and discarding
64
+ * display, permission, and view configuration.
65
+ *
66
+ * The resulting schema is used to drive `sanitizeFieldData` and
67
+ * `formatFieldData` for every field in a record. It is generated once per
68
+ * type and passed to `transformRecordData` for bulk field processing.
69
+ *
70
+ * @param designSchema - The full design_schema object from a type record
71
+ * @returns ValidationSchema with one entry per field
72
+ * @throws never — returns empty schema if designSchema.fields is missing
73
+ * @inputSpec designSchema.fields: Record<fieldName, FieldDefinition> — must match
74
+ * the FieldDefinition interface from src/types/types.ts
75
+ * @inputSpec designSchema.fields[x].data_type: string — required in every field
76
+ * @outputSpec ValidationSchema.fields: Record<string, { data_type, required, ...constraints }>
77
+ * @sideEffects none
78
+ * @calledBy permissions.ts (validateFirstSurfaceUpdatePermissions), any caller
79
+ * needing a validation schema without the full design_schema overhead
80
+ * @testUnit tests/unit/schema-utils.test.ts — 'generateValidationSchema' describe block
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * const schema = generateValidationSchema(record.design_schema)
85
+ * const cleaned = transformRecordData(record.data, schema, 'sanitize')
86
+ * ```
87
+ */
88
+ export function generateValidationSchema(designSchema: any): ValidationSchema {
89
+ const validationSchema: ValidationSchema = {
90
+ fields: {}
91
+ }
92
+
93
+ if (!designSchema.fields) {
94
+ return validationSchema
95
+ }
96
+
97
+ for (const [fieldName, fieldDef] of Object.entries(designSchema.fields)) {
98
+ const field = fieldDef as FieldDefinition
99
+
100
+ // Extract only structural validation properties exactly as declared
101
+ const validationField: any = {
102
+ data_type: field.data_type,
103
+ required: field.required
104
+ }
105
+
106
+ // Add explicit validation constraints exactly as declared
107
+ if (field.validation) {
108
+ Object.assign(validationField, field.validation)
109
+ }
110
+
111
+ // Add type-specific constraint properties (moved out of validation for clarity)
112
+ if (field.options) {
113
+ validationField.options = field.options
114
+ }
115
+
116
+ // Add reference properties if they exist
117
+ if (field.data_type === 'reference' && field.validation) {
118
+ if (field.validation.reference_kind) validationField.reference_kind = field.validation.reference_kind
119
+ if (field.validation.reference_type) validationField.reference_type = field.validation.reference_type
120
+ }
121
+
122
+ validationSchema.fields[fieldName] = validationField
123
+ }
124
+
125
+ return validationSchema
126
+ }
127
+
128
+ /**
129
+ * Coerces and validates a single field value for a write (create/update) operation.
130
+ *
131
+ * Dispatches to a type-specific sanitizer based on `data_type`. Every sanitizer:
132
+ * - Coerces the input to the correct type
133
+ * - Applies constraint validation (min/max/length/pattern/options)
134
+ * - Throws a descriptive `Error` on the first validation failure
135
+ * - Returns the cleaned value on success
136
+ *
137
+ * Unknown `data_type` values pass through unchanged (no throw).
138
+ *
139
+ * @param data - Raw field value from the request body
140
+ * @param data_type - The field's declared data_type from design_schema.fields[x]
141
+ * @param validation - Optional validation constraints from the field definition
142
+ * @returns Sanitized value in the correct type for storage
143
+ * @throws Error — descriptive message naming the field constraint violated
144
+ * @inputSpec data: any — null and undefined are returned as-is without sanitization
145
+ * @inputSpec data_type: string — one of the 21 supported type keys (see switch below)
146
+ * @inputSpec validation: object | undefined — type-specific constraints
147
+ * @outputSpec any — coerced value matching the data_type storage format
148
+ * @sideEffects none
149
+ * @calledBy permissions.ts (validateFirstSurfaceUpdatePermissions, per-field loop)
150
+ * @calls sanitizeText | sanitizeTextarea | sanitizeEmail | sanitizeNumber | etc.
151
+ * @testUnit tests/unit/schema-utils.test.ts — 'sanitizeFieldData' describe block
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * const clean = sanitizeFieldData('hello@EXAMPLE.COM', 'email')
156
+ * // → 'hello@example.com'
157
+ *
158
+ * sanitizeFieldData('not-a-url', 'url')
159
+ * // throws Error('Invalid URL format')
160
+ * ```
161
+ */
162
+ export function sanitizeFieldData(
163
+ data: any,
164
+ data_type: string,
165
+ validation?: any
166
+ ): any {
167
+ if (data === null || data === undefined) {
168
+ return data
169
+ }
170
+
171
+ switch (data_type) {
172
+ case 'text':
173
+ return sanitizeText(data, validation)
174
+ case 'textarea':
175
+ return sanitizeTextarea(data, validation)
176
+ case 'rich_text':
177
+ return sanitizeRichText(data, validation)
178
+ case 'email':
179
+ return sanitizeEmail(data, validation)
180
+ case 'phone':
181
+ return sanitizePhone(data, validation)
182
+ case 'url':
183
+ return sanitizeUrl(data, validation)
184
+ case 'number':
185
+ return sanitizeNumber(data, validation)
186
+ case 'currency':
187
+ return sanitizeCurrency(data, validation)
188
+ case 'range':
189
+ return sanitizeRange(data, validation)
190
+ case 'date':
191
+ return sanitizeDate(data, validation)
192
+ case 'datetime':
193
+ return sanitizeDatetime(data, validation)
194
+ case 'boolean':
195
+ return sanitizeBoolean(data, validation)
196
+ case 'checkbox':
197
+ return sanitizeCheckbox(data, validation)
198
+ case 'select':
199
+ return sanitizeSelect(data, validation)
200
+ case 'multiselect':
201
+ return sanitizeMultiselect(data, validation)
202
+ case 'radio':
203
+ return sanitizeRadio(data, validation)
204
+ case 'color':
205
+ return sanitizeColor(data, validation)
206
+ case 'file':
207
+ return sanitizeFile(data, validation)
208
+ case 'image':
209
+ return sanitizeImage(data, validation)
210
+ case 'json':
211
+ return sanitizeJson(data, validation)
212
+ case 'reference':
213
+ return sanitizeReference(data, validation)
214
+ case 'address':
215
+ return sanitizeAddress(data, validation)
216
+ default:
217
+ return data
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Converts a stored field value to a human-readable display string.
223
+ *
224
+ * Dispatches to a type-specific formatter based on `data_type`. Formatters
225
+ * never throw — if the data cannot be formatted, the raw value is returned.
226
+ * Only types with meaningful display transformations have a case; all others
227
+ * fall through to the default (return data unchanged).
228
+ *
229
+ * @param data - Stored field value (from DB, post-sanitization)
230
+ * @param data_type - The field's declared data_type
231
+ * @param context - Optional context for type-specific formatting (e.g.
232
+ * `context.currency_code` for currency fields, `context.field` for boolean
233
+ * contextual labels like 'Active'/'Inactive')
234
+ * @returns Formatted display value
235
+ * @throws never
236
+ * @inputSpec data: any — null and undefined are returned as-is
237
+ * @inputSpec data_type: string — one of 21 supported keys; unknown → pass-through
238
+ * @inputSpec context: object | undefined — type-specific display hints
239
+ * @outputSpec any — display-ready value; string for most types, raw data for pass-through
240
+ * @sideEffects none
241
+ * @calledBy permissions.ts (sanitizeFirstSurfaceRecordData, per-field loop)
242
+ * @calls formatJson | formatDate | formatDatetime | formatCurrency | etc.
243
+ * @testUnit tests/unit/schema-utils.test.ts — 'formatFieldData' describe block
244
+ *
245
+ * @example
246
+ * ```ts
247
+ * formatFieldData('2024-01-15', 'date')
248
+ * // → 'January 15, 2024'
249
+ *
250
+ * formatFieldData(1234.5, 'currency', { currency_code: 'USD' })
251
+ * // → '$1,234.50'
252
+ * ```
253
+ */
254
+ export function formatFieldData(
255
+ data: any,
256
+ data_type: string,
257
+ context?: any
258
+ ): any {
259
+ if (data === null || data === undefined) {
260
+ return data
261
+ }
262
+
263
+ switch (data_type) {
264
+ case 'json':
265
+ return formatJson(data)
266
+ case 'date':
267
+ return formatDate(data)
268
+ case 'datetime':
269
+ return formatDatetime(data)
270
+ case 'currency':
271
+ return formatCurrency(data, context)
272
+ case 'phone':
273
+ return formatPhone(data)
274
+ case 'url':
275
+ return formatUrl(data)
276
+ case 'reference':
277
+ return formatReference(data, context)
278
+ case 'address':
279
+ return formatAddress(data)
280
+ case 'multiselect':
281
+ return formatMultiselect(data)
282
+ case 'boolean':
283
+ return formatBoolean(data, context)
284
+ default:
285
+ return data
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Applies `sanitizeFieldData` or `formatFieldData` to all fields in a record,
291
+ * using the ValidationSchema for per-field type and constraint information.
292
+ *
293
+ * Fields not present in the schema are passed through unchanged. This is
294
+ * intentional — unknown fields are not rejected here; the PermissionEngine
295
+ * handles field-level access control separately.
296
+ *
297
+ * @param data - Key/value record of field names to raw or stored values
298
+ * @param validationSchema - Schema from `generateValidationSchema`
299
+ * @param operation - 'sanitize' for write path; 'format' for display path
300
+ * @param context - Optional context passed through to formatFieldData
301
+ * @returns Transformed record with the same keys
302
+ * @throws Error (sanitize mode only) — if any field fails validation
303
+ * @inputSpec data: Record<string, any> — flat field map
304
+ * @inputSpec validationSchema: ValidationSchema — from generateValidationSchema
305
+ * @inputSpec operation: 'sanitize' | 'format'
306
+ * @outputSpec Record<string, any> — same keys, transformed values
307
+ * @sideEffects none
308
+ * @calledBy Custom code in v2-custom/ that needs bulk field transformation
309
+ * @calls sanitizeFieldData | formatFieldData (per field)
310
+ * @testUnit tests/unit/schema-utils.test.ts — 'transformRecordData' describe block
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * const schema = generateValidationSchema(type.design_schema)
315
+ * const sanitized = transformRecordData(body.data, schema, 'sanitize')
316
+ * const formatted = transformRecordData(record.data, schema, 'format', ctx)
317
+ * ```
318
+ */
319
+ export function transformRecordData(
320
+ data: Record<string, any>,
321
+ validationSchema: ValidationSchema,
322
+ operation: 'sanitize' | 'format',
323
+ context?: any
324
+ ): Record<string, any> {
325
+ const transformed: Record<string, any> = {}
326
+
327
+ for (const [fieldName, fieldValue] of Object.entries(data)) {
328
+ const fieldValidation = validationSchema.fields[fieldName]
329
+
330
+ if (!fieldValidation) {
331
+ // No validation schema for this field, pass through as-is
332
+ transformed[fieldName] = fieldValue
333
+ continue
334
+ }
335
+
336
+ if (operation === 'sanitize') {
337
+ transformed[fieldName] = sanitizeFieldData(
338
+ fieldValue,
339
+ fieldValidation.data_type,
340
+ fieldValidation
341
+ )
342
+ } else if (operation === 'format') {
343
+ transformed[fieldName] = formatFieldData(
344
+ fieldValue,
345
+ fieldValidation.data_type,
346
+ context
347
+ )
348
+ }
349
+ }
350
+
351
+ return transformed
352
+ }
353
+
354
+ // ─── SANITIZE HELPERS ────────────────────────────────────────────────────────────
355
+
356
+ /**
357
+ * Trims, strips control characters, HTML-escapes, and applies minLength/
358
+ * maxLength/pattern constraints. Throws on minLength/pattern violation;
359
+ * silently truncates on maxLength.
360
+ * @throws Error on minLength or pattern violation
361
+ */
362
+ function sanitizeText(data: any, validation?: any): string {
363
+ let text = String(data).trim()
364
+
365
+ // Remove control characters except newlines and tabs
366
+ text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
367
+
368
+ // Escape HTML entities
369
+ text = text
370
+ .replace(/&/g, '&amp;')
371
+ .replace(/</g, '&lt;')
372
+ .replace(/>/g, '&gt;')
373
+ .replace(/"/g, '&quot;')
374
+ .replace(/'/g, '&#x27;')
375
+
376
+ // Apply length constraints
377
+ if (validation?.minLength && text.length < validation.minLength) {
378
+ throw new Error(`Text must be at least ${validation.minLength} characters`)
379
+ }
380
+ if (validation?.maxLength && text.length > validation.maxLength) {
381
+ text = text.substring(0, validation.maxLength)
382
+ }
383
+
384
+ // Apply pattern validation
385
+ if (validation?.pattern) {
386
+ const regex = new RegExp(validation.pattern)
387
+ if (!regex.test(text)) {
388
+ throw new Error(`Text does not match required pattern`)
389
+ }
390
+ }
391
+
392
+ return text
393
+ }
394
+
395
+ /**
396
+ * Same as sanitizeText but preserves newlines. Strips control chars,
397
+ * HTML-escapes, applies minLength/maxLength.
398
+ * @throws Error on minLength violation
399
+ */
400
+ function sanitizeTextarea(data: any, validation?: any): string {
401
+ let text = String(data).trim()
402
+
403
+ // Remove control characters except newlines and tabs
404
+ text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
405
+
406
+ // Escape HTML entities but preserve line breaks
407
+ text = text
408
+ .replace(/&/g, '&amp;')
409
+ .replace(/</g, '&lt;')
410
+ .replace(/>/g, '&gt;')
411
+ .replace(/"/g, '&quot;')
412
+ .replace(/'/g, '&#x27;')
413
+
414
+ // Apply length constraints
415
+ if (validation?.minLength && text.length < validation.minLength) {
416
+ throw new Error(`Text must be at least ${validation.minLength} characters`)
417
+ }
418
+ if (validation?.maxLength && text.length > validation.maxLength) {
419
+ text = text.substring(0, validation.maxLength)
420
+ }
421
+
422
+ return text
423
+ }
424
+
425
+ /**
426
+ * Allowlist-based HTML sanitizer for rich text. Strips all tags not in the
427
+ * allowed set (`p br strong em u ol ul li a h1-h6`), removes script tags and
428
+ * `on*` event attributes, applies minLength/maxLength.
429
+ * @throws Error on minLength violation
430
+ */
431
+ function sanitizeRichText(data: any, validation?: any): string {
432
+ let html = String(data).trim()
433
+
434
+ // Basic HTML sanitization - allow only safe tags
435
+ const allowedTags = ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'a', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']
436
+ const tagRegex = /<\/?([a-zA-Z][a-zA-Z0-9]*)\b[^>]*>/g
437
+
438
+ html = html.replace(tagRegex, (match, tagName) => {
439
+ if (allowedTags.includes(tagName.toLowerCase())) {
440
+ return match
441
+ }
442
+ return ''
443
+ })
444
+
445
+ // Remove script tags and on* attributes
446
+ html = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
447
+ html = html.replace(/on\w+\s*=/gi, '')
448
+
449
+ // Apply length constraints
450
+ if (validation?.minLength && html.length < validation.minLength) {
451
+ throw new Error(`Content must be at least ${validation.minLength} characters`)
452
+ }
453
+ if (validation?.maxLength && html.length > validation.maxLength) {
454
+ html = html.substring(0, validation.maxLength)
455
+ }
456
+
457
+ return html
458
+ }
459
+
460
+ /**
461
+ * Lowercases, trims, and validates basic `name@domain.tld` format.
462
+ * @throws Error('Invalid email format') on invalid input
463
+ */
464
+ function sanitizeEmail(data: any, validation?: any): string {
465
+ let email = String(data).toLowerCase().trim()
466
+
467
+ // Basic email validation
468
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
469
+ if (!emailRegex.test(email)) {
470
+ throw new Error('Invalid email format')
471
+ }
472
+
473
+ return email
474
+ }
475
+
476
+ /**
477
+ * Strips all non-digit/non-`+` characters. Applies optional pattern validation.
478
+ * @throws Error on pattern mismatch
479
+ */
480
+ function sanitizePhone(data: any, validation?: any): string {
481
+ let phone = String(data).trim()
482
+
483
+ // Remove all non-digit characters except +
484
+ phone = phone.replace(/[^\d+]/g, '')
485
+
486
+ // Apply pattern validation if specified
487
+ if (validation?.pattern) {
488
+ const regex = new RegExp(validation.pattern)
489
+ if (!regex.test(phone)) {
490
+ throw new Error('Phone number does not match required format')
491
+ }
492
+ }
493
+
494
+ return phone
495
+ }
496
+
497
+ /**
498
+ * Parses via `new URL()` and validates `http:` or `https:` protocol.
499
+ * @throws Error on invalid URL or disallowed protocol
500
+ */
501
+ function sanitizeUrl(data: any, validation?: any): string {
502
+ let url = String(data).trim()
503
+
504
+ // Basic URL validation
505
+ try {
506
+ const urlObj = new URL(url)
507
+ // Only allow http/https protocols
508
+ if (!['http:', 'https:'].includes(urlObj.protocol)) {
509
+ throw new Error('Only HTTP and HTTPS URLs are allowed')
510
+ }
511
+ return urlObj.toString()
512
+ } catch {
513
+ throw new Error('Invalid URL format')
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Coerces to Number, applies min/max constraints, rounds down to nearest
519
+ * step if `validation.step` is set.
520
+ * @throws Error on NaN or out-of-range
521
+ */
522
+ function sanitizeNumber(data: any, validation?: any): number {
523
+ let num = Number(data)
524
+
525
+ if (isNaN(num)) {
526
+ throw new Error('Invalid number')
527
+ }
528
+
529
+ // Apply min/max constraints
530
+ if (validation?.min !== undefined && num < validation.min) {
531
+ throw new Error(`Number must be at least ${validation.min}`)
532
+ }
533
+ if (validation?.max !== undefined && num > validation.max) {
534
+ throw new Error(`Number must be at most ${validation.max}`)
535
+ }
536
+
537
+ // Apply step constraint
538
+ if (validation?.step) {
539
+ const remainder = num % validation.step
540
+ if (remainder !== 0) {
541
+ num = num - remainder // Round down to nearest step
542
+ }
543
+ }
544
+
545
+ return num
546
+ }
547
+
548
+ /**
549
+ * Coerces to Number, rounds to 2 decimal places, applies min/max.
550
+ * @throws Error on NaN or out-of-range
551
+ */
552
+ function sanitizeCurrency(data: any, validation?: any): number {
553
+ let num = Number(data)
554
+
555
+ if (isNaN(num)) {
556
+ throw new Error('Invalid currency amount')
557
+ }
558
+
559
+ // Round to 2 decimal places for currency
560
+ num = Math.round(num * 100) / 100
561
+
562
+ // Apply min/max constraints
563
+ if (validation?.min !== undefined && num < validation.min) {
564
+ throw new Error(`Amount must be at least ${validation.min}`)
565
+ }
566
+ if (validation?.max !== undefined && num > validation.max) {
567
+ throw new Error(`Amount must be at most ${validation.max}`)
568
+ }
569
+
570
+ return num
571
+ }
572
+
573
+ /** Delegates to `sanitizeNumber`. @throws same as sanitizeNumber */
574
+ function sanitizeRange(data: any, validation?: any): number {
575
+ return sanitizeNumber(data, validation)
576
+ }
577
+
578
+ /**
579
+ * Parses via `new Date()`, returns ISO date string (`YYYY-MM-DD`). Applies
580
+ * min/max date constraints.
581
+ * @throws Error on invalid date or out-of-range
582
+ */
583
+ function sanitizeDate(data: any, validation?: any): string {
584
+ let dateStr = String(data).trim()
585
+
586
+ // Try to parse as ISO date
587
+ const date = new Date(dateStr)
588
+ if (isNaN(date.getTime())) {
589
+ throw new Error('Invalid date format')
590
+ }
591
+
592
+ // Return as ISO date string
593
+ const isoDate = date.toISOString().split('T')[0]
594
+
595
+ // Apply min/max constraints
596
+ if (validation?.min) {
597
+ const minDate = new Date(validation.min)
598
+ if (date < minDate) {
599
+ throw new Error(`Date must be on or after ${validation.min}`)
600
+ }
601
+ }
602
+ if (validation?.max) {
603
+ const maxDate = new Date(validation.max)
604
+ if (date > maxDate) {
605
+ throw new Error(`Date must be on or before ${validation.max}`)
606
+ }
607
+ }
608
+
609
+ return isoDate
610
+ }
611
+
612
+ /**
613
+ * Parses via `new Date()`, returns full ISO datetime string. Applies
614
+ * min/max datetime constraints.
615
+ * @throws Error on invalid datetime or out-of-range
616
+ */
617
+ function sanitizeDatetime(data: any, validation?: any): string {
618
+ let dateStr = String(data).trim()
619
+
620
+ // Try to parse as ISO datetime
621
+ const date = new Date(dateStr)
622
+ if (isNaN(date.getTime())) {
623
+ throw new Error('Invalid datetime format')
624
+ }
625
+
626
+ // Return as ISO datetime string
627
+ const isoDatetime = date.toISOString()
628
+
629
+ // Apply min/max constraints
630
+ if (validation?.min) {
631
+ const minDate = new Date(validation.min)
632
+ if (date < minDate) {
633
+ throw new Error(`Datetime must be on or after ${validation.min}`)
634
+ }
635
+ }
636
+ if (validation?.max) {
637
+ const maxDate = new Date(validation.max)
638
+ if (date > maxDate) {
639
+ throw new Error(`Datetime must be on or before ${validation.max}`)
640
+ }
641
+ }
642
+
643
+ return isoDatetime
644
+ }
645
+
646
+ /**
647
+ * Accepts `true/false` booleans or truthy/falsy strings
648
+ * (`'true','1','yes','on'` / `'false','0','no','off'`).
649
+ * @throws Error on unrecognised value
650
+ */
651
+ function sanitizeBoolean(data: any, validation?: any): boolean {
652
+ if (typeof data === 'boolean') {
653
+ return data
654
+ }
655
+
656
+ const str = String(data).toLowerCase()
657
+ if (['true', '1', 'yes', 'on'].includes(str)) {
658
+ return true
659
+ } else if (['false', '0', 'no', 'off'].includes(str)) {
660
+ return false
661
+ }
662
+
663
+ throw new Error('Invalid boolean value')
664
+ }
665
+
666
+ /** Delegates to `sanitizeBoolean`. */
667
+ function sanitizeCheckbox(data: any, validation?: any): boolean {
668
+ return sanitizeBoolean(data, validation)
669
+ }
670
+
671
+ /**
672
+ * Validates the value against `validation.options` (string[]).
673
+ * @throws Error('Invalid option selected') if value not in allowed list
674
+ */
675
+ function sanitizeSelect(data: any, validation?: any): string {
676
+ let value = String(data).trim()
677
+
678
+ // Validate against allowed options
679
+ if (validation?.options) {
680
+ // Options are now just an array of strings
681
+ const allowedValues = Array.isArray(validation.options) ? validation.options : []
682
+ if (!allowedValues.includes(value)) {
683
+ throw new Error('Invalid option selected')
684
+ }
685
+ }
686
+
687
+ return value
688
+ }
689
+
690
+ /**
691
+ * Accepts array or comma-separated string. Deduplicates. Validates each
692
+ * value against `validation.options`. Truncates to `validation.max`.
693
+ * @throws Error on invalid option or non-array/string input
694
+ */
695
+ function sanitizeMultiselect(data: any, validation?: any): string[] {
696
+ let values: string[]
697
+
698
+ if (Array.isArray(data)) {
699
+ values = data.map(item => String(item).trim())
700
+ } else if (typeof data === 'string') {
701
+ values = data.split(',').map(item => item.trim())
702
+ } else {
703
+ throw new Error('Multiselect must be an array or comma-separated string')
704
+ }
705
+
706
+ // Remove duplicates
707
+ values = [...new Set(values)]
708
+
709
+ // Validate against allowed options
710
+ if (validation?.options) {
711
+ // Options are now just an array of strings
712
+ const allowedValues = Array.isArray(validation.options) ? validation.options : []
713
+ for (const value of values) {
714
+ if (!allowedValues.includes(value)) {
715
+ throw new Error(`Invalid option: ${value}`)
716
+ }
717
+ }
718
+ }
719
+
720
+ // Apply max selection count
721
+ if (validation?.max && values.length > validation.max) {
722
+ values = values.slice(0, validation.max)
723
+ }
724
+
725
+ return values
726
+ }
727
+
728
+ /** Delegates to `sanitizeSelect`. */
729
+ function sanitizeRadio(data: any, validation?: any): string {
730
+ return sanitizeSelect(data, validation)
731
+ }
732
+
733
+ /**
734
+ * Validates `#RGB` or `#RRGGBB` hex format. Normalizes 3-digit to 6-digit.
735
+ * Returns uppercase. @throws Error on invalid hex format
736
+ */
737
+ function sanitizeColor(data: any, validation?: any): string {
738
+ let color = String(data).trim()
739
+
740
+ // Validate hex color format
741
+ const hexRegex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/
742
+ if (!hexRegex.test(color)) {
743
+ throw new Error('Invalid color format. Use #RRGGBB or #RGB format')
744
+ }
745
+
746
+ // Normalize to 6-digit hex
747
+ if (color.length === 4) {
748
+ color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3]
749
+ }
750
+
751
+ return color.toUpperCase()
752
+ }
753
+
754
+ /**
755
+ * Validates `data.name` presence, optional maxSize and allowedTypes.
756
+ * Sanitizes filename to `[a-zA-Z0-9.-_]` characters only.
757
+ * @throws Error on invalid file data, size, or type
758
+ */
759
+ function sanitizeFile(data: any, validation?: any): any {
760
+ // Basic file validation - would need more sophisticated handling in practice
761
+ if (typeof data !== 'object' || !data.name) {
762
+ throw new Error('Invalid file data')
763
+ }
764
+
765
+ // Validate file size
766
+ if (validation?.maxSize && data.size > validation.maxSize) {
767
+ throw new Error(`File size exceeds maximum of ${validation.maxSize} bytes`)
768
+ }
769
+
770
+ // Validate file type
771
+ if (validation?.allowedTypes && !validation.allowedTypes.includes(data.type)) {
772
+ throw new Error(`File type ${data.type} is not allowed`)
773
+ }
774
+
775
+ // Sanitize filename
776
+ data.name = data.name.replace(/[^a-zA-Z0-9.-]/g, '_')
777
+
778
+ return data
779
+ }
780
+
781
+ /**
782
+ * Delegates to `sanitizeFile`. Image dimension validation is a stub
783
+ * (noted for future implementation).
784
+ * @throws same as sanitizeFile
785
+ */
786
+ function sanitizeImage(data: any, validation?: any): any {
787
+ const file = sanitizeFile(data, validation)
788
+
789
+ // Additional image-specific validation
790
+ if (validation?.maxWidth || validation?.maxHeight) {
791
+ // Would need to actually load and check image dimensions
792
+ // For now, just pass through
793
+ }
794
+
795
+ return file
796
+ }
797
+
798
+ /**
799
+ * Parses JSON strings. Rejects payloads containing `'function'`, `'eval'`,
800
+ * or `'script'` strings as a basic code-injection guard.
801
+ * @throws Error on invalid JSON or dangerous content
802
+ */
803
+ function sanitizeJson(data: any, validation?: any): any {
804
+ if (typeof data === 'string') {
805
+ try {
806
+ data = JSON.parse(data)
807
+ } catch {
808
+ throw new Error('Invalid JSON format')
809
+ }
810
+ }
811
+
812
+ // Basic security check - prevent code injection
813
+ const jsonStr = JSON.stringify(data)
814
+ if (jsonStr.includes('function') || jsonStr.includes('eval') || jsonStr.includes('script')) {
815
+ throw new Error('JSON contains potentially dangerous content')
816
+ }
817
+
818
+ return data
819
+ }
820
+
821
+ /**
822
+ * Validates UUID v1–v5 format. DB-level FK constraints enforce existence.
823
+ * @throws Error('Invalid reference format') on non-UUID input
824
+ */
825
+ function sanitizeReference(data: any, validation?: any): string {
826
+ let ref = String(data).trim()
827
+
828
+ // Validate UUID format
829
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
830
+ if (!uuidRegex.test(ref)) {
831
+ throw new Error('Invalid reference format')
832
+ }
833
+
834
+ // Would need to check existence in referenced table
835
+ // For now, just validate format
836
+
837
+ return ref
838
+ }
839
+
840
+ /**
841
+ * Sanitizes each string field of an address object via `sanitizeText`.
842
+ * Passes non-string fields through unchanged.
843
+ * @throws Error if input is not an object
844
+ */
845
+ function sanitizeAddress(data: any, validation?: any): any {
846
+ if (typeof data !== 'object' || data === null) {
847
+ throw new Error('Address must be an object')
848
+ }
849
+
850
+ // Sanitize each address component
851
+ const sanitized: any = {}
852
+ for (const [key, value] of Object.entries(data)) {
853
+ if (typeof value === 'string') {
854
+ sanitized[key] = sanitizeText(value)
855
+ } else {
856
+ sanitized[key] = value
857
+ }
858
+ }
859
+
860
+ return sanitized
861
+ }
862
+
863
+ // ─── FORMAT HELPERS ────────────────────────────────────────────────────────────
864
+
865
+ /** Formats an object as a 2-space indented JSON string. */
866
+ function formatJson(data: any): string {
867
+ return JSON.stringify(data, null, 2)
868
+ }
869
+
870
+ /** Formats an ISO date string as locale date ('January 15, 2024'). Returns raw data on invalid input. */
871
+ function formatDate(data: string): string {
872
+ const date = new Date(data)
873
+ if (isNaN(date.getTime())) {
874
+ return data
875
+ }
876
+
877
+ return date.toLocaleDateString('en-US', {
878
+ year: 'numeric',
879
+ month: 'long',
880
+ day: 'numeric'
881
+ })
882
+ }
883
+
884
+ /** Formats an ISO datetime string as locale date+time ('January 15, 2024, 02:30 PM'). Returns raw on invalid. */
885
+ function formatDatetime(data: string): string {
886
+ const date = new Date(data)
887
+ if (isNaN(date.getTime())) {
888
+ return data
889
+ }
890
+
891
+ return date.toLocaleString('en-US', {
892
+ year: 'numeric',
893
+ month: 'long',
894
+ day: 'numeric',
895
+ hour: '2-digit',
896
+ minute: '2-digit'
897
+ })
898
+ }
899
+
900
+ /** Formats a number as currency using Intl.NumberFormat. Defaults to USD. context.currency_code overrides. */
901
+ function formatCurrency(data: number, context?: any): string {
902
+ const currency = context?.currency_code || 'USD'
903
+ return new Intl.NumberFormat('en-US', {
904
+ style: 'currency',
905
+ currency: currency
906
+ }).format(data)
907
+ }
908
+
909
+ /** Formats 10-digit US numbers as '(NXX) NXX-XXXX'. 11-digit with leading 1 as '+1 (NXX) NXX-XXXX'. Returns raw otherwise. */
910
+ function formatPhone(data: string): string {
911
+ // Basic US phone formatting
912
+ const phone = data.replace(/\D/g, '')
913
+ if (phone.length === 10) {
914
+ return `(${phone.slice(0, 3)}) ${phone.slice(3, 6)}-${phone.slice(6)}`
915
+ } else if (phone.length === 11 && phone[0] === '1') {
916
+ return `+${phone[0]} (${phone.slice(1, 4)}) ${phone.slice(4, 7)}-${phone.slice(7)}`
917
+ }
918
+
919
+ return data
920
+ }
921
+
922
+ /** Pass-through — URLs are already display-ready. */
923
+ function formatUrl(data: string): string {
924
+ return data
925
+ }
926
+
927
+ /** Pass-through — UUID returned as-is; display resolution requires a DB lookup (not done here). */
928
+ function formatReference(data: string, context?: any): string {
929
+ // Would need to look up the referenced entity
930
+ // For now, return the UUID
931
+ return data
932
+ }
933
+
934
+ /** Joins address components (street, city, state, postal_code, country) into a comma-separated string. */
935
+ function formatAddress(data: any): string {
936
+ if (typeof data !== 'object' || data === null) {
937
+ return String(data)
938
+ }
939
+
940
+ const parts = [
941
+ data.street,
942
+ data.city,
943
+ data.state,
944
+ data.postal_code,
945
+ data.country
946
+ ].filter(Boolean)
947
+
948
+ return parts.join(', ')
949
+ }
950
+
951
+ /** Joins a string array with ', '. Returns String(data) for non-arrays. */
952
+ function formatMultiselect(data: string[]): string {
953
+ if (!Array.isArray(data)) {
954
+ return String(data)
955
+ }
956
+
957
+ return data.join(', ')
958
+ }
959
+
960
+ /** Returns 'Active'/'Inactive' when context.field is 'is_active'; otherwise 'Yes'/'No'. */
961
+ function formatBoolean(data: boolean, context?: any): string {
962
+ if (context?.field === 'is_active') {
963
+ return data ? 'Active' : 'Inactive'
964
+ }
965
+
966
+ return data ? 'Yes' : 'No'
967
+ }