@svgrid/enterprise 2.5.2 → 2.6.0

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