@svgrid/enterprise 2.5.3 → 2.6.1

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 (624) hide show
  1. package/dist/SvAlertRuleEditor.svelte.d.ts +13 -0
  2. package/dist/SvAlertsManager.svelte.d.ts +14 -0
  3. package/dist/SvAlertsPanel.svelte.d.ts +9 -0
  4. package/dist/SvAuthGate.svelte.d.ts +22 -0
  5. package/dist/SvBoard.svelte.d.ts +35 -0
  6. package/dist/SvExportMenu.svelte.d.ts +61 -0
  7. package/{src → dist}/SvExpressionEditor.svelte +4 -1
  8. package/dist/SvExpressionEditor.svelte.d.ts +12 -0
  9. package/dist/SvFileInput.svelte.d.ts +20 -0
  10. package/dist/SvGridAlerts.svelte.d.ts +77 -0
  11. package/dist/SvGridBoard.svelte.d.ts +30 -0
  12. package/{src → dist}/SvGridEditPanel.svelte +173 -28
  13. package/dist/SvGridEditPanel.svelte.d.ts +75 -0
  14. package/dist/SvGridMasterDetail.svelte.d.ts +49 -0
  15. package/dist/SvGridScheduler.svelte.d.ts +30 -0
  16. package/dist/SvImportDialog.svelte.d.ts +88 -0
  17. package/dist/SvLookupInput.svelte.d.ts +21 -0
  18. package/dist/SvPivotDesigner.svelte.d.ts +176 -0
  19. package/dist/SvRecordDetail.svelte.d.ts +58 -0
  20. package/dist/SvSchedule.svelte.d.ts +29 -0
  21. package/dist/SvSchemaChart.svelte.d.ts +61 -0
  22. package/dist/SvSchemaDashboard.svelte.d.ts +46 -0
  23. package/dist/alerts/alert-engine-attach.d.ts +79 -0
  24. package/dist/alerts/alert-engine-attach.js +106 -0
  25. package/dist/alerts/alert-engine.d.ts +42 -0
  26. package/dist/alerts/alert-engine.js +196 -0
  27. package/dist/alerts/alert-formats.d.ts +26 -0
  28. package/dist/alerts/alert-formats.js +58 -0
  29. package/dist/alerts/alert-observer.d.ts +54 -0
  30. package/dist/alerts/alert-observer.js +156 -0
  31. package/dist/alerts/alert-scheduler.d.ts +30 -0
  32. package/dist/alerts/alert-scheduler.js +54 -0
  33. package/dist/alerts/alert-storage.d.ts +31 -0
  34. package/dist/alerts/alert-storage.js +92 -0
  35. package/dist/alerts/alert-store.svelte.d.ts +24 -0
  36. package/dist/alerts/alert-store.svelte.js +69 -0
  37. package/dist/alerts/alert-types.d.ts +86 -0
  38. package/dist/alerts/alert-types.js +1 -0
  39. package/dist/alerts.d.ts +2 -0
  40. package/{src/alerts.ts → dist/alerts.js} +9 -9
  41. package/dist/board.d.ts +7 -0
  42. package/{src/board.ts → dist/board.js} +13 -14
  43. package/dist/cdn/svgrid-enterprise.svelte-external.js +3220 -2607
  44. package/dist/designer/assets/index-BfzoL904.css +1 -0
  45. package/dist/designer/assets/{index-CBYU-KNn.js → index-CKdwDseq.js} +139 -139
  46. package/dist/designer/assets/src-DCKhP5Xy.css +1 -0
  47. package/dist/designer/assets/{src-i309BeJb.js → src-DLsSdh3J.js} +213 -193
  48. package/dist/designer/index.html +4 -4
  49. package/dist/edit-panel.d.ts +105 -0
  50. package/dist/edit-panel.js +350 -0
  51. package/dist/export-conditional.d.ts +31 -0
  52. package/dist/export-conditional.js +65 -0
  53. package/dist/export-ooxml.d.ts +93 -0
  54. package/dist/export-ooxml.js +385 -0
  55. package/dist/export-pdf.d.ts +107 -0
  56. package/dist/export-pdf.js +144 -0
  57. package/dist/export-print.d.ts +55 -0
  58. package/dist/export-print.js +91 -0
  59. package/{src/export-serialize.ts → dist/export-serialize.d.ts} +11 -24
  60. package/dist/export-serialize.js +11 -0
  61. package/dist/export-xls.d.ts +26 -0
  62. package/dist/export-xls.js +157 -0
  63. package/dist/export.d.ts +323 -0
  64. package/dist/export.js +1021 -0
  65. package/dist/expressions/evaluate.d.ts +17 -0
  66. package/dist/expressions/evaluate.js +214 -0
  67. package/dist/expressions/expression-columns.d.ts +61 -0
  68. package/dist/expressions/expression-columns.js +72 -0
  69. package/dist/expressions/expression-types.d.ts +125 -0
  70. package/dist/expressions/expression-types.js +1 -0
  71. package/dist/expressions/parse.d.ts +25 -0
  72. package/dist/expressions/parse.js +537 -0
  73. package/dist/import.d.ts +232 -0
  74. package/dist/import.js +846 -0
  75. package/dist/index.d.ts +66 -0
  76. package/dist/index.js +91 -0
  77. package/dist/install.d.ts +70 -0
  78. package/dist/install.js +61 -0
  79. package/dist/license-core.d.ts +37 -0
  80. package/dist/license-core.js +81 -0
  81. package/dist/license.d.ts +30 -0
  82. package/dist/license.js +139 -0
  83. package/dist/master-detail.d.ts +23 -0
  84. package/dist/master-detail.js +35 -0
  85. package/dist/node/studio.js +683 -13
  86. package/dist/pivot-chart.d.ts +28 -0
  87. package/dist/pivot-chart.js +71 -0
  88. package/dist/pivot-designer.d.ts +69 -0
  89. package/dist/pivot-designer.js +38 -0
  90. package/dist/pivot-enable.d.ts +6 -0
  91. package/{src/pivot-enable.ts → dist/pivot-enable.js} +13 -18
  92. package/dist/pivot.d.ts +111 -0
  93. package/dist/pivot.js +353 -0
  94. package/dist/print.d.ts +33 -0
  95. package/dist/print.js +61 -0
  96. package/dist/revoked.d.ts +1 -0
  97. package/{src/revoked.ts → dist/revoked.js} +5 -6
  98. package/dist/scheduler-assignments.d.ts +64 -0
  99. package/dist/scheduler-assignments.js +92 -0
  100. package/dist/scheduler-axis.d.ts +81 -0
  101. package/dist/scheduler-axis.js +180 -0
  102. package/{src/scheduler-booking.ts → dist/scheduler-booking.d.ts} +10 -43
  103. package/dist/scheduler-booking.js +50 -0
  104. package/dist/scheduler-config.d.ts +177 -0
  105. package/dist/scheduler-config.js +1 -0
  106. package/dist/scheduler-dependencies.d.ts +89 -0
  107. package/dist/scheduler-dependencies.js +179 -0
  108. package/dist/scheduler-freebusy.d.ts +19 -0
  109. package/{src/scheduler-freebusy.ts → dist/scheduler-freebusy.js} +9 -19
  110. package/{src/scheduler-heatmap.ts → dist/scheduler-heatmap.d.ts} +12 -35
  111. package/dist/scheduler-heatmap.js +41 -0
  112. package/dist/scheduler-resource-tree.d.ts +48 -0
  113. package/dist/scheduler-resource-tree.js +78 -0
  114. package/dist/scheduler-slots.d.ts +37 -0
  115. package/dist/scheduler-slots.js +80 -0
  116. package/dist/scheduler-summary.d.ts +41 -0
  117. package/dist/scheduler-summary.js +54 -0
  118. package/dist/scheduler.d.ts +7 -0
  119. package/{src/scheduler.ts → dist/scheduler.js} +13 -14
  120. package/dist/scheduling.d.ts +132 -0
  121. package/dist/scheduling.js +228 -0
  122. package/dist/schema-designer.d.ts +42 -0
  123. package/dist/schema-designer.js +117 -0
  124. package/dist/schema.d.ts +408 -0
  125. package/dist/schema.js +225 -0
  126. package/dist/smart-shim.d.ts +62 -0
  127. package/dist/smart-shim.js +34 -0
  128. package/dist/sources/aggregate.d.ts +43 -0
  129. package/dist/sources/aggregate.js +60 -0
  130. package/dist/sources/auth-supabase.d.ts +69 -0
  131. package/dist/sources/auth-supabase.js +39 -0
  132. package/dist/sources/dashboard.d.ts +65 -0
  133. package/dist/sources/dashboard.js +95 -0
  134. package/dist/sources/field-inference.d.ts +23 -0
  135. package/dist/sources/field-inference.js +52 -0
  136. package/dist/sources/filters.d.ts +35 -0
  137. package/dist/sources/filters.js +52 -0
  138. package/dist/sources/index.d.ts +18 -0
  139. package/dist/sources/index.js +18 -0
  140. package/dist/sources/introspect-supabase.d.ts +30 -0
  141. package/dist/sources/introspect-supabase.js +134 -0
  142. package/dist/sources/realtime-supabase.d.ts +37 -0
  143. package/dist/sources/realtime-supabase.js +52 -0
  144. package/dist/sources/relation-lookup.d.ts +50 -0
  145. package/dist/sources/relation-lookup.js +77 -0
  146. package/dist/sources/rest-adapters.d.ts +48 -0
  147. package/dist/sources/rest-adapters.js +85 -0
  148. package/dist/sources/rest.d.ts +38 -0
  149. package/dist/sources/rest.js +86 -0
  150. package/dist/sources/schema-from-columns.d.ts +37 -0
  151. package/dist/sources/schema-from-columns.js +48 -0
  152. package/dist/sources/supabase.d.ts +33 -0
  153. package/dist/sources/supabase.js +74 -0
  154. package/dist/sources/with-entity-rules.d.ts +25 -0
  155. package/dist/sources/with-entity-rules.js +47 -0
  156. package/dist/sources/with-relation-labels.d.ts +26 -0
  157. package/dist/sources/with-relation-labels.js +38 -0
  158. package/dist/staged-editing.d.ts +85 -0
  159. package/dist/staged-editing.js +0 -0
  160. package/dist/studio/bug-report.d.ts +61 -0
  161. package/dist/studio/bug-report.js +126 -0
  162. package/dist/studio/cli.d.ts +56 -0
  163. package/dist/studio/cli.js +70 -0
  164. package/dist/studio/copilot-core.d.ts +22 -0
  165. package/{src/studio/copilot-core.ts → dist/studio/copilot-core.js} +34 -38
  166. package/dist/studio/csv.d.ts +36 -0
  167. package/dist/studio/csv.js +173 -0
  168. package/dist/studio/db-connect-string.d.ts +45 -0
  169. package/dist/studio/db-connect-string.js +113 -0
  170. package/dist/studio/deploy-cli.d.ts +35 -0
  171. package/dist/studio/deploy-cli.js +81 -0
  172. package/dist/studio/emit-project.d.ts +77 -0
  173. package/dist/studio/emit-project.js +5463 -0
  174. package/dist/studio/emit-schema.d.ts +110 -0
  175. package/dist/studio/emit-schema.js +1128 -0
  176. package/dist/studio/index.d.ts +35 -0
  177. package/dist/studio/index.js +41 -0
  178. package/dist/studio/init-flow.d.ts +58 -0
  179. package/dist/studio/init-flow.js +451 -0
  180. package/dist/studio/introspect-db.d.ts +47 -0
  181. package/dist/studio/introspect-db.js +255 -0
  182. package/dist/studio/introspect-openapi.d.ts +19 -0
  183. package/dist/studio/introspect-openapi.js +244 -0
  184. package/dist/studio/introspect-prisma.d.ts +25 -0
  185. package/dist/studio/introspect-prisma.js +164 -0
  186. package/dist/studio/introspect.d.ts +35 -0
  187. package/dist/studio/introspect.js +306 -0
  188. package/dist/studio/project.d.ts +1448 -0
  189. package/dist/studio/project.js +2187 -0
  190. package/dist/studio/sample-data.d.ts +19 -0
  191. package/dist/studio/sample-data.js +207 -0
  192. package/dist/studio/samples/ats.d.ts +2 -0
  193. package/dist/studio/samples/ats.js +194 -0
  194. package/dist/studio/samples/clinic.d.ts +2 -0
  195. package/dist/studio/samples/clinic.js +171 -0
  196. package/dist/studio/samples/crm.d.ts +2 -0
  197. package/dist/studio/samples/crm.js +280 -0
  198. package/dist/studio/samples/datasets.d.ts +38 -0
  199. package/dist/studio/samples/datasets.js +296 -0
  200. package/dist/studio/samples/ecommerce.d.ts +2 -0
  201. package/dist/studio/samples/ecommerce.js +178 -0
  202. package/dist/studio/samples/events.d.ts +2 -0
  203. package/dist/studio/samples/events.js +192 -0
  204. package/dist/studio/samples/fleet.d.ts +2 -0
  205. package/dist/studio/samples/fleet.js +203 -0
  206. package/dist/studio/samples/gym.d.ts +2 -0
  207. package/dist/studio/samples/gym.js +205 -0
  208. package/dist/studio/samples/hr.d.ts +2 -0
  209. package/dist/studio/samples/hr.js +186 -0
  210. package/dist/studio/samples/index.d.ts +13 -0
  211. package/dist/studio/samples/index.js +44 -0
  212. package/dist/studio/samples/insurance.d.ts +2 -0
  213. package/dist/studio/samples/insurance.js +220 -0
  214. package/dist/studio/samples/inventory.d.ts +2 -0
  215. package/dist/studio/samples/inventory.js +204 -0
  216. package/dist/studio/samples/invoicing.d.ts +2 -0
  217. package/dist/studio/samples/invoicing.js +154 -0
  218. package/dist/studio/samples/library.d.ts +2 -0
  219. package/dist/studio/samples/library.js +171 -0
  220. package/dist/studio/samples/live-data.d.ts +5 -0
  221. package/dist/studio/samples/live-data.js +261 -0
  222. package/dist/studio/samples/projects.d.ts +2 -0
  223. package/dist/studio/samples/projects.js +203 -0
  224. package/dist/studio/samples/realestate.d.ts +2 -0
  225. package/dist/studio/samples/realestate.js +187 -0
  226. package/dist/studio/samples/restaurant.d.ts +2 -0
  227. package/dist/studio/samples/restaurant.js +180 -0
  228. package/dist/studio/samples/school.d.ts +2 -0
  229. package/dist/studio/samples/school.js +188 -0
  230. package/dist/studio/samples/shared.d.ts +142 -0
  231. package/dist/studio/samples/shared.js +164 -0
  232. package/dist/studio/samples/starter.d.ts +4 -0
  233. package/dist/studio/samples/starter.js +237 -0
  234. package/dist/studio/samples/subscriptions.d.ts +2 -0
  235. package/dist/studio/samples/subscriptions.js +175 -0
  236. package/dist/studio/samples/support.d.ts +2 -0
  237. package/dist/studio/samples/support.js +209 -0
  238. package/dist/studio/scaffold-app.d.ts +22 -0
  239. package/{src/studio/scaffold-app.ts → dist/studio/scaffold-app.js} +45 -63
  240. package/dist/studio/scaffold.d.ts +63 -0
  241. package/dist/studio/scaffold.js +346 -0
  242. package/dist/studio/screen-suites.d.ts +193 -0
  243. package/dist/studio/screen-suites.js +297 -0
  244. package/dist/studio/themes.d.ts +34 -0
  245. package/dist/studio/themes.js +41 -0
  246. package/dist/studio/ui-components.d.ts +89 -0
  247. package/dist/studio/ui-components.generated.d.ts +29 -0
  248. package/dist/studio/ui-components.generated.js +7319 -0
  249. package/dist/studio/ui-components.js +673 -0
  250. package/dist/studio/user-error.d.ts +23 -0
  251. package/{src/studio/user-error.ts → dist/studio/user-error.js} +10 -13
  252. package/dist/studio/verify.d.ts +34 -0
  253. package/dist/studio/verify.js +47 -0
  254. package/dist/sveltekit/in-memory.d.ts +14 -0
  255. package/dist/sveltekit/in-memory.js +113 -0
  256. package/dist/sveltekit/index.d.ts +6 -0
  257. package/dist/sveltekit/index.js +5 -0
  258. package/dist/sveltekit/query-plan.d.ts +44 -0
  259. package/dist/sveltekit/query-plan.js +76 -0
  260. package/dist/sveltekit/sql-source.d.ts +47 -0
  261. package/dist/sveltekit/sql-source.js +106 -0
  262. package/dist/sveltekit/sql.d.ts +48 -0
  263. package/dist/sveltekit/sql.js +73 -0
  264. package/dist/sveltekit/transport.d.ts +209 -0
  265. package/dist/sveltekit/transport.js +219 -0
  266. package/{src/sveltekit/types.ts → dist/sveltekit/types.d.ts} +8 -11
  267. package/dist/sveltekit/types.js +1 -0
  268. package/dist/upgrade-prompt.d.ts +16 -0
  269. package/dist/upgrade-prompt.js +141 -0
  270. package/dist/version.d.ts +13 -0
  271. package/{src/version.ts → dist/version.js} +1 -1
  272. package/dist/watermark.d.ts +2 -0
  273. package/dist/watermark.js +146 -0
  274. package/package.json +28 -26
  275. package/dist/designer/assets/index-oYok52zd.css +0 -1
  276. package/dist/designer/assets/src-D-O2F805.css +0 -1
  277. package/src/SvBoard.dom.test.ts +0 -67
  278. package/src/SvGridAlerts.dom.test.ts +0 -113
  279. package/src/SvGridEditPanel.dom.test.ts +0 -320
  280. package/src/SvRecordDetail.dom.test.ts +0 -137
  281. package/src/SvSchedule.dom.test.ts +0 -57
  282. package/src/ai-export-pdf.dom.test.ts +0 -77
  283. package/src/ai-export-xlsx.dom.test.ts +0 -90
  284. package/src/ai-export.dom.test.ts +0 -114
  285. package/src/alerts/alert-engine-attach.ts +0 -165
  286. package/src/alerts/alert-engine.test.ts +0 -135
  287. package/src/alerts/alert-engine.ts +0 -260
  288. package/src/alerts/alert-formats.test.ts +0 -87
  289. package/src/alerts/alert-formats.ts +0 -77
  290. package/src/alerts/alert-observer.test.ts +0 -189
  291. package/src/alerts/alert-observer.ts +0 -208
  292. package/src/alerts/alert-scheduler.ts +0 -96
  293. package/src/alerts/alert-storage.test.ts +0 -54
  294. package/src/alerts/alert-storage.ts +0 -116
  295. package/src/alerts/alert-store.svelte.ts +0 -80
  296. package/src/alerts/alert-types.ts +0 -94
  297. package/src/board.dom.test.ts +0 -941
  298. package/src/edit-panel.test.ts +0 -322
  299. package/src/edit-panel.ts +0 -379
  300. package/src/export-conditional.test.ts +0 -60
  301. package/src/export-conditional.ts +0 -94
  302. package/src/export-ooxml.test.ts +0 -152
  303. package/src/export-ooxml.ts +0 -489
  304. package/src/export-pdf.test.ts +0 -138
  305. package/src/export-pdf.ts +0 -245
  306. package/src/export-print.test.ts +0 -66
  307. package/src/export-print.ts +0 -132
  308. package/src/export-serialize.test.ts +0 -56
  309. package/src/export-xls.ts +0 -199
  310. package/src/export-xlsx-roundtrip.test.ts +0 -120
  311. package/src/export-xlsx.test.ts +0 -150
  312. package/src/export.test.ts +0 -349
  313. package/src/export.ts +0 -1464
  314. package/src/expressions/evaluate.test.ts +0 -111
  315. package/src/expressions/evaluate.ts +0 -237
  316. package/src/expressions/expression-columns.ts +0 -133
  317. package/src/expressions/expression-types.ts +0 -84
  318. package/src/expressions/parse.test.ts +0 -106
  319. package/src/expressions/parse.ts +0 -610
  320. package/src/import-automap.test.ts +0 -131
  321. package/src/import-hardening.test.ts +0 -158
  322. package/src/import.test.ts +0 -415
  323. package/src/import.ts +0 -1044
  324. package/src/index.ts +0 -569
  325. package/src/install.ts +0 -154
  326. package/src/license-core.test.ts +0 -23
  327. package/src/license-core.ts +0 -107
  328. package/src/license-expiry.test.ts +0 -144
  329. package/src/license.ts +0 -157
  330. package/src/master-detail.test.ts +0 -61
  331. package/src/master-detail.ts +0 -42
  332. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -178
  333. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js +0 -3875
  334. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-ZrPsKxob.js.map +0 -1
  335. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js +0 -21
  336. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-ACqvWGE2.js.map +0 -1
  337. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js +0 -1938
  338. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-D3w5jt92.js.map +0 -1
  339. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js +0 -205
  340. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-CC2V3H0y.js.map +0 -1
  341. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js +0 -18
  342. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-BXP37tAa.js.map +0 -1
  343. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js +0 -264
  344. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-DwubnU5b.js.map +0 -1
  345. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js +0 -697
  346. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-hD7f6PUz.js.map +0 -1
  347. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -134
  348. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -1
  349. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -55
  350. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -1
  351. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -2
  352. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -46843
  353. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -1
  354. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -237
  355. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -1
  356. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -2
  357. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -4
  358. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -1
  359. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -7
  360. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -4
  361. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -1
  362. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -5
  363. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -1
  364. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -5
  365. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -1
  366. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -5
  367. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -1
  368. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -6
  369. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -83
  370. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -1
  371. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -559
  372. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -1
  373. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -24
  374. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -1
  375. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -122
  376. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -1
  377. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -2
  378. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -89
  379. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -1
  380. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -241
  381. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -1
  382. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js +0 -4938
  383. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DBx1SCN6.js.map +0 -1
  384. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js +0 -495
  385. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-gjfpSTFS.js.map +0 -1
  386. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js +0 -190
  387. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-DKoOKg8S.js.map +0 -1
  388. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js +0 -8
  389. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-C0MiR7eR.js.map +0 -1
  390. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js +0 -138
  391. package/src/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-B6Y7P0Q-.js.map +0 -1
  392. package/src/pivot-chart.test.ts +0 -86
  393. package/src/pivot-chart.ts +0 -112
  394. package/src/pivot-designer.ts +0 -101
  395. package/src/pivot.test.ts +0 -335
  396. package/src/pivot.ts +0 -578
  397. package/src/print.ts +0 -107
  398. package/src/scheduler-assignments.test.ts +0 -97
  399. package/src/scheduler-assignments.ts +0 -134
  400. package/src/scheduler-axis.test.ts +0 -108
  401. package/src/scheduler-axis.ts +0 -238
  402. package/src/scheduler-booking.test.ts +0 -57
  403. package/src/scheduler-config.ts +0 -179
  404. package/src/scheduler-dependencies.test.ts +0 -155
  405. package/src/scheduler-dependencies.ts +0 -223
  406. package/src/scheduler-freebusy.test.ts +0 -41
  407. package/src/scheduler-heatmap.test.ts +0 -39
  408. package/src/scheduler-resource-tree.test.ts +0 -84
  409. package/src/scheduler-resource-tree.ts +0 -107
  410. package/src/scheduler-slots.test.ts +0 -53
  411. package/src/scheduler-slots.ts +0 -94
  412. package/src/scheduler-summary.test.ts +0 -47
  413. package/src/scheduler-summary.ts +0 -81
  414. package/src/scheduling.test.ts +0 -194
  415. package/src/scheduling.ts +0 -293
  416. package/src/schema-designer.test.ts +0 -121
  417. package/src/schema-designer.ts +0 -142
  418. package/src/schema.test.ts +0 -268
  419. package/src/schema.ts +0 -562
  420. package/src/smart-shim.ts +0 -107
  421. package/src/sources/aggregate.test.ts +0 -79
  422. package/src/sources/aggregate.ts +0 -102
  423. package/src/sources/auth-supabase.test.ts +0 -80
  424. package/src/sources/auth-supabase.ts +0 -87
  425. package/src/sources/dashboard.kpi.test.ts +0 -50
  426. package/src/sources/dashboard.test.ts +0 -61
  427. package/src/sources/dashboard.ts +0 -136
  428. package/src/sources/field-inference.test.ts +0 -60
  429. package/src/sources/field-inference.ts +0 -63
  430. package/src/sources/filters.test.ts +0 -58
  431. package/src/sources/filters.ts +0 -70
  432. package/src/sources/index.ts +0 -67
  433. package/src/sources/introspect-supabase.test.ts +0 -112
  434. package/src/sources/introspect-supabase.ts +0 -139
  435. package/src/sources/realtime-supabase.test.ts +0 -93
  436. package/src/sources/realtime-supabase.ts +0 -99
  437. package/src/sources/relation-lookup.test.ts +0 -99
  438. package/src/sources/relation-lookup.ts +0 -128
  439. package/src/sources/rest-adapters.test.ts +0 -95
  440. package/src/sources/rest-adapters.ts +0 -120
  441. package/src/sources/rest.test.ts +0 -104
  442. package/src/sources/rest.ts +0 -129
  443. package/src/sources/schema-from-columns.test.ts +0 -106
  444. package/src/sources/schema-from-columns.ts +0 -88
  445. package/src/sources/supabase.test.ts +0 -110
  446. package/src/sources/supabase.ts +0 -99
  447. package/src/sources/with-entity-rules.test.ts +0 -104
  448. package/src/sources/with-entity-rules.ts +0 -78
  449. package/src/sources/with-relation-labels.test.ts +0 -75
  450. package/src/sources/with-relation-labels.ts +0 -73
  451. package/src/staged-editing.ts +0 -0
  452. package/src/studio/bug-report.test.ts +0 -101
  453. package/src/studio/bug-report.ts +0 -165
  454. package/src/studio/cli.test.ts +0 -187
  455. package/src/studio/cli.ts +0 -117
  456. package/src/studio/copilot-core.test.ts +0 -45
  457. package/src/studio/csv.test.ts +0 -90
  458. package/src/studio/csv.ts +0 -165
  459. package/src/studio/db-connect-string.test.ts +0 -94
  460. package/src/studio/db-connect-string.ts +0 -128
  461. package/src/studio/deploy-cli.test.ts +0 -56
  462. package/src/studio/deploy-cli.ts +0 -100
  463. package/src/studio/emit-project.test.ts +0 -3324
  464. package/src/studio/emit-project.ts +0 -5377
  465. package/src/studio/emit-schema.test.ts +0 -111
  466. package/src/studio/emit-schema.ts +0 -1181
  467. package/src/studio/index.ts +0 -380
  468. package/src/studio/init-flow.test.ts +0 -514
  469. package/src/studio/init-flow.ts +0 -556
  470. package/src/studio/introspect-db.test.ts +0 -186
  471. package/src/studio/introspect-db.ts +0 -312
  472. package/src/studio/introspect-openapi.test.ts +0 -84
  473. package/src/studio/introspect-openapi.ts +0 -252
  474. package/src/studio/introspect-prisma.test.ts +0 -99
  475. package/src/studio/introspect-prisma.ts +0 -175
  476. package/src/studio/introspect.test.ts +0 -172
  477. package/src/studio/introspect.ts +0 -310
  478. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -1
  479. package/src/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -3
  480. package/src/studio/pipeline.test.ts +0 -42
  481. package/src/studio/project-robust.test.ts +0 -157
  482. package/src/studio/project.test.ts +0 -929
  483. package/src/studio/project.ts +0 -2394
  484. package/src/studio/sample-data.test.ts +0 -58
  485. package/src/studio/sample-data.ts +0 -203
  486. package/src/studio/samples/ats.ts +0 -202
  487. package/src/studio/samples/clinic.ts +0 -179
  488. package/src/studio/samples/crm.ts +0 -291
  489. package/src/studio/samples/datasets.test.ts +0 -84
  490. package/src/studio/samples/datasets.ts +0 -340
  491. package/src/studio/samples/ecommerce.ts +0 -187
  492. package/src/studio/samples/events.ts +0 -201
  493. package/src/studio/samples/fleet.ts +0 -213
  494. package/src/studio/samples/gym.ts +0 -215
  495. package/src/studio/samples/hr.ts +0 -195
  496. package/src/studio/samples/index.ts +0 -55
  497. package/src/studio/samples/insurance.ts +0 -229
  498. package/src/studio/samples/inventory.ts +0 -213
  499. package/src/studio/samples/invoicing.ts +0 -162
  500. package/src/studio/samples/library.ts +0 -180
  501. package/src/studio/samples/live-data.test.ts +0 -99
  502. package/src/studio/samples/live-data.ts +0 -306
  503. package/src/studio/samples/projects.ts +0 -212
  504. package/src/studio/samples/realestate.ts +0 -196
  505. package/src/studio/samples/restaurant.ts +0 -189
  506. package/src/studio/samples/samples.test.ts +0 -304
  507. package/src/studio/samples/school.ts +0 -197
  508. package/src/studio/samples/seed-floor.test.ts +0 -25
  509. package/src/studio/samples/shared.ts +0 -237
  510. package/src/studio/samples/starter.ts +0 -251
  511. package/src/studio/samples/subscriptions.ts +0 -183
  512. package/src/studio/samples/support.ts +0 -216
  513. package/src/studio/scaffold-app.test.ts +0 -91
  514. package/src/studio/scaffold.test.ts +0 -192
  515. package/src/studio/scaffold.ts +0 -390
  516. package/src/studio/screen-suites.test.ts +0 -269
  517. package/src/studio/screen-suites.ts +0 -448
  518. package/src/studio/themes.ts +0 -62
  519. package/src/studio/ui-components-surface.test.ts +0 -185
  520. package/src/studio/ui-components.generated.ts +0 -7154
  521. package/src/studio/ui-components.ts +0 -742
  522. package/src/studio/verify.test.ts +0 -47
  523. package/src/studio/verify.ts +0 -79
  524. package/src/sveltekit/in-memory.test.ts +0 -104
  525. package/src/sveltekit/in-memory.ts +0 -129
  526. package/src/sveltekit/index.ts +0 -24
  527. package/src/sveltekit/query-plan.test.ts +0 -109
  528. package/src/sveltekit/query-plan.ts +0 -125
  529. package/src/sveltekit/sql-source.test.ts +0 -198
  530. package/src/sveltekit/sql-source.ts +0 -171
  531. package/src/sveltekit/sql.test.ts +0 -80
  532. package/src/sveltekit/sql.ts +0 -131
  533. package/src/sveltekit/transport-scope.test.ts +0 -125
  534. package/src/sveltekit/transport.test.ts +0 -201
  535. package/src/sveltekit/transport.ts +0 -373
  536. package/src/upgrade-prompt.test.ts +0 -71
  537. package/src/upgrade-prompt.ts +0 -162
  538. package/src/version.test.ts +0 -17
  539. package/src/watermark.test.ts +0 -97
  540. package/src/watermark.ts +0 -156
  541. /package/{src → dist}/SvAlertRuleEditor.svelte +0 -0
  542. /package/{src → dist}/SvAlertsManager.svelte +0 -0
  543. /package/{src → dist}/SvAlertsPanel.svelte +0 -0
  544. /package/{src → dist}/SvAuthGate.svelte +0 -0
  545. /package/{src → dist}/SvBoard.svelte +0 -0
  546. /package/{src → dist}/SvExportMenu.svelte +0 -0
  547. /package/{src → dist}/SvFileInput.svelte +0 -0
  548. /package/{src → dist}/SvGridAlerts.svelte +0 -0
  549. /package/{src → dist}/SvGridBoard.svelte +0 -0
  550. /package/{src → dist}/SvGridMasterDetail.svelte +0 -0
  551. /package/{src → dist}/SvGridScheduler.svelte +0 -0
  552. /package/{src → dist}/SvImportDialog.svelte +0 -0
  553. /package/{src → dist}/SvLookupInput.svelte +0 -0
  554. /package/{src → dist}/SvPivotDesigner.svelte +0 -0
  555. /package/{src → dist}/SvRecordDetail.svelte +0 -0
  556. /package/{src → dist}/SvSchedule.svelte +0 -0
  557. /package/{src → dist}/SvSchemaChart.svelte +0 -0
  558. /package/{src → dist}/SvSchemaDashboard.svelte +0 -0
  559. /package/{src → dist}/pdfmake-shims.d.ts +0 -0
  560. /package/{src → dist}/smart.export.d.ts +0 -0
  561. /package/{src → dist}/smart.export.js +0 -0
  562. /package/{src → dist}/studio/HANDLERS-DESIGN.md +0 -0
  563. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/_svelte_metadata.json +0 -0
  564. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/_metadata.json +0 -0
  565. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js +0 -0
  566. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/attachments-BOv1rBKp.js.map +0 -0
  567. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js +0 -0
  568. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/constants-B9vgIdCd.js.map +0 -0
  569. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js +0 -0
  570. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/dev-T1qbW_qD.js.map +0 -0
  571. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js +0 -0
  572. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/events-Bo11SfVK.js.map +0 -0
  573. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js +0 -0
  574. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/flags-X7D4vfrZ.js.map +0 -0
  575. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js +0 -0
  576. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/hydration-oyUje5i1.js.map +0 -0
  577. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js +0 -0
  578. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/legacy-client-DeSQgSFm.js.map +0 -0
  579. /package/{src → dist/studio}/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/package.json +0 -0
  580. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js +0 -0
  581. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte.js.map +0 -0
  582. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js +0 -0
  583. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_animate.js.map +0 -0
  584. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_attachments.js +0 -0
  585. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js +0 -0
  586. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_compiler.js.map +0 -0
  587. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js +0 -0
  588. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_easing.js.map +0 -0
  589. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_events.js +0 -0
  590. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js +0 -0
  591. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal.js.map +0 -0
  592. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_client.js +0 -0
  593. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js +0 -0
  594. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_disclose-version.js.map +0 -0
  595. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js +0 -0
  596. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_async.js.map +0 -0
  597. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js +0 -0
  598. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_legacy.js.map +0 -0
  599. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js +0 -0
  600. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_flags_tracing.js.map +0 -0
  601. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_internal_server.js +0 -0
  602. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js +0 -0
  603. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_legacy.js.map +0 -0
  604. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js +0 -0
  605. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_motion.js.map +0 -0
  606. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js +0 -0
  607. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity.js.map +0 -0
  608. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js +0 -0
  609. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_reactivity_window.js.map +0 -0
  610. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_server.js +0 -0
  611. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js +0 -0
  612. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_store.js.map +0 -0
  613. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js +0 -0
  614. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/svelte_transition.js.map +0 -0
  615. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js +0 -0
  616. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-BbWyXNk1.js.map +0 -0
  617. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js +0 -0
  618. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/utils-DI_fC7vt.js.map +0 -0
  619. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js +0 -0
  620. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/validate-Dyr3lQv9.js.map +0 -0
  621. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js +0 -0
  622. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/version-_9FHH-LF.js.map +0 -0
  623. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js +0 -0
  624. /package/{src → dist}/studio/node_modules/.vite/vitest/be65d27ae088a0e03fd8e1331d90b01649464cb6/deps___vitest__/warnings-CUbSPnyP.js.map +0 -0
@@ -1,1181 +0,0 @@
1
- /**
2
- * emitStudioApp - turn a set of `EntitySchema`s into the source files of a
3
- * runnable **create-studio** app (the EntityScreen-style template). This is what
4
- * `npm create @svgrid/studio --from <schema>` writes instead of the seeded
5
- * Customers/Orders: a `schemas.ts` + `data.ts` + one route per entity + the nav
6
- * shell, all driven by the user's real Drizzle / Prisma schema.
7
- *
8
- * Pure string building (like `scaffold` / `scaffoldApp`), and node-safe - it
9
- * lives in the `./studio` subtree so `create-studio` gets it from the Node
10
- * build. It targets the template's conventions: `$lib/EntityScreen.svelte`,
11
- * `$lib/schemas`, `$lib/data`, and the `st-*` / `.home` CSS classes.
12
- */
13
- import { resolveIdField, titleCase, type EntityField, type EntitySchema, type ValidationRuleSpec } from '../schema.js'
14
- import type { GeneratedFile } from './scaffold.js'
15
- import type { EntityDataSource, RestAdapterConfig, RestSource, ShellConfig, ShellStyle, SqlDialectKind, SqlSource, EntityTriggers, TriggerEvent } from './project.js'
16
- import { sanitizeClassName, compileTriggerSteps, TRIGGER_EVENTS } from './project.js'
17
-
18
- /** Build the createKitHandlers `hooks: {...}` option from an entity's triggers.
19
- * before-hooks alias the mutable payload as `v`; after-hooks alias the saved row. */
20
- function triggerHooksOpt(triggers: EntityTriggers | undefined): string | null {
21
- if (!triggers) return null
22
- const SHAPE: Record<TriggerEvent, { param: string; alias: string }> = {
23
- beforeCreate: { param: '{ values }', alias: 'const v = values as Record<string, unknown>' },
24
- beforeUpdate: { param: '{ patch }', alias: 'const v = patch as Record<string, unknown>' },
25
- beforeDelete: { param: '{ id }', alias: 'const v = { id } as Record<string, unknown>' },
26
- afterCreate: { param: '{ row }', alias: 'const v = row as Record<string, unknown>' },
27
- afterUpdate: { param: '{ row }', alias: 'const v = row as Record<string, unknown>' },
28
- afterDelete: { param: '{ id }', alias: 'const v = { id } as Record<string, unknown>' },
29
- }
30
- const entries: string[] = []
31
- for (const ev of TRIGGER_EVENTS) {
32
- const steps = triggers[ev]
33
- if (!steps?.length) continue
34
- const { param, alias } = SHAPE[ev]
35
- const body = compileTriggerSteps(steps).split('\n').map((l) => (l ? ' ' + l : l)).join('\n')
36
- entries.push(` ${ev}: async (${param}) => {\n ${alias}\n${body}\n }`)
37
- }
38
- return entries.length ? `hooks: {\n${entries.join(',\n')},\n }` : null
39
- }
40
- import { generateValue } from './sample-data.js'
41
-
42
- const pascal = (name: string): string =>
43
- titleCase(name).replace(/\s+/g, '')
44
-
45
- const camel = (name: string): string => {
46
- const p = pascal(name)
47
- return p.charAt(0).toLowerCase() + p.slice(1)
48
- }
49
-
50
- /** Entity field type -> a TS property type for the generated row type. */
51
- function tsType(field: EntityField): string {
52
- // A chips/tags editor stores multiple values (string[]) - its seed rows + form
53
- // value are arrays, so the row type must be string[] even on a `text` field.
54
- if (field.input?.editorType === 'chips') return 'string[]'
55
- const type = field.type
56
- if (type === 'number') return 'number'
57
- if (type === 'boolean') return 'boolean'
58
- if (type === 'json') return 'unknown'
59
- return 'string' // text / date / dateString / datetime / enum / relation
60
- }
61
-
62
- /** How many sample rows to seed per entity. */
63
- const SEED_COUNT = 6
64
-
65
- type Names = {
66
- type: string // PascalCase row type, e.g. `User`
67
- schemaVar: string // e.g. `userSchema`
68
- sourceVar: string // e.g. `userSource`
69
- label: string // display label
70
- route: string // route segment (= schema name)
71
- idPrefix: string // seed id prefix for nextId()
72
- }
73
-
74
- export function namesFor(schema: EntitySchema): Names {
75
- return {
76
- type: pascal(schema.name),
77
- schemaVar: `${camel(schema.name)}Schema`,
78
- sourceVar: `${camel(schema.name)}Source`,
79
- label: schema.label ?? titleCase(schema.name),
80
- route: schema.name,
81
- idPrefix: schema.name.charAt(0).toLowerCase() || 'r',
82
- }
83
- }
84
-
85
- /** Lookup variable name for a relation field, e.g. `postAuthorIdLookup`. */
86
- export const lookupVar = (schema: EntitySchema, field: string) => `${camel(schema.name)}${pascal(field)}Lookup`
87
-
88
- function rowType(schema: EntitySchema, derived: Set<string>): string {
89
- // Runtime-filled columns are optional in the row type: relation labels (`derived`)
90
- // and computed / no-code-formula fields (not present on seed rows / inserts).
91
- // So is a field the form only asks for under a condition - a row that never met
92
- // the condition simply does not carry it (a won deal has no "lost reason").
93
- const optional = (f: EntityField) =>
94
- derived.has(f.field) || !!f.formula || !!f.computed || !!f.when?.visible
95
- const lines = schema.fields.map((f) => ` ${f.field}${optional(f) ? '?' : ''}: ${tsType(f)}`)
96
- return `export type ${pascal(schema.name)} = {\n${lines.join('\n')}\n}`
97
- }
98
-
99
- /** Base (unwrapped) in-memory source variable, e.g. `userStore`. */
100
- const storeVar = (schema: EntitySchema) => `${camel(schema.name)}Store`
101
-
102
- type RelationInfo = { fkField: string; displayField: string; related: EntitySchema }
103
-
104
- /**
105
- * Map each relation FK field (whose target resolves) to the denormalized display
106
- * field name that `withRelationLabels` fills onto every row - e.g. `companyId` ->
107
- * `company`, else `<fk>Label`. Used by the grid (via `relationInfos`) and by any
108
- * view that renders a relation as text (board/calendar titles) so it shows the
109
- * related label instead of the raw FK id. Order-dependent collision handling must
110
- * match `relationInfos` exactly, so both go through here.
111
- */
112
- export function relationDisplayFields(schema: EntitySchema, resolve: (name: string) => EntitySchema | undefined): Map<string, string> {
113
- const existing = new Set(schema.fields.map((f) => f.field))
114
- const out = new Map<string, string>()
115
- for (const f of schema.fields) {
116
- if (f.type !== 'relation' || !f.relation) continue
117
- if (!resolve(f.relation.entity)) continue
118
- // Show the label under a friendly name: `authorId` -> `author`, else `<fk>Label`.
119
- let display = /(_id|Id)$/.test(f.field) ? f.field.replace(/(_id|Id)$/, '') : `${f.field}Label`
120
- if (!display || existing.has(display)) display = `${f.field}Label`
121
- while (existing.has(display)) display += '_'
122
- existing.add(display)
123
- out.set(f.field, display)
124
- }
125
- return out
126
- }
127
-
128
- /** Relation fields of a schema whose target is present in the set. */
129
- function relationInfos(schema: EntitySchema, byName: Map<string, EntitySchema>): RelationInfo[] {
130
- const display = relationDisplayFields(schema, (name) => byName.get(name))
131
- const infos: RelationInfo[] = []
132
- for (const f of schema.fields) {
133
- if (f.type !== 'relation' || !f.relation) continue
134
- const related = byName.get(f.relation.entity)
135
- if (!related) continue
136
- infos.push({ fkField: f.field, displayField: display.get(f.field)!, related })
137
- }
138
- return infos
139
- }
140
-
141
- /**
142
- * Rewrite a schema for a linked grid: hide each FK column from the grid (it
143
- * stays a lookup in the form) and append a read-only display column that shows
144
- * the related label (filled by `withRelationLabels` in data.ts).
145
- */
146
- function prepareSchema(schema: EntitySchema, infos: RelationInfo[]): EntitySchema {
147
- const pkId = resolveIdField(schema)
148
- const fkFields = new Set(infos.map((i) => i.fkField))
149
- const fields: EntityField[] = schema.fields.map((f) => {
150
- let nf = f
151
- // The in-memory starter uses string ids, so the key is a text field.
152
- if (f.field === pkId && f.type !== 'text') nf = { ...nf, type: 'text' }
153
- if (fkFields.has(f.field)) {
154
- const prev = typeof nf.hidden === 'object' ? nf.hidden : {}
155
- nf = { ...nf, hidden: { ...prev, grid: true } }
156
- }
157
- return nf
158
- })
159
- for (const i of infos) {
160
- fields.push({ field: i.displayField, type: 'text', label: titleCase(i.displayField), readonly: true, hidden: { form: true } })
161
- }
162
- return { ...schema, fields }
163
- }
164
-
165
- type Prepared = { schema: EntitySchema; infos: RelationInfo[] }
166
-
167
- /** N sample rows per entity, with valid (string) ids + foreign-key references. */
168
- function generateSeed(entries: Prepared[]): Map<string, Array<Record<string, unknown>>> {
169
- const seed = new Map<string, Array<Record<string, unknown>>>()
170
- for (const { schema, infos } of entries) {
171
- const pkId = resolveIdField(schema)
172
- const idPrefix = namesFor(schema).idPrefix
173
- const relPrefix = new Map(infos.map((i) => [i.fkField, namesFor(i.related).idPrefix]))
174
- const display = new Set(infos.map((i) => i.displayField))
175
- const rows: Array<Record<string, unknown>> = []
176
- for (let i = 0; i < SEED_COUNT; i++) {
177
- const row: Record<string, unknown> = {}
178
- for (const f of schema.fields) {
179
- if (display.has(f.field)) continue // relation label, filled at runtime
180
- if (f.field === pkId) row[f.field] = `${idPrefix}${i + 1}`
181
- else if (f.type === 'relation') {
182
- const rp = relPrefix.get(f.field)
183
- row[f.field] = rp ? `${rp}${(i % SEED_COUNT) + 1}` : ''
184
- } else row[f.field] = generateValue(f, i)
185
- }
186
- rows.push(row)
187
- }
188
- seed.set(schema.name, rows)
189
- }
190
- return seed
191
- }
192
-
193
- /** Compile a no-code formula into a JS expression: bare field names -> `$.field`
194
- * (where `$` is the row, typed loosely so arithmetic never trips svelte-check). */
195
- function compileFormula(formula: string, fieldNames: string[]): string {
196
- const names = [...fieldNames].sort((a, b) => b.length - a.length).map((f) => f.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
197
- if (!names.length) return formula
198
- return formula.replace(new RegExp('\\b(' + names.join('|') + ')\\b', 'g'), '$.$1')
199
- }
200
- /** The condition a validation rule asserts must hold (fail -> attach its message). */
201
- function compileValidation(r: ValidationRuleSpec): string {
202
- const lhs = `$[${JSON.stringify(r.field)}]`
203
- const num = (v: unknown) => (r.compareTo ? `Number($[${JSON.stringify(r.compareTo)}])` : Number(v))
204
- const str = (v: unknown) => (r.compareTo ? `String($[${JSON.stringify(r.compareTo)}])` : JSON.stringify(String(v ?? '')))
205
- switch (r.op) {
206
- case 'eq': return `String(${lhs}) === ${str(r.value)}`
207
- case 'ne': return `String(${lhs}) !== ${str(r.value)}`
208
- case 'lt': return `Number(${lhs}) < ${num(r.value)}`
209
- case 'lte': return `Number(${lhs}) <= ${num(r.value)}`
210
- case 'gt': return `Number(${lhs}) > ${num(r.value)}`
211
- case 'gte': return `Number(${lhs}) >= ${num(r.value)}`
212
- case 'required': return `${lhs} != null && ${lhs} !== ''`
213
- case 'maxLen': return `String(${lhs} ?? '').length <= ${Number(r.value)}`
214
- case 'minLen': return `String(${lhs} ?? '').length >= ${Number(r.value)}`
215
- default: return 'true'
216
- }
217
- }
218
-
219
- function schemasModule(entries: Prepared[]): GeneratedFile {
220
- const body = entries
221
- .map(({ schema: s, infos }) => {
222
- const n = namesFor(s)
223
- const derived = new Set(infos.map((i) => i.displayField))
224
- const fieldNames = s.fields.map((f) => f.field)
225
- // Strip the no-code specs from the emitted DATA literal; compile them below
226
- // into real `computed` / `hooks.validate` functions (JSON drops functions).
227
- const clean: EntitySchema = {
228
- ...s,
229
- fields: s.fields.map((f) => { const { formula: _f, ...rest } = f; return rest }),
230
- }
231
- delete (clean as { validations?: unknown }).validations
232
- let out = `${rowType(s, derived)}\n\nexport const ${n.schemaVar}: EntitySchema<${n.type}> = ${JSON.stringify(clean, null, 2)}`
233
-
234
- // Computed fields from formulas.
235
- const aug: string[] = []
236
- for (const f of s.fields) {
237
- if (!f.formula || f.computed) continue
238
- aug.push(`;(${n.schemaVar}.fields.find((f) => f.field === ${JSON.stringify(f.field)})!).computed = (row) => { const $ = row as Record<string, any>; return (${compileFormula(f.formula, fieldNames)}) }`)
239
- }
240
- // Cross-field validation rules -> hooks.validate.
241
- if (s.validations?.length) {
242
- const checks = s.validations.map((r) => ` if (!(${compileValidation(r)})) errors[${JSON.stringify(r.field)}] = ${JSON.stringify(r.message)}`).join('\n')
243
- aug.push(`${n.schemaVar}.hooks = {\n ...${n.schemaVar}.hooks,\n validate: (values) => {\n const $ = values as Record<string, any>\n const errors: Record<string, string> = {}\n${checks}\n return Object.keys(errors).length ? errors : null\n },\n}`)
244
- }
245
- if (aug.length) out += '\n\n' + aug.join('\n')
246
- return out
247
- })
248
- .join('\n\n')
249
- return {
250
- path: 'src/lib/schemas.ts',
251
- description: 'One EntitySchema per entity, generated from your schema. Add computed fields, hooks, or validators here.',
252
- contents: `/**
253
- * Generated from your schema by \`npm create @svgrid/studio --from\`. Each
254
- * EntitySchema drives the grid columns, the edit form, and validation. Add a
255
- * field here (and to its type) and it shows up in the grid and the form.
256
- */
257
- import type { EntitySchema } from '@svgrid/enterprise'
258
-
259
- ${body}
260
- `,
261
- }
262
- }
263
-
264
- // --- REST / SQL source-expression helpers ----------------------------------
265
-
266
- const BACKTICK = String.fromCharCode(96)
267
-
268
- /** Join a base URL and a path, tolerating leading/trailing slashes + empty base. */
269
- function joinUrl(base: string, path: string): string {
270
- const b = (base ?? '').replace(/\/+$/, '')
271
- const p = (path ?? '').replace(/^\/+/, '')
272
- if (!b) return p
273
- if (!p) return b
274
- return `${b}/${p}`
275
- }
276
-
277
- /** `body` + `data.items` -> `body?.data?.items` (safe access into a response). */
278
- function dottedAccess(root: string, dotted: string): string {
279
- return dotted.split('.').filter(Boolean).reduce((acc, key) => `${acc}?.${key}`, root)
280
- }
281
-
282
- /** The REST collection URL with static path params substituted (`{id}` -> value). */
283
- function buildRestUrl(src: RestSource): string {
284
- let path = src.path
285
- for (const p of src.params) {
286
- if (p.location === 'path' && p.value) path = path.split(`{${p.name}}`).join(p.value)
287
- }
288
- return joinUrl(src.baseUrl, path)
289
- }
290
-
291
- /** A `SqlDialect` object literal for the dialect, or null to use adapter defaults. */
292
- function sqlDialectExpr(dialect?: SqlDialectKind): string | null {
293
- switch (dialect) {
294
- case 'postgres':
295
- case 'supabase': return "{ placeholders: '$', ilike: true }"
296
- case 'mysql': return `{ quote: '${BACKTICK}', placeholders: '?' }`
297
- case 'mssql': return "{ placeholders: '@' }"
298
- case 'turso': return "{ placeholders: '?' }" // libSQL / SQLite positional args
299
- default: return null // sqlite / undefined -> adapter defaults
300
- }
301
- }
302
-
303
- const sq = (s: string): string => `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
304
-
305
- /** The generator name for a REST adapter kind. */
306
- export function restAdapterImport(a: RestAdapterConfig): string {
307
- return a.kind === 'dummyjson' ? 'dummyJsonAdapter' : a.kind === 'jsonServer' ? 'jsonServerAdapter' : 'offsetLimitAdapter'
308
- }
309
- /** The adapter spread emitted into the createRestDataSource(...) call - it supplies
310
- * a `buildQuery` (real server paging/sort) + `parse` (row/total unwrap). */
311
- function restAdapterExpr(a: RestAdapterConfig, T: string): string {
312
- if (a.kind === 'dummyjson') return `...dummyJsonAdapter<${T}>(${a.rowsKey ? sq(a.rowsKey) : ''})`
313
- if (a.kind === 'jsonServer') return `...jsonServerAdapter<${T}>()`
314
- const fields: string[] = []
315
- for (const k of ['offsetParam', 'limitParam', 'sortByParam', 'orderParam', 'searchParam', 'rowsKey', 'totalKey'] as const) {
316
- const v = a[k]
317
- if (v) fields.push(`${k}: ${sq(v)}`)
318
- }
319
- return `...offsetLimitAdapter<${T}>(${fields.length ? `{ ${fields.join(', ')} }` : ''})`
320
- }
321
-
322
- /** The `createRestDataSource(...)` call for one entity's REST binding. */
323
- function restStoreExpr(src: RestSource, storeName: string, T: string, schemaVar: string): string {
324
- const opts: string[] = [`url: ${sq(buildRestUrl(src))}`, `schema: ${schemaVar}`]
325
- if (src.idField) opts.push(`idField: ${sq(src.idField)}`)
326
- const headers = src.params.filter((p) => p.location === 'header' && p.value !== undefined && p.value !== '')
327
- if (headers.length) opts.push(`headers: { ${headers.map((h) => `${sq(h.name)}: ${sq(h.value ?? '')}`).join(', ')} }`)
328
- const query = src.params.filter((p) => p.location === 'query' && p.value !== undefined && p.value !== '')
329
- if (query.length) opts.push(`query: { ${query.map((q) => `${sq(q.name)}: ${sq(q.value ?? '')}`).join(', ')} }`)
330
- // An adapter provides both buildQuery (server paging/sort) + parse, so it wins over
331
- // the legacy manual rowsPath/totalPath (which only unwraps a single fetched page).
332
- if (src.adapter) {
333
- opts.push(restAdapterExpr(src.adapter, T))
334
- } else if (src.rowsPath || src.totalPath) {
335
- const rowsExpr = src.rowsPath ? dottedAccess('body', src.rowsPath) : 'body'
336
- const totalExpr = src.totalPath ? dottedAccess('body', src.totalPath) : `(${rowsExpr})?.length`
337
- opts.push(`parse: (body: any) => ({ rows: ${rowsExpr} ?? [], rowCount: Number(${totalExpr} ?? 0) })`)
338
- }
339
- return `const ${storeName} = createRestDataSource<${T}>({ ${opts.join(', ')} })`
340
- }
341
-
342
- type DataModuleNeeds = {
343
- supabase: boolean
344
- supabaseUrl?: string
345
- supabaseKey?: string
346
- /** SQL-bound entities get a connected `+server.ts` API route each. */
347
- sqlRoutes: Array<{ schema: EntitySchema; table: string; dialect?: SqlDialectKind; dbSchema?: string }>
348
- /** Embedded-Postgres (PGlite) entities: one shared client + a table each. */
349
- pglite?: boolean
350
- pgliteTables?: Array<{ schema: EntitySchema; table: string; seed?: Record<string, unknown>[] }>
351
- }
352
-
353
- /** Per-dialect server driver wiring for a connected `+server.ts` (reads `$env DATABASE_URL`). */
354
- const SQL_DRIVERS: Record<'postgres' | 'mysql' | 'mssql' | 'sqlite' | 'turso', { dep: string; imports: string; setup: string; exec: string }> = {
355
- postgres: { dep: 'pg', imports: `import pg from 'pg'`, setup: `const pool = new pg.Pool({ connectionString: env.DATABASE_URL })`, exec: `const result = await pool.query(text, params)\n return result.rows` },
356
- mysql: { dep: 'mysql2', imports: `import mysql from 'mysql2/promise'`, setup: `const pool = mysql.createPool(env.DATABASE_URL ?? '')`, exec: `const [rows] = await pool.query(text, params)\n return rows as Record<string, unknown>[]` },
357
- mssql: { dep: 'mssql', imports: `import mssql from 'mssql'`, setup: `const poolPromise = mssql.connect(env.DATABASE_URL ?? '')`, exec: `const pool = await poolPromise\n const request = pool.request()\n params.forEach((p, i) => request.input('p' + (i + 1), p))\n const result = await request.query(text)\n return result.recordset as Record<string, unknown>[]` },
358
- sqlite: { dep: 'better-sqlite3', imports: `import Database from 'better-sqlite3'`, setup: `const db = new Database(env.DATABASE_URL ?? 'data.db')`, exec: `return db.prepare(text).all(...params) as Record<string, unknown>[]` },
359
- // Turso / libSQL: hosted SQLite over HTTP. DATABASE_URL is the libsql:// URL,
360
- // DATABASE_AUTH_TOKEN the database token. Runs on serverless + edge.
361
- turso: { dep: '@libsql/client', imports: `import { createClient } from '@libsql/client'`, setup: `const client = createClient({ url: env.DATABASE_URL ?? '', authToken: env.DATABASE_AUTH_TOKEN })`, exec: `const rs = await client.execute({ sql: text, args: params })\n return rs.rows as unknown as Record<string, unknown>[]` },
362
- }
363
-
364
- /** A fully-connected SvelteKit API route for a SQL-bound entity. When RBAC is on,
365
- * the route imports the shared access policy and rejects unauthorized writes -
366
- * server-enforced, so a tampered client can't bypass it. When audit is on, every
367
- * successful write is recorded. */
368
- function sqlRouteFile(schema: EntitySchema, table: string, dialect?: SqlDialectKind, feat: { access?: boolean; audit?: boolean; screenIds?: string[]; triggers?: EntityTriggers; dbSchema?: string; tenantField?: string } = {}): GeneratedFile {
369
- const n = namesFor(schema)
370
- const key = (dialect === 'supabase' ? 'postgres' : (dialect ?? 'postgres')) as 'postgres' | 'mysql' | 'mssql' | 'sqlite' | 'turso'
371
- const driver = SQL_DRIVERS[key]
372
- const dialectLiteral = sqlDialectExpr(dialect)
373
- const accessImport = feat.access ? `\nimport { authorizeAction, getServerRole } from '$lib/access'` : ''
374
- const auditImport = feat.audit ? `\nimport { recordAudit } from '$lib/audit'` : ''
375
- const tenantImport = feat.tenantField ? `\nimport { requireTenant } from '$lib/server/tenant'` : ''
376
- // Every connected route validates writes against the schema server-side, and
377
- // (when enabled) authorizes them by role + records an audit entry.
378
- const opts = [`schema: ${n.schemaVar}`, `source`, `validate: true`]
379
- if (feat.access) opts.push(`// Server-enforced RBAC: the caller's role comes from the session (event.locals). Reads\n // are allowed only if the role can open one of this entity's own screens.\n authorize: ({ action, event }) => authorizeAction(getServerRole(event), action, ${JSON.stringify(feat.screenIds ?? [])})`)
380
- if (feat.tenantField) opts.push(`// Multi-tenancy, enforced server-side: reads are filtered to the caller's tenant,\n // creates are stamped with it, and update/delete re-read the target under the\n // scope first - so a guessed id cannot reach another tenant's row.\n scope: ({ event }) => ({ field: ${JSON.stringify(feat.tenantField)}, value: requireTenant(event) })`)
381
- if (feat.audit) opts.push(`// Record every successful write to the audit trail.\n audit: (e) => recordAudit({ entity: ${JSON.stringify(schema.name)}, action: e.action, recordId: e.id, values: e.values as Record<string, unknown> | undefined, actor: String(e.event.locals?.role ?? e.event.locals?.user ?? 'system') })`)
382
- // Server-enforced business rules: the entity's triggers compiled to lifecycle hooks.
383
- const hooks = triggerHooksOpt(feat.triggers)
384
- if (hooks) opts.push(`// Business rules, enforced server-side (a client that skips them still can't write bad data).\n ${hooks}`)
385
- const handlers = `export const { POST } = createKitHandlers({\n ${opts.join(',\n ')},\n})`
386
- return {
387
- path: `src/routes/api/${n.route}/+server.ts`,
388
- description: `Connected API route for ${n.label} (SQL via DATABASE_URL). Runs server-side.`,
389
- contents: `${driver.imports}
390
- import { env } from '$env/dynamic/private'
391
- import { createKitHandlers, createSqlDataSource } from '@svgrid/enterprise'
392
- import { ${n.schemaVar}, type ${n.type} } from '$lib/schemas'${accessImport}${auditImport}${tenantImport}
393
-
394
- ${driver.setup}
395
-
396
- const source = createSqlDataSource<${n.type}>({
397
- schema: ${n.schemaVar},
398
- table: ${JSON.stringify(table)},${feat.dbSchema ? `\n dbSchema: ${JSON.stringify(feat.dbSchema)},` : ''}${dialectLiteral ? `\n dialect: ${dialectLiteral},` : ''}
399
- execute: async (text, params) => {
400
- ${driver.exec}
401
- },
402
- })
403
-
404
- ${handlers}
405
- `,
406
- }
407
- }
408
-
409
- /** Map an EntityField type to a Postgres column type for PGlite DDL. */
410
- const PG_COL_TYPE: Record<string, string> = { number: 'double precision', boolean: 'boolean', date: 'date', datetime: 'timestamptz', json: 'jsonb' }
411
- /** The shared PGlite client + table DDL + one-time seed, injected into data.ts. */
412
- function pgliteBootstrap(tables: Array<{ schema: EntitySchema; table: string; seed?: Record<string, unknown>[] }>, seed: Map<string, Array<Record<string, unknown>>>): string {
413
- const ddls: string[] = []
414
- const seeds: string[] = []
415
- for (const { schema, table, seed: curated } of tables) {
416
- const pk = schema.idField ?? schema.fields.find((f) => f.primaryKey)?.field ?? 'id'
417
- const cols = schema.fields.map((f) => `"${f.field}" ${PG_COL_TYPE[f.type] ?? 'text'}${f.primaryKey || f.field === pk ? ' primary key' : ''}`).join(', ')
418
- ddls.push(`CREATE TABLE IF NOT EXISTS "${table}" (${cols});`)
419
- // Migrate schema additions: fields added after the DB was first created still
420
- // appear (CREATE TABLE IF NOT EXISTS is a no-op once the table exists).
421
- for (const f of schema.fields) {
422
- if (f.primaryKey || f.field === pk) continue
423
- ddls.push(`ALTER TABLE "${table}" ADD COLUMN IF NOT EXISTS "${f.field}" ${PG_COL_TYPE[f.type] ?? 'text'};`)
424
- }
425
- // Prefer rows imported/curated on the source (e.g. a CSV import), else the generator.
426
- const rows = curated ?? seed.get(schema.name) ?? []
427
- seeds.push(` await _pgSeed(${JSON.stringify(table)}, ${JSON.stringify(rows)}, ${JSON.stringify(schema.fields.map((f) => f.field))})`)
428
- }
429
- return `// Embedded Postgres (PGlite), persisted in the browser (IndexedDB) - a real,
430
- // persistent database with ZERO backend setup. To go to production, bind these
431
- // entities to a hosted SQL source (same schema) instead.
432
- const pg = new PGlite('idb://svgrid-studio')
433
- const pgReady = (async () => {
434
- await pg.exec(\`
435
- ${ddls.join('\n ')}
436
- \`)
437
- ${seeds.join('\n')}
438
- })()
439
- async function _pgSeed(table: string, rows: Record<string, unknown>[], cols: string[]): Promise<void> {
440
- if (!rows.length) return
441
- const existing = await pg.query(\`select count(*)::int as n from "\${table}"\`)
442
- if (((existing.rows[0] as { n?: number } | undefined)?.n ?? 0) > 0) return // already seeded
443
- for (const row of rows) {
444
- await pg.query(
445
- \`insert into "\${table}" (\${cols.map((c) => '"' + c + '"').join(', ')}) values (\${cols.map((_, i) => '$' + (i + 1)).join(', ')})\`,
446
- cols.map((c) => { const v = row[c]; return v != null && typeof v === 'object' ? JSON.stringify(v) : (v ?? null) }),
447
- )
448
- }
449
- }
450
- `
451
- }
452
-
453
- // ---------------------------------------------------------------------------
454
- // db/schema.sql + db/seed.sql - plain SQL a user runs against their database
455
- // before first deploy, for SQL entities WITHOUT the Drizzle data layer (which
456
- // owns DDL via migrations when active). This closes the "tables must already
457
- // exist" cliff on every dialect - including MSSQL, which Drizzle can't cover.
458
- // ---------------------------------------------------------------------------
459
-
460
- type DdlDialect = 'postgres' | 'mysql' | 'sqlite' | 'mssql'
461
-
462
- /** Normalize a source dialect to a DDL family (supabase = postgres, turso = sqlite). */
463
- const ddlDialect = (d?: SqlDialectKind): DdlDialect =>
464
- d === 'mysql' ? 'mysql' : d === 'sqlite' || d === 'turso' ? 'sqlite' : d === 'mssql' ? 'mssql' : 'postgres'
465
-
466
- /** Column types per dialect (default column type = the `text` entry). */
467
- const DDL_COL_TYPES: Record<DdlDialect, Record<string, string>> = {
468
- postgres: { text: 'text', number: 'double precision', boolean: 'boolean', date: 'date', dateString: 'date', datetime: 'timestamptz', json: 'jsonb' },
469
- mysql: { text: 'varchar(255)', number: 'double', boolean: 'tinyint(1)', date: 'date', dateString: 'date', datetime: 'datetime', json: 'json' },
470
- sqlite: { text: 'text', number: 'real', boolean: 'integer', date: 'text', dateString: 'text', datetime: 'text', json: 'text' },
471
- mssql: { text: 'nvarchar(255)', number: 'float', boolean: 'bit', date: 'date', dateString: 'date', datetime: 'datetime2', json: 'nvarchar(max)' },
472
- }
473
-
474
- /** Identifier quoter per dialect (doubles the quote char to escape, like sql.ts). */
475
- function ddlQuoter(dialect: DdlDialect): (name: string) => string {
476
- const q = dialect === 'mysql' ? '`' : '"'
477
- return (name) => `${q}${name.replaceAll(q, q + q)}${q}`
478
- }
479
-
480
- /** A SQL literal for a seed value, per dialect. */
481
- function ddlLiteral(v: unknown, dialect: DdlDialect): string {
482
- if (v == null || v === '') return 'NULL'
483
- if (typeof v === 'number') return Number.isFinite(v) ? String(v) : 'NULL'
484
- if (typeof v === 'boolean') return dialect === 'postgres' ? (v ? 'TRUE' : 'FALSE') : v ? '1' : '0'
485
- const s = typeof v === 'object' ? JSON.stringify(v) : String(v)
486
- const escaped = s.replaceAll("'", "''")
487
- return dialect === 'mssql' ? `N'${escaped}'` : `'${escaped}'`
488
- }
489
-
490
- /** `db/schema.sql` + `db/seed.sql` for the project's SQL-bound entities. */
491
- export function sqlDdlFiles(schemas: EntitySchema[], sources: Record<string, EntityDataSource>): GeneratedFile[] {
492
- const { entries, seed } = prepareEntities(schemas)
493
- const sqlEntries = entries
494
- .map((e) => ({ ...e, src: sources[e.schema.name] }))
495
- .filter((e): e is Prepared & { src: SqlSource } => e.src?.kind === 'sql')
496
- if (sqlEntries.length === 0) return []
497
-
498
- const create: string[] = []
499
- const inserts: string[] = []
500
- for (const { schema, infos, src } of sqlEntries) {
501
- const dialect = ddlDialect(src.dialect)
502
- const q = ddlQuoter(dialect)
503
- const types = DDL_COL_TYPES[dialect]
504
- const pk = resolveIdField(schema)
505
- // Relation display fields are filled at runtime (labels), not stored.
506
- const display = new Set(infos.map((i) => i.displayField))
507
- const cols = schema.fields.filter((f) => !display.has(f.field) && !f.formula && !f.computed)
508
- const colDefs = cols.map((f) => ` ${q(f.field)} ${types[f.type] ?? types.text}${f.field === pk ? ' PRIMARY KEY' : ''}`)
509
- const body = `CREATE TABLE ${dialect === 'mssql' ? '' : 'IF NOT EXISTS '}${q(src.table)} (\n${colDefs.join(',\n')}\n)`
510
- create.push(dialect === 'mssql' ? `IF OBJECT_ID(N'${src.table.replaceAll("'", "''")}', N'U') IS NULL\n${body};` : `${body};`)
511
-
512
- const rows = seed.get(schema.name) ?? []
513
- if (rows.length) {
514
- const names = cols.map((f) => q(f.field)).join(', ')
515
- const values = rows.map((r) => ` (${cols.map((f) => ddlLiteral(r[f.field], dialect)).join(', ')})`).join(',\n')
516
- inserts.push(`INSERT INTO ${q(src.table)} (${names}) VALUES\n${values};`)
517
- }
518
- }
519
-
520
- const header = (what: string) =>
521
- `-- ${what} for the app's SQL entities. Generated by SvGrid Studio.\n` +
522
- `-- Run against the database your DATABASE_URL points at (psql/mysql/sqlite3/sqlcmd,\n` +
523
- `-- or your provider's SQL console) before first use. Tip: the Drizzle data layer\n` +
524
- `-- replaces this file with real, versioned migrations.\n\n`
525
- return [
526
- { path: 'db/schema.sql', description: 'CREATE TABLE statements for the SQL entities (run once against your database).', contents: header('Table definitions') + create.join('\n\n') + '\n' },
527
- ...(inserts.length ? [{ path: 'db/seed.sql', description: 'Optional sample rows for the SQL entities.', contents: header('Sample seed rows (optional)') + inserts.join('\n\n') + '\n' }] : []),
528
- ]
529
- }
530
-
531
- function dataModule(
532
- entries: Prepared[],
533
- seed: Map<string, Array<Record<string, unknown>>>,
534
- sources?: Record<string, EntityDataSource>,
535
- supabaseConn?: { url?: string; key?: string },
536
- ): { file: GeneratedFile; needs: DataModuleNeeds } {
537
- const schemas = entries.map((e) => e.schema)
538
- // Each type gets its own `type` keyword - SvelteKit enables verbatimModuleSyntax.
539
- const typeImports = schemas.map((s) => `type ${pascal(s.name)}`).join(', ')
540
- const schemaImports = schemas.map((s) => namesFor(s).schemaVar).join(', ')
541
- const hasRelations = entries.some((e) => e.infos.length > 0)
542
- const entImports = new Set<string>()
543
- const needs: DataModuleNeeds = { supabase: false, sqlRoutes: [] }
544
-
545
- // 1. Base store per entity, branched on its bound data source (default: seeded
546
- // in-memory, so the app runs with no backend).
547
- const stores = entries
548
- .map((e) => {
549
- const T = pascal(e.schema.name)
550
- const sv = namesFor(e.schema).schemaVar
551
- const store = storeVar(e.schema)
552
- const src = sources?.[e.schema.name] ?? { kind: 'memory' as const }
553
- switch (src.kind) {
554
- case 'rest':
555
- entImports.add('createRestDataSource')
556
- if (src.adapter) entImports.add(restAdapterImport(src.adapter))
557
- return restStoreExpr(src, store, T, sv)
558
- case 'sql':
559
- // SQL runs server-side: the client talks to a generated /api/<entity> route.
560
- entImports.add('createKitDataSource')
561
- needs.sqlRoutes.push({ schema: e.schema, table: src.table, dialect: src.dialect, dbSchema: src.schema })
562
- return `const ${store} = createKitDataSource<${T}>({ endpoint: '/api/${namesFor(e.schema).route}' })`
563
- case 'supabase':
564
- entImports.add('createSupabaseDataSource')
565
- needs.supabase = true
566
- // Per-entity url/key overrides the project-level connection for the .env hint.
567
- if (src.url && src.key && !needs.supabaseUrl) { needs.supabaseUrl = src.url; needs.supabaseKey = src.key }
568
- return `const ${store} = createSupabaseDataSource<${T}>({ client: supabaseClient, table: ${sq(src.table)}, schema: ${sv} })`
569
- case 'pglite': {
570
- // Embedded Postgres (PGlite) in the browser - real SQL, persisted to
571
- // IndexedDB, zero backend setup. Shares one `pg` client + `pgReady`.
572
- entImports.add('createSqlDataSource')
573
- needs.pglite = true
574
- needs.pgliteTables ??= []
575
- needs.pgliteTables.push({ schema: e.schema, table: src.table, seed: src.seed })
576
- return `const ${store} = createSqlDataSource<${T}>({ schema: ${sv}, table: ${sq(src.table)}, dialect: { placeholders: '$', ilike: true }, execute: async (text, params) => { await pgReady; return (await pg.query(text, params)).rows as Record<string, unknown>[] } })`
577
- }
578
- default: {
579
- entImports.add('createInMemoryDataSource')
580
- // Prefer curated seed on the source (a sample app), else the realistic generator.
581
- const curated = src.kind === 'memory' ? src.seed : undefined
582
- const rows = JSON.stringify(curated ?? seed.get(e.schema.name) ?? [])
583
- return `const ${store} = createInMemoryDataSource<${T}>(${rows}, ${sv})`
584
- }
585
- }
586
- })
587
- .join('\n')
588
-
589
- // 2. A searchable picker per relation, reading the related store.
590
- const lookups: string[] = []
591
- for (const { schema: s, infos } of entries) {
592
- for (const info of infos) {
593
- const rn = namesFor(info.related)
594
- lookups.push(
595
- `export const ${lookupVar(s, info.fkField)} = createRelationLookup<${rn.type}>({ source: ${storeVar(info.related)}, schema: ${rn.schemaVar}, labelField: '${(s.fields.find((f) => f.field === info.fkField)?.relation?.labelField) ?? 'name'}' })`,
596
- )
597
- }
598
- }
599
- if (hasRelations) { entImports.add('createRelationLookup'); entImports.add('withRelationLabels') }
600
-
601
- // 3. Exported sources: wrap with withRelationLabels so the grid shows labels.
602
- const sourceLines = entries
603
- .map(({ schema: s, infos }) => {
604
- const n = namesFor(s)
605
- if (infos.length === 0) return `export const ${n.sourceVar} = ${storeVar(s)}`
606
- const cfgs = infos.map((i) => `{ fk: '${i.fkField}', lookup: ${lookupVar(s, i.fkField)}, as: '${i.displayField}' }`).join(', ')
607
- return `export const ${n.sourceVar} = withRelationLabels(${storeVar(s)}, [${cfgs}])`
608
- })
609
- .join('\n')
610
-
611
- // Project-level connection fills the .env hint when no entity carries its own url/key.
612
- if (needs.supabase && !needs.supabaseUrl && (supabaseConn?.url || supabaseConn?.key)) {
613
- needs.supabaseUrl = supabaseConn.url
614
- needs.supabaseKey = supabaseConn.key
615
- }
616
- const connImportLine = needs.supabase ? `\nimport { supabaseClient } from './connections'` : ''
617
- const pgliteImportLine = needs.pglite ? `\nimport { PGlite } from '@electric-sql/pglite'` : ''
618
- const pgliteBoot = needs.pglite ? pgliteBootstrap(needs.pgliteTables!, seed) + '\n' : ''
619
-
620
- const file: GeneratedFile = {
621
- path: 'src/lib/data.ts',
622
- description: 'Data sources per entity (in-memory / REST / SQL / Supabase / PGlite) + a searchable lookup per relation.',
623
- contents: `/**
624
- * Data sources. Each entity binds to its own backend - in-memory (seeded), a
625
- * REST endpoint, a SQL table, Supabase, or embedded Postgres (PGlite). In-memory
626
- * + PGlite run with no setup; SQL / Supabase read their connection from \`./connections\`.
627
- */
628
- import { ${[...entImports].join(', ')} } from '@svgrid/enterprise'
629
- import { ${schemaImports}, ${typeImports} } from './schemas'${connImportLine}${pgliteImportLine}
630
-
631
- ${pgliteBoot}${stores}
632
- ${lookups.length ? `\n// Searchable pickers for relation (foreign-key) fields.\n${lookups.join('\n')}\n` : ''}
633
- // Grids show the related label (not the raw id) via withRelationLabels.
634
- ${sourceLines}
635
-
636
- // New-row ids continue after the seeded rows.
637
- let seq = ${SEED_COUNT}
638
- export const nextId = (prefix: string) => \`\${prefix}\${++seq}\`
639
- `,
640
- }
641
- return { file, needs }
642
- }
643
-
644
- /** The Supabase client wiring. Reads the project URL + anon key from PUBLIC_ env vars
645
- * (never inlined into source); the anon key is public / browser-safe, so this is a
646
- * secret-hygiene win and matches SvelteKit convention. The designer-entered values are
647
- * shown as a comment so they can be pasted straight into `.env` / `.env.example`. */
648
- function connectionsModule(needs: DataModuleNeeds): GeneratedFile {
649
- const hint = needs.supabaseUrl || needs.supabaseKey
650
- ? `// From the Studio designer - paste into .env:\n// PUBLIC_SUPABASE_URL=${needs.supabaseUrl ?? ''}\n// PUBLIC_SUPABASE_ANON_KEY=${needs.supabaseKey ?? ''}\n`
651
- : `// Set PUBLIC_SUPABASE_URL and PUBLIC_SUPABASE_ANON_KEY in .env (see .env.example).\n`
652
- return {
653
- path: 'src/lib/connections.ts',
654
- description: 'Supabase client for Supabase-bound entities (reads PUBLIC_SUPABASE_* env vars).',
655
- contents: `import { createClient } from '@supabase/supabase-js'
656
- import { env } from '$env/dynamic/public'
657
- import type { SupabaseClientLike } from '@svgrid/enterprise'
658
-
659
- /**
660
- * Supabase client. The anon key is public (browser-safe) - access is protected by
661
- * your Row Level Security policies. Requires \`@supabase/supabase-js\`.
662
- */
663
- ${hint}export const supabaseClient = createClient(
664
- env.PUBLIC_SUPABASE_URL ?? '',
665
- env.PUBLIC_SUPABASE_ANON_KEY ?? '',
666
- ) as unknown as SupabaseClientLike
667
- `,
668
- }
669
- }
670
-
671
- /**
672
- * A CRUD screen page (grid + form) for a prepared entity schema. `route` and
673
- * `title` default to the entity's, but the Studio designer overrides them so a
674
- * project can have custom routes / titles / multiple screens per entity.
675
- */
676
- export function entityScreenPage(schema: EntitySchema, route?: string, title?: string): GeneratedFile {
677
- const n = namesFor(schema)
678
- const r = route ?? n.route
679
- const t = title ?? n.label
680
- const relationFields = schema.fields.filter((f) => f.type === 'relation' && f.relation)
681
- const lookupImports = relationFields.map((f) => lookupVar(schema, f.field))
682
- const dataImports = [n.sourceVar, ...lookupImports, 'nextId'].join(', ')
683
- const lookupsProp = relationFields.length
684
- ? `\n lookups={{ ${relationFields.map((f) => `${f.field}: ${lookupVar(schema, f.field)}`).join(', ')} }}`
685
- : ''
686
-
687
- return {
688
- path: `src/routes/${r}/+page.svelte`,
689
- description: `CRUD screen for ${t}.`,
690
- contents: `<script lang="ts">
691
- import EntityScreen from '$lib/EntityScreen.svelte'
692
- import { ${n.schemaVar} } from '$lib/schemas'
693
- import { ${dataImports} } from '$lib/data'
694
- </script>
695
-
696
- <h1 class="st__title">${t}</h1>
697
- <EntityScreen schema={${n.schemaVar}} source={${n.sourceVar}}${lookupsProp} newId={() => nextId('${n.idPrefix}')} />
698
- `,
699
- }
700
- }
701
-
702
- export type NavItem = { href: string; label: string; id?: string }
703
-
704
- export function layoutFile(nav: NavItem[], opts: { accent?: string; shell?: ShellConfig; title?: string; themeVars?: Record<string, string>; lightVars?: Record<string, string>; darkVars?: Record<string, string>; dark?: boolean; access?: boolean; auth?: boolean; supabaseAuth?: boolean; authRoutes?: string[]; authAccount?: boolean; i18n?: boolean; appClass?: string } = {}): GeneratedFile {
705
- // Nav is the app's own screens; `/` just redirects to the first one, so no separate
706
- // "Home" link (it would duplicate the first screen).
707
- const links = nav
708
- const shell = opts.shell ?? {}
709
- const style: ShellStyle = shell.style ?? 'sidebar'
710
- // App-level styling hook: an extra class on the shell root so custom.css can target the whole app.
711
- const appCls = sanitizeClassName(opts.appClass)
712
- const appClsAttr = appCls ? ` ${appCls}` : ''
713
- const brand = (shell.brand ?? '').trim() || opts.title || 'My Studio App'
714
- const footer = shell.footer === undefined ? 'Built with SvGrid Studio' : shell.footer
715
- const right = style === 'sidebar' && shell.navPosition === 'right'
716
- // Emit the full theme token bundle so the generated app matches the look chosen
717
- // in the designer. When both light + dark token sets are supplied we ship a
718
- // built-in light/dark switcher: both sets are scoped by [data-theme] on <html>,
719
- // and the bare :root falls back to the mode picked in Studio (pre-hydration).
720
- const defaultMode: 'light' | 'dark' = opts.dark ? 'dark' : 'light'
721
- const withAccent = (m?: Record<string, string>) => {
722
- const v = { ...(m ?? {}) }
723
- if (opts.accent) v['--sg-accent'] = opts.accent
724
- return v
725
- }
726
- const declLines = (m: Record<string, string>) => Object.entries(m).map(([k, v]) => `${k}: ${v};`).join(' ')
727
- const hasSwitch = !!(opts.lightVars && opts.darkVars)
728
- let themeHead = ''
729
- if (hasSwitch) {
730
- const lv = withAccent(opts.lightVars)
731
- const dv = withAccent(opts.darkVars)
732
- const defRule = [declLines(defaultMode === 'dark' ? dv : lv), `color-scheme: ${defaultMode};`].join(' ')
733
- const lightRule = [declLines(lv), 'color-scheme: light;'].join(' ')
734
- const darkRule = [declLines(dv), 'color-scheme: dark;'].join(' ')
735
- themeHead = `\n<svelte:head><style>:root { ${defRule} }\n:root[data-theme="light"] { ${lightRule} }\n:root[data-theme="dark"] { ${darkRule} }</style></svelte:head>\n`
736
- } else {
737
- const vars = withAccent(opts.themeVars)
738
- const varLines = declLines(vars)
739
- const rootRule = [varLines, opts.dark ? 'color-scheme: dark;' : ''].filter(Boolean).join(' ')
740
- themeHead = rootRule ? `\n<svelte:head><style>:root { ${rootRule} }</style></svelte:head>\n` : ''
741
- }
742
-
743
- // i18n: translate nav labels via `nav.<id>` keys (Home has no id -> literal).
744
- const navLabel = opts.i18n ? `{item.id ? $t('nav.' + item.id, item.label) : item.label}` : '{item.label}'
745
- const anchor = `<a class="sv-app__link" class:is-active={page.url.pathname === item.href} href={item.href}>${navLabel}</a>`
746
- // When RBAC is on, hide nav links the current role can't open.
747
- const linkGate = opts.access
748
- ? `{#if !item.id || canScreen($currentRole, item.id)}${anchor}{/if}`
749
- : anchor
750
- const localeSwitcher = opts.i18n
751
- ? `\n <select class="sv-app__locale" aria-label="Language" onchange={(e) => currentLocale.set(e.currentTarget.value as typeof $currentLocale)}>
752
- {#each locales as loc (loc)}<option value={loc} selected={loc === $currentLocale}>{loc}</option>{/each}
753
- </select>`
754
- : ''
755
- const toolbarOn = shell.toolbar !== false && nav.length > 0
756
- // Built-in light/dark switcher (a sun/moon button). Lives in the toolbar's tools
757
- // cluster when the toolbar is on, else trails the nav links. Only rendered when
758
- // both token sets are available (i.e. from the full Studio project emitter).
759
- const themeToggleBtn = `<button type="button" class="sv-app__tool sv-app__theme" aria-label="Toggle color theme" title="Toggle light / dark" onclick={toggleTheme}>
760
- {#if theme === 'dark'}<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>{:else}<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>{/if}
761
- </button>`
762
- // When there's no toolbar, the toggle trails the nav in its own inline slot.
763
- const navToggleSlot = hasSwitch && !toolbarOn ? `\n <div class="sv-app__navtools">${themeToggleBtn}</div>` : ''
764
- // Clicking any nav link closes the mobile drawer.
765
- const linksMarkup = `<nav class="sv-app__links" onclick={() => (navOpen = false)}>
766
- {#each nav as item (item.href)}
767
- ${linkGate}
768
- {/each}
769
- </nav>${localeSwitcher}${navToggleSlot}`
770
- const footMarkup = footer ? `\n <span class="sv-app__foot">{footer}</span>` : ''
771
- const footConst = footer ? `\n const footer = ${JSON.stringify(footer)}` : ''
772
- // Sidebar-only: a collapsible sidebar that docks on wide screens and becomes an
773
- // off-canvas drawer when collapsed or on tablet/phone (<= 1024px). The collapse
774
- // choice persists; tablet/phone start collapsed. top-nav and bottom-nav are
775
- // always-visible bars with no collapse/drawer state.
776
- const sideState = style !== 'sidebar' ? '' : `
777
- let collapsed = $state(false)
778
- let narrow = $state(false)
779
- const drawer = $derived(collapsed || narrow)
780
- function toggleNav() { collapsed = !collapsed; try { localStorage.setItem('svapp:nav', collapsed ? 'c' : 'e') } catch (_) { /* storage blocked */ } }
781
- $effect(() => {
782
- // Tablet / phone (<= 1024px) collapse to a drawer by default.
783
- const mq = window.matchMedia('(max-width: 1024px)')
784
- const sync = () => (narrow = mq.matches)
785
- sync()
786
- try { const s = localStorage.getItem('svapp:nav'); if (s) collapsed = s === 'c' } catch (_) { /* storage blocked */ }
787
- mq.addEventListener('change', sync)
788
- return () => mq.removeEventListener('change', sync)
789
- })`
790
-
791
- // Brand: a company logo image (data URL / URL) when set, else the app name.
792
- const logoConst = shell.logo ? `\n const logo = ${JSON.stringify(shell.logo)}` : ''
793
- const brandInner = shell.logo
794
- ? `<img class="sv-app__logo" src={logo} alt={brand} />`
795
- : `<span class="sv-app__brandtext">{brand}</span>`
796
- const brandLink = `<a class="sv-app__brand" href="/">${brandInner}</a>`
797
- // Collapsed bar (tablet / phone, or a collapsed desktop sidebar): a hamburger +
798
- // brand. The nav slides in as an off-canvas drawer. On desktop the "dock" button
799
- // pins the sidebar back open.
800
- const mobileBar = `<header class="sv-app__mobilebar">
801
- <button type="button" class="sv-app__burger" aria-label="Toggle navigation" aria-expanded={navOpen} onclick={() => (navOpen = !navOpen)}>
802
- <svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
803
- </button>
804
- ${brandLink}
805
- {#if !narrow}<button type="button" class="sv-app__dock" aria-label="Dock sidebar" title="Dock sidebar" onclick={toggleNav}>
806
- <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 6l6 6-6 6"/></svg>
807
- </button>{/if}
808
- </header>
809
- {#if navOpen}<button type="button" class="sv-app__backdrop" aria-label="Close navigation" onclick={() => (navOpen = false)}></button>{/if}`
810
-
811
- // App-chrome toolbar (docked at the top of the content area for both layouts):
812
- // a functional quick-search over the app's screens + an account cluster. Reads as
813
- // a real product header. Opt out with `shell.toolbar === false`.
814
- const initials = ((brand.match(/\b[A-Za-z0-9]/g) ?? []).slice(0, 2).join('') || 'A').toUpperCase()
815
- const brandSlug = brand.toLowerCase().replace(/[^a-z0-9]+/g, '') || 'app'
816
- const resultLabel = opts.i18n ? `{r.id ? $t('nav.' + r.id, r.label) : r.label}` : '{r.label}'
817
- const toolbarMarkup = toolbarOn
818
- ? `<div class="sv-app__toolbar">
819
- <div class="sv-app__search">
820
- <svg class="sv-app__search-ic" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></svg>
821
- <input class="sv-app__search-in" type="search" placeholder="Search {brand}…" bind:value={q} bind:this={searchEl} aria-label="Search screens" />
822
- <kbd class="sv-app__kbd" aria-hidden="true">⌘K</kbd>
823
- {#if results.length}
824
- <div class="sv-app__results">
825
- {#each results as r (r.href)}
826
- <a class="sv-app__result" href={r.href} onclick={() => (q = '')}>${resultLabel}</a>
827
- {/each}
828
- </div>
829
- {/if}
830
- </div>
831
- <div class="sv-app__tools">
832
- ${hasSwitch ? themeToggleBtn : ''}
833
- <div class="sv-app__pop">
834
- <button type="button" class="sv-app__tool" aria-label="Notifications" aria-expanded={bellOpen} onclick={() => { bellOpen = !bellOpen; menuOpen = false }}>
835
- <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></svg>
836
- <span class="sv-app__dot"></span>
837
- </button>
838
- {#if bellOpen}
839
- <div class="sv-app__menu sv-app__menu--notif" role="menu">
840
- <div class="sv-app__menu-head">Notifications</div>
841
- {#each notifications as n (n.title)}
842
- <div class="sv-app__notif" role="menuitem"><span class="sv-app__notif-dot" style:background={n.color}></span><div class="sv-app__notif-body"><strong>{n.title}</strong><span>{n.time}</span></div></div>
843
- {/each}
844
- <button type="button" class="sv-app__menu-foot" onclick={() => (bellOpen = false)}>Mark all as read</button>
845
- </div>
846
- {/if}
847
- </div>
848
- <div class="sv-app__pop">
849
- <button type="button" class="sv-app__avatar" title={brand} aria-label="Account" aria-expanded={menuOpen} onclick={() => { menuOpen = !menuOpen; bellOpen = false }}>{initials}</button>
850
- {#if menuOpen}
851
- <div class="sv-app__menu sv-app__menu--acct" role="menu">
852
- <div class="sv-app__acct-head"><span class="sv-app__avatar sv-app__avatar--lg" aria-hidden="true">{initials}</span><div class="sv-app__acct-id"><strong>${opts.auth ? '{data?.user?.name ?? brand}' : '{brand}'}</strong><span>${opts.auth ? '{data?.user?.email ?? acctEmail}' : '{acctEmail}'}</span></div></div>
853
- <a class="sv-app__menu-item" href="/" role="menuitem" onclick={() => (menuOpen = false)}>Dashboard</a>
854
- ${opts.authAccount
855
- ? `<a class="sv-app__menu-item" href="/account" role="menuitem" onclick={() => (menuOpen = false)}>Account</a>`
856
- : `<button type="button" class="sv-app__menu-item" role="menuitem" onclick={() => (menuOpen = false)}>Profile</button>
857
- <button type="button" class="sv-app__menu-item" role="menuitem" onclick={() => (menuOpen = false)}>Settings</button>`}
858
- ${opts.auth
859
- ? `<form method="POST" action="/logout" class="sv-app__signout"><button type="submit" class="sv-app__menu-item sv-app__menu-item--danger" role="menuitem">Sign out</button></form>`
860
- : opts.supabaseAuth
861
- ? '' // Supabase Auth: the real sign-out lives on SvAuthGate's own bar, so no placeholder here.
862
- : `<button type="button" class="sv-app__menu-item sv-app__menu-item--danger" role="menuitem" onclick={() => (menuOpen = false)}>Sign out</button>`}
863
- </div>
864
- {/if}
865
- </div>
866
- </div>
867
- {#if bellOpen || menuOpen}<button type="button" class="sv-app__scrim" tabindex="-1" aria-label="Close menus" onclick={() => { bellOpen = false; menuOpen = false }}></button>{/if}
868
- </div>\n `
869
- : ''
870
- // Supabase Auth: gate the whole app behind a client-side sign-in. SvAuthGate shows
871
- // the sign-in form when signed out, and the app + a "signed in as … / Sign out" bar
872
- // when signed in. Reads the shared client from $lib/connections.
873
- const content = opts.supabaseAuth
874
- ? `<SvAuthGate client={supabaseClient as unknown as SupabaseAuthClientLike} title={\`Sign in to \${brand}\`}>
875
- {@render children()}
876
- </SvAuthGate>`
877
- : `{@render children()}`
878
- const mainMarkup = `<main class="sv-app__main">
879
- ${toolbarMarkup}<div class="sv-app__content">
880
- ${content}
881
- </div>
882
- </main>`
883
-
884
- const body = style === 'top-nav'
885
- ? `<div class="sv-app sv-app--top${appClsAttr}">
886
- <header class="sv-app__bar">
887
- ${brandLink}
888
- ${linksMarkup}
889
- </header>
890
- ${mainMarkup}${footer ? `\n <footer class="sv-app__footbar">{footer}</footer>` : ''}
891
- </div>`
892
- : style === 'bottom-nav'
893
- ? `<div class="sv-app sv-app--bottom${appClsAttr}">
894
- ${mainMarkup}
895
- <footer class="sv-app__bar sv-app__bar--bottom">
896
- ${brandLink}
897
- ${linksMarkup}
898
- </footer>
899
- </div>`
900
- : `<div class="sv-app sv-app--side${right ? ' sv-app--right' : ''}${appClsAttr}" class:is-drawer={drawer} class:is-navopen={navOpen}>
901
- ${mobileBar}
902
- <aside class="sv-app__side">
903
- <div class="sv-app__sidehead">
904
- ${brandLink}
905
- <button type="button" class="sv-app__collapse" aria-label="Collapse sidebar" title="Collapse sidebar" onclick={toggleNav}>
906
- <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
907
- </button>
908
- </div>
909
- ${linksMarkup}${footMarkup}
910
- </aside>
911
- ${mainMarkup}
912
- </div>`
913
-
914
- const styles = style === 'top-nav'
915
- ? ` .sv-app--top { display: flex; flex-direction: column; min-height: 100vh; }
916
- .sv-app__bar { display: flex; align-items: center; gap: 20px; padding: 12px 22px; border-bottom: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 16%, var(--sg-border, #e6e8ec)); background: var(--sg-header-bg, #f8fafc); }
917
- .sv-app__links { display: flex; flex-direction: row; gap: 4px; flex-wrap: wrap; }
918
- .sv-app__footbar { padding: 12px 22px; border-top: 1px solid var(--sg-border, #e6e8ec); color: var(--sg-muted, #94a3b8); font-size: 13px; }
919
- /* Mobile: the bar stacks and its links scroll horizontally. */
920
- @media (max-width: 640px) {
921
- .sv-app__bar { flex-direction: column; align-items: stretch; gap: 10px; padding: 10px 14px; }
922
- .sv-app__links { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
923
- .sv-app__link { white-space: nowrap; }
924
- }`
925
- : style === 'bottom-nav'
926
- ? ` .sv-app--bottom { display: flex; flex-direction: column; min-height: 100vh; }
927
- .sv-app__bar--bottom {
928
- position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
929
- display: flex; align-items: center; gap: 20px; padding: 12px 22px calc(12px + env(safe-area-inset-bottom, 0px));
930
- border-top: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 16%, var(--sg-border, #e6e8ec));
931
- background: var(--sg-header-bg, #f8fafc);
932
- }
933
- .sv-app__bar--bottom .sv-app__links { display: flex; flex-direction: row; gap: 4px; flex-wrap: wrap; }
934
- /* Clears the fixed bottom bar so page content never renders underneath it. */
935
- .sv-app--bottom .sv-app__main { padding-bottom: 70px; }
936
- /* Mobile: brand drops out so the tab strip stays roomy; ties overflow to a horizontal scroll if there are many screens. */
937
- @media (max-width: 640px) {
938
- .sv-app__bar--bottom { gap: 10px; padding: 6px 12px calc(6px + env(safe-area-inset-bottom, 0px)); }
939
- .sv-app__bar--bottom .sv-app__brand { display: none; }
940
- .sv-app__bar--bottom .sv-app__links { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; -webkit-overflow-scrolling: touch; }
941
- .sv-app__bar--bottom .sv-app__link { white-space: nowrap; }
942
- }`
943
- : ` .sv-app--side { display: grid; grid-template-columns: 240px minmax(0, 1fr); min-height: 100vh; }
944
- .sv-app--side.sv-app--right { grid-template-columns: minmax(0, 1fr) 240px; }
945
- .sv-app--right .sv-app__side { order: 2; border-right: 0; border-left: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 16%, var(--sg-border, #e6e8ec)); }
946
- .sv-app__side { display: flex; flex-direction: column; gap: 4px; padding: 20px 14px; border-right: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 16%, var(--sg-border, #e6e8ec)); background: var(--sg-header-bg, #f8fafc); }
947
- .sv-app__sidehead { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
948
- .sv-app__collapse, .sv-app__dock { display: inline-flex; padding: 5px; border: 0; border-radius: 8px; background: transparent; color: var(--sg-muted, #64748b); cursor: pointer; }
949
- .sv-app__collapse:hover, .sv-app__dock:hover { background: color-mix(in srgb, var(--sg-fg, #0f172a) 8%, transparent); color: var(--sg-fg, #0f172a); }
950
- .sv-app__mobilebar { display: none; align-items: center; gap: 12px; padding: 10px 14px; border-bottom: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 16%, var(--sg-border, #e6e8ec)); background: var(--sg-header-bg, #f8fafc); }
951
- .sv-app__mobilebar .sv-app__dock { margin-left: auto; }
952
- .sv-app__burger { display: inline-flex; padding: 6px; border: 0; border-radius: 8px; background: transparent; color: var(--sg-fg, #0f172a); cursor: pointer; }
953
- .sv-app__backdrop { position: fixed; inset: 0; z-index: 55; border: 0; background: rgba(15, 23, 42, 0.4); cursor: pointer; }
954
- .sv-app__links { display: flex; flex-direction: column; gap: 2px; }
955
- .sv-app__foot { margin-top: auto; padding-top: 14px; color: var(--sg-muted, #94a3b8); font-size: 12px; }
956
- /* Drawer mode: tablet / phone (<= 1024px) or a user-collapsed desktop sidebar.
957
- Driven by the .is-drawer class (JS matchMedia + the collapse toggle), so the
958
- sidebar docks on wide screens and slides in as an off-canvas drawer otherwise. */
959
- .sv-app--side.is-drawer, .sv-app--side.is-drawer.sv-app--right { display: block; }
960
- .sv-app--side.is-drawer .sv-app__mobilebar { display: flex; }
961
- .sv-app--side.is-drawer .sv-app__side {
962
- position: fixed; top: 0; bottom: 0; left: 0; z-index: 60; width: 264px; max-width: 82vw;
963
- transform: translateX(-100%); transition: transform 0.24s ease; box-shadow: 0 0 40px rgba(15, 23, 42, 0.28);
964
- }
965
- .sv-app--side.is-drawer.sv-app--right .sv-app__side { left: auto; right: 0; transform: translateX(100%); border-left: 0; }
966
- .sv-app--side.is-drawer.is-navopen .sv-app__side { transform: translateX(0); }
967
- .sv-app--side.is-drawer .sv-app__sidehead .sv-app__brand { display: none; } /* brand already in the top bar */
968
- .sv-app--side.is-drawer .sv-app__collapse { display: none; } /* dock lives in the top bar when drawered */`
969
-
970
- return {
971
- path: 'src/routes/+layout.svelte',
972
- description: `App shell (${style}): nav linking every screen.`,
973
- contents: `<script lang="ts">
974
- import '../app.css'
975
- import '../custom.css'
976
- import { page } from '$app/state'${opts.access ? `\n import { currentRole, canScreen } from '$lib/access'` : ''}${opts.i18n ? `\n import { t, currentLocale, locales } from '$lib/i18n'` : ''}${opts.auth ? `\n import type { LayoutData } from './$types'` : ''}${opts.supabaseAuth ? `\n import { SvAuthGate, type SupabaseAuthClientLike } from '@svgrid/enterprise'\n import { supabaseClient } from '$lib/connections'` : ''}
977
-
978
- let { children${opts.auth ? ', data' : ''} }${opts.auth ? ": { children: import('svelte').Snippet; data: LayoutData }" : ''} = $props()
979
- const nav = ${JSON.stringify(links)}
980
- const brand = ${JSON.stringify(brand)}${footConst}${logoConst}
981
- let navOpen = $state(false)${opts.auth && opts.access ? `\n // Seed the client role store from the signed-in session (server-resolved).\n $effect(() => { if (data?.role) currentRole.set(data.role as never) })` : ''}
982
- // Close the mobile drawer whenever the route changes.
983
- $effect(() => { void page.url.pathname; navOpen = false })${hasSwitch ? `
984
- // Light/dark switcher: defaults to the mode picked in Studio, then honours the
985
- // visitor's saved choice. Applies via [data-theme] on <html> (see the token
986
- // sets in <svelte:head>), and persists per browser.
987
- let theme = $state<'light' | 'dark'>('${defaultMode}')
988
- function applyTheme(t: 'light' | 'dark') {
989
- theme = t
990
- try { document.documentElement.dataset.theme = t } catch (_) { /* no DOM */ }
991
- try { localStorage.setItem('svapp:theme', t) } catch (_) { /* storage blocked */ }
992
- }
993
- function toggleTheme() { applyTheme(theme === 'dark' ? 'light' : 'dark') }
994
- $effect(() => {
995
- let t: 'light' | 'dark' = '${defaultMode}'
996
- try { const s = localStorage.getItem('svapp:theme'); if (s === 'light' || s === 'dark') t = s } catch (_) { /* storage blocked */ }
997
- applyTheme(t)
998
- })` : ''}${sideState}${toolbarOn ? `
999
- // App-chrome quick-search: filter the screens by label as you type.
1000
- const initials = ${JSON.stringify(initials)}
1001
- const acctEmail = ${JSON.stringify(`admin@${brandSlug}.com`)}
1002
- let q = $state('')
1003
- const results = $derived(q.trim() ? nav.filter((n) => n.label.toLowerCase().includes(q.trim().toLowerCase())).slice(0, 6) : [])
1004
- let bellOpen = $state(false)
1005
- let menuOpen = $state(false)
1006
- let searchEl: HTMLInputElement | null = $state(null)
1007
- const notifications = [
1008
- { title: 'Welcome to ' + brand, time: 'just now', color: 'var(--sg-accent, #6366f1)' },
1009
- { title: 'Your workspace is ready', time: '2m ago', color: '#10b981' },
1010
- { title: 'Sample data loaded', time: '5m ago', color: '#f59e0b' },
1011
- ]
1012
- // Cmd/Ctrl+K focuses search; Escape closes the menus.
1013
- $effect(() => {
1014
- function onKey(e: KeyboardEvent) {
1015
- if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); searchEl?.focus() }
1016
- else if (e.key === 'Escape') { bellOpen = false; menuOpen = false }
1017
- }
1018
- window.addEventListener('keydown', onKey)
1019
- return () => window.removeEventListener('keydown', onKey)
1020
- })
1021
- // Close both menus on route change.
1022
- $effect(() => { void page.url.pathname; bellOpen = false; menuOpen = false })` : ''}
1023
- </script>
1024
- ${themeHead}
1025
-
1026
- ${opts.auth ? `{#if ${JSON.stringify(opts.authRoutes ?? ['/login'])}.includes(page.url.pathname)}
1027
- {@render children()}
1028
- {:else}
1029
- ${body}
1030
- {/if}` : body}
1031
-
1032
- <style>
1033
- ${styles}
1034
- .sv-app__brand { display: inline-flex; align-items: center; font-weight: 700; font-size: 15px; color: var(--sg-fg, #0f172a); text-decoration: none; padding: 4px 8px; }
1035
- .sv-app__logo { max-height: 34px; max-width: 160px; width: auto; object-fit: contain; display: block; }
1036
- .sv-app__link { padding: 7px 10px; border-radius: 8px; color: var(--sg-fg, #334155); text-decoration: none; font-size: 14px; }
1037
- .sv-app__link:hover { background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent); }
1038
- .sv-app__link.is-active { background: color-mix(in srgb, var(--sg-accent, #4f46e5) 14%, transparent); color: var(--sg-accent, #4f46e5); font-weight: 600; }
1039
- .sv-app__main { display: flex; flex-direction: column; min-width: 0; }
1040
- .sv-app__content { padding: 24px 28px; min-width: 0; }
1041
- /* App-chrome toolbar: a sticky product header with quick-search + account cluster. */
1042
- .sv-app__toolbar { position: sticky; top: 0; z-index: 40; display: flex; align-items: center; gap: 16px; padding: 10px 28px; background: color-mix(in srgb, var(--sg-header-bg, #f8fafc) 86%, transparent); backdrop-filter: blur(8px); border-bottom: 1px solid color-mix(in srgb, var(--sg-fg, #0f172a) 10%, var(--sg-border, #e6e8ec)); }
1043
- .sv-app__search { position: relative; flex: 1; max-width: 440px; display: flex; align-items: center; }
1044
- .sv-app__search-ic { position: absolute; left: 11px; color: var(--sg-muted, #94a3b8); pointer-events: none; }
1045
- .sv-app__search-in { width: 100%; font: inherit; font-size: 13.5px; padding: 7px 46px 7px 34px; border: 1px solid var(--sg-border, #e6e8ec); border-radius: 9px; background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); }
1046
- .sv-app__search-in::-webkit-search-cancel-button { -webkit-appearance: none; }
1047
- .sv-app__search-in:focus { outline: none; border-color: var(--sg-accent, #6366f1); box-shadow: 0 0 0 3px color-mix(in srgb, var(--sg-accent, #6366f1) 18%, transparent); }
1048
- .sv-app__results { position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 20; display: flex; flex-direction: column; padding: 5px; background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 10px; box-shadow: 0 12px 32px -8px rgba(15, 23, 42, 0.25); }
1049
- .sv-app__result { padding: 7px 10px; border-radius: 7px; font-size: 13.5px; color: var(--sg-fg, #334155); text-decoration: none; }
1050
- .sv-app__result:hover { background: color-mix(in srgb, var(--sg-accent, #6366f1) 12%, transparent); color: var(--sg-accent, #6366f1); }
1051
- .sv-app__tools { margin-left: auto; display: flex; align-items: center; gap: 10px; }
1052
- .sv-app__tool { position: relative; display: inline-flex; padding: 7px; border: 1px solid var(--sg-border, #e6e8ec); border-radius: 9px; background: var(--sg-bg, #fff); color: var(--sg-muted, #64748b); cursor: pointer; }
1053
- .sv-app__tool:hover { color: var(--sg-fg, #0f172a); border-color: color-mix(in srgb, var(--sg-fg, #0f172a) 20%, var(--sg-border, #e6e8ec)); }
1054
- .sv-app__dot { position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%; background: var(--sg-accent, #6366f1); box-shadow: 0 0 0 2px var(--sg-bg, #fff); }
1055
- .sv-app__avatar { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: 50%; font: inherit; font-size: 12px; font-weight: 700; color: #fff; background: var(--sg-accent, #6366f1); letter-spacing: 0.02em; cursor: pointer; }
1056
- .sv-app__kbd { position: absolute; right: 8px; font-size: 10.5px; font-weight: 600; color: var(--sg-muted, #94a3b8); background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 5px; padding: 1px 5px; pointer-events: none; }
1057
- .sv-app__pop { position: relative; }
1058
- .sv-app__scrim { position: fixed; inset: 0; z-index: 44; border: 0; padding: 0; background: transparent; cursor: default; }
1059
- .sv-app__menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 46; min-width: 232px; padding: 6px; background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 11px; box-shadow: 0 16px 40px -10px rgba(15, 23, 42, 0.3); }
1060
- .sv-app__menu-head { padding: 6px 10px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--sg-muted, #94a3b8); }
1061
- .sv-app__notif { display: flex; gap: 9px; padding: 8px 10px; border-radius: 8px; }
1062
- .sv-app__notif:hover { background: color-mix(in srgb, var(--sg-fg, #0f172a) 5%, transparent); }
1063
- .sv-app__notif-dot { flex: none; width: 8px; height: 8px; margin-top: 5px; border-radius: 50%; }
1064
- .sv-app__notif-body { display: flex; flex-direction: column; min-width: 0; }
1065
- .sv-app__notif-body strong { font-size: 13px; font-weight: 600; color: var(--sg-fg, #0f172a); }
1066
- .sv-app__notif-body span { font-size: 11.5px; color: var(--sg-muted, #94a3b8); }
1067
- .sv-app__menu-foot { width: 100%; margin-top: 4px; padding: 8px; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--sg-accent, #6366f1); background: none; border: none; border-top: 1px solid var(--sg-border, #eef0f3); cursor: pointer; }
1068
- .sv-app__acct-head { display: flex; align-items: center; gap: 10px; padding: 8px 10px 10px; border-bottom: 1px solid var(--sg-border, #eef0f3); margin-bottom: 4px; }
1069
- .sv-app__avatar--lg { width: 38px; height: 38px; font-size: 14px; }
1070
- .sv-app__acct-id { display: flex; flex-direction: column; min-width: 0; }
1071
- .sv-app__acct-id strong { font-size: 13.5px; color: var(--sg-fg, #0f172a); }
1072
- .sv-app__acct-id span { font-size: 12px; color: var(--sg-muted, #94a3b8); overflow: hidden; text-overflow: ellipsis; }
1073
- .sv-app__menu-item { display: block; width: 100%; text-align: left; padding: 8px 10px; font: inherit; font-size: 13px; color: var(--sg-fg, #334155); background: none; border: none; border-radius: 8px; text-decoration: none; cursor: pointer; }
1074
- .sv-app__menu-item:hover { background: color-mix(in srgb, var(--sg-accent, #6366f1) 10%, transparent); color: var(--sg-accent, #6366f1); }
1075
- .sv-app__menu-item--danger:hover { background: color-mix(in srgb, #ef4444 12%, transparent); color: #ef4444; }
1076
- .sv-app__signout { margin: 0; display: block; }
1077
- .sv-app__signout .sv-app__menu-item { width: 100%; }
1078
- .sv-app__locale { margin-top: 10px; padding: 5px 8px; font: inherit; font-size: 12.5px; color: var(--sg-fg, #0f172a); background: var(--sg-bg, #fff); border: 1px solid var(--sg-border, #e6e8ec); border-radius: 8px; }
1079
- .sv-app__theme { align-items: center; justify-content: center; cursor: pointer; }
1080
- /* No-toolbar fallback slot: trails the nav, pushed to the far edge in row bars. */
1081
- .sv-app__navtools { display: flex; align-items: center; }
1082
- .sv-app--top .sv-app__navtools, .sv-app__bar--bottom .sv-app__navtools { margin-left: auto; }
1083
- /* Mobile bar + drawer chrome (hidden on desktop; media queries above switch it on) */
1084
- .sv-app__mobilebar { display: none; align-items: center; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--sg-border, #e6e8ec); background: var(--sg-header-bg, #f8fafc); position: sticky; top: 0; z-index: 50; }
1085
- .sv-app__burger { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; border: 1px solid var(--sg-border, #e6e8ec); border-radius: 9px; background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); cursor: pointer; }
1086
- .sv-app__backdrop { position: fixed; inset: 0; z-index: 55; border: 0; padding: 0; background: rgba(15, 23, 42, 0.42); cursor: pointer; }
1087
- @media (max-width: 860px) { .sv-app__content { padding: 18px 16px; } .sv-app__toolbar { padding: 9px 16px; } }
1088
- @media (max-width: 640px) { .sv-app__content { padding: 14px 12px; } .sv-app__toolbar { padding: 8px 12px; gap: 10px; } .sv-app__tool { display: none; } }
1089
- </style>
1090
- `,
1091
- }
1092
- }
1093
-
1094
- export function homeFile(nav: NavItem[]): GeneratedFile {
1095
- // Land on the app's primary screen (the first dashboard) instead of a generic
1096
- // welcome, so opening the app drops you straight into the real product. A themed
1097
- // splash + link covers the pre-hydration moment and the no-JS case.
1098
- const first = nav[0]?.href ?? '/'
1099
- if (nav.length === 0) {
1100
- return { path: 'src/routes/+page.svelte', description: 'Home page.', contents: `<h1 class="st__title">Your data app</h1>\n<p class="st__sub">Add a screen to get started.</p>\n` }
1101
- }
1102
- return {
1103
- path: 'src/routes/+page.svelte',
1104
- description: 'Home: redirects to the primary dashboard.',
1105
- contents: `<script lang="ts">
1106
- import { onMount } from 'svelte'
1107
- import { goto } from '$app/navigation'
1108
- const home = ${JSON.stringify(first)}
1109
- onMount(() => { goto(home, { replaceState: true }) })
1110
- </script>
1111
-
1112
- <div class="st-home">
1113
- <div class="st-home__spinner" aria-hidden="true"></div>
1114
- <p>Opening your workspace… <a href={home}>Continue</a></p>
1115
- </div>
1116
-
1117
- <style>
1118
- .st-home { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; min-height: 52vh; color: var(--sg-muted, #64748b); font-size: 14px; }
1119
- .st-home__spinner { width: 26px; height: 26px; border-radius: 50%; border: 3px solid var(--sg-border, #e6e8ec); border-top-color: var(--sg-accent, #6366f1); animation: st-home-spin 0.7s linear infinite; }
1120
- .st-home a { color: var(--sg-accent, #6366f1); font-weight: 600; }
1121
- @keyframes st-home-spin { to { transform: rotate(360deg); } }
1122
- </style>
1123
- `,
1124
- }
1125
- }
1126
-
1127
- /**
1128
- * Prepare entities for emission: resolve relations, rewrite each schema so the
1129
- * grid shows a related label column instead of the raw FK, and build the seed.
1130
- * Shared by `emitStudioApp` and the Studio designer's `emitStudioProject`.
1131
- */
1132
- export function prepareEntities(schemas: EntitySchema[]): { entries: Prepared[]; seed: Map<string, Array<Record<string, unknown>>> } {
1133
- for (const s of schemas) resolveIdField(s) // fail fast on an unresolvable key
1134
- const byName = new Map(schemas.map((s) => [s.name, s]))
1135
- const entries: Prepared[] = schemas.map((s) => {
1136
- const infos = relationInfos(s, byName)
1137
- return { schema: prepareSchema(s, infos), infos }
1138
- })
1139
- return { entries, seed: generateSeed(entries) }
1140
- }
1141
-
1142
- /** Emit the shared entity modules: `src/lib/schemas.ts` + `src/lib/data.ts` (+ `connections.ts`). */
1143
- export function emitEntityModules(
1144
- schemas: EntitySchema[],
1145
- opts: { sources?: Record<string, EntityDataSource>; accessEnabled?: boolean; auditEnabled?: boolean; screensByEntity?: Map<string, string[]>; triggers?: Record<string, EntityTriggers>; supabaseConn?: { url?: string; key?: string }; supabaseAuth?: boolean; tenantField?: string; tenantScoped?: (entity: string) => boolean } = {},
1146
- ): { files: GeneratedFile[]; prepared: EntitySchema[] } {
1147
- const { entries, seed } = prepareEntities(schemas)
1148
- const { file: data, needs } = dataModule(entries, seed, opts.sources, opts.supabaseConn)
1149
- const files: GeneratedFile[] = [schemasModule(entries), data]
1150
- // Supabase Auth needs the shared client even with no Supabase-bound entity; seed
1151
- // the .env paste hint from the project connection when nothing else did.
1152
- if ((needs.supabase || opts.supabaseAuth) && !needs.supabaseUrl && (opts.supabaseConn?.url || opts.supabaseConn?.key)) {
1153
- needs.supabaseUrl = opts.supabaseConn.url; needs.supabaseKey = opts.supabaseConn.key
1154
- }
1155
- if (needs.supabase || opts.supabaseAuth) files.push(connectionsModule(needs))
1156
- for (const r of needs.sqlRoutes) files.push(sqlRouteFile(r.schema, r.table, r.dialect, {
1157
- access: opts.accessEnabled,
1158
- audit: opts.auditEnabled,
1159
- screenIds: opts.screensByEntity?.get(r.schema.name) ?? [],
1160
- triggers: opts.triggers?.[r.schema.name],
1161
- dbSchema: r.dbSchema,
1162
- // A shared (reference-data) entity stays global - no scope on its route.
1163
- tenantField: opts.tenantField && opts.tenantScoped?.(r.schema.name) !== false ? opts.tenantField : undefined,
1164
- }))
1165
- return { files, prepared: entries.map((e) => e.schema) }
1166
- }
1167
-
1168
- /**
1169
- * Emit every source file of a create-studio app for the given entities:
1170
- * `schemas.ts`, `data.ts`, one `+page.svelte` per entity, and the nav shell.
1171
- * The template supplies the rest (EntityScreen, theme, configs).
1172
- */
1173
- export function emitStudioApp(schemas: EntitySchema[]): GeneratedFile[] {
1174
- if (schemas.length === 0) throw new Error('emitStudioApp: no schemas to emit')
1175
- const { files, prepared } = emitEntityModules(schemas)
1176
- const nav: NavItem[] = prepared.map((s) => {
1177
- const n = namesFor(s)
1178
- return { href: `/${n.route}`, label: n.label }
1179
- })
1180
- return [...files, ...prepared.map((s) => entityScreenPage(s)), layoutFile(nav), homeFile(nav)]
1181
- }